C-bindings for the Go Cwtch Library
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

379 lines
14 KiB

6 months ago
  1. package utils
  2. import (
  3. "cwtch.im/cwtch/app"
  4. "cwtch.im/cwtch/app/plugins"
  5. "cwtch.im/cwtch/model"
  6. "cwtch.im/cwtch/model/attr"
  7. "cwtch.im/cwtch/protocol/connections"
  8. "encoding/json"
  9. "git.openprivacy.ca/flutter/libcwtch-go/constants"
  10. "git.openprivacy.ca/flutter/libcwtch-go/features/groups"
  11. "git.openprivacy.ca/openprivacy/log"
  12. "strconv"
  13. )
  14. import "cwtch.im/cwtch/event"
  15. type EventProfileEnvelope struct {
  16. Event event.Event
  17. Profile string
  18. }
  19. type EventHandler struct {
  20. app app.Application
  21. appBusQueue event.Queue
  22. profileEvents chan EventProfileEnvelope
  23. }
  24. func NewEventHandler() *EventHandler {
  25. eh := &EventHandler{app: nil, appBusQueue: event.NewQueue(), profileEvents: make(chan EventProfileEnvelope)}
  26. return eh
  27. }
  28. // PublishAppEvent is a way for libCwtch-go to publish an event for consumption by a UI before a Cwtch app has been initialized
  29. // Main use: to signal an error before a cwtch app could be created
  30. func (eh *EventHandler) PublishAppEvent(event event.Event) {
  31. eh.appBusQueue.Publish(event)
  32. }
  33. func (eh *EventHandler) HandleApp(application app.Application) {
  34. eh.app = application
  35. application.GetPrimaryBus().Subscribe(event.NewPeer, eh.appBusQueue)
  36. application.GetPrimaryBus().Subscribe(event.PeerError, eh.appBusQueue)
  37. application.GetPrimaryBus().Subscribe(event.PeerDeleted, eh.appBusQueue)
  38. application.GetPrimaryBus().Subscribe(event.Shutdown, eh.appBusQueue)
  39. application.GetPrimaryBus().Subscribe(event.AppError, eh.appBusQueue)
  40. application.GetPrimaryBus().Subscribe(event.ACNStatus, eh.appBusQueue)
  41. application.GetPrimaryBus().Subscribe(event.ReloadDone, eh.appBusQueue)
  42. application.GetPrimaryBus().Subscribe(event.ACNVersion, eh.appBusQueue)
  43. application.GetPrimaryBus().Subscribe(UpdateGlobalSettings, eh.appBusQueue)
  44. application.GetPrimaryBus().Subscribe(CwtchStarted, eh.appBusQueue)
  45. }
  46. func (eh *EventHandler) GetNextEvent() string {
  47. appChan := eh.appBusQueue.OutChan()
  48. select {
  49. case e := <-appChan:
  50. return eh.handleAppBusEvent(&e)
  51. case ev := <-eh.profileEvents:
  52. return eh.handleProfileEvent(&ev)
  53. }
  54. }
  55. // handleAppBusEvent enriches AppBus events so they are usable with out further data fetches
  56. func (eh *EventHandler) handleAppBusEvent(e *event.Event) string {
  57. log.Debugf("New AppBus Event to Handle: %v", e)
  58. if eh.app != nil {
  59. switch e.EventType {
  60. case event.ACNStatus:
  61. if e.Data[event.Progress] == "100" {
  62. for onion := range eh.app.ListPeers() {
  63. // launch a listen thread (internally this does a check that the protocol engine is not listening)
  64. // and as such is safe to call.
  65. eh.app.GetPeer(onion).Listen()
  66. }
  67. }
  68. case event.NewPeer:
  69. onion := e.Data[event.Identity]
  70. profile := eh.app.GetPeer(e.Data[event.Identity])
  71. log.Debug("New Peer Event: %v", e)
  72. if e.Data["Reload"] != event.True {
  73. eh.startHandlingPeer(onion)
  74. }
  75. tag,isTagged := profile.GetAttribute(app.AttributeTag)
  76. if isTagged {
  77. e.Data[app.AttributeTag] = tag
  78. } else {
  79. // Assume encrypted for non-tagged profiles - this isn't always true, but all post-beta profiles
  80. // are tagged on creation.
  81. e.Data[app.AttributeTag] = constants.ProfileTypeV1Password
  82. }
  83. if e.Data[event.Created] == event.True {
  84. name, _ := profile.GetAttribute(attr.GetLocalScope(constants.Name))
  85. profile.SetAttribute(attr.GetPublicScope(constants.Name), name)
  86. profile.SetAttribute(attr.GetPublicScope(constants.Picture), ImageToString(NewImage(RandomProfileImage(onion), TypeImageDistro)))
  87. }
  88. if e.Data[event.Status] != event.StorageRunning || e.Data[event.Created] == event.True {
  89. profile.SetAttribute(attr.GetLocalScope(constants.PeerOnline), event.False)
  90. eh.app.AddPeerPlugin(onion, plugins.CONNECTIONRETRY)
  91. eh.app.AddPeerPlugin(onion, plugins.NETWORKCHECK)
  92. // If the user has chosen to block unknown profiles
  93. // then explicitly configure the protocol engine to do so..
  94. if ReadGlobalSettings().BlockUnknownConnections {
  95. profile.BlockUnknownConnections()
  96. } else {
  97. // For completeness
  98. profile.AllowUnknownConnections()
  99. }
  100. // Start up the Profile
  101. profile.Listen()
  102. profile.StartPeersConnections()
  103. if _, err := groups.ExperimentGate(ReadGlobalSettings().Experiments); err == nil {
  104. profile.StartServerConnections()
  105. }
  106. }
  107. nick, exists := profile.GetAttribute(attr.GetPublicScope(constants.Name))
  108. if !exists {
  109. nick = onion
  110. }
  111. picVal, ok := profile.GetAttribute(attr.GetPublicScope(constants.Picture))
  112. if !ok {
  113. picVal = ImageToString(NewImage(RandomProfileImage(onion), TypeImageDistro))
  114. }
  115. pic, err := StringToImage(picVal)
  116. if err != nil {
  117. pic = NewImage(RandomProfileImage(onion), TypeImageDistro)
  118. }
  119. picPath := GetPicturePath(pic)
  120. //tag, _ := profile.GetAttribute(app.AttributeTag)
  121. online, _ := profile.GetAttribute(attr.GetLocalScope(constants.PeerOnline))
  122. e.Data[constants.Name] = nick
  123. e.Data[constants.Picture] = picPath
  124. e.Data["Online"] = online
  125. var contacts []Contact
  126. var servers []groups.Server
  127. for _, contact := range profile.GetContacts() {
  128. // Only compile the server info if we have enabled the experiment...
  129. // Note that this means that this info can become stale if when first loaded the experiment
  130. // has been disabled and then is later re-enabled. As such we need to ensure that this list is
  131. // re-fetched when the group experiment is enabled via a dedicated ListServerInfo event...
  132. if profile.GetContact(contact).IsServer() {
  133. groupHandler, err := groups.ExperimentGate(ReadGlobalSettings().Experiments)
  134. if err == nil {
  135. servers = append(servers, groupHandler.GetServerInfo(contact, profile))
  136. }
  137. continue
  138. }
  139. contactInfo := profile.GetContact(contact)
  140. ph := NewPeerHelper(profile)
  141. name := ph.GetNick(contact)
  142. cpicPath := ph.GetProfilePic(contact)
  143. saveHistory, set := contactInfo.GetAttribute(event.SaveHistoryKey)
  144. if !set {
  145. saveHistory = event.DeleteHistoryDefault
  146. }
  147. contacts = append(contacts, Contact{
  148. Name: name,
  149. Onion: contactInfo.Onion,
  150. Status: contactInfo.State,
  151. Picture: cpicPath,
  152. Authorization: string(contactInfo.Authorization),
  153. SaveHistory: saveHistory,
  154. Messages: contactInfo.Timeline.Len(),
  155. Unread: 0,
  156. LastMessage: strconv.Itoa(getLastMessageTime(&contactInfo.Timeline)),
  157. IsGroup: false,
  158. })
  159. }
  160. // We compile and send the groups regardless of the experiment flag, and hide them in the UI
  161. for _, groupId := range profile.GetGroups() {
  162. group := profile.GetGroup(groupId)
  163. // Check that the group is cryptographically valid
  164. if !group.CheckGroup() {
  165. continue
  166. }
  167. ph := NewPeerHelper(profile)
  168. cpicPath := ph.GetProfilePic(groupId)
  169. authorization := model.AuthUnknown
  170. if group.Accepted {
  171. authorization = model.AuthApproved
  172. }
  173. contacts = append(contacts, Contact{
  174. Name: ph.GetNick(groupId),
  175. Onion: group.GroupID,
  176. Status: group.State,
  177. Picture: cpicPath,
  178. Authorization: string(authorization),
  179. SaveHistory: event.SaveHistoryConfirmed,
  180. Messages: group.Timeline.Len(),
  181. Unread: 0,
  182. LastMessage: strconv.Itoa(getLastMessageTime(&group.Timeline)),
  183. IsGroup: true,
  184. GroupServer: group.GroupServer,
  185. })
  186. }
  187. bytes, _ := json.Marshal(contacts)
  188. e.Data["ContactsJson"] = string(bytes)
  189. // Marshal the server list into the new peer event...
  190. serversListBytes, _ := json.Marshal(servers)
  191. e.Data[groups.ServerList] = string(serversListBytes)
  192. log.Debugf("contactsJson %v", e.Data["ContactsJson"])
  193. }
  194. }
  195. json, _ := json.Marshal(e)
  196. return string(json)
  197. }
  198. // handleProfileEvent enriches Profile events so they are usable with out further data fetches
  199. func (eh *EventHandler) handleProfileEvent(ev *EventProfileEnvelope) string {
  200. if eh.app == nil {
  201. log.Errorf("eh.app == nil in handleProfileEvent... this shouldnt happen?")
  202. } else {
  203. peer := eh.app.GetPeer(ev.Profile)
  204. ph := NewPeerHelper(peer)
  205. log.Debugf("New Profile Event to Handle: %v", ev)
  206. switch ev.Event.EventType {
  207. /*
  208. TODO: still handle this somewhere - network info from plugin Network check
  209. case event.NetworkStatus:
  210. online, _ := peer.GetAttribute(attr.GetLocalScope(constants.PeerOnline))
  211. if e.Data[event.Status] == plugins.NetworkCheckSuccess && online == event.False {
  212. peer.SetAttribute(attr.GetLocalScope(constants.PeerOnline), event.True)
  213. uiManager.UpdateNetworkStatus(true)
  214. // TODO we may have to reinitialize the peer
  215. } else if e.Data[event.Status] == plugins.NetworkCheckError && online == event.True {
  216. peer.SetAttribute(attr.GetLocalScope(constants.PeerOnline), event.False)
  217. uiManager.UpdateNetworkStatus(false)
  218. }*/
  219. case event.NewMessageFromPeer: //event.TimestampReceived, event.RemotePeer, event.Data
  220. // only needs contact nickname and picture, for displaying on popup notifications
  221. ev.Event.Data["Nick"] = ph.GetNick(ev.Event.Data["RemotePeer"])
  222. ev.Event.Data["Picture"] = ph.GetProfilePic(ev.Event.Data["RemotePeer"])
  223. case event.NewMessageFromGroup:
  224. // only needs contact nickname and picture, for displaying on popup notifications
  225. ev.Event.Data["Nick"] = ph.GetNick(ev.Event.Data[event.GroupID])
  226. ev.Event.Data["Picture"] = ph.GetProfilePic(ev.Event.Data[event.GroupID])
  227. case event.PeerAcknowledgement:
  228. // No enrichement required
  229. case event.PeerCreated:
  230. handle := ev.Event.Data[event.RemotePeer]
  231. err := EnrichNewPeer(handle, ph, ev)
  232. if err != nil {
  233. return ""
  234. }
  235. case event.GroupCreated:
  236. // This event should only happen after we have validated the invite, as such the error
  237. // condition *should* never happen.
  238. groupPic := ph.GetProfilePic(ev.Event.Data[event.GroupID])
  239. ev.Event.Data["PicturePath"] = groupPic
  240. ev.Event.Data["GroupName"] = ph.GetNick(ev.Event.Data[event.GroupID])
  241. case event.NewGroup:
  242. // This event should only happen after we have validated the invite, as such the error
  243. // condition *should* never happen.
  244. serializedInvite := ev.Event.Data[event.GroupInvite]
  245. if invite, err := model.ValidateInvite(serializedInvite); err == nil {
  246. groupPic := ph.GetProfilePic(invite.GroupID)
  247. ev.Event.Data["PicturePath"] = groupPic
  248. } else {
  249. 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)
  250. return ""
  251. }
  252. case event.PeerStateChange:
  253. cxnState := connections.ConnectionStateToType()[ev.Event.Data[event.ConnectionState]]
  254. contact := peer.GetContact(ev.Event.Data[event.RemotePeer])
  255. if cxnState == connections.AUTHENTICATED && contact == nil {
  256. peer.AddContact(ev.Event.Data[event.RemotePeer], ev.Event.Data[event.RemotePeer], model.AuthUnknown)
  257. return ""
  258. }
  259. if contact != nil {
  260. // No enrichment needed
  261. //uiManager.UpdateContactStatus(contact.Onion, int(cxnState), false)
  262. if cxnState == connections.AUTHENTICATED {
  263. // if known and authed, get vars
  264. peer.SendGetValToPeer(ev.Event.Data[event.RemotePeer], attr.PublicScope, constants.Name)
  265. peer.SendGetValToPeer(ev.Event.Data[event.RemotePeer], attr.PublicScope, constants.Picture)
  266. }
  267. }
  268. case event.NewRetValMessageFromPeer:
  269. // auto handled event means the setting is already done, we're just deciding if we need to tell the UI
  270. onion := ev.Event.Data[event.RemotePeer]
  271. scope := ev.Event.Data[event.Scope]
  272. path := ev.Event.Data[event.Path]
  273. //val := ev.Event.Data[event.Data]
  274. exists, _ := strconv.ParseBool(ev.Event.Data[event.Exists])
  275. if exists && scope == attr.PublicScope {
  276. if _, exists := peer.GetContactAttribute(onion, attr.GetLocalScope(path)); exists {
  277. // we have a locally set ovverride, don't pass this remote set public scope update to UI
  278. return ""
  279. }
  280. }
  281. }
  282. }
  283. json, _ := json.Marshal(unwrap(ev))
  284. return string(json)
  285. }
  286. func unwrap(original *EventProfileEnvelope) *event.Event {
  287. unwrapped := &original.Event
  288. unwrapped.Data["ProfileOnion"] = original.Profile
  289. return unwrapped
  290. }
  291. func (eh *EventHandler) startHandlingPeer(onion string) {
  292. eventBus := eh.app.GetEventBus(onion)
  293. q := event.NewQueue()
  294. eventBus.Subscribe(event.NewMessageFromPeer, q)
  295. eventBus.Subscribe(event.PeerAcknowledgement, q)
  296. eventBus.Subscribe(event.DeleteContact, q)
  297. eventBus.Subscribe(event.AppError, q)
  298. eventBus.Subscribe(event.IndexedAcknowledgement, q)
  299. eventBus.Subscribe(event.IndexedFailure, q)
  300. eventBus.Subscribe(event.NewMessageFromGroup, q)
  301. eventBus.Subscribe(event.GroupCreated, q)
  302. eventBus.Subscribe(event.NewGroup, q)
  303. eventBus.Subscribe(event.AcceptGroupInvite, q)
  304. eventBus.Subscribe(event.SetGroupAttribute, q)
  305. eventBus.Subscribe(event.DeleteGroup, q)
  306. eventBus.Subscribe(event.SendMessageToGroupError, q)
  307. eventBus.Subscribe(event.SendMessageToPeerError, q)
  308. eventBus.Subscribe(event.ServerStateChange, q)
  309. eventBus.Subscribe(event.PeerStateChange, q)
  310. eventBus.Subscribe(event.PeerCreated, q)
  311. eventBus.Subscribe(event.NetworkStatus, q)
  312. eventBus.Subscribe(event.ChangePasswordSuccess, q)
  313. eventBus.Subscribe(event.ChangePasswordError, q)
  314. eventBus.Subscribe(event.NewRetValMessageFromPeer, q)
  315. eventBus.Subscribe(event.SetAttribute, q)
  316. go eh.forwardProfileMessages(onion, q)
  317. }
  318. func (eh *EventHandler) forwardProfileMessages(onion string, q event.Queue) {
  319. log.Infof("Launching Forwarding Goroutine")
  320. // TODO: graceful shutdown, via an injected event of special QUIT type exiting loop/go routine
  321. for {
  322. e := q.Next()
  323. ev := EventProfileEnvelope{Event: e, Profile: onion}
  324. eh.profileEvents <- ev
  325. if ev.Event.EventType == event.Shutdown {
  326. return
  327. }
  328. }
  329. }
  330. func (eh *EventHandler) Push(newEvent event.Event) {
  331. eh.appBusQueue.Publish(newEvent)
  332. }