This repository has been archived on 2023-06-16. You can view files and clone it, but cannot push or open issues or pull requests.
libcwtch-go/lib.go

1549 lines
56 KiB
Go
Raw Normal View History

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
// //Needed to invoke C.free
// #include <stdlib.h>
2021-06-24 22:30:46 +00:00
import "C"
2021-06-24 22:30:46 +00:00
import (
"crypto/rand"
constants2 "cwtch.im/cwtch/model/constants"
"cwtch.im/cwtch/protocol/files"
"encoding/json"
"fmt"
"git.openprivacy.ca/cwtch.im/libcwtch-go/features"
2022-01-18 20:53:55 +00:00
path "path/filepath"
"runtime/pprof"
2021-12-19 01:16:21 +00:00
"os/user"
"runtime"
"strings"
"unsafe"
2022-09-09 01:26:22 +00:00
// Import SQL Cipher
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"
"cwtch.im/cwtch/functionality/filesharing"
2021-06-24 22:30:46 +00:00
"cwtch.im/cwtch/model"
"cwtch.im/cwtch/model/attr"
"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"
"git.openprivacy.ca/cwtch.im/server"
2021-06-24 22:30:46 +00:00
"git.openprivacy.ca/openprivacy/connectivity"
"git.openprivacy.ca/cwtch.im/libcwtch-go/utils"
2021-06-24 22:30:46 +00:00
"encoding/base64"
mrand "math/rand"
"os"
"time"
"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")
)
// supplied by make
var (
buildVer string
buildDate string
)
2021-06-24 22:30:46 +00:00
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
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)
2021-06-24 22:30:46 +00:00
func StartCwtch(appDir string, torPath string) int {
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)
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)
}
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(...)")
log.Debugf("builddate: %v buildver: %v", buildDate, buildVer)
2021-06-24 22:30:46 +00:00
// 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()")
api := utils.LCG_API_Handler{LaunchServers: LaunchServers, StopServers: StopServers}
eventHandler = utils.NewEventHandler(api)
2021-08-24 15:47:31 +00:00
2021-06-24 22:30:46 +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, "~/") {
2022-01-18 20:53:55 +00:00
appDir = path.Join(homeDir, appDir[2:])
}
2021-06-24 22:30:46 +00:00
// 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(utils.CwtchStartError, event.Error, fmt.Sprintf("Error creating appDir %v: %v", appDir, err)))
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)
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
eventHandler.HandleApp(application)
2021-06-24 22:30:46 +00:00
// Settings may have changed...
settings = utils.ReadGlobalSettings()
2021-06-24 22:30:46 +00:00
settingsJson, _ := json.Marshal(settings)
application.LoadProfiles(constants.DefactoPasswordForUnencryptedProfiles)
LoadServers(constants.DefactoPasswordForUnencryptedProfiles)
serversHandler, err := servers.ExperimentGate(utils.ReadGlobalSettings().Experiments)
if err == nil {
serversHandler.Enable()
}
publishLoadedServers()
2022-02-04 00:11:40 +00:00
// 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...
2022-01-13 22:42:31 +00:00
if application == nil {
log.Errorf("startCwtch: primary application object has gone away. assuming application is closing.")
return
}
2022-02-04 00:11:40 +00:00
// Send global settings to the UI...
2021-06-24 22:30:46 +00:00
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))
2022-12-03 00:54:39 +00:00
// torrc.WithLog(path.Join(appDir, "tor", "tor.log"), tor.TorLogLevelNotice)
2022-01-12 22:02:50 +00:00
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{}
}
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-*"))
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(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
}
//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
}
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 cwtch foreground")
2021-06-24 22:30:46 +00:00
if application == nil {
log.Errorf("ReconnectCwtchForeground: Application is nil, presuming stale thread, EXITING Reconnect\n")
return
}
// populate profile list
peerList := application.ListProfiles()
for _, onion := range peerList {
eventHandler.Push(event.NewEvent(event.NewPeer, map[event.Field]string{event.Identity: onion, "Reload": event.True}))
2021-06-24 22:30:46 +00:00
}
settings := utils.ReadGlobalSettings()
2021-11-18 23:44:21 +00:00
groupHandler, _ := groups.ExperimentGate(settings.Experiments)
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-17 20:33:51 +00:00
// Group Experiment Server Refresh
if groupHandler != nil {
profile.StartServerConnections()
2021-11-17 20:33:51 +00:00
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-06-24 22:30:46 +00:00
LoadServers(constants.DefactoPasswordForUnencryptedProfiles)
publishLoadedServers()
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()
}
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 {
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-24 22:30:46 +00:00
}
// A generic method for Rebroadcasting App Events from a UI
//
//export c_SendAppEvent
2021-06-24 22:30:46 +00:00
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()
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)
LoadServers(constants.DefactoPasswordForUnencryptedProfiles)
if !servers.Enabled() {
sh.Enable()
publishLoadedServers()
LaunchServers()
}
2021-10-08 00:11:50 +00:00
} else {
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 {
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)}))
}
}
// File Sharing Experiment Refresh
2022-07-06 19:20:50 +00:00
fs, err := filesharing.FunctionalityGate(settings.Experiments)
if err != nil {
for _, handle := range application.ListProfiles() {
application.GetPeer(handle).StopAllFileShares()
}
2022-07-06 19:20:50 +00:00
} else {
for _, handle := range application.ListProfiles() {
fs.ReShareFiles(application.GetPeer(handle))
}
}
2021-06-24 22:30:46 +00:00
// Explicitly toggle blocking/unblocking of unknown connections for profiles
// that have been loaded.
2021-10-08 00:11:50 +00:00
if settings.BlockUnknownConnections {
for _, onion := range application.ListProfiles() {
2021-06-24 22:30:46 +00:00
application.GetPeer(onion).BlockUnknownConnections()
}
} else {
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
}
}
// A generic method for Rebroadcasting Profile Events from a UI
//
//export c_SendProfileEvent
2021-06-24 22:30:46 +00:00
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
// 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)
}
}
// the pointer returned from this function **must** be freed using c_Free
//
//export c_GetAppBusEvent
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_ActivatePeerEngine
func c_ActivatePeerEngine(onion_ptr *C.char, onion_len C.int) {
ActivatePeerEngine(C.GoStringN(onion_ptr, onion_len))
}
func ActivatePeerEngine(profile string) {
doServers := false
if _, err := groups.ExperimentGate(utils.ReadGlobalSettings().Experiments); err == nil {
doServers = true
}
application.ActivatePeerEngine(profile, true, true, doServers)
}
//export c_DeactivatePeerEngine
func c_DeactivatePeerEngine(onion_ptr *C.char, onion_len C.int) {
DeactivatePeerEngine(C.GoStringN(onion_ptr, onion_len))
}
func DeactivatePeerEngine(profile string) {
application.DeactivatePeerEngine(profile)
}
2021-06-24 22:30:46 +00:00
//export c_CreateProfile
func c_CreateProfile(nick_ptr *C.char, nick_len C.int, pass_ptr *C.char, pass_len C.int, autostart C.char) {
CreateProfile(C.GoStringN(nick_ptr, nick_len), C.GoStringN(pass_ptr, pass_len), autostart == 1)
2021-06-24 22:30:46 +00:00
}
func CreateProfile(nick, pass string, autostart bool) {
autostartVal := constants2.True
if !autostart {
autostartVal = constants2.False
}
tagVal := constants.ProfileTypeV1Password
2021-06-24 22:30:46 +00:00
if pass == constants.DefactoPasswordForUnencryptedProfiles {
tagVal = constants.ProfileTypeV1DefaultPassword
2021-06-24 22:30:46 +00:00
}
application.CreatePeer(nick, pass, map[attr.ZonedPath]string{
attr.ProfileZone.ConstructZonedPath(constants2.Tag): tagVal,
attr.ProfileZone.ConstructZonedPath(constants.PeerAutostart): autostartVal,
})
2021-06-24 22:30:46 +00:00
}
//export c_LoadProfiles
func c_LoadProfiles(passwordPtr *C.char, passwordLen C.int) {
LoadProfiles(C.GoStringN(passwordPtr, passwordLen))
}
func LoadProfiles(pass string) {
application.LoadProfiles(pass)
}
//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.)
func AcceptConversation(profileOnion string, conversationID int) {
2021-06-24 22:30:46 +00:00
profile := application.GetPeer(profileOnion)
if profile != nil {
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)
if profile != nil {
profile.BlockConversation(conversationID)
}
2021-06-24 22:30:46 +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)
if profile != nil {
profile.UnblockConversation(conversationID)
}
}
// the pointer returned from this function **must** be Freed by c_Free
//
//export c_GetMessage
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)
LocalIndex int // local index in the DB (row #). Can be empty (most calls supply it) but lookup by hash will fill it
ContentHash string
2021-06-24 22:30:46 +00:00
ContactImage string
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
// 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)
if profile != nil {
messages, err := profile.GetMostRecentMessages(conversationID, 0, messageIndex, 1)
if err == nil && len(messages) == 1 {
time, _ := time.Parse(time.RFC3339Nano, messages[0].Attr[constants2.AttrSentTimestamp])
message.Message = model.Message{
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,
}
message.ID = messages[0].ID
message.Attributes = messages[0].Attr
message.ContactImage = utils.RandomProfileImage(message.PeerID)
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
// the pointer returned from this function **must** be Freed by c_Free
//
//export c_GetMessageByID
2021-11-26 22:26:26 +00:00
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)))
}
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)
if profile != nil {
dbmessage, attr, err := profile.GetChannelMessage(conversationID, 0, messageID)
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,
}
message.ID = messageID
message.Attributes = attr
message.ContactImage = utils.RandomProfileImage(message.PeerID)
message.ContentHash = model.CalculateContentHash(attr[constants2.AttrAuthor], dbmessage)
2021-11-26 22:26:26 +00:00
}
}
}
bytes, _ := json.Marshal(message)
return string(bytes)
}
// the pointer returned from this function **must** be freed by calling c_Free
//
//export c_GetMessagesByContentHash
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-11-17 22:34:35 +00:00
func GetMessagesByContentHash(profileOnion string, handle int, contentHash string) string {
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)
if profile != nil {
offset, err := profile.GetChannelMessageByContentHash(handle, 0, contentHash)
2021-11-18 23:44:21 +00:00
if err == nil {
messages, err := profile.GetMostRecentMessages(handle, 0, offset, 1)
if err == nil {
time, _ := time.Parse(time.RFC3339Nano, messages[0].Attr[constants2.AttrSentTimestamp])
message.Message = model.Message{
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,
}
message.ID = messages[0].ID
message.Attributes = messages[0].Attr
message.ContactImage = utils.RandomProfileImage(message.PeerID)
message.LocalIndex = offset
message.ContentHash = contentHash
} else {
log.Errorf("error fetching local index {} ", err)
2021-11-26 22:26:26 +00:00
}
2021-11-18 23:44:21 +00:00
}
}
2021-07-06 19:49:47 +00:00
}
bytes, _ := json.Marshal(message)
2021-07-06 19:49:47 +00:00
return string(bytes)
}
// the pointer returned from this function **must** be Freed by c_Free
//
//export c_GetSharedFiles
func c_GetSharedFiles(profile_ptr *C.char, profile_len C.int, conversation_id C.int) *C.char {
profile := C.GoStringN(profile_ptr, profile_len)
return C.CString(GetSharedFiles(profile, int(conversation_id)))
}
func GetSharedFiles(profileOnion string, conversationID int) string {
if application != nil {
profile := application.GetPeer(profileOnion)
if profile != nil {
fh, err := filesharing.FunctionalityGate(utils.ReadGlobalSettings().Experiments)
if err != nil {
log.Errorf("file sharing error: %v", err)
} else {
data, err := json.Marshal(fh.GetSharedFiles(profile, conversationID))
if err == nil {
return string(data)
}
log.Errorf("error marshalling shared files..%v", err)
}
}
}
return ""
}
//export c_RestartSharing
func c_RestartSharing(profile_ptr *C.char, profile_len C.int, filekey_ptr *C.char, filekey_ptr_len C.int) {
profile := C.GoStringN(profile_ptr, profile_len)
filekey := C.GoStringN(filekey_ptr, filekey_ptr_len)
RestartSharing(profile, filekey)
}
func RestartSharing(profileOnion string, filekey string) {
if application != nil {
profile := application.GetPeer(profileOnion)
if profile != nil {
fh, err := filesharing.FunctionalityGate(utils.ReadGlobalSettings().Experiments)
if err != nil {
log.Errorf("file sharing error: %v", err)
} else {
fh.RestartFileShare(profile, filekey)
}
}
}
}
//export c_StopSharing
func c_StopSharing(profile_ptr *C.char, profile_len C.int, filekey_ptr *C.char, filekey_ptr_len C.int) {
profile := C.GoStringN(profile_ptr, profile_len)
filekey := C.GoStringN(filekey_ptr, filekey_ptr_len)
StopSharing(profile, filekey)
}
func StopSharing(profileOnion string, filekey string) {
if application != nil {
profile := application.GetPeer(profileOnion)
if profile != nil {
profile.StopFileShare(filekey)
}
}
}
// the pointer returned from this function **must** be Freed by c_Free
//
//export c_GetMessages
func c_GetMessages(profile_ptr *C.char, profile_len C.int, conversation_id C.int, message_index C.int, count C.int) *C.char {
profile := C.GoStringN(profile_ptr, profile_len)
return C.CString(GetMessages(profile, int(conversation_id), int(message_index), int(count)))
}
func GetMessages(profileOnion string, conversationID int, messageIndex int, count int) string {
var emessages []EnhancedMessage = make([]EnhancedMessage, count)
// 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)
if profile != nil {
messages, err := profile.GetMostRecentMessages(conversationID, 0, messageIndex, count)
if err == nil {
for i, message := range messages {
time, _ := time.Parse(time.RFC3339Nano, message.Attr[constants2.AttrSentTimestamp])
emessages[i].Message = model.Message{
Message: message.Body,
Acknowledged: message.Attr[constants2.AttrAck] == constants2.True,
Error: message.Attr[constants2.AttrErr],
PeerID: message.Attr[constants2.AttrAuthor],
Timestamp: time,
}
emessages[i].ID = message.ID
emessages[i].Attributes = message.Attr
emessages[i].ContactImage = utils.RandomProfileImage(emessages[i].PeerID)
emessages[i].ContentHash = model.CalculateContentHash(message.Attr[constants2<