Official cwtch.im peer and server implementations. https://cwtch.im
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.

375 lines
13KB

  1. package peer
  2. import (
  3. "cwtch.im/cwtch/event"
  4. "cwtch.im/cwtch/model"
  5. "cwtch.im/cwtch/protocol"
  6. "cwtch.im/cwtch/protocol/connections"
  7. "encoding/base32"
  8. "encoding/base64"
  9. "encoding/json"
  10. "errors"
  11. "git.openprivacy.ca/openprivacy/libricochet-go/log"
  12. "github.com/golang/protobuf/proto"
  13. "strings"
  14. "sync"
  15. "time"
  16. )
  17. // cwtchPeer manages incoming and outgoing connections and all processing for a Cwtch cwtchPeer
  18. type cwtchPeer struct {
  19. Profile *model.Profile
  20. mutex sync.Mutex
  21. shutdown bool
  22. queue event.Queue
  23. eventBus event.Manager
  24. }
  25. // CwtchPeer provides us with a way of testing systems built on top of cwtch without having to
  26. // directly implement a cwtchPeer.
  27. type CwtchPeer interface {
  28. Init(event.Manager)
  29. PeerWithOnion(string)
  30. InviteOnionToGroup(string, string) error
  31. SendMessageToPeer(string, string) string
  32. TrustPeer(string) error
  33. BlockPeer(string) error
  34. UnblockPeer(string) error
  35. AcceptInvite(string) error
  36. RejectInvite(string)
  37. DeleteContact(string)
  38. DeleteGroup(string)
  39. JoinServer(string)
  40. SendMessageToGroup(string, string) error
  41. SendMessageToGroupTracked(string, string) (string, error)
  42. GetProfile() *model.Profile
  43. GetPeerState(string) connections.ConnectionState
  44. StartGroup(string) (string, []byte, error)
  45. ImportGroup(string) (string, error)
  46. ExportGroup(string) (string, error)
  47. GetGroup(string) *model.Group
  48. GetGroupState(string) connections.ConnectionState
  49. GetGroups() []string
  50. AddContact(nick, onion string, trusted bool)
  51. GetContacts() []string
  52. GetContact(string) *model.PublicProfile
  53. Listen()
  54. StartPeersConnections()
  55. StartGroupConnections()
  56. Shutdown()
  57. }
  58. // NewCwtchPeer creates and returns a new cwtchPeer with the given name.
  59. func NewCwtchPeer(name string) CwtchPeer {
  60. cp := new(cwtchPeer)
  61. cp.Profile = model.GenerateNewProfile(name)
  62. cp.shutdown = false
  63. return cp
  64. }
  65. // FromProfile generates a new peer from a profile.
  66. func FromProfile(profile *model.Profile) CwtchPeer {
  67. cp := new(cwtchPeer)
  68. cp.Profile = profile
  69. return cp
  70. }
  71. // Init instantiates a cwtchPeer
  72. func (cp *cwtchPeer) Init(eventBus event.Manager) {
  73. cp.queue = event.NewQueue()
  74. go cp.eventHandler()
  75. cp.eventBus = eventBus
  76. cp.eventBus.Subscribe(event.EncryptedGroupMessage, cp.queue)
  77. cp.eventBus.Subscribe(event.NewGroupInvite, cp.queue)
  78. cp.eventBus.Subscribe(event.ServerStateChange, cp.queue)
  79. cp.eventBus.Subscribe(event.PeerStateChange, cp.queue)
  80. }
  81. // ImportGroup intializes a group from an imported source rather than a peer invite
  82. func (cp *cwtchPeer) ImportGroup(exportedInvite string) (groupID string, err error) {
  83. if strings.HasPrefix(exportedInvite, "torv3") {
  84. data, err := base64.StdEncoding.DecodeString(exportedInvite[5+44:])
  85. if err == nil {
  86. cpp := &protocol.CwtchPeerPacket{}
  87. err = proto.Unmarshal(data, cpp)
  88. if err == nil {
  89. jsobj, err := proto.Marshal(cpp.GetGroupChatInvite())
  90. if err == nil {
  91. cp.eventBus.Publish(event.NewEvent(event.NewGroupInvite, map[event.Field]string{
  92. event.GroupInvite: string(jsobj),
  93. }))
  94. } else {
  95. log.Errorf("error serializing group: %v", err)
  96. }
  97. return cpp.GroupChatInvite.GetGroupName(), nil
  98. }
  99. }
  100. } else {
  101. err = errors.New("unsupported exported group type")
  102. }
  103. return
  104. }
  105. // ExportGroup serializes a group invite so it can be given offline
  106. func (cp *cwtchPeer) ExportGroup(groupID string) (string, error) {
  107. group := cp.Profile.GetGroupByGroupID(groupID)
  108. if group != nil {
  109. invite, err := group.Invite(group.GetInitialMessage())
  110. if err == nil {
  111. exportedInvite := "torv3" + base64.StdEncoding.EncodeToString(cp.Profile.Ed25519PublicKey) + base64.StdEncoding.EncodeToString(invite)
  112. return exportedInvite, err
  113. }
  114. }
  115. return "", errors.New("group id could not be found")
  116. }
  117. // StartGroup create a new group linked to the given server and returns the group ID, an invite or an error.
  118. func (cp *cwtchPeer) StartGroup(server string) (string, []byte, error) {
  119. return cp.StartGroupWithMessage(server, []byte{})
  120. }
  121. // StartGroupWithMessage create a new group linked to the given server and returns the group ID, an invite or an error.
  122. func (cp *cwtchPeer) StartGroupWithMessage(server string, initialMessage []byte) (groupID string, invite []byte, err error) {
  123. groupID, invite, err = cp.Profile.StartGroupWithMessage(server, initialMessage)
  124. if err == nil {
  125. group := cp.GetGroup(groupID)
  126. jsobj, err := json.Marshal(group)
  127. if err == nil {
  128. cp.eventBus.Publish(event.NewEvent(event.GroupCreated, map[event.Field]string{
  129. event.Data: string(jsobj),
  130. }))
  131. }
  132. } else {
  133. log.Errorf("error creating group: %v", err)
  134. }
  135. return
  136. }
  137. // GetGroups returns an unordered list of all group IDs.
  138. func (cp *cwtchPeer) GetGroups() []string {
  139. return cp.Profile.GetGroups()
  140. }
  141. // GetGroup returns a pointer to a specific group, nil if no group exists.
  142. func (cp *cwtchPeer) GetGroup(groupID string) *model.Group {
  143. return cp.Profile.GetGroupByGroupID(groupID)
  144. }
  145. func (cp *cwtchPeer) AddContact(nick, onion string, trusted bool) {
  146. decodedPub, _ := base32.StdEncoding.DecodeString(strings.ToUpper(onion))
  147. pp := &model.PublicProfile{Name: nick, Ed25519PublicKey: decodedPub, Trusted: trusted, Blocked: false, Onion: onion, Attributes: map[string]string{"nick": nick}}
  148. cp.Profile.AddContact(onion, pp)
  149. pd, _ := json.Marshal(pp)
  150. cp.eventBus.Publish(event.NewEvent(event.PeerCreated, map[event.Field]string{
  151. event.Data: string(pd),
  152. event.RemotePeer: onion,
  153. }))
  154. }
  155. // GetContacts returns an unordered list of onions
  156. func (cp *cwtchPeer) GetContacts() []string {
  157. return cp.Profile.GetContacts()
  158. }
  159. // GetContact returns a given contact, nil is no such contact exists
  160. func (cp *cwtchPeer) GetContact(onion string) *model.PublicProfile {
  161. contact, _ := cp.Profile.GetContact(onion)
  162. return contact
  163. }
  164. // GetProfile returns the profile associated with this cwtchPeer.
  165. func (cp *cwtchPeer) GetProfile() *model.Profile {
  166. return cp.Profile
  167. }
  168. func (cp *cwtchPeer) GetPeerState(onion string) connections.ConnectionState {
  169. return connections.ConnectionStateToType[cp.Profile.Contacts[onion].State]
  170. }
  171. func (cp *cwtchPeer) GetGroupState(groupid string) connections.ConnectionState {
  172. return connections.ConnectionStateToType[cp.Profile.Groups[groupid].State]
  173. }
  174. // PeerWithOnion is the entry point for cwtchPeer relationships
  175. func (cp *cwtchPeer) PeerWithOnion(onion string) {
  176. cp.eventBus.Publish(event.NewEvent(event.PeerRequest, map[event.Field]string{event.RemotePeer: onion}))
  177. }
  178. // DeleteContact deletes a peer from the profile, storage, and handling
  179. func (cp *cwtchPeer) DeleteContact(onion string) {
  180. cp.Profile.DeleteContact(onion)
  181. cp.eventBus.Publish(event.NewEventList(event.DeleteContact, event.RemotePeer, onion))
  182. }
  183. // DeleteGroup deletes a Group from the profile, storage, and handling
  184. func (cp *cwtchPeer) DeleteGroup(groupID string) {
  185. cp.Profile.DeleteGroup(groupID)
  186. cp.eventBus.Publish(event.NewEventList(event.DeleteGroup, event.GroupID, groupID))
  187. }
  188. // InviteOnionToGroup kicks off the invite process
  189. func (cp *cwtchPeer) InviteOnionToGroup(onion string, groupid string) error {
  190. group := cp.Profile.GetGroupByGroupID(groupid)
  191. if group == nil {
  192. return errors.New("invalid group id")
  193. }
  194. invite, err := group.Invite(group.InitialMessage)
  195. if err == nil {
  196. cp.eventBus.Publish(event.NewEvent(event.InvitePeerToGroup, map[event.Field]string{event.RemotePeer: onion, event.GroupInvite: string(invite)}))
  197. }
  198. return err
  199. }
  200. // JoinServer manages a new server connection with the given onion address
  201. func (cp *cwtchPeer) JoinServer(onion string) {
  202. cp.eventBus.Publish(event.NewEvent(event.JoinServer, map[event.Field]string{event.GroupServer: onion}))
  203. }
  204. // SendMessageToGroup attempts to sent the given message to the given group id.
  205. // TODO: Deprecate in favour of SendMessageToGroupTracked
  206. func (cp *cwtchPeer) SendMessageToGroup(groupid string, message string) error {
  207. _, err := cp.SendMessageToGroupTracked(groupid, message)
  208. return err
  209. }
  210. // SendMessageToGroup attempts to sent the given message to the given group id.
  211. // It returns the signature of the message which can be used to identify it in any UX layer.
  212. func (cp *cwtchPeer) SendMessageToGroupTracked(groupid string, message string) (string, error) {
  213. group := cp.Profile.GetGroupByGroupID(groupid)
  214. if group == nil {
  215. return "", errors.New("invalid group id")
  216. }
  217. ct, sig, err := cp.Profile.EncryptMessageToGroup(message, groupid)
  218. if err == nil {
  219. cp.eventBus.Publish(event.NewEvent(event.SendMessageToGroup, map[event.Field]string{event.GroupServer: group.GroupServer, event.Ciphertext: string(ct), event.Signature: string(sig)}))
  220. }
  221. return string(sig), err
  222. }
  223. func (cp *cwtchPeer) SendMessageToPeer(onion string, message string) string {
  224. event := event.NewEvent(event.SendMessageToPeer, map[event.Field]string{event.RemotePeer: onion, event.Data: message})
  225. cp.eventBus.Publish(event)
  226. return event.EventID
  227. }
  228. // TrustPeer sets an existing peer relationship to trusted
  229. func (cp *cwtchPeer) TrustPeer(peer string) error {
  230. err := cp.Profile.TrustPeer(peer)
  231. if err == nil {
  232. cp.PeerWithOnion(peer)
  233. }
  234. return err
  235. }
  236. // BlockPeer blocks an existing peer relationship.
  237. func (cp *cwtchPeer) BlockPeer(peer string) error {
  238. err := cp.Profile.BlockPeer(peer)
  239. cp.eventBus.Publish(event.NewEvent(event.BlockPeer, map[event.Field]string{event.RemotePeer: peer}))
  240. return err
  241. }
  242. // UnblockPeer blocks an existing peer relationship.
  243. func (cp *cwtchPeer) UnblockPeer(peer string) error {
  244. err := cp.Profile.UnblockPeer(peer)
  245. cp.eventBus.Publish(event.NewEvent(event.UnblockPeer, map[event.Field]string{event.RemotePeer: peer}))
  246. return err
  247. }
  248. // AcceptInvite accepts a given existing group invite
  249. func (cp *cwtchPeer) AcceptInvite(groupID string) error {
  250. err := cp.Profile.AcceptInvite(groupID)
  251. if err != nil {
  252. return err
  253. }
  254. cp.eventBus.Publish(event.NewEvent(event.AcceptGroupInvite, map[event.Field]string{event.GroupID: groupID}))
  255. cp.JoinServer(cp.Profile.Groups[groupID].GroupServer)
  256. return nil
  257. }
  258. // RejectInvite rejects a given group invite.
  259. func (cp *cwtchPeer) RejectInvite(groupID string) {
  260. cp.Profile.RejectInvite(groupID)
  261. }
  262. // Listen makes the peer open a listening port to accept incoming connections (and be detactably online)
  263. func (cp *cwtchPeer) Listen() {
  264. log.Debugf("cwtchPeer Listen sending ProtocolEngineStartListen\n")
  265. cp.eventBus.Publish(event.NewEvent(event.ProtocolEngineStartListen, map[event.Field]string{}))
  266. }
  267. // StartGroupConnections attempts to connect to all group servers (thus initiating reconnect attempts in the conectionsmanager)
  268. func (cp *cwtchPeer) StartPeersConnections() {
  269. for _, contact := range cp.GetContacts() {
  270. cp.PeerWithOnion(contact)
  271. }
  272. }
  273. // StartPeerConnections attempts to connect to all peers (thus initiating reconnect attempts in the conectionsmanager)
  274. func (cp *cwtchPeer) StartGroupConnections() {
  275. joinedServers := map[string]bool{}
  276. for _, groupID := range cp.GetGroups() {
  277. // Only send a join server packet if we haven't joined this server yet...
  278. group := cp.GetGroup(groupID)
  279. if joined := joinedServers[groupID]; group.Accepted && !joined {
  280. log.Infof("Join Server %v (%v)\n", group.GroupServer, joined)
  281. cp.JoinServer(group.GroupServer)
  282. joinedServers[group.GroupServer] = true
  283. }
  284. }
  285. }
  286. // Shutdown kills all connections and cleans up all goroutines for the peer
  287. func (cp *cwtchPeer) Shutdown() {
  288. cp.shutdown = true
  289. cp.queue.Shutdown()
  290. }
  291. // eventHandler process events from other subsystems
  292. func (cp *cwtchPeer) eventHandler() {
  293. for {
  294. ev := cp.queue.Next()
  295. switch ev.EventType {
  296. case event.EncryptedGroupMessage:
  297. ok, groupID, message, seen := cp.Profile.AttemptDecryption([]byte(ev.Data[event.Ciphertext]), []byte(ev.Data[event.Signature]))
  298. if ok && !seen {
  299. cp.eventBus.Publish(event.NewEvent(event.NewMessageFromGroup, map[event.Field]string{event.TimestampReceived: message.Received.Format(time.RFC3339Nano), event.TimestampSent: message.Timestamp.Format(time.RFC3339Nano), event.Data: message.Message, event.GroupID: groupID, event.Signature: string(message.Signature), event.PreviousSignature: string(message.PreviousMessageSig), event.RemotePeer: message.PeerID}))
  300. }
  301. case event.NewGroupInvite:
  302. var groupInvite protocol.GroupChatInvite
  303. err := proto.Unmarshal([]byte(ev.Data[event.GroupInvite]), &groupInvite)
  304. if err != nil {
  305. log.Errorf("NewGroupInvite could not json decode invite: %v\n", err)
  306. }
  307. cp.Profile.ProcessInvite(&groupInvite, ev.Data[event.RemotePeer])
  308. case event.PeerStateChange:
  309. if _, exists := cp.Profile.Contacts[ev.Data[event.RemotePeer]]; exists {
  310. cp.Profile.Contacts[ev.Data[event.RemotePeer]].State = ev.Data[event.ConnectionState]
  311. }
  312. case event.ServerStateChange:
  313. for _, group := range cp.Profile.Groups {
  314. if group.GroupServer == ev.Data[event.GroupServer] {
  315. group.State = ev.Data[event.ConnectionState]
  316. }
  317. }
  318. default:
  319. if ev.EventType != "" {
  320. log.Errorf("peer event handler received an event it was not subscribed for: %v", ev.EventType)
  321. }
  322. return
  323. }
  324. }
  325. }