diff --git a/lib.go b/lib.go index f20cbf6..c4766b1 100644 --- a/lib.go +++ b/lib.go @@ -68,9 +68,10 @@ func c_StartCwtch(dir_c *C.char, len C.int, tor_c *C.char, torLen C.int) int8 { // 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 { - eventHandler = utils.NewEventHandler() log.SetLevel(log.LevelInfo) + + 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" { @@ -87,7 +88,16 @@ func StartCwtch(appDir string, torPath string) int { } func _startCwtch(appDir string, torPath string) { - // Exclude Tapir wire Messages (We need a TRACE level) + 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 + } + + // Exclude Tapir wire Messages + //(We need a TRACE level) log.ExcludeFromPattern("service.go") // Ensure that the application directory exists...and then initialize settings.. @@ -107,6 +117,9 @@ func _startCwtch(appDir string, torPath string) { panic(err) } + log.Infof("Creating new EventHandler()") + eventHandler = utils.NewEventHandler() + log.Infof("making directory %v", appDir) os.MkdirAll(path.Join(appDir, "/.tor", "tor"), 0700) tor.NewTorrc().WithSocksPort(port).WithOnionTrafficOnly().WithControlPort(controlPort).WithHashedPassword(base64.StdEncoding.EncodeToString(key)).Build(filepath.Join(appDir, ".tor", "tor", "torrc")) @@ -162,6 +175,7 @@ func c_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 @@ -333,10 +347,18 @@ func c_GetAppBusEvent() *C.char { // GetAppBusEvent blocks until an event func GetAppBusEvent() string { + log.Infof("appbusevent called") + for eventHandler == nil { + log.Infof("waiting for eventHandler != nil") + time.Sleep(time.Second) + } + var json = "" for json == "" { + log.Infof("waiting for json != ''") json = eventHandler.GetNextEvent() } + log.Infof("appbusevent: %v", json) return json } @@ -700,11 +722,15 @@ func ShutdownCwtch() { eventHandler.Push(event.NewEvent(event.Shutdown, map[event.Field]string{})) // Allow for the shutdown events to go through and then purge everything else... - log.Debugf("Shutting Down Application...") + log.Infof("Shutting Down Application...") application.Shutdown() - log.Debugf("Shutting Down ACN...") + log.Infof("Shutting Down ACN...") globalACN.Close() - log.Debugf("Library Shutdown Complete!") + log.Infof("Library Shutdown Complete!") + // do not remove - important for state checks elsewhere + application = nil + globalACN = nil + eventHandler = nil } } diff --git a/utils/eventHandler.go b/utils/eventHandler.go index 311c00a..978fb49 100644 --- a/utils/eventHandler.go +++ b/utils/eventHandler.go @@ -65,149 +65,151 @@ func (eh *EventHandler) GetNextEvent() string { // handleAppBusEvent enriches AppBus events so they are usable with out further data fetches func (eh *EventHandler) handleAppBusEvent(e *event.Event) string { log.Debugf("New AppBus Event to Handle: %v", e) - switch e.EventType { - case event.ACNStatus: - if e.Data[event.Progress] == "100" { - for onion := range eh.app.ListPeers() { - // launch a listen thread (internally this does a check that the protocol engine is not listening) - // and as such is safe to call. - eh.app.GetPeer(onion).Listen() - } - } - case event.NewPeer: - onion := e.Data[event.Identity] - profile := eh.app.GetPeer(e.Data[event.Identity]) - log.Debug("New Peer Event: %v", e) - eh.startHandlingPeer(onion) - - if e.Data[event.Created] == event.True { - name, _ := profile.GetAttribute(attr.GetLocalScope(constants.Name)) - profile.SetAttribute(attr.GetPublicScope(constants.Name), name) - profile.SetAttribute(attr.GetPublicScope(constants.Picture), ImageToString(NewImage(RandomProfileImage(onion), TypeImageDistro))) - } - if e.Data[event.Status] != event.StorageRunning || e.Data[event.Created] == event.True { - profile.SetAttribute(attr.GetLocalScope(constants.PeerOnline), event.False) - eh.app.AddPeerPlugin(onion, plugins.CONNECTIONRETRY) - eh.app.AddPeerPlugin(onion, plugins.NETWORKCHECK) - - // If the user has chosen to block unknown profiles - // then explicitly configure the protocol engine to do so.. - if ReadGlobalSettings().BlockUnknownConnections { - profile.BlockUnknownConnections() - } else { - // For completeness - profile.AllowUnknownConnections() - } - - // Start up the Profile - profile.Listen() - profile.StartPeersConnections() - if _, err := groups.ExperimentGate(ReadGlobalSettings().Experiments); err == nil { - profile.StartServerConnections() - } - } - - nick, exists := profile.GetAttribute(attr.GetPublicScope(constants.Name)) - if !exists { - nick = onion - } - - picVal, ok := profile.GetAttribute(attr.GetPublicScope(constants.Picture)) - if !ok { - picVal = ImageToString(NewImage(RandomProfileImage(onion), TypeImageDistro)) - } - pic, err := StringToImage(picVal) - if err != nil { - pic = NewImage(RandomProfileImage(onion), TypeImageDistro) - } - picPath := GetPicturePath(pic) - - //tag, _ := profile.GetAttribute(app.AttributeTag) - - online, _ := profile.GetAttribute(attr.GetLocalScope(constants.PeerOnline)) - - e.Data[constants.Name] = nick - e.Data[constants.Picture] = picPath - e.Data["Online"] = online - - var contacts []Contact - var servers []groups.Server - for _, contact := range profile.GetContacts() { - - // Only compile the server info if we have enabled the experiment... - // Note that this means that this info can become stale if when first loaded the experiment - // has been disabled and then is later re-enabled. As such we need to ensure that this list is - // re-fetched when the group experiment is enabled via a dedicated ListServerInfo event... - if profile.GetContact(contact).IsServer() { - groupHandler, err := groups.ExperimentGate(ReadGlobalSettings().Experiments) - if err == nil { - servers = append(servers, groupHandler.GetServerInfo(contact, profile)) + if eh.app != nil { + switch e.EventType { + case event.ACNStatus: + if e.Data[event.Progress] == "100" { + for onion := range eh.app.ListPeers() { + // launch a listen thread (internally this does a check that the protocol engine is not listening) + // and as such is safe to call. + eh.app.GetPeer(onion).Listen() + } + } + case event.NewPeer: + onion := e.Data[event.Identity] + profile := eh.app.GetPeer(e.Data[event.Identity]) + log.Debug("New Peer Event: %v", e) + eh.startHandlingPeer(onion) + + if e.Data[event.Created] == event.True { + name, _ := profile.GetAttribute(attr.GetLocalScope(constants.Name)) + profile.SetAttribute(attr.GetPublicScope(constants.Name), name) + profile.SetAttribute(attr.GetPublicScope(constants.Picture), ImageToString(NewImage(RandomProfileImage(onion), TypeImageDistro))) + } + if e.Data[event.Status] != event.StorageRunning || e.Data[event.Created] == event.True { + profile.SetAttribute(attr.GetLocalScope(constants.PeerOnline), event.False) + eh.app.AddPeerPlugin(onion, plugins.CONNECTIONRETRY) + eh.app.AddPeerPlugin(onion, plugins.NETWORKCHECK) + + // If the user has chosen to block unknown profiles + // then explicitly configure the protocol engine to do so.. + if ReadGlobalSettings().BlockUnknownConnections { + profile.BlockUnknownConnections() + } else { + // For completeness + profile.AllowUnknownConnections() + } + + // Start up the Profile + profile.Listen() + profile.StartPeersConnections() + if _, err := groups.ExperimentGate(ReadGlobalSettings().Experiments); err == nil { + profile.StartServerConnections() } - continue } - contactInfo := profile.GetContact(contact) - ph := NewPeerHelper(profile) - name := ph.GetNick(contact) - cpicPath := ph.GetProfilePic(contact) - saveHistory, set := contactInfo.GetAttribute(event.SaveHistoryKey) - if !set { - saveHistory = event.DeleteHistoryDefault + nick, exists := profile.GetAttribute(attr.GetPublicScope(constants.Name)) + if !exists { + nick = onion } - contacts = append(contacts, Contact{ - Name: name, - Onion: contactInfo.Onion, - Status: contactInfo.State, - Picture: cpicPath, - Authorization: string(contactInfo.Authorization), - SaveHistory: saveHistory, - Messages: contactInfo.Timeline.Len(), - Unread: 0, - LastMessage: strconv.Itoa(getLastMessageTime(&contactInfo.Timeline)), - IsGroup: false, - }) + + picVal, ok := profile.GetAttribute(attr.GetPublicScope(constants.Picture)) + if !ok { + picVal = ImageToString(NewImage(RandomProfileImage(onion), TypeImageDistro)) + } + pic, err := StringToImage(picVal) + if err != nil { + pic = NewImage(RandomProfileImage(onion), TypeImageDistro) + } + picPath := GetPicturePath(pic) + + //tag, _ := profile.GetAttribute(app.AttributeTag) + + online, _ := profile.GetAttribute(attr.GetLocalScope(constants.PeerOnline)) + + e.Data[constants.Name] = nick + e.Data[constants.Picture] = picPath + e.Data["Online"] = online + + var contacts []Contact + var servers []groups.Server + for _, contact := range profile.GetContacts() { + + // Only compile the server info if we have enabled the experiment... + // Note that this means that this info can become stale if when first loaded the experiment + // has been disabled and then is later re-enabled. As such we need to ensure that this list is + // re-fetched when the group experiment is enabled via a dedicated ListServerInfo event... + if profile.GetContact(contact).IsServer() { + groupHandler, err := groups.ExperimentGate(ReadGlobalSettings().Experiments) + if err == nil { + servers = append(servers, groupHandler.GetServerInfo(contact, profile)) + } + continue + } + + contactInfo := profile.GetContact(contact) + ph := NewPeerHelper(profile) + name := ph.GetNick(contact) + cpicPath := ph.GetProfilePic(contact) + saveHistory, set := contactInfo.GetAttribute(event.SaveHistoryKey) + if !set { + saveHistory = event.DeleteHistoryDefault + } + contacts = append(contacts, Contact{ + Name: name, + Onion: contactInfo.Onion, + Status: contactInfo.State, + Picture: cpicPath, + Authorization: string(contactInfo.Authorization), + SaveHistory: saveHistory, + Messages: contactInfo.Timeline.Len(), + Unread: 0, + LastMessage: strconv.Itoa(getLastMessageTime(&contactInfo.Timeline)), + IsGroup: false, + }) + } + + // We compile and send the groups regardless of the experiment flag, and hide them in the UI + for _, groupId := range profile.GetGroups() { + group := profile.GetGroup(groupId) + + // Check that the group is cryptographically valid + if !group.CheckGroup() { + continue + } + + ph := NewPeerHelper(profile) + cpicPath := ph.GetProfilePic(groupId) + + authorization := model.AuthUnknown + if group.Accepted { + authorization = model.AuthApproved + } + + contacts = append(contacts, Contact{ + Name: ph.GetNick(groupId), + Onion: group.GroupID, + Status: group.State, + Picture: cpicPath, + Authorization: string(authorization), + SaveHistory: event.SaveHistoryConfirmed, + Messages: group.Timeline.Len(), + Unread: 0, + LastMessage: strconv.Itoa(getLastMessageTime(&group.Timeline)), + IsGroup: true, + GroupServer: group.GroupServer, + }) + } + + bytes, _ := json.Marshal(contacts) + e.Data["ContactsJson"] = string(bytes) + + // Marshal the server list into the new peer event... + serversListBytes, _ := json.Marshal(servers) + e.Data[groups.ServerList] = string(serversListBytes) + + log.Infof("contactsJson %v", e.Data["ContactsJson"]) } - - // We compile and send the groups regardless of the experiment flag, and hide them in the UI - for _, groupId := range profile.GetGroups() { - group := profile.GetGroup(groupId) - - // Check that the group is cryptographically valid - if !group.CheckGroup() { - continue - } - - ph := NewPeerHelper(profile) - cpicPath := ph.GetProfilePic(groupId) - - authorization := model.AuthUnknown - if group.Accepted { - authorization = model.AuthApproved - } - - contacts = append(contacts, Contact{ - Name: ph.GetNick(groupId), - Onion: group.GroupID, - Status: group.State, - Picture: cpicPath, - Authorization: string(authorization), - SaveHistory: event.SaveHistoryConfirmed, - Messages: group.Timeline.Len(), - Unread: 0, - LastMessage: strconv.Itoa(getLastMessageTime(&group.Timeline)), - IsGroup: true, - GroupServer: group.GroupServer, - }) - } - - bytes, _ := json.Marshal(contacts) - e.Data["ContactsJson"] = string(bytes) - - // Marshal the server list into the new peer event... - serversListBytes, _ := json.Marshal(servers) - e.Data[groups.ServerList] = string(serversListBytes) - - log.Infof("contactsJson %v", e.Data["ContactsJson"]) } json, _ := json.Marshal(e) @@ -216,92 +218,95 @@ func (eh *EventHandler) handleAppBusEvent(e *event.Event) string { // handleProfileEvent enriches Profile events so they are usable with out further data fetches func (eh *EventHandler) handleProfileEvent(ev *EventProfileEnvelope) string { + if eh.app == nil { + log.Errorf("eh.app == nil in handleProfileEvent... this shouldnt happen?") + } else { + peer := eh.app.GetPeer(ev.Profile) + ph := NewPeerHelper(peer) + log.Debugf("New Profile Event to Handle: %v", ev) + switch ev.Event.EventType { - peer := eh.app.GetPeer(ev.Profile) - ph := NewPeerHelper(peer) - log.Debugf("New Profile Event to Handle: %v", ev) - switch ev.Event.EventType { + /* + TODO: still handle this somewhere - network info from plugin Network check + case event.NetworkStatus: + online, _ := peer.GetAttribute(attr.GetLocalScope(constants.PeerOnline)) + if e.Data[event.Status] == plugins.NetworkCheckSuccess && online == event.False { + peer.SetAttribute(attr.GetLocalScope(constants.PeerOnline), event.True) + uiManager.UpdateNetworkStatus(true) + // TODO we may have to reinitialize the peer + } else if e.Data[event.Status] == plugins.NetworkCheckError && online == event.True { + peer.SetAttribute(attr.GetLocalScope(constants.PeerOnline), event.False) + uiManager.UpdateNetworkStatus(false) + }*/ - /* - TODO: still handle this somewhere - network info from plugin Network check - case event.NetworkStatus: - online, _ := peer.GetAttribute(attr.GetLocalScope(constants.PeerOnline)) - if e.Data[event.Status] == plugins.NetworkCheckSuccess && online == event.False { - peer.SetAttribute(attr.GetLocalScope(constants.PeerOnline), event.True) - uiManager.UpdateNetworkStatus(true) - // TODO we may have to reinitialize the peer - } else if e.Data[event.Status] == plugins.NetworkCheckError && online == event.True { - peer.SetAttribute(attr.GetLocalScope(constants.PeerOnline), event.False) - uiManager.UpdateNetworkStatus(false) - }*/ - - case event.NewMessageFromPeer: //event.TimestampReceived, event.RemotePeer, event.Data - // only needs contact nickname and picture, for displaying on popup notifications - ev.Event.Data["Nick"] = ph.GetNick(ev.Event.Data["RemotePeer"]) - ev.Event.Data["Picture"] = ph.GetProfilePic(ev.Event.Data["RemotePeer"]) - case event.NewMessageFromGroup: - // only needs contact nickname and picture, for displaying on popup notifications - ev.Event.Data["Nick"] = ph.GetNick(ev.Event.Data[event.GroupID]) - ev.Event.Data["Picture"] = ph.GetProfilePic(ev.Event.Data[event.GroupID]) - case event.PeerAcknowledgement: - // No enrichement required - case event.PeerCreated: - handle := ev.Event.Data[event.RemotePeer] - err := EnrichNewPeer(handle, ph, ev) - if err != nil { - return "" - } - case event.GroupCreated: - // This event should only happen after we have validated the invite, as such the error - // condition *should* never happen. - - groupPic := ph.GetProfilePic(ev.Event.Data[event.GroupID]) - ev.Event.Data["PicturePath"] = groupPic - ev.Event.Data["GroupName"] = ph.GetNick(ev.Event.Data[event.GroupID]) - - case event.NewGroup: - // This event should only happen after we have validated the invite, as such the error - // condition *should* never happen. - serializedInvite := ev.Event.Data[event.GroupInvite] - if invite, err := model.ValidateInvite(serializedInvite); err == nil { - groupPic := ph.GetProfilePic(invite.GroupID) - ev.Event.Data["PicturePath"] = groupPic - } else { - log.Errorf("received a new group event which contained an invalid invite %v. this should never happen and likely means there is a bug in cwtch. Please file a ticket @ https://git.openprivcy.ca/cwtch.im/cwtch", err) - return "" - } - case event.PeerStateChange: - cxnState := connections.ConnectionStateToType()[ev.Event.Data[event.ConnectionState]] - contact := peer.GetContact(ev.Event.Data[event.RemotePeer]) - - if cxnState == connections.AUTHENTICATED && contact == nil { - peer.AddContact(ev.Event.Data[event.RemotePeer], ev.Event.Data[event.RemotePeer], model.AuthUnknown) - return "" - } - - if contact != nil { - // No enrichment needed - //uiManager.UpdateContactStatus(contact.Onion, int(cxnState), false) - if cxnState == connections.AUTHENTICATED { - // if known and authed, get vars - peer.SendGetValToPeer(ev.Event.Data[event.RemotePeer], attr.PublicScope, constants.Name) - peer.SendGetValToPeer(ev.Event.Data[event.RemotePeer], attr.PublicScope, constants.Picture) - } - } - - case event.NewRetValMessageFromPeer: - // auto handled event means the setting is already done, we're just deciding if we need to tell the UI - onion := ev.Event.Data[event.RemotePeer] - scope := ev.Event.Data[event.Scope] - path := ev.Event.Data[event.Path] - //val := ev.Event.Data[event.Data] - exists, _ := strconv.ParseBool(ev.Event.Data[event.Exists]) - - if exists && scope == attr.PublicScope { - if _, exists := peer.GetContactAttribute(onion, attr.GetLocalScope(path)); exists { - // we have a locally set ovverride, don't pass this remote set public scope update to UI + case event.NewMessageFromPeer: //event.TimestampReceived, event.RemotePeer, event.Data + // only needs contact nickname and picture, for displaying on popup notifications + ev.Event.Data["Nick"] = ph.GetNick(ev.Event.Data["RemotePeer"]) + ev.Event.Data["Picture"] = ph.GetProfilePic(ev.Event.Data["RemotePeer"]) + case event.NewMessageFromGroup: + // only needs contact nickname and picture, for displaying on popup notifications + ev.Event.Data["Nick"] = ph.GetNick(ev.Event.Data[event.GroupID]) + ev.Event.Data["Picture"] = ph.GetProfilePic(ev.Event.Data[event.GroupID]) + case event.PeerAcknowledgement: + // No enrichement required + case event.PeerCreated: + handle := ev.Event.Data[event.RemotePeer] + err := EnrichNewPeer(handle, ph, ev) + if err != nil { return "" } + case event.GroupCreated: + // This event should only happen after we have validated the invite, as such the error + // condition *should* never happen. + + groupPic := ph.GetProfilePic(ev.Event.Data[event.GroupID]) + ev.Event.Data["PicturePath"] = groupPic + ev.Event.Data["GroupName"] = ph.GetNick(ev.Event.Data[event.GroupID]) + + case event.NewGroup: + // This event should only happen after we have validated the invite, as such the error + // condition *should* never happen. + serializedInvite := ev.Event.Data[event.GroupInvite] + if invite, err := model.ValidateInvite(serializedInvite); err == nil { + groupPic := ph.GetProfilePic(invite.GroupID) + ev.Event.Data["PicturePath"] = groupPic + } else { + log.Errorf("received a new group event which contained an invalid invite %v. this should never happen and likely means there is a bug in cwtch. Please file a ticket @ https://git.openprivcy.ca/cwtch.im/cwtch", err) + return "" + } + case event.PeerStateChange: + cxnState := connections.ConnectionStateToType()[ev.Event.Data[event.ConnectionState]] + contact := peer.GetContact(ev.Event.Data[event.RemotePeer]) + + if cxnState == connections.AUTHENTICATED && contact == nil { + peer.AddContact(ev.Event.Data[event.RemotePeer], ev.Event.Data[event.RemotePeer], model.AuthUnknown) + return "" + } + + if contact != nil { + // No enrichment needed + //uiManager.UpdateContactStatus(contact.Onion, int(cxnState), false) + if cxnState == connections.AUTHENTICATED { + // if known and authed, get vars + peer.SendGetValToPeer(ev.Event.Data[event.RemotePeer], attr.PublicScope, constants.Name) + peer.SendGetValToPeer(ev.Event.Data[event.RemotePeer], attr.PublicScope, constants.Picture) + } + } + + case event.NewRetValMessageFromPeer: + // auto handled event means the setting is already done, we're just deciding if we need to tell the UI + onion := ev.Event.Data[event.RemotePeer] + scope := ev.Event.Data[event.Scope] + path := ev.Event.Data[event.Path] + //val := ev.Event.Data[event.Data] + exists, _ := strconv.ParseBool(ev.Event.Data[event.Exists]) + + if exists && scope == attr.PublicScope { + if _, exists := peer.GetContactAttribute(onion, attr.GetLocalScope(path)); exists { + // we have a locally set ovverride, don't pass this remote set public scope update to UI + return "" + } + } } }