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")
|
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 (
|
const (
|
||||||
// ConversationNotificationPolicyDefault enum for conversations indicating to use global notification policy
|
// ConversationNotificationPolicyDefault enum for conversations indicating to use global notification policy
|
||||||
ConversationNotificationPolicyDefault = "ConversationNotificationPolicy.Default"
|
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":
|
case "app":
|
||||||
generatedBindings = generateAppFunction(generatedBindings, fName, parts[2:])
|
generatedBindings = generateAppFunction(generatedBindings, fName, parts[2:])
|
||||||
|
case "(json)app":
|
||||||
|
generatedBindings = generateJsonAppFunction(generatedBindings, fName, parts[2:])
|
||||||
case "profile":
|
case "profile":
|
||||||
generatedBindings = generateProfileFunction(generatedBindings, fName, parts[2:])
|
generatedBindings = generateProfileFunction(generatedBindings, fName, parts[2:])
|
||||||
case "(json)profile":
|
case "(json)profile":
|
||||||
|
@ -233,6 +235,30 @@ func {{FNAME}}({{GO_ARGS_SPEC}}) {
|
||||||
return bindings
|
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 {
|
func generateProfileFunction(bindings string, name string, argsTypes []string) string {
|
||||||
appPrototype := `
|
appPrototype := `
|
||||||
//export c_{{FNAME}}
|
//export c_{{FNAME}}
|
||||||
|
|
2
go.mod
2
go.mod
|
@ -10,6 +10,8 @@ require (
|
||||||
github.com/mutecomm/go-sqlcipher/v4 v4.4.2
|
github.com/mutecomm/go-sqlcipher/v4 v4.4.2
|
||||||
)
|
)
|
||||||
|
|
||||||
|
replace cwtch.im/cwtch => /home/sarah/workspace/src/cwtch.im/cwtch
|
||||||
|
|
||||||
require (
|
require (
|
||||||
filippo.io/edwards25519 v1.0.0 // indirect
|
filippo.io/edwards25519 v1.0.0 // indirect
|
||||||
git.openprivacy.ca/cwtch.im/tapir v0.6.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.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 h1:pzzWnyXQHsnKDNHhG+JRTlDg3Eit62grfkM6KeUiv8g=
|
||||||
cwtch.im/cwtch v0.18.10-0.20230227213132-dfaed356c491/go.mod h1:h8S7EgEM+8pE1k+XLB5jAFdIPlOzwoXEY0GH5mQye5A=
|
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-0.20230227220552-0f83fb79f0c7 h1:hHKB6bPgYPwjOBGg3JR/3ovmSys1rKs6nsnpkrXslbU=
|
||||||
cwtch.im/cwtch v0.18.10/go.mod h1:h8S7EgEM+8pE1k+XLB5jAFdIPlOzwoXEY0GH5mQye5A=
|
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-rc.1/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns=
|
||||||
filippo.io/edwards25519 v1.0.0 h1:0wAIcmJUqRdI8IJ/3eGi5/HwXZWPujYXXlkrQogz0Ek=
|
filippo.io/edwards25519 v1.0.0 h1:0wAIcmJUqRdI8IJ/3eGi5/HwXZWPujYXXlkrQogz0Ek=
|
||||||
filippo.io/edwards25519 v1.0.0/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns=
|
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-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 h1:3qF+Z8Hkrw9sOhrFHti9TlB1Hkac1x+DNRkv0XQiFjo=
|
||||||
golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
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.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.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-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-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
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-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.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.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-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-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/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 CreateProfile name password bool:autostart
|
||||||
app LoadProfiles password
|
app LoadProfiles password
|
||||||
app DeleteProfile profile 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 ChangePassword string:current string:newPassword string:newPasswordAgain
|
||||||
profile ExportProfile string:file
|
profile ExportProfile string:file
|
||||||
|
|
||||||
|
@ -40,3 +40,7 @@ import "cwtch.im/cwtch/functionality/filesharing"
|
||||||
@profile-experiment VerifyOrResumeDownload filesharing conversation string:filekey
|
@profile-experiment VerifyOrResumeDownload filesharing conversation string:filekey
|
||||||
@(json)profile-experiment EnhancedShareFile filesharing conversation string:filepath
|
@(json)profile-experiment EnhancedShareFile filesharing conversation string:filepath
|
||||||
@(json)profile-experiment EnhancedGetSharedFiles filesharing conversation
|
@(json)profile-experiment EnhancedGetSharedFiles filesharing conversation
|
||||||
|
|
||||||
|
|
||||||
|
# Server Hosting Experiment
|
||||||
|
# app-experiment ServerFunctionality
|
|
@ -8,9 +8,14 @@ import "C"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"cwtch.im/cwtch/event"
|
"cwtch.im/cwtch/event"
|
||||||
|
"cwtch.im/cwtch/model/attr"
|
||||||
|
"cwtch.im/cwtch/model/constants"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"git.openprivacy.ca/cwtch.im/cwtch-autobindings/experiments/servers"
|
||||||
"git.openprivacy.ca/cwtch.im/cwtch-autobindings/utils"
|
"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"
|
"git.openprivacy.ca/openprivacy/log"
|
||||||
"os"
|
"os"
|
||||||
"os/user"
|
"os/user"
|
||||||
|
@ -18,11 +23,11 @@ import (
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
mrand "math/rand"
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/base64"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
// Import SQL Cipher
|
|
||||||
_ "github.com/mutecomm/go-sqlcipher/v4"
|
_ "github.com/mutecomm/go-sqlcipher/v4"
|
||||||
|
|
||||||
"cwtch.im/cwtch/app"
|
"cwtch.im/cwtch/app"
|
||||||
"git.openprivacy.ca/openprivacy/connectivity"
|
"git.openprivacy.ca/openprivacy/connectivity"
|
||||||
|
|
||||||
|
@ -121,9 +126,9 @@ func _startCwtch(appDir string, torPath string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Infof("Creating new EventHandler()")
|
log.Infof("Creating new EventHandler()")
|
||||||
|
|
||||||
eventHandler = utils.NewEventHandler()
|
eventHandler = utils.NewEventHandler()
|
||||||
|
|
||||||
|
|
||||||
// Exclude Tapir wire Messages
|
// Exclude Tapir wire Messages
|
||||||
//(We need a TRACE level)
|
//(We need a TRACE level)
|
||||||
log.ExcludeFromPattern("service.go")
|
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.GetPrimaryBus().Publish(event.NewEvent(app.CwtchStarted, map[event.Field]string{}))
|
||||||
application.QueryACNVersion()
|
application.QueryACNVersion()
|
||||||
application.LoadProfiles(app.DefactoPasswordForUnencryptedProfiles)
|
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
|
// 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
|
//export c_ResetTor
|
||||||
func c_ResetTor() {
|
func c_ResetTor() {
|
||||||
ResetTor()
|
ResetTor()
|
||||||
|
@ -277,6 +395,93 @@ func ResetTor() {
|
||||||
log.Infof("Restarted")
|
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
|
//export c_UpdateSettings
|
||||||
func c_UpdateSettings(json_ptr *C.char, json_len C.int) {
|
func c_UpdateSettings(json_ptr *C.char, json_len C.int) {
|
||||||
settingsJson := C.GoStringN(json_ptr, json_len)
|
settingsJson := C.GoStringN(json_ptr, json_len)
|
||||||
|
@ -289,6 +494,92 @@ func UpdateSettings(settingsJson string) {
|
||||||
application.UpdateSettings(newSettings)
|
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}}
|
{{BINDINGS}}
|
||||||
|
|
||||||
// Leave as is, needed by ffi
|
// Leave as is, needed by ffi
|
||||||
|
|
|
@ -3,7 +3,7 @@ package utils
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"git.openprivacy.ca/cwtch.im/cwtch-autobindings/features/servers"
|
"git.openprivacy.ca/cwtch.im/cwtch-autobindings/experiments/servers"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
@ -38,16 +38,25 @@ type EventHandler struct {
|
||||||
app app.Application
|
app app.Application
|
||||||
appBusQueue event.Queue
|
appBusQueue event.Queue
|
||||||
profileEvents chan EventProfileEnvelope
|
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...
|
// We should be reading from profile events pretty quickly, but we make this buffer fairly large...
|
||||||
const profileEventsBufferSize = 512
|
const profileEventsBufferSize = 512
|
||||||
|
|
||||||
func NewEventHandler() *EventHandler {
|
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
|
return eh
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (eh *EventHandler) AddModule(ehi EventHandlerInterface) {
|
||||||
|
eh.modules = append(eh.modules, ehi)
|
||||||
|
}
|
||||||
|
|
||||||
func (eh *EventHandler) HandleApp(application app.Application) {
|
func (eh *EventHandler) HandleApp(application app.Application) {
|
||||||
eh.app = application
|
eh.app = application
|
||||||
application.GetPrimaryBus().Subscribe(event.NewPeer, eh.appBusQueue)
|
application.GetPrimaryBus().Subscribe(event.NewPeer, eh.appBusQueue)
|
||||||
|
@ -119,6 +128,11 @@ func (eh *EventHandler) handleAppBusEvent(e *event.Event) string {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
acnStatus = newAcnStatus
|
acnStatus = newAcnStatus
|
||||||
|
|
||||||
|
for _, module := range eh.modules {
|
||||||
|
module.OnACNStatusEvent(eh.app, e)
|
||||||
|
}
|
||||||
|
|
||||||
case event.NewPeer:
|
case event.NewPeer:
|
||||||
onion := e.Data[event.Identity]
|
onion := e.Data[event.Identity]
|
||||||
profile := eh.app.GetPeer(e.Data[event.Identity])
|
profile := eh.app.GetPeer(e.Data[event.Identity])
|
||||||
|
|
Loading…
Reference in New Issue