2021-12-06 20:23:57 +00:00
//package cwtch
2021-06-24 22:30:46 +00:00
2021-12-06 20:23:57 +00:00
package main
2021-06-24 22:30:46 +00:00
2021-08-25 05:01:53 +00:00
// //Needed to invoke C.free
// #include <stdlib.h>
2021-06-24 22:30:46 +00:00
import "C"
2021-08-25 05:01:53 +00:00
2021-06-24 22:30:46 +00:00
import (
"crypto/rand"
2021-09-30 17:21:36 +00:00
"encoding/json"
"fmt"
2022-01-18 20:49:52 +00:00
"io/ioutil"
2022-01-18 20:53:55 +00:00
path "path/filepath"
2022-01-06 21:31:21 +00:00
"strconv"
2021-12-19 01:16:21 +00:00
constants2 "cwtch.im/cwtch/model/constants"
2021-12-18 01:06:54 +00:00
"git.openprivacy.ca/cwtch.im/libcwtch-go/features"
2021-11-18 23:44:21 +00:00
// Import SQL Cipher
2021-09-30 17:21:36 +00:00
"os/user"
"runtime"
"strings"
"unsafe"
2021-12-19 01:16:21 +00:00
_ "github.com/mutecomm/go-sqlcipher/v4"
2021-06-24 22:30:46 +00:00
"cwtch.im/cwtch/app"
"cwtch.im/cwtch/event"
2021-09-30 17:21:36 +00:00
"cwtch.im/cwtch/functionality/filesharing"
2021-06-24 22:30:46 +00:00
"cwtch.im/cwtch/model"
"cwtch.im/cwtch/model/attr"
"cwtch.im/cwtch/peer"
2021-08-05 23:29:20 +00:00
"git.openprivacy.ca/cwtch.im/libcwtch-go/constants"
"git.openprivacy.ca/cwtch.im/libcwtch-go/features/groups"
2021-11-01 16:10:56 +00:00
"git.openprivacy.ca/cwtch.im/libcwtch-go/features/servers"
2021-11-02 01:48:00 +00:00
"git.openprivacy.ca/cwtch.im/server"
2021-06-24 22:30:46 +00:00
"git.openprivacy.ca/openprivacy/connectivity"
2021-08-05 23:29:20 +00:00
"git.openprivacy.ca/cwtch.im/libcwtch-go/utils"
2021-06-24 22:30:46 +00:00
"encoding/base64"
mrand "math/rand"
"os"
"time"
2021-09-30 17:21:36 +00:00
"git.openprivacy.ca/openprivacy/connectivity/tor"
"git.openprivacy.ca/openprivacy/log"
2021-06-24 22:30:46 +00:00
)
const (
// ProfileOnion is an event field that contains the handle for a given profile.
// todo: this should probably be moved back into Cwtch, and renamed ProfileHandle (onions are too tor-specific)
ProfileOnion = event . Field ( "ProfileOnion" )
)
var application app . Application
2021-10-08 00:11:50 +00:00
var globalAppDir string
2022-01-12 22:02:50 +00:00
var globalTorPath string
2021-06-24 22:30:46 +00:00
var eventHandler * utils . EventHandler
2022-01-12 22:02:50 +00:00
var globalACN connectivity . ProxyACN
2021-06-24 22:30:46 +00:00
// ChatMessage API currently not officially documented, see
// https://git.openprivacy.ca/cwtch.im/secure-development-handbook/issues/3
// for latest updates for now
//
// A ChatMessage is the application-layer Cwtch message, delivered to the UI
// as serialized json.
type ChatMessage struct {
O int ` json:"o" `
D string ` json:"d" `
}
//export c_StartCwtch
2021-08-27 22:16:07 +00:00
func c_StartCwtch ( dir_c * C . char , len C . int , tor_c * C . char , torLen C . int ) C . int {
2021-10-15 17:59:42 +00:00
applicationDirectory := C . GoStringN ( dir_c , len )
torDirectory := C . GoStringN ( tor_c , torLen )
return C . int ( StartCwtch ( applicationDirectory , torDirectory ) )
2021-06-24 22:30:46 +00:00
}
// 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 {
2021-07-06 00:44:21 +00:00
if logfile := os . Getenv ( "LOG_FILE" ) ; logfile != "" {
filelog , err := log . NewFile ( log . LevelInfo , logfile )
if err == nil {
2021-10-15 17:59:42 +00:00
filelog . SetUseColor ( false )
2021-07-06 00:44:21 +00:00
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 )
}
}
2021-08-28 05:55:16 +00:00
if runtime . GOOS == "android" {
log . SetUseColor ( false )
}
2021-12-19 01:20:08 +00:00
log . SetLevel ( log . LevelInfo )
if logLevel := os . Getenv ( "LOG_LEVEL" ) ; strings . ToLower ( logLevel ) == "debug" {
log . SetLevel ( log . LevelDebug )
}
2021-06-24 22:30:46 +00:00
log . Infof ( "StartCwtch(...)" )
// 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 ) {
2022-01-12 22:11:35 +00:00
log . Infof ( "application: %v eventHandler: %v" , application , eventHandler )
2021-06-24 22:30:46 +00:00
if application != nil {
log . Infof ( "_startCwtch detected existing application; resuming instead of relaunching" )
ReconnectCwtchForeground ( )
return
}
2021-08-24 15:47:31 +00:00
log . Infof ( "Creating new EventHandler()" )
eventHandler = utils . NewEventHandler ( )
2021-06-24 22:30:46 +00:00
// Exclude Tapir wire Messages
//(We need a TRACE level)
log . ExcludeFromPattern ( "service.go" )
2021-07-07 22:57:57 +00:00
// 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 , "~/" ) {
2022-01-18 20:53:55 +00:00
appDir = path . Join ( homeDir , appDir [ 2 : ] )
2021-07-07 22:57:57 +00:00
}
2021-06-24 22:30:46 +00:00
// Ensure that the application directory exists...and then initialize settings..
2021-07-07 22:57:57 +00:00
err := os . MkdirAll ( appDir , 0700 )
if err != nil {
log . Errorf ( "Error creating appDir %v: %v\n" , appDir , err )
2021-11-26 01:59:00 +00:00
eventHandler . Push ( event . NewEventList ( utils . CwtchStartError , event . Error , fmt . Sprintf ( "Error creating appDir %v: %v" , appDir , err ) ) )
2021-07-07 22:57:57 +00:00
return
}
2021-10-15 17:59:42 +00:00
err = utils . InitGlobalSettingsFile ( appDir , constants . DefactoPasswordForUnencryptedProfiles )
if err != nil {
log . Errorf ( "error initializing global settings file %. Global settings might not be loaded or saves" , err )
}
2021-06-24 22:30:46 +00:00
log . Infof ( "Loading Cwtch Directory %v and tor path: %v" , appDir , torPath )
log . Infof ( "making directory %v" , appDir )
2022-01-18 20:53:55 +00:00
err = os . MkdirAll ( path . Join ( appDir , "tor" ) , 0700 )
2021-10-15 17:59:42 +00:00
if err != nil {
log . Errorf ( "error creating tor data directory: %v. Aborting app start up" , err )
2021-11-26 01:59:00 +00:00
eventHandler . Push ( event . NewEventList ( utils . CwtchStartError , event . Error , fmt . Sprintf ( "Error connecting to Tor: %v" , err ) ) )
2021-10-15 17:59:42 +00:00
return
}
2022-01-12 22:02:50 +00:00
// Allow the user of a custom torrc
settings := utils . ReadGlobalSettings ( )
2021-10-08 00:11:50 +00:00
globalAppDir = appDir
2022-01-12 22:02:50 +00:00
globalTorPath = torPath
globalACN = connectivity . NewProxyACN ( buildACN ( * settings , globalTorPath , globalAppDir ) )
application = app . NewApp ( & globalACN , appDir )
servers . InitServers ( & globalACN , appDir , eventHandler . Push )
2021-06-24 22:30:46 +00:00
2021-11-26 01:59:00 +00:00
eventHandler . HandleApp ( application )
2021-06-24 22:30:46 +00:00
peer . DefaultEventsToHandle = [ ] event . Type {
event . EncryptedGroupMessage ,
2021-12-06 20:22:49 +00:00
event . NewMessageFromPeerEngine ,
2021-06-24 22:30:46 +00:00
event . PeerAcknowledgement ,
event . PeerError ,
event . SendMessageToPeerError ,
event . SendMessageToGroupError ,
event . NewGetValMessageFromPeer ,
event . PeerStateChange ,
event . NewRetValMessageFromPeer ,
event . NewGroupInvite ,
event . ServerStateChange ,
event . ProtocolEngineStopped ,
event . RetryServerRequest ,
2021-09-30 17:21:36 +00:00
event . ManifestReceived ,
2021-11-04 21:10:21 +00:00
event . FileDownloaded ,
2021-06-24 22:30:46 +00:00
}
2022-01-12 22:02:50 +00:00
2022-01-18 20:49:52 +00:00
// Settings may have changed...
settings = utils . ReadGlobalSettings ( )
2021-06-24 22:30:46 +00:00
settingsJson , _ := json . Marshal ( settings )
2021-11-26 01:59:00 +00:00
application . LoadProfiles ( constants . DefactoPasswordForUnencryptedProfiles )
LoadServers ( constants . DefactoPasswordForUnencryptedProfiles )
serversHandler , err := servers . ExperimentGate ( utils . ReadGlobalSettings ( ) . Experiments )
if err == nil {
serversHandler . Enable ( )
}
2021-11-02 02:36:14 +00:00
publishLoadedServers ( )
2021-10-29 23:14:08 +00:00
LaunchServers ( )
2021-06-24 22:30:46 +00:00
// Send global settings to the UI...
application . GetPrimaryBus ( ) . Publish ( event . NewEvent ( utils . UpdateGlobalSettings , map [ event . Field ] string { event . Data : string ( settingsJson ) } ) )
log . Infof ( "libcwtch-go application launched" )
application . GetPrimaryBus ( ) . Publish ( event . NewEvent ( utils . CwtchStarted , map [ event . Field ] string { } ) )
application . QueryACNVersion ( )
}
2022-01-12 22:02:50 +00:00
func buildACN ( settings utils . GlobalSettings , torPath string , appDir string ) connectivity . ACN {
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 )
2022-01-18 20:53:55 +00:00
err = os . MkdirAll ( path . Join ( appDir , "tor" ) , 0700 )
2022-01-12 22:02:50 +00:00
if err != nil {
log . Errorf ( "error creating tor data directory: %v. Aborting app start up" , err )
eventHandler . Push ( event . NewEventList ( utils . CwtchStartError , event . Error , fmt . Sprintf ( "Error connecting to Tor: %v" , err ) ) )
return & connectivity . ErrorACN { }
}
if settings . AllowAdvancedTorConfig {
controlPort = settings . CustomControlPort
socksPort = settings . CustomSocksPort
}
torrc := tor . NewTorrc ( ) . WithSocksPort ( socksPort ) . WithOnionTrafficOnly ( ) . WithControlPort ( controlPort ) . WithHashedPassword ( base64 . StdEncoding . EncodeToString ( key ) )
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
utils . WriteGlobalSettings ( settings )
}
2022-01-18 20:53:55 +00:00
err = torrc . Build ( path . Join ( appDir , "tor" , "torrc" ) )
2022-01-12 22:02:50 +00:00
if err != nil {
log . Errorf ( "error constructing torrc: %v" , err )
eventHandler . Push ( event . NewEventList ( utils . CwtchStartError , event . Error , fmt . Sprintf ( "Error connecting to Tor: %v" , err ) ) )
return & connectivity . ErrorACN { }
}
2022-01-18 20:49:52 +00:00
dataDir := settings . TorCacheDir
if ! settings . UseTorCache {
// purge data dir directories if we are not using them for a cache
torDir := path . Join ( appDir , "tor" )
2022-01-18 20:53:55 +00:00
files , err := path . Glob ( path . Join ( torDir , "data-dir-*" ) )
2022-01-18 20:49:52 +00:00
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 = ioutil . TempDir ( torDir , "data-dir-" ) ; err != nil {
eventHandler . Push ( event . NewEventList ( utils . CwtchStartError , event . Error , fmt . Sprintf ( "Error connecting to Tor: %v" , err ) ) )
return & connectivity . ErrorACN { }
}
}
// Persist Current Data Dir as Tor Cache...
settings . TorCacheDir = dataDir
utils . WriteGlobalSettings ( settings )
acn , err := tor . NewTorACNWithAuth ( appDir , torPath , dataDir , controlPort , tor . HashedPasswordAuthenticator { Password : base64 . StdEncoding . EncodeToString ( key ) } )
2022-01-12 22:02:50 +00:00
if err != nil {
log . Errorf ( "Error connecting to Tor replacing with ErrorACN: %v\n" , err )
eventHandler . Push ( event . NewEventList ( utils . CwtchStartError , event . Error , fmt . Sprintf ( "Error connecting to Tor: %v" , err ) ) )
acn = & connectivity . ErrorACN { }
}
return acn
}
2021-06-24 22:30:46 +00:00
//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 cwtchforeground" )
if application == nil {
log . Errorf ( "ReconnectCwtchForeground: Application is nil, presuming stale thread, EXITING Reconnect\n" )
return
}
// populate profile list
2021-10-15 20:09:36 +00:00
peerList := application . ListProfiles ( )
for _ , onion := range peerList {
2022-01-06 21:31:21 +00:00
eventHandler . Push ( event . NewEvent ( event . NewPeer , map [ event . Field ] string { event . Identity : onion } ) )
2021-06-24 22:30:46 +00:00
}
2021-06-30 20:28:16 +00:00
settings := utils . ReadGlobalSettings ( )
2021-11-18 23:44:21 +00:00
groupHandler , _ := groups . ExperimentGate ( settings . Experiments )
2021-10-15 20:19:14 +00:00
for _ , profileOnion := range peerList {
2021-06-24 22:30:46 +00:00
// fix peerpeercontact message counts
2021-11-17 20:33:51 +00:00
profile := application . GetPeer ( profileOnion )
2021-11-18 23:44:21 +00:00
conversations , _ := profile . FetchConversations ( )
2021-11-17 20:33:51 +00:00
for _ , conversation := range conversations {
2022-01-18 20:49:52 +00:00
if ( conversation . IsGroup ( ) && groupHandler != nil ) || ! conversation . IsServer ( ) {
2021-11-18 23:44:21 +00:00
totalMessages , _ := profile . GetChannelMessageCount ( conversation . ID , 0 )
2021-06-30 20:28:16 +00:00
eventHandler . Push ( event . NewEvent ( event . MessageCounterResync , map [ event . Field ] string {
2021-11-18 23:44:21 +00:00
event . Identity : profileOnion ,
2021-11-17 20:33:51 +00:00
event . ConversationID : strconv . Itoa ( conversation . ID ) ,
2021-11-18 23:44:21 +00:00
event . Data : strconv . Itoa ( totalMessages ) ,
2021-06-30 20:28:16 +00:00
} ) )
}
2021-11-17 20:33:51 +00:00
}
2021-06-30 20:28:16 +00:00
2021-11-17 20:33:51 +00:00
// Group Experiment Server Refresh
if groupHandler != nil {
serverListForOnion := groupHandler . GetServerInfoList ( profile )
2021-06-30 20:28:16 +00:00
serversListBytes , _ := json . Marshal ( serverListForOnion )
eventHandler . Push ( event . NewEvent ( groups . UpdateServerInfo , map [ event . Field ] string { "ProfileOnion" : profileOnion , groups . ServerList : string ( serversListBytes ) } ) )
2021-06-24 22:30:46 +00:00
}
2021-06-30 20:28:16 +00:00
}
2021-06-24 22:30:46 +00:00
2021-11-26 01:59:00 +00:00
LoadServers ( constants . DefactoPasswordForUnencryptedProfiles )
2021-11-02 02:36:14 +00:00
publishLoadedServers ( )
2021-06-30 20:28:16 +00:00
settingsJson , _ := json . Marshal ( settings )
application . GetPrimaryBus ( ) . Publish ( event . NewEvent ( utils . UpdateGlobalSettings , map [ event . Field ] string { event . Data : string ( settingsJson ) } ) )
2021-06-24 22:30:46 +00:00
application . GetPrimaryBus ( ) . Publish ( event . NewEvent ( utils . CwtchStarted , map [ event . Field ] string { } ) )
application . QueryACNStatus ( )
application . QueryACNVersion ( )
}
2021-11-02 02:36:14 +00:00
func publishLoadedServers ( ) {
2021-10-29 23:14:08 +00:00
serversHandler , err := servers . ExperimentGate ( utils . ReadGlobalSettings ( ) . Experiments )
if err == nil {
serversList := serversHandler . ListServers ( )
for _ , server := range serversList {
2021-11-02 01:48:00 +00:00
serverInfo := serversHandler . GetServerInfo ( server )
ev := event . NewEvent ( servers . NewServer , make ( map [ event . Field ] string ) )
serverInfo . EnrichEvent ( & ev )
application . GetPrimaryBus ( ) . Publish ( ev )
2021-10-29 23:14:08 +00:00
}
2021-06-30 20:28:16 +00:00
}
2021-06-24 22:30:46 +00:00
}
//export c_SendAppEvent
// A generic method for Rebroadcasting App Events from a UI
func c_SendAppEvent ( json_ptr * C . char , json_len C . int ) {
eventJson := C . GoStringN ( json_ptr , json_len )
SendAppEvent ( eventJson )
}
// SendAppEvent is a generic method for Rebroadcasting App Events from a UI
func SendAppEvent ( eventJson string ) {
// Convert the Event Json back to a typed Event Struct, this will make the
// rest of the logic nicer.
var new_event event . Event
json . Unmarshal ( [ ] byte ( eventJson ) , & new_event )
2021-06-25 05:16:47 +00:00
log . Debugf ( "Event: %v" , new_event . EventType )
2021-06-24 22:30:46 +00:00
// We need to update the local cache
2021-06-25 05:24:10 +00:00
// Ideally I think this would be pushed back into Cwtch
2021-06-24 22:30:46 +00:00
switch new_event . EventType {
case utils . UpdateGlobalSettings :
var globalSettings utils . GlobalSettings
err := json . Unmarshal ( [ ] byte ( new_event . Data [ event . Data ] ) , & globalSettings )
if err != nil {
log . Errorf ( "Error Unmarshalling Settings %v [%v]" , err , new_event . Data [ event . Data ] )
}
log . Debugf ( "New Settings %v" , globalSettings )
utils . WriteGlobalSettings ( globalSettings )
2021-10-08 00:11:50 +00:00
settings := utils . ReadGlobalSettings ( )
2021-11-26 01:59:00 +00:00
sh , err := servers . ExperimentGate ( settings . Experiments )
2021-10-08 00:11:50 +00:00
if err == nil {
2022-01-12 22:02:50 +00:00
servers . InitServers ( & globalACN , globalAppDir , eventHandler . Push )
2021-11-26 01:59:00 +00:00
LoadServers ( constants . DefactoPasswordForUnencryptedProfiles )
if ! servers . Enabled ( ) {
sh . Enable ( )
publishLoadedServers ( )
LaunchServers ( )
}
2021-10-08 00:11:50 +00:00
} else {
2021-11-26 01:59:00 +00:00
servers . Disable ( )
2021-10-08 00:11:50 +00:00
}
2021-06-24 22:30:46 +00:00
// Group Experiment Refresh
2021-10-08 00:11:50 +00:00
groupHandler , err := groups . ExperimentGate ( settings . Experiments )
2021-06-24 22:30:46 +00:00
if err == nil {
2021-10-15 20:19:14 +00:00
for _ , profileOnion := range application . ListProfiles ( ) {
2021-06-24 22:30:46 +00:00
serverListForOnion := groupHandler . GetServerInfoList ( application . GetPeer ( profileOnion ) )
serversListBytes , _ := json . Marshal ( serverListForOnion )
eventHandler . Push ( event . NewEvent ( groups . UpdateServerInfo , map [ event . Field ] string { "ProfileOnion" : profileOnion , groups . ServerList : string ( serversListBytes ) } ) )
}
}
// Explicitly toggle blocking/unblocking of unknown connections for profiles
// that have been loaded.
2021-10-08 00:11:50 +00:00
if settings . BlockUnknownConnections {
2021-10-15 20:19:14 +00:00
for _ , onion := range application . ListProfiles ( ) {
2021-06-24 22:30:46 +00:00
application . GetPeer ( onion ) . BlockUnknownConnections ( )
}
} else {
2021-10-15 20:19:14 +00:00
for _ , onion := range application . ListProfiles ( ) {
2021-06-24 22:30:46 +00:00
application . GetPeer ( onion ) . AllowUnknownConnections ( )
}
}
case utils . SetLoggingLevel :
_ , warn := new_event . Data [ utils . Warn ]
2021-10-15 17:59:42 +00:00
_ , err := new_event . Data [ utils . Error ]
2021-06-24 22:30:46 +00:00
_ , debug := new_event . Data [ utils . Debug ]
_ , info := new_event . Data [ utils . Info ]
// Assign logging level in priority order. The highest logging level wins in the
// event of multiple fields.
if info {
log . SetLevel ( log . LevelInfo )
} else if warn {
log . SetLevel ( log . LevelWarn )
2021-10-15 17:59:42 +00:00
} else if err {
2021-06-24 22:30:46 +00:00
log . SetLevel ( log . LevelError )
} else if debug {
log . SetLevel ( log . LevelDebug )
}
default : // do nothing
}
}
//export c_SendProfileEvent
// A generic method for Rebroadcasting Profile Events from a UI
func c_SendProfileEvent ( onion_ptr * C . char , onion_len C . int , json_ptr * C . char , json_len C . int ) {
onion := C . GoStringN ( onion_ptr , onion_len )
eventJson := C . GoStringN ( json_ptr , json_len )
SendProfileEvent ( onion , eventJson )
}
const (
AddContact = event . Type ( "AddContact" )
ImportString = event . Field ( "ImportString" )
)
// SendProfileEvent is a generic method for Rebroadcasting Profile Events from a UI
2021-08-27 22:16:07 +00:00
// Should generally be used for rapidly prototyping new APIs
2021-06-24 22:30:46 +00:00
func SendProfileEvent ( onion string , eventJson string ) {
// Convert the Event Json back to a typed Event Struct, this will make the
// rest of the logic nicer.
var new_event event . Event
json . Unmarshal ( [ ] byte ( eventJson ) , & new_event )
log . Infof ( "Event: %v %v" , onion , new_event )
// Get the correct Peer
peer := application . GetPeer ( onion )
if peer == nil {
return
}
// We need to update the local cache
// Ideally I think this would be pushed back into Cwtch
switch new_event . EventType {
default :
// rebroadcast catch all
log . Infof ( "Received Event %v for %v but no libCwtch handler found, relaying the event directly" , new_event , onion )
application . GetEventBus ( onion ) . Publish ( new_event )
}
}
//export c_GetAppBusEvent
2021-08-25 05:01:53 +00:00
// the pointer returned from this function **must** be freed using c_Free
2021-06-24 22:30:46 +00:00
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_CreateProfile
func c_CreateProfile ( nick_ptr * C . char , nick_len C . int , pass_ptr * C . char , pass_len C . int ) {
CreateProfile ( C . GoStringN ( nick_ptr , nick_len ) , C . GoStringN ( pass_ptr , pass_len ) )
}
func CreateProfile ( nick , pass string ) {
if pass == constants . DefactoPasswordForUnencryptedProfiles {
application . CreateTaggedPeer ( nick , pass , constants . ProfileTypeV1DefaultPassword )
} else {
application . CreateTaggedPeer ( nick , pass , constants . ProfileTypeV1Password )
}
}
//export c_LoadProfiles
func c_LoadProfiles ( passwordPtr * C . char , passwordLen C . int ) {
LoadProfiles ( C . GoStringN ( passwordPtr , passwordLen ) )
}
func LoadProfiles ( pass string ) {
application . LoadProfiles ( pass )
}
2021-11-19 19:50:37 +00:00
//export c_AcceptConversation
func c_AcceptConversation ( profilePtr * C . char , profileLen C . int , conversation_id C . int ) {
AcceptConversation ( C . GoStringN ( profilePtr , profileLen ) , int ( conversation_id ) )
2021-06-24 22:30:46 +00:00
}
2021-11-23 22:30:16 +00:00
// AcceptConversation takes in a profileOnion and a handle to either a group or a peer and authorizes the handle
2021-06-24 22:30:46 +00:00
// for further action (e.g. messaging / connecting to the server / joining the group etc.)
2021-12-01 12:16:15 +00:00
func AcceptConversation ( profileOnion string , conversationID int ) {
2021-06-24 22:30:46 +00:00
profile := application . GetPeer ( profileOnion )
2021-12-01 12:16:15 +00:00
profile . AcceptConversation ( conversationID )
2021-06-24 22:30:46 +00:00
}
//export c_BlockContact
2021-11-17 22:34:35 +00:00
func c_BlockContact ( profilePtr * C . char , profileLen C . int , conversation_id C . int ) {
BlockContact ( C . GoStringN ( profilePtr , profileLen ) , int ( conversation_id ) )
2021-06-24 22:30:46 +00:00
}
2021-11-17 20:33:51 +00:00
func BlockContact ( profileOnion string , conversationID int ) {
profile := application . GetPeer ( profileOnion )
profile . BlockConversation ( conversationID )
2021-06-24 22:30:46 +00:00
}
2022-01-06 21:31:21 +00:00
//export c_UnblockContact
func c_UnblockContact ( profilePtr * C . char , profileLen C . int , conversation_id C . int ) {
UnblockContact ( C . GoStringN ( profilePtr , profileLen ) , int ( conversation_id ) )
}
func UnblockContact ( profileOnion string , conversationID int ) {
profile := application . GetPeer ( profileOnion )
profile . UnblockConversation ( conversationID )
}
2021-06-24 22:30:46 +00:00
//export c_GetMessage
2021-08-25 05:01:53 +00:00
// the pointer returned from this function **must** be Freed by c_Free
2021-11-17 22:34:35 +00:00
func c_GetMessage ( profile_ptr * C . char , profile_len C . int , conversation_id C . int , message_index C . int ) * C . char {
2021-06-24 22:30:46 +00:00
profile := C . GoStringN ( profile_ptr , profile_len )
2021-11-17 22:34:35 +00:00
return C . CString ( GetMessage ( profile , int ( conversation_id ) , int ( message_index ) ) )
2021-06-24 22:30:46 +00:00
}
// EnhancedMessage wraps a Cwtch model.Message with some additional data to reduce calls from the UI.
type EnhancedMessage struct {
model . Message
2021-11-18 23:44:21 +00:00
ID int // the actual ID of the message in the database (not the row number)
2022-01-20 05:51:19 +00:00
LocalIndex int // local index in the DB (row #). Can be empty (most calls supply it) but lookup by hash will fill it
2022-01-20 17:37:39 +00:00
ContentHash string
2021-06-24 22:30:46 +00:00
ContactImage string
2021-12-01 12:16:15 +00:00
Attributes map [ string ] string
2021-06-24 22:30:46 +00:00
}
2021-11-17 22:34:35 +00:00
func GetMessage ( profileOnion string , conversationID int , messageIndex int ) string {
2021-06-24 22:30:46 +00:00
var message EnhancedMessage
2021-06-30 18:10:08 +00:00
// There is an edge case that can happen on Android when the app is shutdown while fetching messages...
// The worker threads that are spawned can become activated again when the app is opened attempt to finish their job...
// In that case we skip processing and just return the empty message...
// Note: This is far less likely to happen now that the UI only requests messages *after* syncing has happened and
// these requests complete almost immediately v.s. being stalled for seconds to minutes on large groups.
if application != nil {
profile := application . GetPeer ( profileOnion )
2021-11-18 23:44:21 +00:00
messages , err := profile . GetMostRecentMessages ( conversationID , 0 , messageIndex , 1 )
if err == nil && len ( messages ) == 1 {
time , _ := time . Parse ( time . RFC3339Nano , messages [ 0 ] . Attr [ constants2 . AttrSentTimestamp ] )
2021-11-17 22:34:35 +00:00
message . Message = model . Message {
2021-11-18 23:44:21 +00:00
Message : messages [ 0 ] . Body ,
2021-11-17 22:34:35 +00:00
Acknowledged : messages [ 0 ] . Attr [ constants2 . AttrAck ] == constants2 . True ,
2021-11-18 23:44:21 +00:00
Error : messages [ 0 ] . Attr [ constants2 . AttrErr ] ,
PeerID : messages [ 0 ] . Attr [ constants2 . AttrAuthor ] ,
Timestamp : time ,
2021-06-30 18:10:08 +00:00
}
2021-11-18 23:44:21 +00:00
message . ID = messages [ 0 ] . ID
2021-12-01 12:16:15 +00:00
message . Attributes = messages [ 0 ] . Attr
2021-11-18 23:44:21 +00:00
message . ContactImage = utils . RandomProfileImage ( message . PeerID )
2022-01-20 17:37:39 +00:00
message . ContentHash = model . CalculateContentHash ( messages [ 0 ] . Attr [ constants2 . AttrAuthor ] , messages [ 0 ] . Body )
2021-06-24 22:30:46 +00:00
}
}
bytes , _ := json . Marshal ( message )
return string ( bytes )
}
2021-11-26 22:26:26 +00:00
//export c_GetMessageByID
// the pointer returned from this function **must** be Freed by c_Free
func c_GetMessageByID ( profile_ptr * C . char , profile_len C . int , conversation_id C . int , message_index C . int ) * C . char {
profile := C . GoStringN ( profile_ptr , profile_len )
return C . CString ( GetMessageByID ( profile , int ( conversation_id ) , int ( message_index ) ) )
}
2022-01-20 05:51:19 +00:00
func GetMessageByID ( profileOnion string , conversationID int , messageID int ) string {
2021-11-26 22:26:26 +00:00
var message EnhancedMessage
// There is an edge case that can happen on Android when the app is shutdown while fetching messages...
// The worker threads that are spawned can become activated again when the app is opened attempt to finish their job...
// In that case we skip processing and just return the empty message...
// Note: This is far less likely to happen now that the UI only requests messages *after* syncing has happened and
// these requests complete almost immediately v.s. being stalled for seconds to minutes on large groups.
if application != nil {
profile := application . GetPeer ( profileOnion )
2022-01-20 05:51:19 +00:00
dbmessage , attr , err := profile . GetChannelMessage ( conversationID , 0 , messageID )
2021-11-26 22:26:26 +00:00
if err == nil {
time , _ := time . Parse ( time . RFC3339Nano , attr [ constants2 . AttrSentTimestamp ] )
message . Message = model . Message {
Message : dbmessage ,
Acknowledged : attr [ constants2 . AttrAck ] == constants2 . True ,
Error : attr [ constants2 . AttrErr ] ,
PeerID : attr [ constants2 . AttrAuthor ] ,
Timestamp : time ,
}
2022-01-20 05:51:19 +00:00
message . ID = messageID
2021-12-01 12:16:15 +00:00
message . Attributes = attr
2021-11-26 22:26:26 +00:00
message . ContactImage = utils . RandomProfileImage ( message . PeerID )
2022-01-20 17:37:39 +00:00
message . ContentHash = model . CalculateContentHash ( attr [ constants2 . AttrAuthor ] , dbmessage )
2021-11-26 22:26:26 +00:00
}
}
bytes , _ := json . Marshal ( message )
return string ( bytes )
}
2021-07-06 19:49:47 +00:00
//export c_GetMessagesByContentHash
2021-08-25 05:01:53 +00:00
// the pointer returned from this function **must** be freed by calling c_Free
2021-11-17 22:34:35 +00:00
func c_GetMessagesByContentHash ( profile_ptr * C . char , profile_len C . int , conversation_id C . int , contenthash_ptr * C . char , contenthash_len C . int ) * C . char {
2021-07-06 19:49:47 +00:00
profile := C . GoStringN ( profile_ptr , profile_len )
contentHash := C . GoStringN ( contenthash_ptr , contenthash_len )
2021-11-17 22:34:35 +00:00
return C . CString ( GetMessagesByContentHash ( profile , int ( conversation_id ) , contentHash ) )
2021-08-25 05:01:53 +00:00
}
2021-11-17 22:34:35 +00:00
func GetMessagesByContentHash ( profileOnion string , handle int , contentHash string ) string {
2022-01-20 05:51:19 +00:00
var message EnhancedMessage
2021-07-06 19:49:47 +00:00
if application != nil {
2021-11-18 23:44:21 +00:00
profile := application . GetPeer ( profileOnion )
2021-11-23 22:30:16 +00:00
offset , err := profile . GetChannelMessageByContentHash ( handle , 0 , contentHash )
2021-11-18 23:44:21 +00:00
if err == nil {
2021-11-23 22:30:16 +00:00
messages , err := profile . GetMostRecentMessages ( handle , 0 , offset , 1 )
2021-11-18 23:44:21 +00:00
if err == nil {
2021-11-26 22:26:26 +00:00
time , _ := time . Parse ( time . RFC3339Nano , messages [ 0 ] . Attr [ constants2 . AttrSentTimestamp ] )
2022-01-20 05:51:19 +00:00
message . Message = model . Message {
2021-11-26 22:26:26 +00:00
Message : messages [ 0 ] . Body ,
Acknowledged : messages [ 0 ] . Attr [ constants2 . AttrAck ] == constants2 . True ,
Error : messages [ 0 ] . Attr [ constants2 . AttrErr ] ,
PeerID : messages [ 0 ] . Attr [ constants2 . AttrAuthor ] ,
Timestamp : time ,
}
2022-01-20 05:51:19 +00:00
message . ID = messages [ 0 ] . ID
message . Attributes = messages [ 0 ] . Attr
message . ContactImage = utils . RandomProfileImage ( message . PeerID )
message . LocalIndex = offset
2022-01-20 17:37:39 +00:00
message . ContentHash = contentHash
2021-11-23 22:30:16 +00:00
} else {
log . Errorf ( "error fetching local index {} " , err )
2021-11-18 23:44:21 +00:00
}
}
2021-07-06 19:49:47 +00:00
}
2022-01-20 05:51:19 +00:00
bytes , _ := json . Marshal ( message )
2021-07-06 19:49:47 +00:00
return string ( bytes )
}
2021-11-17 22:34:35 +00:00
//export c_FreePointer
// Dangerous function. Should only be used as documented in `MEMORY.md`
func c_FreePointer ( ptr * C . char ) {
C . free ( unsafe . Pointer ( ptr ) )
}
2021-06-24 22:30:46 +00:00
//export c_SendMessage
2021-11-17 20:33:51 +00:00
func c_SendMessage ( profile_ptr * C . char , profile_len C . int , conversation_id C . int , msg_ptr * C . char , msg_len C . int ) {
2021-06-24 22:30:46 +00:00
profile := C . GoStringN ( profile_ptr , profile_len )
msg := C . GoStringN ( msg_ptr , msg_len )
2021-11-17 22:34:35 +00:00
SendMessage ( profile , int ( conversation_id ) , msg )
2021-06-24 22:30:46 +00:00
}
2021-11-17 20:33:51 +00:00
func SendMessage ( profileOnion string , conversationID int , msg string ) {
2021-06-24 22:30:46 +00:00
profile := application . GetPeer ( profileOnion )
2021-11-17 20:33:51 +00:00
profile . SendMessage ( conversationID , msg )
2021-06-24 22:30:46 +00:00
}
//export c_SendInvitation
2021-11-18 23:44:21 +00:00
func c_SendInvitation ( profile_ptr * C . char , profile_len C . int , conversation_id C . int , target_id C . int ) {
2021-06-24 22:30:46 +00:00
profile := C . GoStringN ( profile_ptr , profile_len )
2021-11-17 22:34:35 +00:00
SendInvitation ( profile , int ( conversation_id ) , int ( target_id ) )
2021-06-24 22:30:46 +00:00
}
2021-11-17 20:33:51 +00:00
// SendInvitation sends an invitation from `profileOnion` to contact `handle` (peer or group)
2021-06-24 22:30:46 +00:00
// asking them to add the contact `target` (also peer or group).
// For groups, the profile must already have `target` as a contact.
2021-11-17 20:33:51 +00:00
func SendInvitation ( profileOnion string , conversationID int , targetID int ) {
2021-06-24 22:30:46 +00:00
profile := application . GetPeer ( profileOnion )
2021-11-17 20:33:51 +00:00
profile . SendInviteToConversation ( conversationID , targetID )
2021-06-24 22:30:46 +00:00
}
2021-09-30 17:21:36 +00:00
//export c_ShareFile
2021-11-17 22:34:35 +00:00
func c_ShareFile ( profile_ptr * C . char , profile_len C . int , conversation_id C . int , filepath_ptr * C . char , filepath_len C . int ) {
2021-09-30 17:21:36 +00:00
profile := C . GoStringN ( profile_ptr , profile_len )
2021-10-15 17:59:42 +00:00
sharefilepath := C . GoStringN ( filepath_ptr , filepath_len )
2021-11-17 22:34:35 +00:00
ShareFile ( profile , int ( conversation_id ) , sharefilepath )
2021-09-30 17:21:36 +00:00
}
2021-11-18 23:44:21 +00:00
func ShareFile ( profileOnion string , conversationID int , sharefilepath string ) {
2021-09-30 17:21:36 +00:00
profile := application . GetPeer ( profileOnion )
fh , err := filesharing . FunctionalityGate ( utils . ReadGlobalSettings ( ) . Experiments )
if err != nil {
log . Errorf ( "file sharing error: %v" , err )
} else {
2021-11-17 20:33:51 +00:00
err = fh . ShareFile ( sharefilepath , profile , conversationID )
2021-10-15 17:59:42 +00:00
if err != nil {
log . Errorf ( "error sharing file: %v" , err )
}
2021-09-30 17:21:36 +00:00
}
}
//export c_DownloadFile
2021-11-17 22:34:35 +00:00
func c_DownloadFile ( profile_ptr * C . char , profile_len C . int , conversation_id C . int , filepath_ptr * C . char , filepath_len C . int , manifestpath_ptr * C . char , manifestpath_len C . int , filekey_ptr * C . char , filekey_len C . int ) {
2021-09-30 17:21:36 +00:00
profile := C . GoStringN ( profile_ptr , profile_len )
2021-10-15 17:59:42 +00:00
downloadfilepath := C . GoStringN ( filepath_ptr , filepath_len )
2021-09-30 17:21:36 +00:00
manifestpath := C . GoStringN ( manifestpath_ptr , manifestpath_len )
filekey := C . GoStringN ( filekey_ptr , filekey_len )
2021-11-17 22:34:35 +00:00
DownloadFile ( profile , int ( conversation_id ) , downloadfilepath , manifestpath , filekey )
2021-09-30 17:21:36 +00:00
}
2021-11-17 20:33:51 +00:00
func DownloadFile ( profileOnion string , conversationID int , filepath , manifestpath , filekey string ) {
2021-09-30 17:21:36 +00:00
profile := application . GetPeer ( profileOnion )
fh , err := filesharing . FunctionalityGate ( utils . ReadGlobalSettings ( ) . Experiments )
if err != nil {
log . Errorf ( "file sharing error: %v" , err )
} else {
2021-11-17 20:33:51 +00:00
fh . DownloadFile ( profile , conversationID , filepath , manifestpath , filekey )
2021-09-30 17:21:36 +00:00
}
}
//export c_CheckDownloadStatus
func c_CheckDownloadStatus ( profilePtr * C . char , profileLen C . int , fileKeyPtr * C . char , fileKeyLen C . int ) {
CheckDownloadStatus ( C . GoStringN ( profilePtr , profileLen ) , C . GoStringN ( fileKeyPtr , fileKeyLen ) )
}
func CheckDownloadStatus ( profileOnion , fileKey string ) {
profile := application . GetPeer ( profileOnion )
2021-11-04 21:10:21 +00:00
path , _ := profile . GetScopedZonedAttribute ( attr . LocalScope , attr . FilesharingZone , fmt . Sprintf ( "%s.path" , fileKey ) )
if _ , exists := profile . GetScopedZonedAttribute ( attr . LocalScope , attr . FilesharingZone , fmt . Sprintf ( "%s.complete" , fileKey ) ) ; exists {
2021-09-30 17:21:36 +00:00
eventHandler . Push ( event . NewEvent ( event . FileDownloaded , map [ event . Field ] string {
ProfileOnion : profileOnion ,
event . FileKey : fileKey ,
event . FilePath : path ,
event . TempFile : "" ,
} ) )
2021-11-04 21:10:21 +00:00
} else {
log . Infof ( "CheckDownloadStatus found .path but not .complete" )
2021-11-02 21:31:14 +00:00
eventHandler . Push ( event . NewEvent ( event . FileDownloadProgressUpdate , map [ event . Field ] string {
2021-11-04 21:10:21 +00:00
ProfileOnion : profileOnion ,
event . FileKey : fileKey ,
event . Progress : "-1" ,
event . FileSizeInChunks : "-1" ,
event . FilePath : path ,
2021-11-02 21:31:14 +00:00
} ) )
2021-09-30 17:21:36 +00:00
}
}
2021-11-04 21:10:21 +00:00
//export c_VerifyOrResumeDownload
2021-11-17 22:34:35 +00:00
func c_VerifyOrResumeDownload ( profile_ptr * C . char , profile_len C . int , conversation_id C . int , filekey_ptr * C . char , filekey_len C . int ) {
2021-11-04 21:10:21 +00:00
profile := C . GoStringN ( profile_ptr , profile_len )
filekey := C . GoStringN ( filekey_ptr , filekey_len )
2021-11-17 22:34:35 +00:00
VerifyOrResumeDownload ( profile , int ( conversation_id ) , filekey )
2021-11-04 21:10:21 +00:00
}
2021-11-17 20:33:51 +00:00
func VerifyOrResumeDownload ( profileOnion string , conversationID int , fileKey string ) {
2021-11-04 21:10:21 +00:00
profile := application . GetPeer ( profileOnion )
if manifestFilePath , exists := profile . GetScopedZonedAttribute ( attr . LocalScope , attr . FilesharingZone , fmt . Sprintf ( "%v.manifest" , fileKey ) ) ; exists {
if downloadfilepath , exists := profile . GetScopedZonedAttribute ( attr . LocalScope , attr . FilesharingZone , fmt . Sprintf ( "%s.path" , fileKey ) ) ; exists {
log . Infof ( "resuming %s" , fileKey )
2021-11-17 20:33:51 +00:00
DownloadFile ( profileOnion , conversationID , downloadfilepath , manifestFilePath , fileKey )
2021-11-04 21:10:21 +00:00
} else {
log . Errorf ( "found manifest path but not download path for %s" , fileKey )
}
} else {
log . Errorf ( "no stored manifest path found for %s" , fileKey )
}
}
2021-06-24 22:30:46 +00:00
//export c_ResetTor
func c_ResetTor ( ) {
ResetTor ( )
}
func ResetTor ( ) {
2022-01-12 22:02:50 +00:00
log . Infof ( "Replacing ACN with new Tor..." )
2022-01-19 20:59:39 +00:00
settings := utils . ReadGlobalSettings ( )
2022-01-19 21:00:32 +00:00
2022-01-19 20:59:39 +00:00
globalACN . Close ( ) // we need to close first if dateDir is the same, otherwise buildACN can't launch tor.
globalACN . ReplaceACN ( buildACN ( * settings , globalTorPath , globalAppDir ) )
// We need to update settings on reset as buildACN can alter settings, otherwise the next reset will be broken...
settings = utils . ReadGlobalSettings ( )
settingsJson , _ := json . Marshal ( settings )
application . GetPrimaryBus ( ) . Publish ( event . NewEvent ( utils . UpdateGlobalSettings , map [ event . Field ] string { event . Data : string ( settingsJson ) } ) )
2021-11-30 21:31:21 +00:00
log . Infof ( "Restarted" )
2021-06-24 22:30:46 +00:00
}
//export c_CreateGroup
func c_CreateGroup ( profile_ptr * C . char , profile_len C . int , server_ptr * C . char , server_len C . int , name_ptr * C . char , name_len C . int ) {
profile := C . GoStringN ( profile_ptr , profile_len )
server := C . GoStringN ( server_ptr , server_len )
name := C . GoStringN ( name_ptr , name_len )
CreateGroup ( profile , server , name )
}
// CreateGroup takes in a profile and server in addition to a name and creates a new group.
2021-10-15 17:59:42 +00:00
func CreateGroup ( profileHandle string , server string , name string ) {
profile := application . GetPeer ( profileHandle )
2021-06-24 22:30:46 +00:00
_ , err := groups . ExperimentGate ( utils . ReadGlobalSettings ( ) . Experiments )
if err == nil {
2021-11-17 20:33:51 +00:00
conversationID , err := profile . StartGroup ( name , server )
2021-06-24 22:30:46 +00:00
if err == nil {
2021-11-17 20:33:51 +00:00
log . Debugf ( "created group %v on %v: $v" , profileHandle , server , conversationID )
2021-06-24 22:30:46 +00:00
} else {
2021-10-15 17:59:42 +00:00
log . Errorf ( "error creating group or %v on server %v: %v" , profileHandle , server , err )
2021-06-24 22:30:46 +00:00
}
}
}
//export c_DeleteProfile
func c_DeleteProfile ( profile_ptr * C . char , profile_len C . int , password_ptr * C . char , password_len C . int ) {
profile := C . GoStringN ( profile_ptr , profile_len )
password := C . GoStringN ( password_ptr , password_len )
DeleteProfile ( profile , password )
}
// DeleteProfile deletes a profile given the right password
func DeleteProfile ( profile string , password string ) {
// allow a blank password to delete "unencrypted" accounts...
if password == "" {
password = constants . DefactoPasswordForUnencryptedProfiles
}
application . DeletePeer ( profile , password )
}
2021-08-27 20:25:41 +00:00
//export c_ArchiveConversation
2021-11-17 22:34:35 +00:00
func c_ArchiveConversation ( profile_ptr * C . char , profile_len C . int , conversation_id C . int ) {
2021-06-24 22:30:46 +00:00
profile := C . GoStringN ( profile_ptr , profile_len )
2021-11-17 22:34:35 +00:00
ArchiveConversation ( profile , int ( conversation_id ) )
2021-06-24 22:30:46 +00:00
}
2021-08-27 20:25:41 +00:00
// ArchiveConversation sets the conversation to archived
2021-11-17 20:33:51 +00:00
func ArchiveConversation ( profileHandle string , conversationID int ) {
2021-10-15 17:59:42 +00:00
profile := application . GetPeer ( profileHandle )
2021-11-17 20:33:51 +00:00
profile . SetConversationAttribute ( conversationID , attr . LocalScope . ConstructScopedZonedPath ( attr . ProfileZone . ConstructZonedPath ( constants . Archived ) ) , constants2 . True )
2021-06-24 22:30:46 +00:00
}
2021-08-27 20:25:41 +00:00
//export c_DeleteContact
2021-11-17 22:34:35 +00:00
func c_DeleteContact ( profile_ptr * C . char , profile_len C . int , conversation_id C . int ) {
2021-06-24 22:30:46 +00:00
profile := C . GoStringN ( profile_ptr , profile_len )
2021-11-17 22:34:35 +00:00
DeleteContact ( profile , int ( conversation_id ) )
2021-06-24 22:30:46 +00:00
}
2021-08-27 20:25:41 +00:00
// DeleteContact removes all trace of the contact from the profile
2021-11-17 20:33:51 +00:00
func DeleteContact ( profileHandle string , conversationID int ) {
2021-10-15 17:59:42 +00:00
profile := application . GetPeer ( profileHandle )
2021-11-17 20:33:51 +00:00
profile . DeleteConversation ( conversationID )
2021-06-24 22:30:46 +00:00
}
//export c_ImportBundle
func c_ImportBundle ( profile_ptr * C . char , profile_len C . int , bundle_ptr * C . char , bundle_len C . int ) {
profile := C . GoStringN ( profile_ptr , profile_len )
name := C . GoStringN ( bundle_ptr , bundle_len )
ImportBundle ( profile , name )
}
// ImportBundle takes in a handle to a profile and an invite string which could have one of many
// different formats (e.g. a peer address, a group invite, a server key bundle, or a combination)
func ImportBundle ( profileOnion string , bundle string ) {
profile := application . GetPeer ( profileOnion )
2021-11-17 20:33:51 +00:00
response := profile . ImportBundle ( bundle )
2021-06-24 22:30:46 +00:00
2021-11-17 20:33:51 +00:00
// We might have added a new server, so refresh the server list if applicable...
groupHandler , err := groups . ExperimentGate ( utils . ReadGlobalSettings ( ) . Experiments )
if err == nil {
serverListForOnion := groupHandler . GetServerInfoList ( profile )
serversListBytes , _ := json . Marshal ( serverListForOnion )
eventHandler . Push ( event . NewEvent ( groups . UpdateServerInfo , map [ event . Field ] string { "ProfileOnion" : profileOnion , groups . ServerList : string ( serversListBytes ) } ) )
2021-06-24 22:30:46 +00:00
}
2021-11-17 20:33:51 +00:00
2021-06-24 22:30:46 +00:00
eventHandler . Push ( event . NewEvent ( event . AppError , map [ event . Field ] string { event . Data : response . Error ( ) } ) )
}
2021-08-27 22:16:07 +00:00
//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 )
}
2021-10-15 20:09:36 +00:00
// SetProfileAttribute provides a wrapper around profile.SetScopedZonedAttribute
2021-10-15 20:48:05 +00:00
// 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.
2021-10-15 20:09:36 +00:00
// Key must have the format zone.key where Zone is defined in Cwtch. Unknown zones are not permitted.
2021-08-27 22:16:07 +00:00
func SetProfileAttribute ( profileOnion string , key string , value string ) {
profile := application . GetPeer ( profileOnion )
2021-10-15 20:19:14 +00:00
zone , key := attr . ParseZone ( key )
2021-10-15 20:48:05 +00:00
// TODO We only allow public.profile.zone 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 )
2021-10-15 20:09:36 +00:00
} else {
log . Errorf ( "attempted to set an attribute with an unknown zone: %v" , key )
}
2021-08-27 22:16:07 +00:00
}
2021-11-17 22:34:35 +00:00
//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 ) {
2021-08-27 22:16:07 +00:00
profileOnion := C . GoStringN ( profile_ptr , profile_len )
key := C . GoStringN ( key_ptr , key_len )
value := C . GoStringN ( val_ptr , val_len )
2021-11-17 22:34:35 +00:00
SetConversationAttribute ( profileOnion , int ( conversation_id ) , key , value )
2021-08-27 22:16:07 +00:00
}
2021-11-17 22:34:35 +00:00
// SetConversationAttribute provides a wrapper around profile.SetProfileAttribute
func SetConversationAttribute ( profileOnion string , conversationID int , key string , value string ) {
2021-08-27 22:16:07 +00:00
profile := application . GetPeer ( profileOnion )
2021-11-17 22:34:35 +00:00
zone , key := attr . ParseZone ( key )
profile . SetConversationAttribute ( conversationID , attr . LocalScope . ConstructScopedZonedPath ( zone . ConstructZonedPath ( key ) ) , value )
2021-06-24 22:30:46 +00:00
}
2021-11-30 21:31:21 +00:00
//export c_SetMessageAttribute
func c_SetMessageAttribute ( profile_ptr * C . char , profile_len C . int , conversation_id C . int , channel_id C . int , message_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 )
SetMessageAttribute ( profileOnion , int ( conversation_id ) , int ( channel_id ) , int ( message_id ) , key , value )
}
// SetMessageAttribute is a wrapper around `UpdateMessageAttribute` on profile that allows the creation or update of a
// given message attribute on a conversation/channel.
// Errors if `profileOnion` is not associated to an existing & loaded profile,
// of if `UpdateMessageAttribute` fails
func SetMessageAttribute ( profileOnion string , conversationID int , channelID int , messageID int , attributeKey string , attributeValue string ) {
profile := application . GetPeer ( profileOnion )
2021-12-01 12:16:15 +00:00
if profile == nil {
2021-11-30 21:31:21 +00:00
log . Errorf ( "called SetMessageAttribute with invalid profile handle: %v" , profileOnion )
return
}
err := profile . UpdateMessageAttribute ( conversationID , channelID , messageID , attributeKey , attributeValue )
if err != nil {
log . Errorf ( "error updating message attribute: %v" , err )
}
}
2021-12-18 01:06:54 +00:00
//export c_ChangePassword
2022-01-06 21:31:21 +00:00
func c_ChangePassword ( profile_ptr * C . char , profile_len C . int , oldpassword_ptr * C . char , oldpassword_len C . int , newpassword_ptr * C . char , newpassword_len C . int , newpassword_again_ptr * C . char , newpassword_again_len C . int ) {
2021-12-18 01:06:54 +00:00
profileOnion := C . GoStringN ( profile_ptr , profile_len )
oldPassword := C . GoStringN ( oldpassword_ptr , oldpassword_len )
newPassword := C . GoStringN ( newpassword_ptr , newpassword_len )
newPasswordAgain := C . GoStringN ( newpassword_again_ptr , newpassword_again_len )
ChangePassword ( profileOnion , oldPassword , newPassword , newPasswordAgain )
}
// ChangePassword provides a wrapper around profile.ChangePassword
func ChangePassword ( profileOnion string , oldPassword string , newPassword string , newPasswordAgain string ) {
profile := application . GetPeer ( profileOnion )
log . Infof ( "changing password for %v" , profileOnion )
err := profile . ChangePassword ( oldPassword , newPassword , newPasswordAgain )
log . Infof ( "change password result %v" , err )
if err == nil {
eventHandler . Push ( event . NewEvent ( event . AppError , map [ event . Field ] string { event . Data : features . ConstructResponse ( "changepassword" , constants . StatusSuccess ) . Error ( ) } ) )
} else {
eventHandler . Push ( event . NewEvent ( event . AppError , map [ event . Field ] string { event . Data : features . ConstructResponse ( "changepassword" , err . Error ( ) ) . Error ( ) } ) )
}
}
2021-06-24 22:30:46 +00:00
//export c_ShutdownCwtch
func c_ShutdownCwtch ( ) {
ShutdownCwtch ( )
}
// ShutdownCwtch is a safe way to shutdown any active cwtch applications and associated ACNs
func ShutdownCwtch ( ) {
2022-01-12 22:02:50 +00:00
if application != nil {
2021-06-24 22:30:46 +00:00
// Kill the isolate
eventHandler . Push ( event . NewEvent ( event . Shutdown , map [ event . Field ] string { } ) )
2021-11-26 01:59:00 +00:00
servers . Shutdown ( )
2021-06-24 22:30:46 +00:00
// 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
}
}
2021-10-08 00:11:50 +00:00
//***** Server APIs *****
//export c_LoadServers
func c_LoadServers ( passwordPtr * C . char , passwordLen C . int ) {
LoadServers ( C . GoStringN ( passwordPtr , passwordLen ) )
}
func LoadServers ( password string ) {
serversHandler , err := servers . ExperimentGate ( utils . ReadGlobalSettings ( ) . Experiments )
if err == nil {
serversList , err := serversHandler . LoadServers ( password )
if err != nil {
log . Errorf ( "Error attempting to load servers :%s\n" , err )
application . GetPrimaryBus ( ) . Publish ( event . NewEventList ( servers . ZeroServersLoaded ) )
} else if len ( serversList ) == 0 {
application . GetPrimaryBus ( ) . Publish ( event . NewEventList ( servers . ZeroServersLoaded ) )
} else {
for _ , serverOnion := range serversList {
2021-11-02 01:48:00 +00:00
serverInfo := serversHandler . GetServerInfo ( serverOnion )
log . Debugf ( "Load Server NewServer event: %s" , serverInfo )
ev := event . NewEvent ( servers . NewServer , make ( map [ event . Field ] string ) )
serverInfo . EnrichEvent ( & ev )
application . GetPrimaryBus ( ) . Publish ( ev )
2021-11-02 04:30:03 +00:00
if serverInfo . Autostart {
LaunchServer ( serverOnion )
}
2021-10-08 00:11:50 +00:00
}
}
}
}
//export c_CreateServer
2021-10-29 23:14:08 +00:00
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 )
2021-10-08 00:11:50 +00:00
}
2021-10-29 23:14:08 +00:00
func CreateServer ( password string , description string , autostart bool ) {
2021-10-08 00:11:50 +00:00
serversHandler , err := servers . ExperimentGate ( utils . ReadGlobalSettings ( ) . Experiments )
if err == nil {
2021-10-29 23:14:08 +00:00
s , err := serversHandler . CreateServer ( password )
2021-10-08 00:11:50 +00:00
if err != nil {
log . Errorf ( "Could not create new server: %s\n" , err )
} else {
2021-10-29 23:14:08 +00:00
s . SetAttribute ( server . AttrDescription , description )
if autostart {
s . SetAttribute ( server . AttrAutostart , "true" )
} else {
s . SetAttribute ( server . AttrAutostart , "false" )
}
if password == constants . DefactoPasswordForUnencryptedProfiles {
s . SetAttribute ( server . AttrStorageType , server . StorageTypeDefaultPassword )
} else {
s . SetAttribute ( server . AttrStorageType , server . StorageTypePassword )
}
2021-11-02 01:48:00 +00:00
serverInfo := serversHandler . GetServerInfo ( s . Onion ( ) )
log . Debugf ( "Creating Server NewServer event: %s" , serverInfo )
ev := event . NewEvent ( servers . NewServer , make ( map [ event . Field ] string ) )
serverInfo . EnrichEvent ( & ev )
application . GetPrimaryBus ( ) . Publish ( ev )
2021-11-03 18:56:20 +00:00
if autostart {
LaunchServer ( s . Onion ( ) )
}
2021-10-08 00:11:50 +00:00
}
}
}
//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 ) {
serversHandler , err := servers . ExperimentGate ( utils . ReadGlobalSettings ( ) . Experiments )
if err == nil {
2021-11-02 01:48:00 +00:00
serversHandler . StopServer ( onion )
application . GetPrimaryBus ( ) . Publish ( event . NewEventList ( servers . ServerIntentUpdate , event . Identity , onion , servers . Intent , servers . IntentStopped ) )
2021-11-04 01:19:30 +00:00
err := serversHandler . DeleteServer ( onion , currentPassword )
if err == nil {
application . GetPrimaryBus ( ) . Publish ( event . NewEventList ( servers . ServerDeleted , event . Status , constants . StatusSuccess , event . Identity , onion ) )
} else {
application . GetPrimaryBus ( ) . Publish ( event . NewEventList ( servers . ServerDeleted , event . Status , constants . StatusError , event . Error , err . Error ( ) , event . Identity , onion ) )
}
2021-10-08 00:11:50 +00:00
}
}
//export c_LaunchServers
func c_LaunchServers ( ) {
LaunchServers ( )
}
func LaunchServers ( ) {
serversHandler , err := servers . ExperimentGate ( utils . ReadGlobalSettings ( ) . Experiments )
if err == nil {
for _ , onion := range serversHandler . ListServers ( ) {
autostart := false
if s := serversHandler . GetServer ( onion ) ; s != nil {
autostart = s . GetAttribute ( server . AttrAutostart ) == "true"
}
if autostart {
LaunchServer ( onion )
}
}
}
}
//export c_LaunchServer
func c_LaunchServer ( onionPtr * C . char , onionLen C . int ) {
LaunchServer ( C . GoStringN ( onionPtr , onionLen ) )
}
func LaunchServer ( onion string ) {
serversHandler , err := servers . ExperimentGate ( utils . ReadGlobalSettings ( ) . Experiments )
if err == nil {
serversHandler . LaunchServer ( onion )
2021-11-02 01:48:00 +00:00
application . GetPrimaryBus ( ) . Publish ( event . NewEventList ( servers . ServerIntentUpdate , event . Identity , onion , servers . Intent , servers . IntentRunning ) )
2021-10-08 00:11:50 +00:00
}
}
2021-11-02 01:48:00 +00:00
//export c_StopServer
func c_StopServer ( onionPtr * C . char , onionLen C . int ) {
StopServer ( C . GoStringN ( onionPtr , onionLen ) )
2021-10-08 00:11:50 +00:00
}
2021-11-02 01:48:00 +00:00
func StopServer ( onion string ) {
2021-10-08 00:11:50 +00:00
serversHandler , err := servers . ExperimentGate ( utils . ReadGlobalSettings ( ) . Experiments )
if err == nil {
2021-11-02 01:48:00 +00:00
serversHandler . StopServer ( onion )
application . GetPrimaryBus ( ) . Publish ( event . NewEventList ( servers . ServerIntentUpdate , event . Identity , onion , servers . Intent , servers . IntentStopped ) )
2021-10-08 00:11:50 +00:00
}
}
2021-11-02 01:48:00 +00:00
//export c_StopServers
func c_StopServers ( ) {
StopServers ( )
2021-10-08 00:11:50 +00:00
}
2021-11-02 01:48:00 +00:00
func StopServers ( ) {
2021-10-08 00:11:50 +00:00
serversHandler , err := servers . ExperimentGate ( utils . ReadGlobalSettings ( ) . Experiments )
if err == nil {
for _ , onion := range serversHandler . ListServers ( ) {
2021-11-02 01:48:00 +00:00
StopServer ( onion )
2021-10-08 00:11:50 +00:00
}
}
}
2021-11-02 01:48:00 +00:00
//export c_DestroyServers
func c_DestroyServers ( ) {
DestroyServers ( )
}
func DestroyServers ( ) {
serversHandler , err := servers . ExperimentGate ( utils . ReadGlobalSettings ( ) . Experiments )
if err == nil {
serversHandler . DestroyServers ( )
}
}
2021-10-29 23:14:08 +00:00
//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 ) {
serversHandler , err := servers . ExperimentGate ( utils . ReadGlobalSettings ( ) . Experiments )
if err == nil {
server := serversHandler . GetServer ( onion )
if server != nil {
server . SetAttribute ( key , val )
}
}
}
2021-10-08 00:11:50 +00:00
// ***** END Server APIs *****
2021-06-24 22:30:46 +00:00
// Leave as is, needed by ffi
2021-12-06 20:23:57 +00:00
func main ( ) { }