First cut of App Experiments
This commit is contained in:
parent
853ab1b936
commit
cb314528c3
103
acn.go
103
acn.go
|
@ -1,103 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"cwtch.im/cwtch/app"
|
||||
"cwtch.im/cwtch/event"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"git.openprivacy.ca/openprivacy/connectivity"
|
||||
"git.openprivacy.ca/openprivacy/connectivity/tor"
|
||||
"git.openprivacy.ca/openprivacy/log"
|
||||
mrand "math/rand"
|
||||
"os"
|
||||
path "path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
CwtchStarted = event.Type("CwtchStarted")
|
||||
CwtchStartError = event.Type("CwtchStartError")
|
||||
UpdateGlobalSettings = event.Type("UpdateGlobalSettings")
|
||||
)
|
||||
|
||||
func buildACN(settings app.GlobalSettings, torPath string, appDir string) (connectivity.ACN, app.GlobalSettings) {
|
||||
|
||||
mrand.Seed(int64(time.Now().Nanosecond()))
|
||||
socksPort := mrand.Intn(1000) + 9600
|
||||
controlPort := socksPort + 1
|
||||
|
||||
// generate a random password (actually random, stored in memory, for the control port)
|
||||
key := make([]byte, 64)
|
||||
_, err := rand.Read(key)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
log.Infof("making directory %v", appDir)
|
||||
err = os.MkdirAll(path.Join(appDir, "tor"), 0700)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("error creating tor data directory: %v. Aborting app start up", err)
|
||||
eventHandler.Push(event.NewEventList(CwtchStartError, event.Error, fmt.Sprintf("Error connecting to Tor: %v", err)))
|
||||
return &connectivity.ErrorACN{}, settings
|
||||
}
|
||||
|
||||
if settings.AllowAdvancedTorConfig {
|
||||
controlPort = settings.CustomControlPort
|
||||
socksPort = settings.CustomSocksPort
|
||||
}
|
||||
|
||||
torrc := tor.NewTorrc().WithSocksPort(socksPort).WithOnionTrafficOnly().WithControlPort(controlPort).WithHashedPassword(base64.StdEncoding.EncodeToString(key))
|
||||
// torrc.WithLog(path.Join(appDir, "tor", "tor.log"), tor.TorLogLevelNotice)
|
||||
if settings.UseCustomTorrc {
|
||||
customTorrc := settings.CustomTorrc
|
||||
torrc.WithCustom(strings.Split(customTorrc, "\n"))
|
||||
} else {
|
||||
// Fallback to showing the freshly generated torrc for this session.
|
||||
settings.CustomTorrc = torrc.Preview()
|
||||
settings.CustomControlPort = controlPort
|
||||
settings.CustomSocksPort = socksPort
|
||||
}
|
||||
|
||||
err = torrc.Build(path.Join(appDir, "tor", "torrc"))
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("error constructing torrc: %v", err)
|
||||
eventHandler.Push(event.NewEventList(CwtchStartError, event.Error, fmt.Sprintf("Error connecting to Tor: %v", err)))
|
||||
return &connectivity.ErrorACN{}, settings
|
||||
}
|
||||
|
||||
dataDir := settings.TorCacheDir
|
||||
if !settings.UseTorCache {
|
||||
|
||||
// purge data dir directories if we are not using them for a cache
|
||||
torDir := path.Join(appDir, "tor")
|
||||
files, err := path.Glob(path.Join(torDir, "data-dir-*"))
|
||||
if err != nil {
|
||||
log.Errorf("could not construct filesystem glob: %v", err)
|
||||
}
|
||||
for _, f := range files {
|
||||
if err := os.RemoveAll(f); err != nil {
|
||||
log.Errorf("could not remove data-dir: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if dataDir, err = os.MkdirTemp(torDir, "data-dir-"); err != nil {
|
||||
eventHandler.Push(event.NewEventList(CwtchStartError, event.Error, fmt.Sprintf("Error connecting to Tor: %v", err)))
|
||||
return &connectivity.ErrorACN{}, settings
|
||||
}
|
||||
}
|
||||
|
||||
// Persist Current Data Dir as Tor Cache...
|
||||
settings.TorCacheDir = dataDir
|
||||
|
||||
acn, err := tor.NewTorACNWithAuth(appDir, torPath, dataDir, controlPort, tor.HashedPasswordAuthenticator{Password: base64.StdEncoding.EncodeToString(key)})
|
||||
if err != nil {
|
||||
log.Errorf("Error connecting to Tor replacing with ErrorACN: %v\n", err)
|
||||
eventHandler.Push(event.NewEventList(CwtchStartError, event.Error, fmt.Sprintf("Error connecting to Tor: %v", err)))
|
||||
acn = &connectivity.ErrorACN{}
|
||||
}
|
||||
return acn, settings
|
||||
}
|
|
@ -1,84 +0,0 @@
|
|||
package main
|
||||
|
||||
import "C"
|
||||
import (
|
||||
"cwtch.im/cwtch/model/attr"
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
// TODO: At some point these functions should also be autogenerated
|
||||
|
||||
// Attribute is a struct to return the dual values of an attempt at a Get*Attribute API call, meant to be json serialized
|
||||
type Attribute struct {
|
||||
Exists bool
|
||||
Value string
|
||||
}
|
||||
|
||||
//export c_GetProfileAttribute
|
||||
func c_GetProfileAttribute(profile_ptr *C.char, profile_len C.int, key_ptr *C.char, key_len C.int) *C.char {
|
||||
profileOnion := C.GoStringN(profile_ptr, profile_len)
|
||||
key := C.GoStringN(key_ptr, key_len)
|
||||
return C.CString(GetProfileAttribute(profileOnion, key))
|
||||
}
|
||||
|
||||
// GetProfileAttribute provides a wrapper around profile.GetScopedZonedAttribute
|
||||
// Key must have the format zone.key where Zone is defined in Cwtch. Unknown zones are not permitted.
|
||||
// Currently forcing the Public Scope
|
||||
// Returns json of Attribute
|
||||
func GetProfileAttribute(profileOnion string, key string) string {
|
||||
profile := application.GetPeer(profileOnion)
|
||||
if profile != nil {
|
||||
zone, key := attr.ParseZone(key)
|
||||
|
||||
res, exists := profile.GetScopedZonedAttribute(attr.PublicScope, zone, key)
|
||||
attr := Attribute{exists, res}
|
||||
json, _ := json.Marshal(attr)
|
||||
return string(json)
|
||||
|
||||
}
|
||||
empty := Attribute{false, ""}
|
||||
json, _ := json.Marshal(empty)
|
||||
return (string(json))
|
||||
}
|
||||
|
||||
//export c_SetConversationAttribute
|
||||
func c_SetConversationAttribute(profile_ptr *C.char, profile_len C.int, conversation_id C.int, key_ptr *C.char, key_len C.int, val_ptr *C.char, val_len C.int) {
|
||||
profileOnion := C.GoStringN(profile_ptr, profile_len)
|
||||
key := C.GoStringN(key_ptr, key_len)
|
||||
value := C.GoStringN(val_ptr, val_len)
|
||||
SetConversationAttribute(profileOnion, int(conversation_id), key, value)
|
||||
}
|
||||
|
||||
// SetConversationAttribute provides a wrapper around profile.SetProfileAttribute
|
||||
// key is of format Zone.Key, and the API forces the Local Scope
|
||||
func SetConversationAttribute(profileOnion string, conversationID int, key string, value string) {
|
||||
profile := application.GetPeer(profileOnion)
|
||||
zone, key := attr.ParseZone(key)
|
||||
profile.SetConversationAttribute(conversationID, attr.LocalScope.ConstructScopedZonedPath(zone.ConstructZonedPath(key)), value)
|
||||
}
|
||||
|
||||
//export c_GetConversationAttribute
|
||||
func c_GetConversationAttribute(profile_ptr *C.char, profile_len C.int, conversation_id C.int, key_ptr *C.char, key_len C.int) *C.char {
|
||||
profileOnion := C.GoStringN(profile_ptr, profile_len)
|
||||
key := C.GoStringN(key_ptr, key_len)
|
||||
return C.CString(GetConversationAttribute(profileOnion, int(conversation_id), key))
|
||||
}
|
||||
|
||||
// GetGonversationAttribute provides a wrapper around profile.GetGonversationAttribute
|
||||
// key is of format Scope.Zone.Key
|
||||
// Returns json of an Attribute
|
||||
func GetConversationAttribute(profileOnion string, conversationID int, key string) string {
|
||||
profile := application.GetPeer(profileOnion)
|
||||
if profile != nil {
|
||||
scope, zonekey := attr.ParseScope(key)
|
||||
zone, key := attr.ParseZone(zonekey)
|
||||
|
||||
res, err := profile.GetConversationAttribute(conversationID, scope.ConstructScopedZonedPath(zone.ConstructZonedPath(key)))
|
||||
attr := Attribute{err == nil, res}
|
||||
json, _ := json.Marshal(attr)
|
||||
return string(json)
|
||||
}
|
||||
empty := Attribute{false, ""}
|
||||
json, _ := json.Marshal(empty)
|
||||
return (string(json))
|
||||
}
|
|
@ -11,6 +11,13 @@ const (
|
|||
NotificationConversation = NotificationType("ContactInfo")
|
||||
)
|
||||
|
||||
const (
|
||||
// StatusSuccess is an event response for event.Status signifying a call succeeded
|
||||
StatusSuccess = "success"
|
||||
// StatusError is an event response for event.Status signifying a call failed in error, ideally accompanied by a event.Error
|
||||
StatusError = "error"
|
||||
)
|
||||
|
||||
const (
|
||||
// ConversationNotificationPolicyDefault enum for conversations indicating to use global notification policy
|
||||
ConversationNotificationPolicyDefault = "ConversationNotificationPolicy.Default"
|
||||
|
|
|
@ -0,0 +1,337 @@
|
|||
package servers
|
||||
|
||||
import (
|
||||
"cwtch.im/cwtch/app"
|
||||
"cwtch.im/cwtch/event"
|
||||
"git.openprivacy.ca/cwtch.im/cwtch-autobindings/constants"
|
||||
"git.openprivacy.ca/cwtch.im/server"
|
||||
"git.openprivacy.ca/openprivacy/connectivity"
|
||||
"git.openprivacy.ca/openprivacy/log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const serversExperiment = "servers-experiment"
|
||||
|
||||
const (
|
||||
ZeroServersLoaded = event.Type("ZeroServersLoaded")
|
||||
NewServer = event.Type("NewServer")
|
||||
ServerIntentUpdate = event.Type("ServerIntentUpdate")
|
||||
ServerDeleted = event.Type("ServerDeleted")
|
||||
ServerStatsUpdate = event.Type("ServerStatsUpdate")
|
||||
)
|
||||
|
||||
const (
|
||||
Intent = event.Field("Intent")
|
||||
TotalMessages = event.Field("TotalMessages")
|
||||
Connections = event.Field("Connections")
|
||||
)
|
||||
|
||||
const (
|
||||
IntentRunning = "running"
|
||||
IntentStopped = "stopped"
|
||||
)
|
||||
|
||||
type ServerInfo struct {
|
||||
Onion string
|
||||
ServerBundle string
|
||||
Autostart bool
|
||||
Running bool
|
||||
Description string
|
||||
StorageType string
|
||||
}
|
||||
|
||||
type PublishFn func(event.Event)
|
||||
|
||||
var lock sync.Mutex
|
||||
var appServers server.Servers
|
||||
var killStatsUpdate chan bool = make(chan bool, 1)
|
||||
var enabled bool = false
|
||||
|
||||
func InitServers(acn connectivity.ACN, appdir string, ) *ServersFunctionality {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
if appServers == nil {
|
||||
serversDir := filepath.Join(appdir, "servers")
|
||||
err := os.MkdirAll(serversDir, 0700)
|
||||
if err != nil {
|
||||
log.Errorf("Could not init servers directory: %s", err)
|
||||
}
|
||||
appServers = server.NewServers(acn, serversDir)
|
||||
}
|
||||
return new(ServersFunctionality)
|
||||
}
|
||||
|
||||
// track acnStatus across events
|
||||
var acnStatus = -1
|
||||
|
||||
func (sf *ServersFunctionality) OnACNStatusEvent(appl app.Application, e *event.Event) {
|
||||
newAcnStatus, err := strconv.Atoi(e.Data[event.Progress])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if newAcnStatus == 100 {
|
||||
if acnStatus != 100 {
|
||||
// just came online
|
||||
}
|
||||
} else {
|
||||
if acnStatus == 100 {
|
||||
for _, onion := range sf.ListServers() {
|
||||
sf.StopServer(appl, onion)
|
||||
}
|
||||
}
|
||||
}
|
||||
acnStatus = newAcnStatus
|
||||
}
|
||||
|
||||
func (sh *ServersFunctionality) Disable() {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
if appServers != nil {
|
||||
appServers.Stop()
|
||||
}
|
||||
if enabled {
|
||||
enabled = false
|
||||
killStatsUpdate <- true
|
||||
}
|
||||
}
|
||||
|
||||
func (sh *ServersFunctionality) Enabled() bool {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
return enabled
|
||||
}
|
||||
|
||||
// ServersFunctionality provides experiment gated server functionality
|
||||
type ServersFunctionality struct {
|
||||
}
|
||||
|
||||
func (sh *ServersFunctionality) UpdateSettings(appl app.Application, acn connectivity.ACN) {
|
||||
if appl.IsFeatureEnabled(serversExperiment) {
|
||||
sh.Disable()
|
||||
} else {
|
||||
sh.LoadServers(appl, acn, app.DefactoPasswordForUnencryptedProfiles)
|
||||
if !sh.Enabled() {
|
||||
sh.Enable(appl)
|
||||
serversList := sh.ListServers()
|
||||
for _, server := range serversList {
|
||||
serverInfo := sh.GetServerInfo(server)
|
||||
ev := event.NewEvent(NewServer, make(map[event.Field]string))
|
||||
serverInfo.EnrichEvent(&ev)
|
||||
appl.GetPrimaryBus().Publish(ev)
|
||||
}
|
||||
sh.LaunchServers(appl, acn)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (sh *ServersFunctionality) SetServerAttribute(appl app.Application, handle string, key string, val string) {
|
||||
if appl.IsFeatureEnabled(serversExperiment) {
|
||||
server := sh.GetServer(handle)
|
||||
if server != nil {
|
||||
server.SetAttribute(key, val)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (sh *ServersFunctionality) StopServers(appl app.Application) {
|
||||
// we are always permitted to stop servers
|
||||
for _, onion := range sh.ListServers() {
|
||||
sh.StopServer(appl, onion)
|
||||
}
|
||||
}
|
||||
|
||||
func (sh *ServersFunctionality) LaunchServers(appl app.Application, acn connectivity.ACN) {
|
||||
if appl.IsFeatureEnabled(serversExperiment) {
|
||||
acnStatus, _ := acn.GetBootstrapStatus()
|
||||
if acnStatus == 100 {
|
||||
for _, onion := range sh.ListServers() {
|
||||
autostart := false
|
||||
if s := sh.GetServer(onion); s != nil {
|
||||
autostart = s.GetAttribute(server.AttrAutostart) == "true"
|
||||
}
|
||||
if autostart {
|
||||
sh.LaunchServer(appl, onion)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (sf *ServersFunctionality) Enable(application app.Application) {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
if appServers != nil && !enabled {
|
||||
enabled = true
|
||||
go cacheForwardServerMetricUpdates(application)
|
||||
}
|
||||
}
|
||||
|
||||
func (sf *ServersFunctionality) LoadServers(appl app.Application, acn connectivity.ACN, password string) {
|
||||
serversList, err := appServers.LoadServers(password)
|
||||
if err != nil {
|
||||
log.Errorf("Error attempting to load servers :%s\n", err)
|
||||
appl.GetPrimaryBus().Publish(event.NewEventList(ZeroServersLoaded))
|
||||
} else if len(serversList) == 0 {
|
||||
log.Debugln("Loaded 0 servers")
|
||||
appl.GetPrimaryBus().Publish(event.NewEventList(ZeroServersLoaded))
|
||||
} else {
|
||||
acnStatus, _ := acn.GetBootstrapStatus()
|
||||
for _, serverOnion := range serversList {
|
||||
serverInfo := sf.GetServerInfo(serverOnion)
|
||||
log.Debugf("Load Server NewServer event: %s", serverInfo)
|
||||
ev := event.NewEvent(NewServer, make(map[event.Field]string))
|
||||
serverInfo.EnrichEvent(&ev)
|
||||
appl.GetPrimaryBus().Publish(ev)
|
||||
if serverInfo.Autostart && acnStatus == 100 {
|
||||
sf.LaunchServer(appl, serverOnion)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// server:1.3/libcwtch-go:1.4 accidentally enabled monitor logging by default. make sure it's turned off
|
||||
for _, onion := range serversList {
|
||||
server := appServers.GetServer(onion)
|
||||
server.SetMonitorLogging(false)
|
||||
}
|
||||
}
|
||||
|
||||
func (sf *ServersFunctionality) CreateServer(appl app.Application, password string, description string, autostart bool) {
|
||||
s, err := appServers.CreateServer(password)
|
||||
if err != nil {
|
||||
log.Errorf("Could not create new server: %s\n", err)
|
||||
} else {
|
||||
s.SetAttribute(server.AttrDescription, description)
|
||||
if autostart {
|
||||
s.SetAttribute(server.AttrAutostart, "true")
|
||||
} else {
|
||||
s.SetAttribute(server.AttrAutostart, "false")
|
||||
}
|
||||
if password == app.DefactoPasswordForUnencryptedProfiles {
|
||||
s.SetAttribute(server.AttrStorageType, server.StorageTypeDefaultPassword)
|
||||
} else {
|
||||
s.SetAttribute(server.AttrStorageType, server.StorageTypePassword)
|
||||
}
|
||||
serverInfo := sf.GetServerInfo(s.Onion())
|
||||
log.Debugf("Creating Server NewServer event: %s", serverInfo)
|
||||
ev := event.NewEvent(NewServer, make(map[event.Field]string))
|
||||
serverInfo.EnrichEvent(&ev)
|
||||
appl.GetPrimaryBus().Publish(ev)
|
||||
if autostart {
|
||||
sf.LaunchServer(appl, s.Onion())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (sf *ServersFunctionality) GetServer(onion string) server.Server {
|
||||
return appServers.GetServer(onion)
|
||||
}
|
||||
|
||||
func (sf *ServersFunctionality) GetServerStatistics(onion string) server.Statistics {
|
||||
s := appServers.GetServer(onion)
|
||||
if s != nil {
|
||||
return s.GetStatistics()
|
||||
}
|
||||
return server.Statistics{}
|
||||
}
|
||||
|
||||
func (sf *ServersFunctionality) ListServers() []string {
|
||||
return appServers.ListServers()
|
||||
}
|
||||
|
||||
func (sf *ServersFunctionality) DeleteServer(appl app.Application, onion string, currentPassword string) error {
|
||||
sf.StopServer(appl, onion)
|
||||
appl.GetPrimaryBus().Publish(event.NewEventList(ServerIntentUpdate, event.Identity, onion, Intent, IntentStopped))
|
||||
err := appServers.DeleteServer(onion, currentPassword)
|
||||
if err == nil {
|
||||
appl.GetPrimaryBus().Publish(event.NewEventList(ServerDeleted, event.Status, constants.StatusSuccess, event.Identity, onion))
|
||||
} else {
|
||||
appl.GetPrimaryBus().Publish(event.NewEventList(ServerDeleted, event.Status, constants.StatusError, event.Error, err.Error(), event.Identity, onion))
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (sf *ServersFunctionality) LaunchServer(appl app.Application, onion string) {
|
||||
if appl.IsFeatureEnabled(serversExperiment) {
|
||||
appServers.LaunchServer(onion)
|
||||
server := appServers.GetServer(onion)
|
||||
if server != nil {
|
||||
newStats := server.GetStatistics()
|
||||
appl.GetPrimaryBus().Publish(event.NewEventList(ServerStatsUpdate, event.Identity, onion, TotalMessages, strconv.Itoa(newStats.TotalMessages), Connections, strconv.Itoa(newStats.TotalConnections)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (sf *ServersFunctionality) StopServer(appl app.Application, onion string) {
|
||||
// we are always permitted to stop servers
|
||||
appServers.StopServer(onion)
|
||||
appl.GetPrimaryBus().Publish(event.NewEventList(ServerIntentUpdate, event.Identity, onion, Intent, IntentStopped))
|
||||
}
|
||||
|
||||
func (sf *ServersFunctionality) DestroyServers() {
|
||||
// we are always permitted to destroy servers
|
||||
appServers.Destroy()
|
||||
}
|
||||
|
||||
func (sf *ServersFunctionality) GetServerInfo(onion string) *ServerInfo {
|
||||
s := sf.GetServer(onion)
|
||||
var serverInfo ServerInfo
|
||||
serverInfo.Onion = s.Onion()
|
||||
serverInfo.ServerBundle = s.ServerBundle()
|
||||
serverInfo.Autostart = s.GetAttribute(server.AttrAutostart) == "true"
|
||||
running, _ := s.CheckStatus()
|
||||
serverInfo.Running = running
|
||||
serverInfo.Description = s.GetAttribute(server.AttrDescription)
|
||||
serverInfo.StorageType = s.GetAttribute(server.AttrStorageType)
|
||||
return &serverInfo
|
||||
}
|
||||
|
||||
func (si *ServerInfo) EnrichEvent(e *event.Event) {
|
||||
e.Data["Onion"] = si.Onion
|
||||
e.Data["ServerBundle"] = si.ServerBundle
|
||||
e.Data["Description"] = si.Description
|
||||
e.Data["StorageType"] = si.StorageType
|
||||
if si.Autostart {
|
||||
e.Data["Autostart"] = "true"
|
||||
} else {
|
||||
e.Data["Autostart"] = "false"
|
||||
}
|
||||
if si.Running {
|
||||
e.Data["Running"] = "true"
|
||||
} else {
|
||||
e.Data["Running"] = "false"
|
||||
}
|
||||
}
|
||||
|
||||
// cacheForwardServerMetricUpdates every minute gets metrics for all servers, and if they have changed, sends events to the UI
|
||||
func cacheForwardServerMetricUpdates(appl app.Application) {
|
||||
var cache map[string]server.Statistics = make(map[string]server.Statistics)
|
||||
duration := time.Second // allow first load
|
||||
for {
|
||||
select {
|
||||
case <-time.After(duration):
|
||||
duration = time.Minute
|
||||
serverList := appServers.ListServers()
|
||||
for _, serverOnion := range serverList {
|
||||
server := appServers.GetServer(serverOnion)
|
||||
if running, err := server.CheckStatus(); running && err == nil {
|
||||
newStats := server.GetStatistics()
|
||||
if stats, ok := cache[serverOnion]; !ok || stats.TotalConnections != newStats.TotalConnections || stats.TotalMessages != newStats.TotalMessages {
|
||||
cache[serverOnion] = newStats
|
||||
appl.GetPrimaryBus().Publish(event.NewEventList(ServerStatsUpdate, event.Identity, serverOnion, TotalMessages, strconv.Itoa(newStats.TotalMessages), Connections, strconv.Itoa(newStats.TotalConnections)))
|
||||
}
|
||||
}
|
||||
}
|
||||
case <-killStatsUpdate:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (sh *ServersFunctionality) Shutdown() {
|
||||
sh.Disable()
|
||||
appServers.Destroy()
|
||||
}
|
|
@ -1,220 +0,0 @@
|
|||
package servers
|
||||
|
||||
import (
|
||||
"cwtch.im/cwtch/event"
|
||||
"fmt"
|
||||
"git.openprivacy.ca/cwtch.im/server"
|
||||
"git.openprivacy.ca/openprivacy/connectivity"
|
||||
"git.openprivacy.ca/openprivacy/log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const serversExperiment = "servers-experiment"
|
||||
|
||||
const (
|
||||
ZeroServersLoaded = event.Type("ZeroServersLoaded")
|
||||
NewServer = event.Type("NewServer")
|
||||
ServerIntentUpdate = event.Type("ServerIntentUpdate")
|
||||
ServerDeleted = event.Type("ServerDeleted")
|
||||
ServerStatsUpdate = event.Type("ServerStatsUpdate")
|
||||
)
|
||||
|
||||
const (
|
||||
Intent = event.Field("Intent")
|
||||
TotalMessages = event.Field("TotalMessages")
|
||||
Connections = event.Field("Connections")
|
||||
)
|
||||
|
||||
const (
|
||||
IntentRunning = "running"
|
||||
IntentStopped = "stopped"
|
||||
)
|
||||
|
||||
// TODO: move into Cwtch model/attr
|
||||
|
||||
type ServerInfo struct {
|
||||
Onion string
|
||||
ServerBundle string
|
||||
Autostart bool
|
||||
Running bool
|
||||
Description string
|
||||
StorageType string
|
||||
}
|
||||
|
||||
type PublishFn func(event.Event)
|
||||
|
||||
var lock sync.Mutex
|
||||
var appServers server.Servers
|
||||
var publishFn PublishFn
|
||||
var killStatsUpdate chan bool = make(chan bool, 1)
|
||||
var enabled bool = false
|
||||
|
||||
func InitServers(acn connectivity.ACN, appdir string, pfn PublishFn) {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
if appServers == nil {
|
||||
serversDir := filepath.Join(appdir, "servers")
|
||||
err := os.MkdirAll(serversDir, 0700)
|
||||
if err != nil {
|
||||
log.Errorf("Could not init servers directory: %s", err)
|
||||
}
|
||||
appServers = server.NewServers(acn, serversDir)
|
||||
publishFn = pfn
|
||||
}
|
||||
}
|
||||
|
||||
func Disable() {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
if appServers != nil {
|
||||
appServers.Stop()
|
||||
}
|
||||
if enabled {
|
||||
enabled = false
|
||||
killStatsUpdate <- true
|
||||
}
|
||||
}
|
||||
|
||||
func Enabled() bool {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
return enabled
|
||||
}
|
||||
|
||||
// ServersFunctionality provides experiment gated server functionality
|
||||
type ServersFunctionality struct {
|
||||
}
|
||||
|
||||
// ExperimentGate returns ServersFunctionality if the experiment is enabled, and an error otherwise.
|
||||
func ExperimentGate(experimentMap map[string]bool) (*ServersFunctionality, error) {
|
||||
if experimentMap[serversExperiment] {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
return &ServersFunctionality{}, nil
|
||||
}
|
||||
return nil, fmt.Errorf("gated by %v", serversExperiment)
|
||||
}
|
||||
|
||||
func (sf *ServersFunctionality) Enable() {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
if appServers != nil && !enabled {
|
||||
enabled = true
|
||||
go cacheForwardServerMetricUpdates()
|
||||
}
|
||||
}
|
||||
|
||||
func (sf *ServersFunctionality) LoadServers(password string) ([]string, error) {
|
||||
servers, err := appServers.LoadServers(password)
|
||||
// server:1.3/libcwtch-go:1.4 accidentally enabled monitor logging by default. make sure it's turned off
|
||||
for _, onion := range servers {
|
||||
server := appServers.GetServer(onion)
|
||||
server.SetMonitorLogging(false)
|
||||
}
|
||||
return servers, err
|
||||
}
|
||||
|
||||
func (sf *ServersFunctionality) CreateServer(password string) (server.Server, error) {
|
||||
return appServers.CreateServer(password)
|
||||
}
|
||||
|
||||
func (sf *ServersFunctionality) GetServer(onion string) server.Server {
|
||||
return appServers.GetServer(onion)
|
||||
}
|
||||
|
||||
func (sf *ServersFunctionality) GetServerStatistics(onion string) server.Statistics {
|
||||
s := appServers.GetServer(onion)
|
||||
if s != nil {
|
||||
return s.GetStatistics()
|
||||
}
|
||||
return server.Statistics{}
|
||||
}
|
||||
|
||||
func (sf *ServersFunctionality) ListServers() []string {
|
||||
return appServers.ListServers()
|
||||
}
|
||||
|
||||
func (sf *ServersFunctionality) DeleteServer(onion string, currentPassword string) error {
|
||||
return appServers.DeleteServer(onion, currentPassword)
|
||||
}
|
||||
|
||||
func (sf *ServersFunctionality) LaunchServer(onion string) {
|
||||
appServers.LaunchServer(onion)
|
||||
server := appServers.GetServer(onion)
|
||||
if server != nil {
|
||||
newStats := server.GetStatistics()
|
||||
publishFn(event.NewEventList(ServerStatsUpdate, event.Identity, onion, TotalMessages, strconv.Itoa(newStats.TotalMessages), Connections, strconv.Itoa(newStats.TotalConnections)))
|
||||
}
|
||||
}
|
||||
|
||||
func (sf *ServersFunctionality) StopServer(onion string) {
|
||||
appServers.StopServer(onion)
|
||||
}
|
||||
|
||||
func (sf *ServersFunctionality) DestroyServers() {
|
||||
appServers.Destroy()
|
||||
}
|
||||
|
||||
func (sf *ServersFunctionality) GetServerInfo(onion string) *ServerInfo {
|
||||
s := sf.GetServer(onion)
|
||||
var serverInfo ServerInfo
|
||||
serverInfo.Onion = s.Onion()
|
||||
serverInfo.ServerBundle = s.ServerBundle()
|
||||
serverInfo.Autostart = s.GetAttribute(server.AttrAutostart) == "true"
|
||||
running, _ := s.CheckStatus()
|
||||
serverInfo.Running = running
|
||||
serverInfo.Description = s.GetAttribute(server.AttrDescription)
|
||||
serverInfo.StorageType = s.GetAttribute(server.AttrStorageType)
|
||||
return &serverInfo
|
||||
}
|
||||
|
||||
func (si *ServerInfo) EnrichEvent(e *event.Event) {
|
||||
e.Data["Onion"] = si.Onion
|
||||
e.Data["ServerBundle"] = si.ServerBundle
|
||||
e.Data["Description"] = si.Description
|
||||
e.Data["StorageType"] = si.StorageType
|
||||
if si.Autostart {
|
||||
e.Data["Autostart"] = "true"
|
||||
} else {
|
||||
e.Data["Autostart"] = "false"
|
||||
}
|
||||
if si.Running {
|
||||
e.Data["Running"] = "true"
|
||||
} else {
|
||||
e.Data["Running"] = "false"
|
||||
}
|
||||
}
|
||||
|
||||
// cacheForwardServerMetricUpdates every minute gets metrics for all servers, and if they have changed, sends events to the UI
|
||||
func cacheForwardServerMetricUpdates() {
|
||||
var cache map[string]server.Statistics = make(map[string]server.Statistics)
|
||||
duration := time.Second // allow first load
|
||||
for {
|
||||
select {
|
||||
case <-time.After(duration):
|
||||
duration = time.Minute
|
||||
serverList := appServers.ListServers()
|
||||
for _, serverOnion := range serverList {
|
||||
server := appServers.GetServer(serverOnion)
|
||||
if running, err := server.CheckStatus(); running && err == nil {
|
||||
newStats := server.GetStatistics()
|
||||
if stats, ok := cache[serverOnion]; !ok || stats.TotalConnections != newStats.TotalConnections || stats.TotalMessages != newStats.TotalMessages {
|
||||
cache[serverOnion] = newStats
|
||||
publishFn(event.NewEventList(ServerStatsUpdate, event.Identity, serverOnion, TotalMessages, strconv.Itoa(newStats.TotalMessages), Connections, strconv.Itoa(newStats.TotalConnections)))
|
||||
}
|
||||
}
|
||||
}
|
||||
case <-killStatsUpdate:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Shutdown() {
|
||||
Disable()
|
||||
appServers.Destroy()
|
||||
}
|
|
@ -49,6 +49,8 @@ func main() {
|
|||
|
||||
case "app":
|
||||
generatedBindings = generateAppFunction(generatedBindings, fName, parts[2:])
|
||||
case "(json)app":
|
||||
generatedBindings = generateJsonAppFunction(generatedBindings, fName, parts[2:])
|
||||
case "profile":
|
||||
generatedBindings = generateProfileFunction(generatedBindings, fName, parts[2:])
|
||||
case "(json)profile":
|
||||
|
@ -233,6 +235,30 @@ func {{FNAME}}({{GO_ARGS_SPEC}}) {
|
|||
return bindings
|
||||
}
|
||||
|
||||
func generateJsonAppFunction(bindings string, name string, argsTypes []string) string {
|
||||
appPrototype := `
|
||||
//export c_{{FNAME}}
|
||||
func c_{{FNAME}}({{C_ARGS}}) *C.char {
|
||||
return C.CString({{FNAME}}({{C2GO_ARGS}}))
|
||||
}
|
||||
|
||||
func {{FNAME}}({{GO_ARGS_SPEC}}) string {
|
||||
return application.{{LIBNAME}}({{GO_ARG}})
|
||||
}
|
||||
`
|
||||
|
||||
cArgs, c2GoArgs, goSpec, gUse := mapArgs(argsTypes)
|
||||
appPrototype = strings.ReplaceAll(appPrototype, "{{FNAME}}", strings.TrimPrefix(name, "Enhanced"))
|
||||
appPrototype = strings.ReplaceAll(appPrototype, "{{LIBNAME}}", name)
|
||||
appPrototype = strings.ReplaceAll(appPrototype, "{{C_ARGS}}", cArgs)
|
||||
appPrototype = strings.ReplaceAll(appPrototype, "{{C2GO_ARGS}}", c2GoArgs)
|
||||
appPrototype = strings.ReplaceAll(appPrototype, "{{GO_ARGS_SPEC}}", goSpec)
|
||||
appPrototype = strings.ReplaceAll(appPrototype, "{{GO_ARG}}", gUse)
|
||||
|
||||
bindings += appPrototype
|
||||
return bindings
|
||||
}
|
||||
|
||||
func generateProfileFunction(bindings string, name string, argsTypes []string) string {
|
||||
appPrototype := `
|
||||
//export c_{{FNAME}}
|
||||
|
|
2
go.mod
2
go.mod
|
@ -10,6 +10,8 @@ require (
|
|||
github.com/mutecomm/go-sqlcipher/v4 v4.4.2
|
||||
)
|
||||
|
||||
replace cwtch.im/cwtch => /home/sarah/workspace/src/cwtch.im/cwtch
|
||||
|
||||
require (
|
||||
filippo.io/edwards25519 v1.0.0 // indirect
|
||||
git.openprivacy.ca/cwtch.im/tapir v0.6.0 // indirect
|
||||
|
|
18
go.sum
18
go.sum
|
@ -1,16 +1,8 @@
|
|||
cwtch.im/cwtch v0.18.0/go.mod h1:StheazFFY7PKqBbEyDVLhzWW6WOat41zV0ckC240c5Y=
|
||||
cwtch.im/cwtch v0.18.10-0.20230221235514-49e0d849fa3e h1:5Gu6fKJNcTR7zzNj/JBtdrZqtkGh4eP/FQPwrF6sY5A=
|
||||
cwtch.im/cwtch v0.18.10-0.20230221235514-49e0d849fa3e/go.mod h1:h8S7EgEM+8pE1k+XLB5jAFdIPlOzwoXEY0GH5mQye5A=
|
||||
cwtch.im/cwtch v0.18.10-0.20230225151912-b22b8f329771 h1:Rc+3E0FGDbia35//hI/dH4feTww/w2gz4/2nHYI8sq0=
|
||||
cwtch.im/cwtch v0.18.10-0.20230225151912-b22b8f329771/go.mod h1:h8S7EgEM+8pE1k+XLB5jAFdIPlOzwoXEY0GH5mQye5A=
|
||||
cwtch.im/cwtch v0.18.10-0.20230227200154-03da50b85054 h1:PeEVr0+KSqJlL41UkCRLKIo8DJRTWKlbxsvXacp/Z+U=
|
||||
cwtch.im/cwtch v0.18.10-0.20230227200154-03da50b85054/go.mod h1:h8S7EgEM+8pE1k+XLB5jAFdIPlOzwoXEY0GH5mQye5A=
|
||||
cwtch.im/cwtch v0.18.10-0.20230227200719-c90301bb4cfa h1:U+zbOAapOb1SAaoFuBywi9wb3e8/xcpZSNBry6EPfj4=
|
||||
cwtch.im/cwtch v0.18.10-0.20230227200719-c90301bb4cfa/go.mod h1:h8S7EgEM+8pE1k+XLB5jAFdIPlOzwoXEY0GH5mQye5A=
|
||||
cwtch.im/cwtch v0.18.10-0.20230227213132-dfaed356c491 h1:pzzWnyXQHsnKDNHhG+JRTlDg3Eit62grfkM6KeUiv8g=
|
||||
cwtch.im/cwtch v0.18.10-0.20230227213132-dfaed356c491/go.mod h1:h8S7EgEM+8pE1k+XLB5jAFdIPlOzwoXEY0GH5mQye5A=
|
||||
cwtch.im/cwtch v0.18.10 h1:iTzLzlms1mgn8kLfClU/yAWIVWVRRT8UmfbDNli9dzE=
|
||||
cwtch.im/cwtch v0.18.10/go.mod h1:h8S7EgEM+8pE1k+XLB5jAFdIPlOzwoXEY0GH5mQye5A=
|
||||
cwtch.im/cwtch v0.18.10-0.20230227220552-0f83fb79f0c7 h1:hHKB6bPgYPwjOBGg3JR/3ovmSys1rKs6nsnpkrXslbU=
|
||||
cwtch.im/cwtch v0.18.10-0.20230227220552-0f83fb79f0c7/go.mod h1:h8S7EgEM+8pE1k+XLB5jAFdIPlOzwoXEY0GH5mQye5A=
|
||||
filippo.io/edwards25519 v1.0.0-rc.1/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns=
|
||||
filippo.io/edwards25519 v1.0.0 h1:0wAIcmJUqRdI8IJ/3eGi5/HwXZWPujYXXlkrQogz0Ek=
|
||||
filippo.io/edwards25519 v1.0.0/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns=
|
||||
|
@ -100,8 +92,12 @@ golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPh
|
|||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d h1:3qF+Z8Hkrw9sOhrFHti9TlB1Hkac1x+DNRkv0XQiFjo=
|
||||
golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/mobile v0.0.0-20221110043201-43a038452099 h1:aIu0lKmfdgtn2uTj7JI2oN4TUrQvgB+wzTPO23bCKt8=
|
||||
golang.org/x/mobile v0.0.0-20221110043201-43a038452099/go.mod h1:aAjjkJNdrh3PMckS4B10TGS2nag27cbKR1y2BpUxsiY=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
|
@ -152,6 +148,8 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
|
|||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
|
||||
golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
|
6
spec
6
spec
|
@ -8,7 +8,7 @@ app DeactivatePeerEngine profile
|
|||
app CreateProfile name password bool:autostart
|
||||
app LoadProfiles password
|
||||
app DeleteProfile profile password
|
||||
app ImportProfile string:file password
|
||||
(json)app EnhancedImportProfile string:file password
|
||||
profile ChangePassword string:current string:newPassword string:newPasswordAgain
|
||||
profile ExportProfile string:file
|
||||
|
||||
|
@ -40,3 +40,7 @@ import "cwtch.im/cwtch/functionality/filesharing"
|
|||
@profile-experiment VerifyOrResumeDownload filesharing conversation string:filekey
|
||||
@(json)profile-experiment EnhancedShareFile filesharing conversation string:filepath
|
||||
@(json)profile-experiment EnhancedGetSharedFiles filesharing conversation
|
||||
|
||||
|
||||
# Server Hosting Experiment
|
||||
# app-experiment ServerFunctionality
|
|
@ -8,9 +8,14 @@ import "C"
|
|||
|
||||
import (
|
||||
"cwtch.im/cwtch/event"
|
||||
"cwtch.im/cwtch/model/attr"
|
||||
"cwtch.im/cwtch/model/constants"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"git.openprivacy.ca/cwtch.im/cwtch-autobindings/experiments/servers"
|
||||
"git.openprivacy.ca/cwtch.im/cwtch-autobindings/utils"
|
||||
_ "git.openprivacy.ca/cwtch.im/server"
|
||||
"git.openprivacy.ca/openprivacy/connectivity/tor"
|
||||
"git.openprivacy.ca/openprivacy/log"
|
||||
"os"
|
||||
"os/user"
|
||||
|
@ -18,11 +23,11 @@ import (
|
|||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
mrand "math/rand"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"unsafe"
|
||||
|
||||
// Import SQL Cipher
|
||||
_ "github.com/mutecomm/go-sqlcipher/v4"
|
||||
|
||||
"cwtch.im/cwtch/app"
|
||||
"git.openprivacy.ca/openprivacy/connectivity"
|
||||
|
||||
|
@ -121,9 +126,9 @@ func _startCwtch(appDir string, torPath string) {
|
|||
}
|
||||
|
||||
log.Infof("Creating new EventHandler()")
|
||||
|
||||
eventHandler = utils.NewEventHandler()
|
||||
|
||||
|
||||
// Exclude Tapir wire Messages
|
||||
//(We need a TRACE level)
|
||||
log.ExcludeFromPattern("service.go")
|
||||
|
@ -187,6 +192,11 @@ func _startCwtch(appDir string, torPath string) {
|
|||
application.GetPrimaryBus().Publish(event.NewEvent(app.CwtchStarted, map[event.Field]string{}))
|
||||
application.QueryACNVersion()
|
||||
application.LoadProfiles(app.DefactoPasswordForUnencryptedProfiles)
|
||||
|
||||
serverExperiment = servers.InitServers(&globalACN, appDir)
|
||||
eventHandler.AddModule(serverExperiment)
|
||||
serverExperiment.Enable(application)
|
||||
serverExperiment.LoadServers(application, &globalACN, app.DefactoPasswordForUnencryptedProfiles)
|
||||
}
|
||||
|
||||
// the pointer returned from this function **must** be freed using c_Free
|
||||
|
@ -255,6 +265,114 @@ func ShutdownCwtch() {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: At some point these functions should also be autogenerated
|
||||
|
||||
// Attribute is a struct to return the dual values of an attempt at a Get*Attribute API call, meant to be json serialized
|
||||
type Attribute struct {
|
||||
Exists bool
|
||||
Value string
|
||||
}
|
||||
|
||||
//export c_SetProfileAttribute
|
||||
func c_SetProfileAttribute(profile_ptr *C.char, profile_len C.int, key_ptr *C.char, key_len C.int, val_ptr *C.char, val_len C.int) {
|
||||
profileOnion := C.GoStringN(profile_ptr, profile_len)
|
||||
key := C.GoStringN(key_ptr, key_len)
|
||||
value := C.GoStringN(val_ptr, val_len)
|
||||
SetProfileAttribute(profileOnion, key, value)
|
||||
}
|
||||
|
||||
// SetProfileAttribute provides a wrapper around profile.SetScopedZonedAttribute
|
||||
// WARNING: Because this function is potentially dangerous all keys and zones must be added
|
||||
// explicitly. If you are attempting to added behaviour to the UI that requires the existence of new keys
|
||||
// you probably want to be building out functionality/subsystem in the UI itself.
|
||||
// Key must have the format zone.key where Zone is defined in Cwtch. Unknown zones are not permitted.
|
||||
func SetProfileAttribute(profileOnion string, key string, value string) {
|
||||
profile := application.GetPeer(profileOnion)
|
||||
if profile != nil {
|
||||
zone, key := attr.ParseZone(key)
|
||||
|
||||
// TODO We only allow public.profile.key to be set for now.
|
||||
// All other scopes and zones need to be added explicitly or handled by Cwtch.
|
||||
if zone == attr.ProfileZone && key == constants.Name {
|
||||
profile.SetScopedZonedAttribute(attr.PublicScope, attr.ProfileZone, constants.Name, value)
|
||||
} else if zone == attr.ProfileZone && key == constants.PeerAutostart {
|
||||
profile.SetScopedZonedAttribute(attr.LocalScope, attr.ProfileZone, constants.PeerAutostart, value)
|
||||
} else {
|
||||
log.Errorf("attempted to set an attribute with an unknown zone: %v", key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//export c_GetProfileAttribute
|
||||
func c_GetProfileAttribute(profile_ptr *C.char, profile_len C.int, key_ptr *C.char, key_len C.int) *C.char {
|
||||
profileOnion := C.GoStringN(profile_ptr, profile_len)
|
||||
key := C.GoStringN(key_ptr, key_len)
|
||||
return C.CString(GetProfileAttribute(profileOnion, key))
|
||||
}
|
||||
|
||||
// GetProfileAttribute provides a wrapper around profile.GetScopedZonedAttribute
|
||||
// Key must have the format zone.key where Zone is defined in Cwtch. Unknown zones are not permitted.
|
||||
// Currently forcing the Public Scope
|
||||
// Returns json of Attribute
|
||||
func GetProfileAttribute(profileOnion string, key string) string {
|
||||
profile := application.GetPeer(profileOnion)
|
||||
if profile != nil {
|
||||
zone, key := attr.ParseZone(key)
|
||||
|
||||
res, exists := profile.GetScopedZonedAttribute(attr.PublicScope, zone, key)
|
||||
attr := Attribute{exists, res}
|
||||
json, _ := json.Marshal(attr)
|
||||
return string(json)
|
||||
|
||||
}
|
||||
empty := Attribute{false, ""}
|
||||
json, _ := json.Marshal(empty)
|
||||
return (string(json))
|
||||
}
|
||||
|
||||
//export c_SetConversationAttribute
|
||||
func c_SetConversationAttribute(profile_ptr *C.char, profile_len C.int, conversation_id C.int, key_ptr *C.char, key_len C.int, val_ptr *C.char, val_len C.int) {
|
||||
profileOnion := C.GoStringN(profile_ptr, profile_len)
|
||||
key := C.GoStringN(key_ptr, key_len)
|
||||
value := C.GoStringN(val_ptr, val_len)
|
||||
SetConversationAttribute(profileOnion, int(conversation_id), key, value)
|
||||
}
|
||||
|
||||
// SetConversationAttribute provides a wrapper around profile.SetProfileAttribute
|
||||
// key is of format Zone.Key, and the API forces the Local Scope
|
||||
func SetConversationAttribute(profileOnion string, conversationID int, key string, value string) {
|
||||
profile := application.GetPeer(profileOnion)
|
||||
zone, key := attr.ParseZone(key)
|
||||
profile.SetConversationAttribute(conversationID, attr.LocalScope.ConstructScopedZonedPath(zone.ConstructZonedPath(key)), value)
|
||||
}
|
||||
|
||||
//export c_GetConversationAttribute
|
||||
func c_GetConversationAttribute(profile_ptr *C.char, profile_len C.int, conversation_id C.int, key_ptr *C.char, key_len C.int) *C.char {
|
||||
profileOnion := C.GoStringN(profile_ptr, profile_len)
|
||||
key := C.GoStringN(key_ptr, key_len)
|
||||
return C.CString(GetConversationAttribute(profileOnion, int(conversation_id), key))
|
||||
}
|
||||
|
||||
// GetGonversationAttribute provides a wrapper around profile.GetGonversationAttribute
|
||||
// key is of format Scope.Zone.Key
|
||||
// Returns json of an Attribute
|
||||
func GetConversationAttribute(profileOnion string, conversationID int, key string) string {
|
||||
profile := application.GetPeer(profileOnion)
|
||||
if profile != nil {
|
||||
scope, zonekey := attr.ParseScope(key)
|
||||
zone, key := attr.ParseZone(zonekey)
|
||||
|
||||
res, err := profile.GetConversationAttribute(conversationID, scope.ConstructScopedZonedPath(zone.ConstructZonedPath(key)))
|
||||
attr := Attribute{err == nil, res}
|
||||
json, _ := json.Marshal(attr)
|
||||
return string(json)
|
||||
}
|
||||
empty := Attribute{false, ""}
|
||||
json, _ := json.Marshal(empty)
|
||||
return (string(json))
|
||||
}
|
||||
|
||||
|
||||
//export c_ResetTor
|
||||
func c_ResetTor() {
|
||||
ResetTor()
|
||||
|
@ -277,6 +395,93 @@ func ResetTor() {
|
|||
log.Infof("Restarted")
|
||||
}
|
||||
|
||||
const (
|
||||
CwtchStarted = event.Type("CwtchStarted")
|
||||
CwtchStartError = event.Type("CwtchStartError")
|
||||
UpdateGlobalSettings = event.Type("UpdateGlobalSettings")
|
||||
)
|
||||
|
||||
func buildACN(settings app.GlobalSettings, torPath string, appDir string) (connectivity.ACN, app.GlobalSettings) {
|
||||
|
||||
mrand.Seed(int64(time.Now().Nanosecond()))
|
||||
socksPort := mrand.Intn(1000) + 9600
|
||||
controlPort := socksPort + 1
|
||||
|
||||
// generate a random password (actually random, stored in memory, for the control port)
|
||||
key := make([]byte, 64)
|
||||
_, err := rand.Read(key)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
log.Infof("making directory %v", appDir)
|
||||
err = os.MkdirAll(path.Join(appDir, "tor"), 0700)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("error creating tor data directory: %v. Aborting app start up", err)
|
||||
eventHandler.Push(event.NewEventList(CwtchStartError, event.Error, fmt.Sprintf("Error connecting to Tor: %v", err)))
|
||||
return &connectivity.ErrorACN{}, settings
|
||||
}
|
||||
|
||||
if settings.AllowAdvancedTorConfig {
|
||||
controlPort = settings.CustomControlPort
|
||||
socksPort = settings.CustomSocksPort
|
||||
}
|
||||
|
||||
torrc := tor.NewTorrc().WithSocksPort(socksPort).WithOnionTrafficOnly().WithControlPort(controlPort).WithHashedPassword(base64.StdEncoding.EncodeToString(key))
|
||||
// torrc.WithLog(path.Join(appDir, "tor", "tor.log"), tor.TorLogLevelNotice)
|
||||
if settings.UseCustomTorrc {
|
||||
customTorrc := settings.CustomTorrc
|
||||
torrc.WithCustom(strings.Split(customTorrc, "\n"))
|
||||
} else {
|
||||
// Fallback to showing the freshly generated torrc for this session.
|
||||
settings.CustomTorrc = torrc.Preview()
|
||||
settings.CustomControlPort = controlPort
|
||||
settings.CustomSocksPort = socksPort
|
||||
}
|
||||
|
||||
err = torrc.Build(path.Join(appDir, "tor", "torrc"))
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("error constructing torrc: %v", err)
|
||||
eventHandler.Push(event.NewEventList(CwtchStartError, event.Error, fmt.Sprintf("Error connecting to Tor: %v", err)))
|
||||
return &connectivity.ErrorACN{}, settings
|
||||
}
|
||||
|
||||
dataDir := settings.TorCacheDir
|
||||
if !settings.UseTorCache {
|
||||
|
||||
// purge data dir directories if we are not using them for a cache
|
||||
torDir := path.Join(appDir, "tor")
|
||||
files, err := path.Glob(path.Join(torDir, "data-dir-*"))
|
||||
if err != nil {
|
||||
log.Errorf("could not construct filesystem glob: %v", err)
|
||||
}
|
||||
for _, f := range files {
|
||||
if err := os.RemoveAll(f); err != nil {
|
||||
log.Errorf("could not remove data-dir: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if dataDir, err = os.MkdirTemp(torDir, "data-dir-"); err != nil {
|
||||
eventHandler.Push(event.NewEventList(CwtchStartError, event.Error, fmt.Sprintf("Error connecting to Tor: %v", err)))
|
||||
return &connectivity.ErrorACN{}, settings
|
||||
}
|
||||
}
|
||||
|
||||
// Persist Current Data Dir as Tor Cache...
|
||||
settings.TorCacheDir = dataDir
|
||||
|
||||
acn, err := tor.NewTorACNWithAuth(appDir, torPath, dataDir, controlPort, tor.HashedPasswordAuthenticator{Password: base64.StdEncoding.EncodeToString(key)})
|
||||
if err != nil {
|
||||
log.Errorf("Error connecting to Tor replacing with ErrorACN: %v\n", err)
|
||||
eventHandler.Push(event.NewEventList(CwtchStartError, event.Error, fmt.Sprintf("Error connecting to Tor: %v", err)))
|
||||
acn = &connectivity.ErrorACN{}
|
||||
}
|
||||
return acn, settings
|
||||
}
|
||||
|
||||
|
||||
//export c_UpdateSettings
|
||||
func c_UpdateSettings(json_ptr *C.char, json_len C.int) {
|
||||
settingsJson := C.GoStringN(json_ptr, json_len)
|
||||
|
@ -289,6 +494,92 @@ func UpdateSettings(settingsJson string) {
|
|||
application.UpdateSettings(newSettings)
|
||||
}
|
||||
|
||||
//***** Server APIs *****
|
||||
|
||||
var serverExperiment *servers.ServersFunctionality
|
||||
|
||||
//export c_LoadServers
|
||||
func c_LoadServers(passwordPtr *C.char, passwordLen C.int) {
|
||||
LoadServers(C.GoStringN(passwordPtr, passwordLen))
|
||||
}
|
||||
|
||||
func LoadServers(password string) {
|
||||
serverExperiment.LoadServers(application, &globalACN, password)
|
||||
}
|
||||
|
||||
//export c_CreateServer
|
||||
func c_CreateServer(passwordPtr *C.char, passwordLen C.int, descPtr *C.char, descLen C.int, autostart C.char) {
|
||||
CreateServer(C.GoStringN(passwordPtr, passwordLen), C.GoStringN(descPtr, descLen), autostart == 1)
|
||||
}
|
||||
|
||||
func CreateServer(password string, description string, autostart bool) {
|
||||
serverExperiment.CreateServer(application, password, description, autostart)
|
||||
}
|
||||
|
||||
//export c_DeleteServer
|
||||
func c_DeleteServer(onionPtr *C.char, onionLen C.int, currentPasswordPtr *C.char, currentPasswordLen C.int) {
|
||||
DeleteServer(C.GoStringN(onionPtr, onionLen), C.GoStringN(currentPasswordPtr, currentPasswordLen))
|
||||
}
|
||||
|
||||
func DeleteServer(onion string, currentPassword string) {
|
||||
serverExperiment.DeleteServer(application, onion, currentPassword)
|
||||
}
|
||||
|
||||
//export c_LaunchServers
|
||||
func c_LaunchServers() {
|
||||
LaunchServers()
|
||||
}
|
||||
|
||||
func LaunchServers() {
|
||||
serverExperiment.LaunchServers(application, &globalACN)
|
||||
}
|
||||
|
||||
//export c_LaunchServer
|
||||
func c_LaunchServer(onionPtr *C.char, onionLen C.int) {
|
||||
LaunchServer(C.GoStringN(onionPtr, onionLen))
|
||||
}
|
||||
|
||||
func LaunchServer(onion string) {
|
||||
serverExperiment.LaunchServer(application, onion)
|
||||
}
|
||||
|
||||
//export c_StopServer
|
||||
func c_StopServer(onionPtr *C.char, onionLen C.int) {
|
||||
StopServer(C.GoStringN(onionPtr, onionLen))
|
||||
}
|
||||
|
||||
func StopServer(onion string) {
|
||||
serverExperiment.StopServer(application, onion)
|
||||
}
|
||||
|
||||
//export c_StopServers
|
||||
func c_StopServers() {
|
||||
StopServers()
|
||||
}
|
||||
|
||||
func StopServers() {
|
||||
serverExperiment.StopServers(application)
|
||||
}
|
||||
|
||||
//export c_DestroyServers
|
||||
func c_DestroyServers() {
|
||||
DestroyServers()
|
||||
}
|
||||
|
||||
func DestroyServers() {
|
||||
serverExperiment.DestroyServers()
|
||||
}
|
||||
|
||||
//export c_SetServerAttribute
|
||||
func c_SetServerAttribute(onionPtr *C.char, onionLen C.int, keyPtr *C.char, keyLen C.int, valPtr *C.char, valLen C.int) {
|
||||
SetServerAttribute(C.GoStringN(onionPtr, onionLen), C.GoStringN(keyPtr, keyLen), C.GoStringN(valPtr, valLen))
|
||||
}
|
||||
|
||||
func SetServerAttribute(onion string, key string, val string) {
|
||||
serverExperiment.SetServerAttribute(application, onion, key, val)
|
||||
}
|
||||
|
||||
|
||||
{{BINDINGS}}
|
||||
|
||||
// Leave as is, needed by ffi
|
||||
|
|
|
@ -3,7 +3,7 @@ package utils
|
|||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"git.openprivacy.ca/cwtch.im/cwtch-autobindings/features/servers"
|
||||
"git.openprivacy.ca/cwtch.im/cwtch-autobindings/experiments/servers"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
|
@ -38,16 +38,25 @@ type EventHandler struct {
|
|||
app app.Application
|
||||
appBusQueue event.Queue
|
||||
profileEvents chan EventProfileEnvelope
|
||||
modules []EventHandlerInterface
|
||||
}
|
||||
|
||||
type EventHandlerInterface interface {
|
||||
OnACNStatusEvent(appl app.Application, e *event.Event)
|
||||
}
|
||||
|
||||
// We should be reading from profile events pretty quickly, but we make this buffer fairly large...
|
||||
const profileEventsBufferSize = 512
|
||||
|
||||
func NewEventHandler() *EventHandler {
|
||||
eh := &EventHandler{app: nil, appBusQueue: event.NewQueue(), profileEvents: make(chan EventProfileEnvelope, profileEventsBufferSize)}
|
||||
eh := &EventHandler{app: nil, appBusQueue: event.NewQueue(), profileEvents: make(chan EventProfileEnvelope, profileEventsBufferSize), modules: nil}
|
||||
return eh
|
||||
}
|
||||
|
||||
func (eh *EventHandler) AddModule(ehi EventHandlerInterface) {
|
||||
eh.modules = append(eh.modules, ehi)
|
||||
}
|
||||
|
||||
func (eh *EventHandler) HandleApp(application app.Application) {
|
||||
eh.app = application
|
||||
application.GetPrimaryBus().Subscribe(event.NewPeer, eh.appBusQueue)
|
||||
|
@ -119,6 +128,11 @@ func (eh *EventHandler) handleAppBusEvent(e *event.Event) string {
|
|||
}
|
||||
}
|
||||
acnStatus = newAcnStatus
|
||||
|
||||
for _, module := range eh.modules {
|
||||
module.OnACNStatusEvent(eh.app, e)
|
||||
}
|
||||
|
||||
case event.NewPeer:
|
||||
onion := e.Data[event.Identity]
|
||||
profile := eh.app.GetPeer(e.Data[event.Identity])
|
||||
|
|
Loading…
Reference in New Issue