2023-02-21 20:31:49 +00:00
//package cwtch
package main
// //Needed to invoke C.free
// #include <stdlib.h>
import "C"
import (
"cwtch.im/cwtch/event"
2023-02-28 17:56:09 +00:00
"cwtch.im/cwtch/model/attr"
"cwtch.im/cwtch/model/constants"
2023-03-13 20:12:50 +00:00
"cwtch.im/cwtch/settings"
2023-02-21 20:31:49 +00:00
"encoding/json"
"fmt"
"git.openprivacy.ca/cwtch.im/cwtch-autobindings/utils"
2023-02-28 17:56:09 +00:00
"git.openprivacy.ca/openprivacy/connectivity/tor"
2023-02-21 20:31:49 +00:00
"git.openprivacy.ca/openprivacy/log"
"os"
"os/user"
path "path/filepath"
"runtime"
2023-04-13 17:38:54 +00:00
"runtime/pprof"
2023-02-21 20:31:49 +00:00
"strings"
2024-02-12 18:48:47 +00:00
"strconv"
2023-02-21 20:31:49 +00:00
"time"
2023-02-28 17:56:09 +00:00
mrand "math/rand"
"crypto/rand"
"encoding/base64"
2023-02-21 20:31:49 +00:00
"unsafe"
_ "github.com/mutecomm/go-sqlcipher/v4"
"cwtch.im/cwtch/app"
"git.openprivacy.ca/openprivacy/connectivity"
2023-05-24 21:05:47 +00:00
"sync"
2023-02-21 20:31:49 +00:00
{ { IMPORTS } }
)
// supplied by make
var (
buildVer string
buildDate string
)
var application app . Application
var globalAppDir string
var globalTorPath string
var eventHandler * utils . EventHandler
var globalACN connectivity . ProxyACN
// Dangerous function. Should only be used as documented in `MEMORY.md`
//
//export c_FreePointer
func c_FreePointer ( ptr * C . char ) {
C . free ( unsafe . Pointer ( ptr ) )
}
//export c_Started
func c_Started ( ) C . int {
return C . int ( Started ( ) )
}
// Started returns 1 if application is initialized and 0 if it is null
func Started ( ) int {
if application == nil {
return 0
}
return 1
}
//export c_StartCwtch
func c_StartCwtch ( dir_c * C . char , len C . int , tor_c * C . char , torLen C . int ) C . int {
applicationDirectory := C . GoStringN ( dir_c , len )
torDirectory := C . GoStringN ( tor_c , torLen )
return C . int ( StartCwtch ( applicationDirectory , torDirectory ) )
}
// StartCwtch starts cwtch in the library and initlaizes all data structures
//
// GetAppbusEvents is always safe to use
// the rest of functions are unsafe until the CwtchStarted event has been received indicating StartCwtch has completed
// returns:
// message: CwtchStarted when start up is complete and app is safe to use
// CwtchStartError message when start up fails (includes event.Error data field)
func StartCwtch ( appDir string , torPath string ) int {
if logfile := os . Getenv ( "LOG_FILE" ) ; logfile != "" {
filelog , err := log . NewFile ( log . LevelInfo , logfile )
if err == nil {
filelog . SetUseColor ( false )
log . SetStd ( filelog )
} else {
// not so likely to be seen since we're usually creating file log in situations we can't access console logs...
log . Errorf ( "could not create file log: %v\n" , err )
}
}
if runtime . GOOS == "android" {
log . SetUseColor ( false )
}
log . SetLevel ( log . LevelInfo )
if logLevel := os . Getenv ( "LOG_LEVEL" ) ; strings . ToLower ( logLevel ) == "debug" {
log . SetLevel ( log . LevelDebug )
}
log . Infof ( "StartCwtch(...)" )
log . Debugf ( "builddate: %v buildver: %v" , buildDate , buildVer )
// Quick hack check that we're being called with the correct params
// On android a stale worker could be calling us with "last apps" directory. Best to abort fast so the app can make a new worker
if runtime . GOOS == "android" {
fh , err := os . Open ( torPath )
if err != nil {
log . Errorf ( "%v" , err )
log . Errorf ( "failed to stat tor, skipping StartCwtch(). potentially normal if the app was reinstalled or the device was restarted; this workorder should get canceled soon" )
return 1
}
_ = fh . Close ( )
}
go _startCwtch ( appDir , torPath )
return 0
}
func _startCwtch ( appDir string , torPath string ) {
log . Infof ( "application: %v eventHandler: %v" , application , eventHandler )
if application != nil {
log . Infof ( "_startCwtch detected existing application; resuming instead of relaunching" )
ReconnectCwtchForeground ( )
return
}
log . Infof ( "Creating new EventHandler()" )
eventHandler = utils . NewEventHandler ( )
2023-02-28 17:56:09 +00:00
2023-02-21 20:31:49 +00:00
// Exclude Tapir wire Messages
//(We need a TRACE level)
log . ExcludeFromPattern ( "service.go" )
// Environment variables don't get '~' expansion so if CWTCH_DIR was set, it likely needs manual handling
usr , _ := user . Current ( )
homeDir := usr . HomeDir
if appDir == "~" {
appDir = homeDir
} else if strings . HasPrefix ( appDir , "~/" ) {
appDir = path . Join ( homeDir , appDir [ 2 : ] )
}
// Ensure that the application directory exists...and then initialize settings..
err := os . MkdirAll ( appDir , 0700 )
if err != nil {
log . Errorf ( "Error creating appDir %v: %v\n" , appDir , err )
eventHandler . Push ( event . NewEventList ( CwtchStartError , event . Error , fmt . Sprintf ( "Error creating appDir %v: %v" , appDir , err ) ) )
return
}
log . Infof ( "Loading Cwtch Directory %v and tor path: %v" , appDir , torPath )
2023-03-13 20:12:50 +00:00
settings . InitGlobalSettingsFile ( appDir , app . DefactoPasswordForUnencryptedProfiles )
2023-02-21 20:31:49 +00:00
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
}
// Allow the user of a custom torrc
globalAppDir = appDir
globalTorPath = torPath
2023-03-01 17:48:34 +00:00
settingsFile := app . LoadAppSettings ( appDir )
2023-05-24 19:27:04 +00:00
// start with an Error ACN
2023-06-12 17:25:16 +00:00
erracn := connectivity . NewErrorACN ( fmt . Errorf ( "initializing tor" ) )
globalACN = connectivity . NewProxyACN ( & erracn )
2023-02-21 20:31:49 +00:00
application = app . NewApp ( & globalACN , appDir , settingsFile )
// Subscribe to all App Events...
eventHandler . HandleApp ( application )
// FIXME: This code exists to allow the Splash Screen test in the new UI integration tests to pass
// it doesn't actually fix the problem in theory, and we should get around to ensuring that application
// is safe to access even if shutdown is called concurrently...
if application == nil {
log . Errorf ( "startCwtch: primary application object has gone away. assuming application is closing." )
return
}
// Send global settings to the UI...
2023-05-24 19:27:04 +00:00
globalSettings := application . ReadSettings ( )
settingsJson , _ := json . Marshal ( globalSettings )
2023-03-13 20:12:50 +00:00
application . GetPrimaryBus ( ) . Publish ( event . NewEvent ( settings . UpdateGlobalSettings , map [ event . Field ] string { event . Data : string ( settingsJson ) } ) )
2023-02-21 20:31:49 +00:00
log . Infof ( "libcwtch-go application launched" )
2023-03-13 20:12:50 +00:00
application . GetPrimaryBus ( ) . Publish ( event . NewEvent ( settings . CwtchStarted , map [ event . Field ] string { } ) )
2023-02-21 20:31:49 +00:00
application . QueryACNVersion ( )
2023-02-28 17:56:09 +00:00
2023-03-01 17:48:34 +00:00
{ { EXPERIMENT_REGISTER } }
2023-05-24 19:27:04 +00:00
2023-05-24 21:05:47 +00:00
// Finally attempt to set up a proper Tor
// Note: ResetTor launches an internal goroutine so this is non-blocking...
ResetTor ( )
2023-02-21 20:31:49 +00:00
}
// the pointer returned from this function **must** be freed using c_Free
//
//export c_GetAppBusEvent
func c_GetAppBusEvent ( ) * C . char {
return C . CString ( GetAppBusEvent ( ) )
}
// GetAppBusEvent blocks until an event
func GetAppBusEvent ( ) string {
for eventHandler == nil {
log . Debugf ( "waiting for eventHandler != nil" )
time . Sleep ( time . Second )
}
var json = ""
for json == "" {
json = eventHandler . GetNextEvent ( )
}
return json
}
//export c_ReconnectCwtchForeground
func c_ReconnectCwtchForeground ( ) {
ReconnectCwtchForeground ( )
}
// Like StartCwtch, but StartCwtch has already been called so we don't need to restart Tor etc (probably)
// Do need to re-send initial state tho, eg profiles that are already loaded
func ReconnectCwtchForeground ( ) {
log . Infof ( "Reconnecting cwtch foreground" )
if application == nil {
log . Errorf ( "ReconnectCwtchForeground: Application is nil, presuming stale thread, EXITING Reconnect\n" )
return
}
2023-04-17 17:48:58 +00:00
// Repopulate the UI on Android by iterating through all loaded profiles and treating them as New Peer loads. The ReloadEvent field
// suppresses any listen/connection actions and simply sends updated attributes to the UI.
// The UI is designed in such a way that "NewPeer" events are treated as updates.
// TODO: if/when we break apart NewPeer into smaller chunks for the UI to fetch, the need to do this here will go away.
peerList := application . ListProfiles ( )
for _ , onion := range peerList {
eventHandler . Push ( event . NewEvent ( event . NewPeer , map [ event . Field ] string { event . Identity : onion , utils . ReloadEvent : event . True } ) )
}
2023-02-21 20:31:49 +00:00
settingsJson , _ := json . Marshal ( application . ReadSettings ( ) )
2023-03-13 20:12:50 +00:00
application . GetPrimaryBus ( ) . Publish ( event . NewEvent ( settings . UpdateGlobalSettings , map [ event . Field ] string { event . Data : string ( settingsJson ) } ) )
2024-02-27 02:01:46 +00:00
application . GetPrimaryBus ( ) . Publish ( event . NewEvent ( settings . CwtchStarted , map [ event . Field ] string { utils . ReloadEvent : event . True } ) )
2023-02-21 20:31:49 +00:00
application . QueryACNStatus ( )
application . QueryACNVersion ( )
}
//export c_ShutdownCwtch
func c_ShutdownCwtch ( ) {
ShutdownCwtch ( )
}
// ShutdownCwtch is a safe way to shutdown any active cwtch applications and associated ACNs
func ShutdownCwtch ( ) {
if application != nil {
// Kill the isolate
eventHandler . Push ( event . NewEvent ( event . Shutdown , map [ event . Field ] string { } ) )
// Allow for the shutdown events to go through and then purge everything else...
log . Infof ( "Shutting Down Application..." )
application . Shutdown ( )
log . Infof ( "Shutting Down ACN..." )
globalACN . Close ( )
log . Infof ( "Library Shutdown Complete!" )
// do not remove - important for state checks elsewhere
application = nil
eventHandler = nil
}
}
2023-02-28 17:56:09 +00:00
// 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 )
2023-04-03 18:19:45 +00:00
// TODO We only allow certain public.profile.key to be set for now.
2023-02-28 17:56:09 +00:00
// 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 )
2023-04-03 18:19:45 +00:00
} else if zone == attr . ProfileZone && key == constants . ProfileAttribute1 {
profile . SetScopedZonedAttribute ( attr . PublicScope , attr . ProfileZone , constants . ProfileAttribute1 , value )
} else if zone == attr . ProfileZone && key == constants . ProfileAttribute2 {
profile . SetScopedZonedAttribute ( attr . PublicScope , attr . ProfileZone , constants . ProfileAttribute2 , value )
} else if zone == attr . ProfileZone && key == constants . ProfileAttribute3 {
profile . SetScopedZonedAttribute ( attr . PublicScope , attr . ProfileZone , constants . ProfileAttribute3 , value )
2023-04-04 20:55:11 +00:00
} else if zone == attr . ProfileZone && key == constants . ProfileStatus {
profile . SetScopedZonedAttribute ( attr . PublicScope , attr . ProfileZone , constants . ProfileStatus , value )
2023-02-28 17:56:09 +00:00
} else if zone == attr . ProfileZone && key == constants . PeerAutostart {
profile . SetScopedZonedAttribute ( attr . LocalScope , attr . ProfileZone , constants . PeerAutostart , value )
2023-09-13 18:50:13 +00:00
} else if zone == attr . ProfileZone && key == constants . PeerAppearOffline {
profile . SetScopedZonedAttribute ( attr . LocalScope , attr . ProfileZone , constants . PeerAppearOffline , value )
2023-02-28 17:56:09 +00:00
} 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 ) )
}
2023-02-21 20:31:49 +00:00
//export c_ResetTor
func c_ResetTor ( ) {
ResetTor ( )
}
2023-05-24 21:05:47 +00:00
var torLock sync . Mutex
2023-02-21 20:31:49 +00:00
2023-05-24 21:05:47 +00:00
func ResetTor ( ) {
go func ( ) {
// prevent concurrent calls to this method...
torLock . Lock ( )
defer torLock . Unlock ( )
log . Infof ( "Replacing ACN with new Tor..." )
settings := application . ReadSettings ( )
globalACN . Close ( ) // we need to close first if dateDir is the same, otherwise buildACN can't launch tor.
newAcn , settings , err := buildACN ( settings , globalTorPath , globalAppDir )
// only update settings if successful.
if err == nil {
// Only update Tor specific settings...
currentSettings := application . ReadSettings ( )
currentSettings . TorCacheDir = settings . TorCacheDir
currentSettings . CustomControlPort = settings . CustomControlPort
currentSettings . CustomSocksPort = settings . CustomSocksPort
currentSettings . CustomTorrc = settings . CustomTorrc
application . UpdateSettings ( currentSettings )
// We need to update settings on reset as buildACN can alter settings, otherwise the next reset will be broken...
settings = application . ReadSettings ( )
settingsJson , _ := json . Marshal ( settings )
application . GetPrimaryBus ( ) . Publish ( event . NewEvent ( UpdateGlobalSettings , map [ event . Field ] string { event . Data : string ( settingsJson ) } ) )
2023-06-13 17:05:47 +00:00
}
// replace ACN regardlesss
globalACN . ReplaceACN ( newAcn )
application . QueryACNStatus ( )
application . QueryACNVersion ( )
2023-05-24 21:05:47 +00:00
log . Infof ( "Restarted" )
} ( )
2023-02-21 20:31:49 +00:00
}
2023-02-28 17:56:09 +00:00
const (
CwtchStarted = event . Type ( "CwtchStarted" )
CwtchStartError = event . Type ( "CwtchStartError" )
UpdateGlobalSettings = event . Type ( "UpdateGlobalSettings" )
)
2023-05-24 21:05:47 +00:00
func buildACN ( globalSettings settings . GlobalSettings , torPath string , appDir string ) ( connectivity . ACN , settings . GlobalSettings , error ) {
2023-02-28 17:56:09 +00:00
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 ) ) )
2023-06-12 17:17:02 +00:00
erracn := connectivity . NewErrorACN ( err )
return & erracn , globalSettings , err
2023-02-28 17:56:09 +00:00
}
2023-03-13 20:12:50 +00:00
if globalSettings . AllowAdvancedTorConfig {
controlPort = globalSettings . CustomControlPort
socksPort = globalSettings . CustomSocksPort
2023-02-28 17:56:09 +00:00
}
2023-04-04 22:49:09 +00:00
// Override Ports if on Tails...
if cwtchTails := os . Getenv ( "CWTCH_TAILS" ) ; strings . ToLower ( cwtchTails ) == "true" {
log . Infof ( "CWTCH_TAILS environment variable set... overriding tor config..." )
2024-02-12 18:48:47 +00:00
2023-04-04 22:49:09 +00:00
controlPort = 9051
2024-02-12 18:48:47 +00:00
// In tails 5.13 the control port was changed to 951
// so read the Tails Version File and if it exists...
b , err := os . ReadFile ( "/etc/amnesia/version" )
if err == nil {
// the file should start with the version followed
// by a space...
versionEnd := strings . Index ( string ( b ) , " " )
versionStr := string ( b ) [ : versionEnd ]
version , err := strconv . ParseFloat ( versionStr , 64 )
if err == nil {
log . Infof ( "Confirming Tails Version: %v" , version )
// assert the control port if we are at the dedicated version...
// we know this change happened sometime after 5.11
if version >= 5.13 {
controlPort = 951
}
} else {
log . Errorf ( "Unable to confirm Tails version. CWTCH_TAILS options may not function correctly." )
}
}
2023-04-04 22:49:09 +00:00
socksPort = 9050
globalSettings . CustomControlPort = controlPort
globalSettings . CustomSocksPort = socksPort
globalSettings . AllowAdvancedTorConfig = true
}
2023-02-28 17:56:09 +00:00
torrc := tor . NewTorrc ( ) . WithSocksPort ( socksPort ) . WithOnionTrafficOnly ( ) . WithControlPort ( controlPort ) . WithHashedPassword ( base64 . StdEncoding . EncodeToString ( key ) )
// torrc.WithLog(path.Join(appDir, "tor", "tor.log"), tor.TorLogLevelNotice)
2023-03-13 20:12:50 +00:00
if globalSettings . UseCustomTorrc {
customTorrc := globalSettings . CustomTorrc
2023-02-28 17:56:09 +00:00
torrc . WithCustom ( strings . Split ( customTorrc , "\n" ) )
} else {
// Fallback to showing the freshly generated torrc for this session.
2023-03-13 20:12:50 +00:00
globalSettings . CustomTorrc = torrc . Preview ( )
globalSettings . CustomControlPort = controlPort
globalSettings . CustomSocksPort = socksPort
2023-02-28 17:56:09 +00:00
}
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 ) ) )
2023-06-12 17:17:02 +00:00
erracn := connectivity . NewErrorACN ( err )
return & erracn , globalSettings , err
2023-02-28 17:56:09 +00:00
}
2023-03-13 20:12:50 +00:00
dataDir := globalSettings . TorCacheDir
if ! globalSettings . UseTorCache {
2023-02-28 17:56:09 +00:00
// 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 ) ) )
2023-06-12 17:17:02 +00:00
erracn := connectivity . NewErrorACN ( err )
return & erracn , globalSettings , err
2023-02-28 17:56:09 +00:00
}
}
// Persist Current Data Dir as Tor Cache...
2023-03-13 20:12:50 +00:00
globalSettings . TorCacheDir = dataDir
2023-02-28 17:56:09 +00:00
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 ) ) )
2023-06-12 17:17:02 +00:00
erracn := connectivity . NewErrorACN ( err )
acn = & erracn
2023-02-28 17:56:09 +00:00
}
2023-05-24 21:05:47 +00:00
return acn , globalSettings , err
2023-02-28 17:56:09 +00:00
}
2023-02-27 21:32:53 +00:00
//export c_UpdateSettings
func c_UpdateSettings ( json_ptr * C . char , json_len C . int ) {
settingsJson := C . GoStringN ( json_ptr , json_len )
UpdateSettings ( settingsJson )
}
func UpdateSettings ( settingsJson string ) {
2023-03-13 20:12:50 +00:00
var newSettings settings . GlobalSettings
2023-02-27 21:32:53 +00:00
json . Unmarshal ( [ ] byte ( settingsJson ) , & newSettings )
application . UpdateSettings ( newSettings )
2023-03-01 20:09:26 +00:00
{ { EXPERIMENT_UPDATESETTINGS } }
2023-02-27 21:32:53 +00:00
}
2023-04-13 17:38:54 +00:00
//export c_GetDebugInfo
func c_GetDebugInfo ( ) * C . char {
return C . CString ( GetDebugInfo ( ) )
}
type DebugInfo struct {
BuildVersion string
BuildDate string
HeapAllocated float64
HeapInUse float64
HeapReleased float64
HeapObjects uint64
NumThreads uint64
SystemMemory float64
}
func GetDebugInfo ( ) string {
var memstats runtime . MemStats
runtime . ReadMemStats ( & memstats )
const MegaByte = 1024.0 * 1024.0
debugInfo := new ( DebugInfo )
debugInfo . BuildVersion = buildVer
debugInfo . BuildDate = buildDate
debugInfo . HeapAllocated = float64 ( memstats . HeapAlloc ) / MegaByte
debugInfo . HeapObjects = memstats . HeapObjects
debugInfo . NumThreads = uint64 ( runtime . NumGoroutine ( ) )
debugInfo . HeapReleased = float64 ( memstats . HeapReleased ) / MegaByte
debugInfo . HeapInUse = float64 ( memstats . HeapInuse ) / MegaByte
debugInfo . SystemMemory = float64 ( memstats . Sys ) / MegaByte
if os . Getenv ( "CWTCH_PROFILE" ) == "1" {
pprof . Lookup ( "goroutine" ) . WriteTo ( os . Stdout , 1 )
f , _ := os . Create ( "mem.prof" )
pprof . WriteHeapProfile ( f )
}
data , _ := json . Marshal ( debugInfo )
return string ( data )
}
2023-02-28 17:56:09 +00:00
2023-02-21 20:31:49 +00:00
{ { BINDINGS } }
// Leave as is, needed by ffi
func main ( ) { }