//package cwtch package main // //Needed to invoke C.free // #include import "C" import ( "crypto/rand" "encoding/json" "fmt" "os/user" "runtime" "strconv" "strings" "unsafe" "cwtch.im/cwtch/app" "cwtch.im/cwtch/event" "cwtch.im/cwtch/functionality/filesharing" "cwtch.im/cwtch/model" "cwtch.im/cwtch/model/attr" "cwtch.im/cwtch/peer" "git.openprivacy.ca/cwtch.im/libcwtch-go/constants" contact "git.openprivacy.ca/cwtch.im/libcwtch-go/features/contacts" "git.openprivacy.ca/cwtch.im/libcwtch-go/features/groups" "git.openprivacy.ca/cwtch.im/libcwtch-go/utils" "git.openprivacy.ca/openprivacy/connectivity" "encoding/base64" mrand "math/rand" "os" "path" "path/filepath" "time" "git.openprivacy.ca/openprivacy/connectivity/tor" "git.openprivacy.ca/openprivacy/log" ) 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 var eventHandler *utils.EventHandler var globalACN connectivity.ACN // 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 { dir := C.GoStringN(dir_c, len) tor := C.GoStringN(tor_c, torLen) return C.int(StartCwtch(dir, tor)) } // 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) filelog.SetUseColor(false) if err == nil { 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) } if logLevel := os.Getenv("LOG_LEVEL"); strings.ToLower(logLevel) == "debug" { log.SetLevel(log.LevelDebug) } 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) { log.Infof("application: %v eventHandler: %v acn: %v", application, eventHandler, globalACN) if application != nil { log.Infof("_startCwtch detected existing application; resuming instead of relaunching") ReconnectCwtchForeground() return } log.Infof("Creating new EventHandler()") eventHandler = utils.NewEventHandler() // 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 = filepath.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.PublishAppEvent(event.NewEventList(utils.CwtchStartError, event.Error, fmt.Sprintf("Error creating appDir %v: %v", appDir, err))) return } utils.InitGlobalSettingsFile(appDir, constants.DefactoPasswordForUnencryptedProfiles) log.Infof("Loading Cwtch Directory %v and tor path: %v", appDir, torPath) mrand.Seed(int64(time.Now().Nanosecond())) port := mrand.Intn(1000) + 9600 controlPort := port + 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) os.MkdirAll(path.Join(appDir, "tor"), 0700) tor.NewTorrc().WithSocksPort(port).WithOnionTrafficOnly().WithControlPort(controlPort).WithHashedPassword(base64.StdEncoding.EncodeToString(key)).Build(filepath.Join(appDir, "tor", "torrc")) acn, err := tor.NewTorACNWithAuth(appDir, torPath, controlPort, tor.HashedPasswordAuthenticator{Password: base64.StdEncoding.EncodeToString(key)}) if err != nil { log.Errorf("Error connecting to Tor replacing with ErrorACN: %v\n", err) eventHandler.PublishAppEvent(event.NewEventList(utils.CwtchStartError, event.Error, fmt.Sprintf("Error connecting to Tor: %v", err))) return } globalACN = acn newApp := app.NewApp(acn, appDir) eventHandler.HandleApp(newApp) peer.DefaultEventsToHandle = []event.Type{ event.EncryptedGroupMessage, event.NewMessageFromPeer, event.PeerAcknowledgement, event.PeerError, event.SendMessageToPeerError, event.SendMessageToGroupError, event.NewGetValMessageFromPeer, event.PeerStateChange, event.NewRetValMessageFromPeer, event.NewGroupInvite, event.ServerStateChange, event.ProtocolEngineStopped, event.RetryServerRequest, event.ManifestReceived, } settings := utils.ReadGlobalSettings() settingsJson, _ := json.Marshal(settings) newApp.LoadProfiles(constants.DefactoPasswordForUnencryptedProfiles) application = newApp // 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() } //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 peerList := application.ListPeers() for onion := range peerList { eventHandler.Push(event.NewEvent(event.NewPeer, map[event.Field]string{event.Identity: onion, event.Created: event.False, "Reload": event.True})) } settings := utils.ReadGlobalSettings() for profileOnion := range peerList { // fix peerpeercontact message counts contactList := application.GetPeer(profileOnion).GetContacts() for _, handle := range contactList { totalMessages := application.GetPeer(profileOnion).GetContact(handle).Timeline.Len() + len(application.GetPeer(profileOnion).GetContact(handle).UnacknowledgedMessages) eventHandler.Push(event.NewEvent(event.MessageCounterResync, map[event.Field]string{ event.Identity: profileOnion, event.RemotePeer: handle, event.Data: strconv.Itoa(totalMessages), })) } // Group Experiment Refresh groupHandler, err := groups.ExperimentGate(settings.Experiments) if err == nil { // fix peergroupcontact message counts groupList := application.GetPeer(profileOnion).GetGroups() for _, groupID := range groupList { totalMessages := application.GetPeer(profileOnion).GetGroup(groupID).Timeline.Len() + len(application.GetPeer(profileOnion).GetGroup(groupID).UnacknowledgedMessages) eventHandler.Push(event.NewEvent(event.MessageCounterResync, map[event.Field]string{ event.Identity: profileOnion, event.GroupID: groupID, event.Data: strconv.Itoa(totalMessages), })) } 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)})) } } settingsJson, _ := json.Marshal(settings) application.GetPrimaryBus().Publish(event.NewEvent(utils.UpdateGlobalSettings, map[event.Field]string{event.Data: string(settingsJson)})) application.GetPrimaryBus().Publish(event.NewEvent(utils.CwtchStarted, map[event.Field]string{})) application.QueryACNStatus() application.QueryACNVersion() } //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) log.Debugf("Event: %v", new_event.EventType) // We need to update the local cache // Ideally I think this would be pushed back into Cwtch 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) // Group Experiment Refresh groupHandler, err := groups.ExperimentGate(utils.ReadGlobalSettings().Experiments) if err == nil { for profileOnion := range application.ListPeers() { 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. if utils.ReadGlobalSettings().BlockUnknownConnections { for onion := range application.ListPeers() { application.GetPeer(onion).BlockUnknownConnections() } } else { for onion := range application.ListPeers() { application.GetPeer(onion).AllowUnknownConnections() } } case utils.SetLoggingLevel: _, warn := new_event.Data[utils.Warn] _, error := new_event.Data[utils.Error] _, 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) } else if error { 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 // Should generally be used for rapidly prototyping new APIs 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 { // DEPRECATED: use ImportBundle case AddContact: pf, _ := contact.FunctionalityGate(utils.ReadGlobalSettings().Experiments) err := pf.HandleImportString(peer, new_event.Data[ImportString]) eventHandler.Push(event.NewEvent(event.AppError, map[event.Field]string{event.Data: err.Error()})) // DEPRECATED: use SetProfileAttribute() case event.SetAttribute: peer.SetAttribute(new_event.Data[event.Key], new_event.Data[event.Data]) // DEPRECATED: use SetContactAttribute() case event.SetPeerAttribute: peer.SetContactAttribute(new_event.Data[event.RemotePeer], new_event.Data[event.Key], new_event.Data[event.Data]) // DEPRECATED: use AcceptContact() and BlockContact() case event.SetPeerAuthorization: peer.SetContactAuthorization(new_event.Data[event.RemotePeer], model.Authorization(new_event.Data[event.Authorization])) // If approved (e.g. after an unblock) we want to kick off peering again... if model.Authorization(new_event.Data[event.Authorization]) == model.AuthApproved { peer.PeerWithOnion(new_event.Data[event.RemotePeer]) } 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 // the pointer returned from this function **must** be freed using c_Free 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 } type Profile struct { Name string `json:"name"` Onion string `json:"onion"` ImagePath string `json:"imagePath"` } //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) } //export c_AcceptContact func c_AcceptContact(profilePtr *C.char, profileLen C.int, handlePtr *C.char, handleLen C.int) { AcceptContact(C.GoStringN(profilePtr, profileLen), C.GoStringN(handlePtr, handleLen)) } // AcceptContact takes in a profileOnion and a handle to either a group or a peer and authorizes the handle // for further action (e.g. messaging / connecting to the server / joining the group etc.) func AcceptContact(profileOnion string, handle string) { profile := application.GetPeer(profileOnion) profileHandler := utils.NewPeerHelper(profile) if profileHandler.IsGroup(handle) { profile.AcceptInvite(handle) } else { err := profile.SetContactAuthorization(handle, model.AuthApproved) if err == nil { eventHandler.Push(event.NewEvent(event.PeerStateChange, map[event.Field]string{ ProfileOnion: profileOnion, event.RemotePeer: handle, "authorization": string(model.AuthApproved), })) } else { log.Errorf("error accepting contact: %s", err.Error()) } } } //export c_RejectInvite func c_RejectInvite(profilePtr *C.char, profileLen C.int, handlePtr *C.char, handleLen C.int) { RejectInvite(C.GoStringN(profilePtr, profileLen), C.GoStringN(handlePtr, handleLen)) } // RejectInvite rejects a group invite func RejectInvite(profileOnion string, handle string) { log.Debugf("rejecting invite %v for %v", handle, profileOnion) profile := application.GetPeer(profileOnion) profileHandler := utils.NewPeerHelper(profile) if profileHandler.IsGroup(handle) { profile.RejectInvite(handle) log.Debugf("successfully rejected invite %v for %v", handle, profileOnion) } } //export c_BlockContact func c_BlockContact(profilePtr *C.char, profileLen C.int, handlePtr *C.char, handleLen C.int) { BlockContact(C.GoStringN(profilePtr, profileLen), C.GoStringN(handlePtr, handleLen)) } func BlockContact(profile, handle string) { err := application.GetPeer(profile).SetContactAuthorization(handle, model.AuthBlocked) if err == nil { eventHandler.Push(event.NewEvent(event.PeerStateChange, map[event.Field]string{ ProfileOnion: profile, event.RemotePeer: handle, "authorization": string(model.AuthBlocked), })) } else { log.Errorf("error blocking contact: %s", err.Error()) } } //export c_UpdateMessageFlags func c_UpdateMessageFlags(profile_ptr *C.char, profile_len C.int, handle_ptr *C.char, handle_len C.int, mIdx C.int, message_flags C.ulong) { profile := C.GoStringN(profile_ptr, profile_len) handle := C.GoStringN(handle_ptr, handle_len) UpdateMessageFlags(profile, handle, int(mIdx), int64(message_flags)) } // UpdateMessageFlags sets the messages flags on a given message for a given profile. // gomobile doesn't support uint64...so here we are.... func UpdateMessageFlags(profileOnion, handle string, mIdx int, flags int64) { profile := application.GetPeer(profileOnion) if profile != nil { profile.UpdateMessageFlags(handle, mIdx, uint64(flags)) } else { log.Errorf("called updatemessageflags with invalid profile onion") } } //export c_GetMessage // the pointer returned from this function **must** be Freed by c_Free func c_GetMessage(profile_ptr *C.char, profile_len C.int, handle_ptr *C.char, handle_len C.int, message_index C.int) *C.char { profile := C.GoStringN(profile_ptr, profile_len) handle := C.GoStringN(handle_ptr, handle_len) return C.CString(GetMessage(profile, handle, int(message_index))) } // EnhancedMessage wraps a Cwtch model.Message with some additional data to reduce calls from the UI. type EnhancedMessage struct { model.Message ContactImage string } func GetMessage(profileOnion, handle string, message_index int) string { 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) ph := utils.NewPeerHelper(profile) if ph.IsGroup(handle) { if profile.GetGroup(handle) != nil { // If we are safely within the limits of the timeline just grab the message at the index.. if len(profile.GetGroup(handle).Timeline.Messages) > message_index { message.Message = profile.GetGroup(handle).Timeline.Messages[message_index] message.ContactImage = ph.GetProfilePic(message.Message.PeerID) } else { // Message Index Request exceeded Timeline, most likely reason is this is a request for an // unacknowledged sent message (it can take a many seconds for a message to be confirmed in the worst // case). offset := message_index - len(profile.GetGroup(handle).Timeline.Messages) if len(profile.GetGroup(handle).UnacknowledgedMessages) > offset { message.Message = profile.GetGroup(handle).UnacknowledgedMessages[offset] message.ContactImage = ph.GetProfilePic(message.Message.PeerID) } else { log.Errorf("Couldn't find message in timeline or unacked messages, probably transient threading issue, but logging for visibility..") } } } } else { if profile.GetContact(handle) != nil { // If we are safely within the limits of the timeline just grab the message at the index.. if len(profile.GetContact(handle).Timeline.Messages) > message_index { message.Message = profile.GetContact(handle).Timeline.Messages[message_index] message.ContactImage = ph.GetProfilePic(handle) } else { // Otherwise Send a counter resync event...this shouldn't really happen for p2p messages so we // throw an error. log.Errorf("peerpeercontact getmessage out of range; sending counter resync just in case") eventHandler.Push(event.NewEvent(event.MessageCounterResync, map[event.Field]string{ event.Identity: profileOnion, event.RemotePeer: handle, event.Data: strconv.Itoa(len(profile.GetContact(handle).Timeline.Messages)), })) } } } } bytes, _ := json.Marshal(message) return string(bytes) } //export c_GetMessagesByContentHash // the pointer returned from this function **must** be freed by calling c_Free func c_GetMessagesByContentHash(profile_ptr *C.char, profile_len C.int, handle_ptr *C.char, handle_len C.int, contenthash_ptr *C.char, contenthash_len C.int) *C.char { profile := C.GoStringN(profile_ptr, profile_len) handle := C.GoStringN(handle_ptr, handle_len) contentHash := C.GoStringN(contenthash_ptr, contenthash_len) return C.CString(GetMessagesByContentHash(profile, handle, contentHash)) } //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)) } func GetMessagesByContentHash(profileOnion, handle string, contentHash string) string { var indexedMessages []model.LocallyIndexedMessage if application != nil { profile := application.GetPeer(profileOnion) ph := utils.NewPeerHelper(profile) var err error timeline := ph.GetTimeline(handle) if timeline != nil { indexedMessages, err = timeline.GetMessagesByHash(contentHash) if err != nil { indexedMessages = []model.LocallyIndexedMessage{} } } } bytes, _ := json.Marshal(indexedMessages) return string(bytes) } //export c_SendMessage func c_SendMessage(profile_ptr *C.char, profile_len C.int, handle_ptr *C.char, handle_len C.int, msg_ptr *C.char, msg_len C.int) { profile := C.GoStringN(profile_ptr, profile_len) handle := C.GoStringN(handle_ptr, handle_len) msg := C.GoStringN(msg_ptr, msg_len) SendMessage(profile, handle, msg) } func SendMessage(profileOnion, handle, msg string) { profile := application.GetPeer(profileOnion) ph := utils.NewPeerHelper(profile) if ph.IsGroup(handle) { groupHandler, err := groups.ExperimentGate(utils.ReadGlobalSettings().Experiments) if err == nil { groupHandler.SendMessage(profile, handle, msg) profile.SetGroupAttribute(handle, attr.GetLocalScope(constants.Archived), event.False) } } else { contactHandler, _ := contact.FunctionalityGate(utils.ReadGlobalSettings().Experiments) contactHandler.SendMessage(profile, handle, msg) profile.SetContactAttribute(handle, attr.GetLocalScope(constants.Archived), event.False) } } //export c_SendInvitation func c_SendInvitation(profile_ptr *C.char, profile_len C.int, handle_ptr *C.char, handle_len C.int, target_ptr *C.char, target_len C.int) { profile := C.GoStringN(profile_ptr, profile_len) handle := C.GoStringN(handle_ptr, handle_len) target := C.GoStringN(target_ptr, target_len) SendInvitation(profile, handle, target) } // Send an invitation from `profileOnion` to contact `handle` (peer or group) // asking them to add the contact `target` (also peer or group). // For groups, the profile must already have `target` as a contact. func SendInvitation(profileOnion, handle, target string) { profile := application.GetPeer(profileOnion) ph := utils.NewPeerHelper(profile) var invite ChatMessage if ph.IsGroup(target) { bundle, _ := profile.GetContact(profile.GetGroup(target).GroupServer).GetAttribute(string(model.BundleType)) inviteStr, err := profile.GetGroup(target).Invite() if err == nil { invite = ChatMessage{O: 101, D: fmt.Sprintf("tofubundle:server:%s||%s", base64.StdEncoding.EncodeToString([]byte(bundle)), inviteStr)} } } else { invite = ChatMessage{O: 100, D: target} } inviteBytes, err := json.Marshal(invite) if err != nil { log.Errorf("malformed invite: %v", err) } else { SendMessage(profileOnion, handle, string(inviteBytes)) } } //export c_ShareFile func c_ShareFile(profile_ptr *C.char, profile_len C.int, handle_ptr *C.char, handle_len C.int, filepath_ptr *C.char, filepath_len C.int) { profile := C.GoStringN(profile_ptr, profile_len) handle := C.GoStringN(handle_ptr, handle_len) filepath := C.GoStringN(filepath_ptr, filepath_len) ShareFile(profile, handle, filepath) } func ShareFile(profileOnion, handle, filepath string) { profile := application.GetPeer(profileOnion) fh, err := filesharing.FunctionalityGate(utils.ReadGlobalSettings().Experiments) if err != nil { log.Errorf("file sharing error: %v", err) } else { fh.ShareFile(filepath, profile, handle) } } //export c_DownloadFile func c_DownloadFile(profile_ptr *C.char, profile_len C.int, handle_ptr *C.char, handle_len 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) { profile := C.GoStringN(profile_ptr, profile_len) handle := C.GoStringN(handle_ptr, handle_len) filepath := C.GoStringN(filepath_ptr, filepath_len) manifestpath := C.GoStringN(manifestpath_ptr, manifestpath_len) filekey := C.GoStringN(filekey_ptr, filekey_len) DownloadFile(profile, handle, filepath, manifestpath, filekey) } func DownloadFile(profileOnion, handle, filepath, manifestpath, filekey string) { profile := application.GetPeer(profileOnion) fh, err := filesharing.FunctionalityGate(utils.ReadGlobalSettings().Experiments) if err != nil { log.Errorf("file sharing error: %v", err) } else { fh.DownloadFile(profile, handle, filepath, manifestpath, filekey) } } //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) if path, exists := profile.GetAttribute(attr.GetLocalScope(fileKey)); exists { eventHandler.Push(event.NewEvent(event.FileDownloaded, map[event.Field]string{ ProfileOnion: profileOnion, event.FileKey: fileKey, event.FilePath: path, event.TempFile: "", })) } } //export c_ResetTor func c_ResetTor() { ResetTor() } func ResetTor() { globalACN.Restart() } //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. func CreateGroup(profile string, server string, name string) { peer := application.GetPeer(profile) _, err := groups.ExperimentGate(utils.ReadGlobalSettings().Experiments) if err == nil { gid, _, err := peer.StartGroup(server) if err == nil { log.Debugf("created group %v on %v: $v", profile, server, gid) // set the group name peer.SetGroupAttribute(gid, attr.GetLocalScope("name"), name) } else { log.Errorf("error creating group or %v on server %v: %v", profile, server, err) } } } //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) } //export c_ArchiveConversation func c_ArchiveConversation(profile_ptr *C.char, profile_len C.int, contact_ptr *C.char, contact_len C.int) { profile := C.GoStringN(profile_ptr, profile_len) contact := C.GoStringN(contact_ptr, contact_len) ArchiveConversation(profile, contact) } // ArchiveConversation sets the conversation to archived func ArchiveConversation(profile string, handle string) { peer := application.GetPeer(profile) ph := utils.NewPeerHelper(peer) if ph.IsGroup(handle) { peer.SetGroupAttribute(handle, attr.GetLocalScope(constants.Archived), event.True) } else { peer.SetContactAttribute(handle, attr.GetLocalScope(constants.Archived), event.True) } } //export c_DeleteContact func c_DeleteContact(profile_ptr *C.char, profile_len C.int, hanlde_ptr *C.char, handle_len C.int) { profile := C.GoStringN(profile_ptr, profile_len) groupID := C.GoStringN(hanlde_ptr, handle_len) DeleteContact(profile, groupID) } // DeleteContact removes all trace of the contact from the profile func DeleteContact(profile string, handle string) { peer := application.GetPeer(profile) ph := utils.NewPeerHelper(peer) if ph.IsGroup(handle) { _, err := groups.ExperimentGate(utils.ReadGlobalSettings().Experiments) if err == nil { peer.DeleteGroup(handle) } } else { peer.DeleteContact(handle) } } //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) peerHandler, _ := contact.FunctionalityGate(utils.ReadGlobalSettings().Experiments) response := peerHandler.HandleImportString(profile, bundle) if strings.Contains(response.Error(), "invalid_import_string") { groupHandler, err := groups.ExperimentGate(utils.ReadGlobalSettings().Experiments) if err == nil { response = groupHandler.HandleImportString(profile, bundle) eventHandler.Push(event.NewEvent(event.AppError, map[event.Field]string{event.Data: response.Error()})) // We might have added a new server, so refresh the server list... serverListForOnion := groupHandler.GetServerInfoList(profile) serversListBytes, _ := json.Marshal(serverListForOnion) eventHandler.Push(event.NewEvent(groups.UpdateServerInfo, map[event.Field]string{"ProfileOnion": profileOnion, groups.ServerList: string(serversListBytes)})) return } } eventHandler.Push(event.NewEvent(event.AppError, map[event.Field]string{event.Data: response.Error()})) } //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.SetAttribute func SetProfileAttribute(profileOnion string, key string, value string) { profile := application.GetPeer(profileOnion) profile.SetAttribute(key, value) } //export c_SetContactAttribute func c_SetContactAttribute(profile_ptr *C.char, profile_len C.int, contact_ptr *C.char, contact_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) contactHandle := C.GoStringN(contact_ptr, contact_len) key := C.GoStringN(key_ptr, key_len) value := C.GoStringN(val_ptr, val_len) SetContactAttribute(profileOnion, contactHandle, key, value) } // SetContactAttribute provides a wrapper around profile.SetProfileAttribute func SetContactAttribute(profileOnion string, contactHandle string, key string, value string) { profile := application.GetPeer(profileOnion) profile.SetContactAttribute(contactHandle, key, value) } //export c_SetGroupAttribute func c_SetGroupAttribute(profile_ptr *C.char, profile_len C.int, group_ptr *C.char, group_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) groupHandle := C.GoStringN(group_ptr, group_len) key := C.GoStringN(key_ptr, key_len) value := C.GoStringN(val_ptr, val_len) SetGroupAttribute(profileOnion, groupHandle, key, value) } // SetGroupAttribute provides a wrapper around profile.SetGroupAttribute, gated by global experiments... func SetGroupAttribute(profileOnion string, groupHandle string, key string, value string) { profile := application.GetPeer(profileOnion) _, err := groups.ExperimentGate(utils.ReadGlobalSettings().Experiments) if err == nil { profile.SetGroupAttribute(groupHandle, key, value) } } //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 && globalACN != 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 globalACN = nil eventHandler = nil } } // Leave as is, needed by ffi func main() {}