Merge pull request 'stale process detection on android' (#63) from rrgtj into trunk
continuous-integration/drone/push Build is passing Details

Reviewed-on: #63
This commit is contained in:
Sarah Jamie Lewis 2021-06-21 17:51:49 -07:00
commit c864bccbb9
2 changed files with 256 additions and 225 deletions

36
lib.go
View File

@ -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 // message: CwtchStarted when start up is complete and app is safe to use
// CwtchStartError message when start up fails (includes event.Error data field) // CwtchStartError message when start up fails (includes event.Error data field)
func StartCwtch(appDir string, torPath string) int { func StartCwtch(appDir string, torPath string) int {
eventHandler = utils.NewEventHandler()
log.SetLevel(log.LevelInfo) log.SetLevel(log.LevelInfo)
log.Infof("StartCwtch(...)")
// Quick hack check that we're being called with the correct params // 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 // 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" { if runtime.GOOS == "android" {
@ -87,7 +88,16 @@ func StartCwtch(appDir string, torPath string) int {
} }
func _startCwtch(appDir string, torPath string) { 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") log.ExcludeFromPattern("service.go")
// Ensure that the application directory exists...and then initialize settings.. // Ensure that the application directory exists...and then initialize settings..
@ -107,6 +117,9 @@ func _startCwtch(appDir string, torPath string) {
panic(err) panic(err)
} }
log.Infof("Creating new EventHandler()")
eventHandler = utils.NewEventHandler()
log.Infof("making directory %v", appDir) log.Infof("making directory %v", appDir)
os.MkdirAll(path.Join(appDir, "/.tor", "tor"), 0700) 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")) 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) // 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 // Do need to re-send initial state tho, eg profiles that are already loaded
func ReconnectCwtchForeground() { func ReconnectCwtchForeground() {
log.Infof("Reconnecting cwtchforeground")
if application == nil { if application == nil {
log.Errorf("ReconnectCwtchForeground: Application is nil, presuming stale thread, EXITING Reconnect\n") log.Errorf("ReconnectCwtchForeground: Application is nil, presuming stale thread, EXITING Reconnect\n")
return return
@ -333,10 +347,18 @@ func c_GetAppBusEvent() *C.char {
// GetAppBusEvent blocks until an event // GetAppBusEvent blocks until an event
func GetAppBusEvent() string { func GetAppBusEvent() string {
log.Infof("appbusevent called")
for eventHandler == nil {
log.Infof("waiting for eventHandler != nil")
time.Sleep(time.Second)
}
var json = "" var json = ""
for json == "" { for json == "" {
log.Infof("waiting for json != ''")
json = eventHandler.GetNextEvent() json = eventHandler.GetNextEvent()
} }
log.Infof("appbusevent: %v", json)
return json return json
} }
@ -700,11 +722,15 @@ func ShutdownCwtch() {
eventHandler.Push(event.NewEvent(event.Shutdown, map[event.Field]string{})) eventHandler.Push(event.NewEvent(event.Shutdown, map[event.Field]string{}))
// Allow for the shutdown events to go through and then purge everything else... // 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() application.Shutdown()
log.Debugf("Shutting Down ACN...") log.Infof("Shutting Down ACN...")
globalACN.Close() 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
} }
} }

View File

@ -65,149 +65,151 @@ func (eh *EventHandler) GetNextEvent() string {
// handleAppBusEvent enriches AppBus events so they are usable with out further data fetches // handleAppBusEvent enriches AppBus events so they are usable with out further data fetches
func (eh *EventHandler) handleAppBusEvent(e *event.Event) string { func (eh *EventHandler) handleAppBusEvent(e *event.Event) string {
log.Debugf("New AppBus Event to Handle: %v", e) log.Debugf("New AppBus Event to Handle: %v", e)
switch e.EventType { if eh.app != nil {
case event.ACNStatus: switch e.EventType {
if e.Data[event.Progress] == "100" { case event.ACNStatus:
for onion := range eh.app.ListPeers() { if e.Data[event.Progress] == "100" {
// launch a listen thread (internally this does a check that the protocol engine is not listening) for onion := range eh.app.ListPeers() {
// and as such is safe to call. // launch a listen thread (internally this does a check that the protocol engine is not listening)
eh.app.GetPeer(onion).Listen() // and as such is safe to call.
} eh.app.GetPeer(onion).Listen()
} }
case event.NewPeer: }
onion := e.Data[event.Identity] case event.NewPeer:
profile := eh.app.GetPeer(e.Data[event.Identity]) onion := e.Data[event.Identity]
log.Debug("New Peer Event: %v", e) profile := eh.app.GetPeer(e.Data[event.Identity])
eh.startHandlingPeer(onion) log.Debug("New Peer Event: %v", e)
eh.startHandlingPeer(onion)
if e.Data[event.Created] == event.True {
name, _ := profile.GetAttribute(attr.GetLocalScope(constants.Name)) if e.Data[event.Created] == event.True {
profile.SetAttribute(attr.GetPublicScope(constants.Name), name) name, _ := profile.GetAttribute(attr.GetLocalScope(constants.Name))
profile.SetAttribute(attr.GetPublicScope(constants.Picture), ImageToString(NewImage(RandomProfileImage(onion), TypeImageDistro))) 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) if e.Data[event.Status] != event.StorageRunning || e.Data[event.Created] == event.True {
eh.app.AddPeerPlugin(onion, plugins.CONNECTIONRETRY) profile.SetAttribute(attr.GetLocalScope(constants.PeerOnline), event.False)
eh.app.AddPeerPlugin(onion, plugins.NETWORKCHECK) 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 the user has chosen to block unknown profiles
if ReadGlobalSettings().BlockUnknownConnections { // then explicitly configure the protocol engine to do so..
profile.BlockUnknownConnections() if ReadGlobalSettings().BlockUnknownConnections {
} else { profile.BlockUnknownConnections()
// For completeness } else {
profile.AllowUnknownConnections() // For completeness
} profile.AllowUnknownConnections()
}
// Start up the Profile
profile.Listen() // Start up the Profile
profile.StartPeersConnections() profile.Listen()
if _, err := groups.ExperimentGate(ReadGlobalSettings().Experiments); err == nil { profile.StartPeersConnections()
profile.StartServerConnections() 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))
} }
continue
} }
contactInfo := profile.GetContact(contact) nick, exists := profile.GetAttribute(attr.GetPublicScope(constants.Name))
ph := NewPeerHelper(profile) if !exists {
name := ph.GetNick(contact) nick = onion
cpicPath := ph.GetProfilePic(contact)
saveHistory, set := contactInfo.GetAttribute(event.SaveHistoryKey)
if !set {
saveHistory = event.DeleteHistoryDefault
} }
contacts = append(contacts, Contact{
Name: name, picVal, ok := profile.GetAttribute(attr.GetPublicScope(constants.Picture))
Onion: contactInfo.Onion, if !ok {
Status: contactInfo.State, picVal = ImageToString(NewImage(RandomProfileImage(onion), TypeImageDistro))
Picture: cpicPath, }
Authorization: string(contactInfo.Authorization), pic, err := StringToImage(picVal)
SaveHistory: saveHistory, if err != nil {
Messages: contactInfo.Timeline.Len(), pic = NewImage(RandomProfileImage(onion), TypeImageDistro)
Unread: 0, }
LastMessage: strconv.Itoa(getLastMessageTime(&contactInfo.Timeline)), picPath := GetPicturePath(pic)
IsGroup: false,
}) //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) 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 // handleProfileEvent enriches Profile events so they are usable with out further data fetches
func (eh *EventHandler) handleProfileEvent(ev *EventProfileEnvelope) string { 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) TODO: still handle this somewhere - network info from plugin Network check
log.Debugf("New Profile Event to Handle: %v", ev) case event.NetworkStatus:
switch ev.Event.EventType { 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
TODO: still handle this somewhere - network info from plugin Network check // only needs contact nickname and picture, for displaying on popup notifications
case event.NetworkStatus: ev.Event.Data["Nick"] = ph.GetNick(ev.Event.Data["RemotePeer"])
online, _ := peer.GetAttribute(attr.GetLocalScope(constants.PeerOnline)) ev.Event.Data["Picture"] = ph.GetProfilePic(ev.Event.Data["RemotePeer"])
if e.Data[event.Status] == plugins.NetworkCheckSuccess && online == event.False { case event.NewMessageFromGroup:
peer.SetAttribute(attr.GetLocalScope(constants.PeerOnline), event.True) // only needs contact nickname and picture, for displaying on popup notifications
uiManager.UpdateNetworkStatus(true) ev.Event.Data["Nick"] = ph.GetNick(ev.Event.Data[event.GroupID])
// TODO we may have to reinitialize the peer ev.Event.Data["Picture"] = ph.GetProfilePic(ev.Event.Data[event.GroupID])
} else if e.Data[event.Status] == plugins.NetworkCheckError && online == event.True { case event.PeerAcknowledgement:
peer.SetAttribute(attr.GetLocalScope(constants.PeerOnline), event.False) // No enrichement required
uiManager.UpdateNetworkStatus(false) case event.PeerCreated:
}*/ handle := ev.Event.Data[event.RemotePeer]
err := EnrichNewPeer(handle, ph, ev)
case event.NewMessageFromPeer: //event.TimestampReceived, event.RemotePeer, event.Data if err != nil {
// 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 "" 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 ""
}
}
} }
} }