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.

cwtch_peer.go 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356
  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/connectivity"
  12. "git.openprivacy.ca/openprivacy/libricochet-go/log"
  13. "github.com/golang/protobuf/proto"
  14. "strings"
  15. "sync"
  16. "time"
  17. )
  18. // cwtchPeer manages incoming and outgoing connections and all processing for a Cwtch cwtchPeer
  19. type cwtchPeer struct {
  20. Profile *model.Profile
  21. mutex sync.Mutex
  22. shutdown bool
  23. started bool
  24. queue *event.Queue
  25. eventBus event.Manager
  26. }
  27. // CwtchPeer provides us with a way of testing systems built on top of cwtch without having to
  28. // directly implement a cwtchPeer.
  29. type CwtchPeer interface {
  30. Init(connectivity.ACN, event.Manager)
  31. PeerWithOnion(string) *connections.PeerPeerConnection
  32. InviteOnionToGroup(string, string) error
  33. SendMessageToPeer(string, string) string
  34. TrustPeer(string) error
  35. BlockPeer(string) error
  36. AcceptInvite(string) error
  37. RejectInvite(string)
  38. JoinServer(string)
  39. SendMessageToGroup(string, string) error
  40. SendMessageToGroupTracked(string, string) (string, error)
  41. GetProfile() *model.Profile
  42. GetPeerState(string) connections.ConnectionState
  43. StartGroup(string) (string, []byte, error)
  44. ImportGroup(string) (string, error)
  45. ExportGroup(string) (string, error)
  46. GetGroup(string) *model.Group
  47. GetGroupState(string) connections.ConnectionState
  48. GetGroups() []string
  49. AddContact(nick, onion string, trusted bool)
  50. GetContacts() []string
  51. GetContact(string) *model.PublicProfile
  52. IsStarted() bool
  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(acn connectivity.ACN, eventBus event.Manager) {
  73. cp.queue = event.NewEventQueue(100)
  74. go cp.eventHandler()
  75. cp.eventBus = eventBus
  76. cp.eventBus.Subscribe(event.EncryptedGroupMessage, cp.queue.EventChannel)
  77. cp.eventBus.Subscribe(event.NewGroupInvite, cp.queue.EventChannel)
  78. cp.eventBus.Subscribe(event.ServerStateChange, cp.queue.EventChannel)
  79. cp.eventBus.Subscribe(event.PeerStateChange, cp.queue.EventChannel)
  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.ConnectionStateType[cp.Profile.Contacts[onion].State]
  170. }
  171. func (cp *cwtchPeer) GetGroupState(groupid string) connections.ConnectionState {
  172. return connections.ConnectionStateType[cp.Profile.Groups[groupid].State]
  173. }
  174. // PeerWithOnion is the entry point for cwtchPeer relationships
  175. func (cp *cwtchPeer) PeerWithOnion(onion string) *connections.PeerPeerConnection {
  176. cp.eventBus.Publish(event.NewEvent(event.PeerRequest, map[event.Field]string{event.RemotePeer: onion}))
  177. return nil
  178. }
  179. // InviteOnionToGroup kicks off the invite process
  180. func (cp *cwtchPeer) InviteOnionToGroup(onion string, groupid string) error {
  181. group := cp.Profile.GetGroupByGroupID(groupid)
  182. if group == nil {
  183. return errors.New("invalid group id")
  184. }
  185. invite, err := group.Invite(group.InitialMessage)
  186. if err == nil {
  187. cp.eventBus.Publish(event.NewEvent(event.InvitePeerToGroup, map[event.Field]string{event.RemotePeer: onion, event.GroupInvite: string(invite)}))
  188. }
  189. return err
  190. }
  191. // JoinServer manages a new server connection with the given onion address
  192. func (cp *cwtchPeer) JoinServer(onion string) {
  193. cp.eventBus.Publish(event.NewEvent(event.JoinServer, map[event.Field]string{event.GroupServer: onion}))
  194. }
  195. // SendMessageToGroup attempts to sent the given message to the given group id.
  196. // TODO: Deprecate in favour of SendMessageToGroupTracked
  197. func (cp *cwtchPeer) SendMessageToGroup(groupid string, message string) error {
  198. _, err := cp.SendMessageToGroupTracked(groupid, message)
  199. return err
  200. }
  201. // SendMessageToGroup attempts to sent the given message to the given group id.
  202. // It returns the signature of the message which can be used to identify it in any UX layer.
  203. func (cp *cwtchPeer) SendMessageToGroupTracked(groupid string, message string) (string, error) {
  204. group := cp.Profile.GetGroupByGroupID(groupid)
  205. if group == nil {
  206. return "", errors.New("invalid group id")
  207. }
  208. ct, sig, err := cp.Profile.EncryptMessageToGroup(message, groupid)
  209. if err == nil {
  210. cp.eventBus.Publish(event.NewEvent(event.SendMessageToGroup, map[event.Field]string{event.GroupServer: group.GroupServer, event.Ciphertext: string(ct), event.Signature: string(sig)}))
  211. }
  212. return string(sig), err
  213. }
  214. func (cp *cwtchPeer) SendMessageToPeer(onion string, message string) string {
  215. event := event.NewEvent(event.SendMessageToPeer, map[event.Field]string{event.RemotePeer: onion, event.Data: message})
  216. cp.eventBus.Publish(event)
  217. return event.EventID
  218. }
  219. // TrustPeer sets an existing peer relationship to trusted
  220. func (cp *cwtchPeer) TrustPeer(peer string) error {
  221. err := cp.Profile.TrustPeer(peer)
  222. if err == nil {
  223. cp.PeerWithOnion(peer)
  224. }
  225. return err
  226. }
  227. // BlockPeer blocks an existing peer relationship.
  228. func (cp *cwtchPeer) BlockPeer(peer string) error {
  229. err := cp.Profile.BlockPeer(peer)
  230. cp.eventBus.Publish(event.NewEvent(event.BlockPeer, map[event.Field]string{event.RemotePeer: peer}))
  231. return err
  232. }
  233. // AcceptInvite accepts a given existing group invite
  234. func (cp *cwtchPeer) AcceptInvite(groupID string) error {
  235. err := cp.Profile.AcceptInvite(groupID)
  236. if err != nil {
  237. return err
  238. }
  239. cp.eventBus.Publish(event.NewEvent(event.AcceptGroupInvite, map[event.Field]string{event.GroupID: groupID}))
  240. cp.JoinServer(cp.Profile.Groups[groupID].GroupServer)
  241. return nil
  242. }
  243. // RejectInvite rejects a given group invite.
  244. func (cp *cwtchPeer) RejectInvite(groupID string) {
  245. cp.Profile.RejectInvite(groupID)
  246. }
  247. // Listen makes the peer open a listening port to accept incoming connections (and be detactably online)
  248. func (cp *cwtchPeer) Listen() {
  249. cp.eventBus.Publish(event.NewEvent(event.ProtocolEngineStartListen, map[event.Field]string{}))
  250. }
  251. // StartGroupConnections attempts to connect to all group servers (thus initiating reconnect attempts in the conectionsmanager)
  252. func (cp *cwtchPeer) StartPeersConnections() {
  253. for _, contact := range cp.GetContacts() {
  254. cp.PeerWithOnion(contact)
  255. }
  256. }
  257. // StartPeerConnections attempts to connect to all peers (thus initiating reconnect attempts in the conectionsmanager)
  258. func (cp *cwtchPeer) StartGroupConnections() {
  259. joinedServers := map[string]bool{}
  260. for _, groupID := range cp.GetGroups() {
  261. // Only send a join server packet if we haven't joined this server yet...
  262. group := cp.GetGroup(groupID)
  263. if joined := joinedServers[groupID]; group.Accepted && !joined {
  264. cp.JoinServer(group.GroupServer)
  265. joinedServers[group.GroupServer] = true
  266. }
  267. }
  268. }
  269. // Shutdown kills all connections and cleans up all goroutines for the peer
  270. func (cp *cwtchPeer) Shutdown() {
  271. cp.shutdown = true
  272. cp.queue.Shutdown()
  273. }
  274. // IsStarted returns true if Listen() has successfully been run before on this connection (ever). TODO: we will need to properly unset this flag on error if we want to support resumption in the future
  275. func (cp *cwtchPeer) IsStarted() bool {
  276. return cp.started
  277. }
  278. // eventHandler process events from other subsystems
  279. func (cp *cwtchPeer) eventHandler() {
  280. for {
  281. ev := cp.queue.Next()
  282. switch ev.EventType {
  283. case event.EncryptedGroupMessage:
  284. ok, groupID, message, seen := cp.Profile.AttemptDecryption([]byte(ev.Data[event.Ciphertext]), []byte(ev.Data[event.Signature]))
  285. if ok && !seen {
  286. 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}))
  287. }
  288. case event.NewGroupInvite:
  289. var groupInvite protocol.GroupChatInvite
  290. proto.Unmarshal([]byte(ev.Data[event.GroupInvite]), &groupInvite)
  291. cp.Profile.ProcessInvite(&groupInvite, ev.Data[event.RemotePeer])
  292. case event.PeerStateChange:
  293. if _, exists := cp.Profile.Contacts[ev.Data[event.RemotePeer]]; exists {
  294. cp.Profile.Contacts[ev.Data[event.RemotePeer]].State = ev.Data[event.ConnectionState]
  295. }
  296. case event.ServerStateChange:
  297. for _, group := range cp.Profile.Groups {
  298. if group.GroupServer == ev.Data[event.GroupServer] {
  299. group.State = ev.Data[event.ConnectionState]
  300. }
  301. }
  302. default:
  303. if ev.EventType != "" {
  304. log.Errorf("peer event handler received an event it was not subscribed for: %v", ev.EventType)
  305. }
  306. return
  307. }
  308. }
  309. }