Compare commits

...

222 Commits

Author SHA1 Message Date
Dan Ballard 3964348dd0 Merge pull request 'Expose Profile Tags for Handling Unencrypted Profiles' (#66) from fixees into trunk
continuous-integration/drone/push Build is passing Details
Reviewed-on: #66
2021-06-24 10:42:40 -07:00
Sarah Jamie Lewis ae16c7c6a5 Expose Profile Tags for Handling Unencrypted Profiles
continuous-integration/drone/pr Build is passing Details
2021-06-24 10:38:51 -07:00
Sarah Jamie Lewis 69f14f9bb1 Merge pull request 'dualpane settings wiring' (#65) from dualpane into trunk
continuous-integration/drone/push Build is passing Details
Reviewed-on: #65
2021-06-24 08:49:03 -07:00
erinn f1775eb975 dualpane settings wiring
continuous-integration/drone/pr Build is passing Details
2021-06-24 00:34:06 -07:00
erinn c1b7e4c75d Merge pull request 'Remove Unused Map' (#64) from fixees into trunk
continuous-integration/drone/push Build is passing Details
Reviewed-on: #64
2021-06-22 16:58:34 -07:00
Sarah Jamie Lewis a460b900b6 Reset
continuous-integration/drone/pr Build is passing Details
2021-06-22 16:58:01 -07:00
Sarah Jamie Lewis b2d8f3f34a Reconnect Fix
continuous-integration/drone/pr Build is passing Details
2021-06-22 16:56:53 -07:00
Sarah Jamie Lewis 6c5b48311d Only launch 1 profile handling thread per profile (on reload)
continuous-integration/drone/pr Build is passing Details
2021-06-22 15:46:28 -07:00
Sarah Jamie Lewis b33c6c77dd Remove Unused Map
continuous-integration/drone/pr Build is passing Details
2021-06-22 15:34:46 -07:00
Sarah Jamie Lewis c864bccbb9 Merge pull request 'stale process detection on android' (#63) from rrgtj into trunk
continuous-integration/drone/push Build is passing Details
Reviewed-on: #63
2021-06-21 17:51:49 -07:00
erinn 5dddb16f2b Merge branch 'trunk' of git.openprivacy.ca:flutter/libcwtch-go into rrgtj
continuous-integration/drone/pr Build is passing Details
2021-06-21 17:47:55 -07:00
erinn 001470257a process fixes for android 2021-06-21 17:47:43 -07:00
Sarah Jamie Lewis b37f283fe3 Merge pull request 'tag unpassworded users' (#62) from tagPass into trunk
continuous-integration/drone/push Build is passing Details
Reviewed-on: #62
2021-06-21 15:14:46 -07:00
Dan Ballard 2f495dfd62 tag unpassworded users
continuous-integration/drone/pr Build is passing Details
2021-06-21 15:11:56 -07:00
erinn e09e9c09a7 Merge branch 'trunk' of git.openprivacy.ca:flutter/libcwtch-go into rrgtj 2021-06-17 18:11:35 -07:00
erinn ae3aef0518 idk 2021-06-17 18:11:30 -07:00
Sarah Jamie Lewis 0f53ae0062 Merge pull request 'if no remote name, reask' (#61) from getNameAfterApproved into trunk
continuous-integration/drone/push Build is passing Details
Reviewed-on: #61
2021-06-17 17:23:08 -07:00
Sarah Jamie Lewis f6eaa2d691 Merge branch 'trunk' into getNameAfterApproved
continuous-integration/drone/pr Build is passing Details
2021-06-17 17:23:03 -07:00
Sarah Jamie Lewis 8f1b1b8630 Merge pull request 'check acn status on reconnect' (#60) from reconnACNStatus into trunk
continuous-integration/drone/push Build is passing Details
Reviewed-on: #60
2021-06-17 17:21:57 -07:00
Dan Ballard e2cf1d25e3 if no remote name, reask
continuous-integration/drone/pr Build is passing Details
2021-06-17 17:05:07 -07:00
Dan Ballard 7b3ff2c1af check acn status on reconnect
continuous-integration/drone/pr Build is passing Details
2021-06-17 16:58:11 -07:00
Sarah Jamie Lewis c6f1c4d0f9 Merge pull request 'counter sync events' (#59) from countersync into trunk
continuous-integration/drone/push Build is passing Details
Reviewed-on: #59
2021-06-17 15:45:23 -07:00
erinn e7ef2fef4a counter sync events
continuous-integration/drone/pr Build is passing Details
2021-06-17 15:41:41 -07:00
erinn ffaca8d876 Merge pull request 'Fix import bundle for groups to auto accept' (#58) from beta_fixes into trunk
continuous-integration/drone/push Build is passing Details
Reviewed-on: #58
2021-06-17 13:53:14 -07:00
Sarah Jamie Lewis 6070d378c5 Merge branch 'trunk' into beta_fixes
continuous-integration/drone/pr Build is passing Details
2021-06-17 13:50:58 -07:00
Sarah Jamie Lewis fafdb8132a Merge pull request 'remove QueryACNVersion API, just do it on app launch; removed unused GetProfiles API' (#57) from fixReconnect into trunk
continuous-integration/drone/push Build is passing Details
Reviewed-on: #57
2021-06-17 13:50:50 -07:00
Sarah Jamie Lewis 9a764b5b35 Fix import bundle for groups to auto accept
continuous-integration/drone/pr Build is passing Details
2021-06-17 13:49:20 -07:00
Dan Ballard 3261d18118 remove QueryACNVersion API, just do it on app launch; removed unused GetProfiles API
continuous-integration/drone/pr Build is passing Details
2021-06-17 11:53:41 -07:00
Sarah Jamie Lewis 08b1b19f7a Merge pull request 'add group message popup notifications' (#56) from groupnotifs into trunk
continuous-integration/drone/push Build is passing Details
Reviewed-on: #56
2021-06-16 17:00:11 -07:00
erinn 204cc35e0d switch-ffi
continuous-integration/drone/pr Build is passing Details
2021-06-16 16:46:27 -07:00
erinn 28011ae674 add group message popup notifications 2021-06-16 16:45:15 -07:00
erinn eb4150c0be Merge branch 'trunk' of git.openprivacy.ca:flutter/libcwtch-go into groupnotifs 2021-06-16 15:59:00 -07:00
erinn d9f9489fa2 add group message popup notifications 2021-06-16 15:58:54 -07:00
erinn 854dc1946f Merge pull request 'Shutdown Event' (#55) from beta_fixes into trunk
continuous-integration/drone/push Build is passing Details
Reviewed-on: #55
2021-06-16 14:55:53 -07:00
Sarah Jamie Lewis 26b0151e79 Shutdown Event
continuous-integration/drone/pr Build is passing Details
2021-06-16 14:11:40 -07:00
Sarah Jamie Lewis abe06912a1 Merge pull request 'add mutex on StartCwtch and ReconnectCwtchForeground' (#54) from fixReconnect into trunk
continuous-integration/drone/push Build is passing Details
Reviewed-on: #54
2021-06-16 12:03:23 -07:00
Dan Ballard 11f09c95d2 remove mutex 'fix', instead just don't reconnect, assume whole stale thread and abandon
continuous-integration/drone/pr Build is passing Details
2021-06-16 11:41:36 -07:00
Dan Ballard 9b18bef47a add mutex on StartCwtch and ReconnectCwtchForeground
continuous-integration/drone/pr Build is passing Details
2021-06-16 08:58:53 -07:00
Sarah Jamie Lewis c5f36a9480 Merge pull request 'goStart' (#53) from goStart into trunk
continuous-integration/drone/push Build is passing Details
Reviewed-on: #53
2021-06-15 11:07:03 -07:00
Dan Ballard 192d98a89b Cleaned out more dead code; fixed bug where remote attrs could override local attrs in UI
continuous-integration/drone/pr Build is passing Details
2021-06-15 11:03:53 -07:00
Dan Ballard 00c5412551 finish removing unused code (getACNEvents, getContactEvents, getRepaintEvent, SelectProfile
continuous-integration/drone/pr Build is passing Details
2021-06-15 10:28:19 -07:00
Dan Ballard d921612d25 move acn and cwtch app creation into a go routine, report success or error by message 2021-06-15 10:28:19 -07:00
erinn 39187a7db7 Merge pull request 'Delete Peer and Leave Conversation' (#52) from beta_fixes into trunk
continuous-integration/drone/push Build is passing Details
Reviewed-on: #52
2021-06-14 17:28:27 -07:00
erinn 6fabfa507d Merge branch 'trunk' into beta_fixes
continuous-integration/drone/pr Build is passing Details
2021-06-14 17:28:21 -07:00
Sarah Jamie Lewis 6560fcef49 Delete Peer and Leave Conversation
continuous-integration/drone/pr Build is passing Details
2021-06-14 17:23:47 -07:00
Sarah Jamie Lewis 033de73b6a Merge pull request 'android service and notifications' (#51) from androidservice into trunk
continuous-integration/drone/push Build is passing Details
Reviewed-on: #51
2021-06-11 14:41:20 -07:00
erinn 370e6090ce loglevel oops
continuous-integration/drone/pr Build is passing Details
2021-06-11 14:26:55 -07:00
erinn 61d078f645 ffi switch oops 2021-06-11 14:25:00 -07:00
erinn af4d994d6a Merge branch 'trunk' of git.openprivacy.ca:flutter/libcwtch-go into androidservice 2021-06-11 14:21:16 -07:00
erinn 78a2e033dc android service and notification support 2021-06-11 14:21:09 -07:00
Dan Ballard fddfd41fbf Merge pull request 'Upgrade cwtch' (#50) from update_flags into trunk
continuous-integration/drone/push Build is passing Details
Reviewed-on: #50
2021-06-10 11:36:27 -07:00
Sarah Jamie Lewis 936c45f234 Upgrade cwtch
continuous-integration/drone/pr Build is passing Details
2021-06-10 10:59:22 -07:00
Dan Ballard 08fe76bf30 Merge pull request 'Use int64 for UpdateMessageFlags api so gomobile can generate it' (#49) from update_flags into trunk
continuous-integration/drone/push Build is passing Details
Reviewed-on: #49
2021-06-09 12:45:50 -07:00
Sarah Jamie Lewis 2b86f7b282 Use int64 for UpdateMessageFlags api so gomobile can generate it
continuous-integration/drone/pr Build is passing Details
2021-06-09 11:58:23 -07:00
Dan Ballard 33945af046 Merge pull request 'Add UpdateMessageFlags' (#48) from update_flags into trunk
continuous-integration/drone/push Build is passing Details
Reviewed-on: #48
2021-06-09 11:31:59 -07:00
Sarah Jamie Lewis 9b1814e43d Add UpdateMessageFlags
continuous-integration/drone/pr Build is passing Details
2021-06-09 11:27:51 -07:00
Sarah Jamie Lewis 4dd8d74bfb Merge pull request 'api call for QueryACNVersion' (#47) from acnver into trunk
continuous-integration/drone/push Build is passing Details
Reviewed-on: #47
2021-06-07 09:13:58 -07:00
Sarah Jamie Lewis 772cf7949a Merge branch 'trunk' into acnver
continuous-integration/drone/pr Build is passing Details
2021-06-07 09:13:52 -07:00
Dan Ballard 62a55a6fec api call for QueryACNVersion
continuous-integration/drone/pr Build is passing Details
2021-06-06 10:40:58 -07:00
erinn 6a0e839bb6 Merge pull request 'ui-fixes' (#46) from ui-fixes into trunk
continuous-integration/drone/push Build is passing Details
Reviewed-on: #46
2021-06-02 12:40:33 -07:00
Sarah Jamie Lewis 53d11dc45d spelling
continuous-integration/drone/pr Build is passing Details
2021-06-02 12:36:26 -07:00
Sarah Jamie Lewis ee47798714 staticcheck fixes 2021-06-02 12:36:26 -07:00
Sarah Jamie Lewis 6f01c992af Fix Tor Crashes, Upgrade Cwtch/Tapir 2021-06-02 12:36:26 -07:00
Dan Ballard 4f625c7f8e Merge pull request 'Optimistically Sync Servers on Import Bundle' (#45) from ui-fixes into trunk
continuous-integration/drone/push Build is passing Details
Reviewed-on: #45
2021-05-31 16:30:47 -07:00
Sarah Jamie Lewis ed868c140b Optimistically Sync Servers on Import Bundle
continuous-integration/drone/pr Build is passing Details
2021-05-31 16:26:51 -07:00
erinn a98b5deb02 Merge pull request 'Create & Leave Groups' (#44) from ui-fixes into trunk
continuous-integration/drone/push Build is passing Details
Reviewed-on: #44
2021-05-28 14:20:54 -07:00
Sarah Jamie Lewis af338e7e95 Updae Cwtch
continuous-integration/drone/pr Build is passing Details
2021-05-28 11:27:59 -07:00
Sarah Jamie Lewis 1787f87607 Creat & Leave Groups
continuous-integration/drone/pr Build is failing Details
2021-05-28 02:12:41 -07:00
Dan Ballard 47f8cf2f42 Merge pull request 'Upgrade Cwtch + Send Message to Peer Error Propagation.' (#43) from ui-fixes into trunk
continuous-integration/drone/push Build is passing Details
Reviewed-on: #43
2021-05-26 15:58:42 -07:00
Sarah Jamie Lewis b4681241bc go.sum
continuous-integration/drone/pr Build is passing Details
2021-05-26 15:48:01 -07:00
Sarah Jamie Lewis 30490f7b9d Upgrade Cwtch
continuous-integration/drone/pr Build is passing Details
2021-05-26 15:46:24 -07:00
Sarah Jamie Lewis 406b3510b8 IndexedError + Default Theme to Dark 2021-05-26 15:21:01 -07:00
erinn 84d85b7694 Merge pull request 'Turn off old groups + Upgrade Cwtch' (#42) from groups into trunk
continuous-integration/drone/push Build is passing Details
Reviewed-on: #42
2021-05-19 18:14:21 -07:00
Sarah Jamie Lewis c9d589a27a Upgrade Cwtch
continuous-integration/drone/pr Build is passing Details
2021-05-19 16:42:39 -07:00
Sarah Jamie Lewis 66d7c63c08 Turn off old groups in UI 2021-05-19 16:42:39 -07:00
erinn 83c3ad21c3 Merge pull request 'Using new group API to avoid replicated code' (#41) from groups into trunk
continuous-integration/drone/push Build is passing Details
Reviewed-on: #41
2021-05-18 16:00:20 -07:00
erinn 299595180b Merge branch 'trunk' into groups
continuous-integration/drone/pr Build is passing Details
2021-05-18 16:00:12 -07:00
Sarah Jamie Lewis dd0fc13b89 Using new group API to avoid replicated code
continuous-integration/drone/pr Build is passing Details
2021-05-18 13:42:38 -07:00
erinn 2788c068dd Merge pull request 'Upgrade groups' (#40) from groups into trunk
continuous-integration/drone/push Build is passing Details
Reviewed-on: #40
2021-05-18 13:00:17 -07:00
Sarah Jamie Lewis 2c76504e0b Upgrade groups
continuous-integration/drone/pr Build is passing Details
2021-05-18 12:55:25 -07:00
Sarah Jamie Lewis bef54c6091 Merge pull request 'add sendinvitation' (#38) from sendinvite into trunk
continuous-integration/drone/push Build is passing Details
Reviewed-on: #38
2021-05-11 15:37:53 -07:00
erinn 54c2048f58 addressing comments on #38
continuous-integration/drone/pr Build is passing Details
2021-05-11 15:35:58 -07:00
erinn 33c42f32d2 Merge branch 'trunk' of git.openprivacy.ca:flutter/libcwtch-go into sendinvite
continuous-integration/drone/pr Build is passing Details
2021-05-10 18:57:59 -07:00
erinn a1095b7c35 Merge pull request 'Version Bump to 0.7.6' (#37) from groups into trunk
continuous-integration/drone/push Build is passing Details
Reviewed-on: #37
2021-05-10 17:12:03 -07:00
Sarah Jamie Lewis 62db995842 Version Bump to 0.7.6
continuous-integration/drone/pr Build was killed Details
2021-05-10 17:11:23 -07:00
erinn f6d4708501 adding sendinvitation 2021-05-10 16:58:25 -07:00
erinn dbf12007c7 Merge pull request 'Upgrade Cwtch for SendMessageToGroupError' (#36) from groups into trunk
continuous-integration/drone/push Build is passing Details
Reviewed-on: #36
2021-05-10 16:57:28 -07:00
Sarah Jamie Lewis 8a67b88f2e Upgrade Cwtch for SendMessageToGroupError
continuous-integration/drone/pr Build is passing Details
2021-05-10 16:54:37 -07:00
erinn 7ad9c25b19 Merge pull request 'Support partially syncing groups (faster syncing)' (#35) from groups into trunk
continuous-integration/drone/push Build is passing Details
Reviewed-on: #35
2021-05-07 16:49:39 -07:00
erinn f4ea408b7b Merge branch 'trunk' into groups
continuous-integration/drone/pr Build is passing Details
2021-05-07 16:49:31 -07:00
Sarah Jamie Lewis c1121f13a1 Support partially syncing groups (faster syncing)
continuous-integration/drone/pr Build is passing Details
Also properly handle server connection issues for groups
2021-05-07 16:35:07 -07:00
erinn df414ed23f Merge pull request 'New Cwtch Group API' (#34) from groups into trunk
continuous-integration/drone/push Build is passing Details
Reviewed-on: #34
2021-05-05 15:21:57 -07:00
Sarah Jamie Lewis 699d249ad7 Switch FFI
continuous-integration/drone/pr Build is passing Details
2021-05-05 15:19:07 -07:00
Sarah Jamie Lewis c2e3488a45 Remove Debug
continuous-integration/drone/pr Build is passing Details
2021-05-05 15:17:35 -07:00
Sarah Jamie Lewis 4df9a22075 New Cwtch Group API
continuous-integration/drone/pr Build is passing Details
2021-05-05 13:35:14 -07:00
Sarah Jamie Lewis 8f493b8fbb Merge pull request 'subscribe to indexed acks' (#33) from indexedacks into trunk
continuous-integration/drone/push Build is passing Details
Reviewed-on: #33
2021-05-03 11:51:25 -07:00
erinn 7f5e25d55a update cwtch.im dep
continuous-integration/drone/pr Build is failing Details
2021-05-03 11:47:07 -07:00
erinn b5f4e5c0e7 subscribe to indexed acks
continuous-integration/drone/pr Build is failing Details
2021-05-03 11:37:34 -07:00
erinn d5e9907ca4 Merge pull request 'Return Enhanced Peer Message' (#32) from groups into trunk
continuous-integration/drone/push Build is passing Details
Reviewed-on: #32
2021-05-02 20:49:50 -07:00
Sarah Jamie Lewis aef23fde74 Return Enhanced Peer Message
continuous-integration/drone/pr Build is passing Details
2021-05-02 20:45:18 -07:00
erinn 3f07d9a134 Merge pull request 'Accept/Reject Group Invite' (#31) from groups into trunk
continuous-integration/drone/push Build is passing Details
Reviewed-on: #31
2021-04-28 15:46:27 -07:00
erinn f4e8bb3e64 Merge branch 'trunk' into groups
continuous-integration/drone/pr Build is passing Details
2021-04-28 15:46:14 -07:00
Sarah Jamie Lewis e679b5f47b Accept/Reject Group Invite
continuous-integration/drone/pr Build is passing Details
2021-04-28 15:24:40 -07:00
erinn 75dcf71c02 Merge pull request 'bugfixes from android testing' (#30) from groups into trunk
continuous-integration/drone/push Build is passing Details
Reviewed-on: #30
2021-04-28 14:11:26 -07:00
erinn 7a1cc55781 Merge branch 'trunk' into groups
continuous-integration/drone/pr Build is passing Details
2021-04-28 14:11:17 -07:00
Dan Ballard d7e6af917c drone.yml: drone gitea release: reenable trigger restrictions
continuous-integration/drone/push Build is passing Details
2021-04-27 16:33:23 -07:00
Dan Ballard 31b5b4b13e drone.yml: drone gitea release try to get triggerable rm all restrictions
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/tag Build is passing Details
2021-04-27 16:20:52 -07:00
Dan Ballard 3637ea6875 drone.yml: drone gitea release try to get triggerable
continuous-integration/drone/push Build is passing Details
2021-04-27 14:14:43 -07:00
Dan Ballard 4b95bbfe3e drone.yml: drone gitea release try to get triggerable
continuous-integration/drone/push Build is passing Details
2021-04-27 13:34:05 -07:00
Dan Ballard 8c6156385a drone.yml: react to tag and publish results
continuous-integration/drone/push Build is passing Details
2021-04-27 12:16:03 -07:00
erinn 5ccbb5b7d4 Merge branch 'trunk' into groups
continuous-integration/drone/pr Build is passing Details
2021-04-27 12:14:08 -07:00
Sarah Jamie Lewis 3d2c8c9fbf bugfixes from android testing
continuous-integration/drone/pr Build is passing Details
2021-04-27 12:12:26 -07:00
erinn c6c9f034ae Merge pull request 'Upgrade Cwtch and Enhance New Group Invite' (#29) from groups into trunk
continuous-integration/drone/push Build is passing Details
Reviewed-on: #29
2021-04-23 14:29:19 -07:00
Sarah Jamie Lewis a99a7c659e Upgrade Cwtch and Enhance New Group Invite
continuous-integration/drone/pr Build is passing Details
2021-04-23 14:18:45 -07:00
erinn 65fd80b21a Merge pull request 'Set Group Attribute' (#28) from groups into trunk
continuous-integration/drone/push Build is failing Details
Reviewed-on: #28
2021-04-23 13:02:24 -07:00
Sarah Jamie Lewis d5f8dbc48b SetGroupAttribute
continuous-integration/drone/pr Build is passing Details
2021-04-23 12:56:20 -07:00
Sarah Jamie Lewis 3985b350f8 BUGFIX: Don't send Server contacts to the UI
continuous-integration/drone/pr Build is passing Details
2021-04-23 12:31:35 -07:00
erinn b05c734ab7 Merge pull request 'Import Group Bundles and Sending Group Messages' (#26) from server-lists into trunk
continuous-integration/drone/push Build is failing Details
Reviewed-on: #26
2021-04-22 14:30:01 -07:00
erinn 996052cb13 Merge branch 'trunk' into server-lists
continuous-integration/drone/pr Build is passing Details
2021-04-22 14:29:48 -07:00
Sarah Jamie Lewis 624f56f6f8 Import Group Bundles and Sending Group Messages
continuous-integration/drone/pr Build is passing Details
2021-04-22 14:28:58 -07:00
erinn f21ded32c4 Merge pull request 'Group Functionality Experiments with Server Lists' (#25) from server-lists into trunk
continuous-integration/drone/push Build is passing Details
Reviewed-on: #25
2021-04-20 16:04:10 -07:00
Sarah Jamie Lewis 347bb27310 Push Group Information to UI, Fix bug with fetching messages for groups
continuous-integration/drone/pr Build is passing Details
2021-04-20 15:24:00 -07:00
Sarah Jamie Lewis f9b4e1179e Group Functionality Experiments with Server Lists
continuous-integration/drone/pr Build is passing Details
2021-04-15 15:18:16 -07:00
erinn 154ba4610d Merge pull request 'ACNRestart API and Auto Re-listen on Tor Setup' (#24) from acn into trunk
continuous-integration/drone/push Build is passing Details
Reviewed-on: #24
2021-04-13 15:33:15 -07:00
Sarah Jamie Lewis 92402c08f5 ACNRestart API and Auto Re-listen on Tor Setup
continuous-integration/drone/pr Build is passing Details
2021-04-13 15:26:48 -07:00
Sarah Jamie Lewis 32de6272c1 Merge pull request 'sort and update contact list' (#23) from clsort into trunk
continuous-integration/drone/push Build is passing Details
Reviewed-on: #23
2021-04-13 14:01:23 -07:00
erinn c3973ecd92 Merge branch 'trunk' into clsort
continuous-integration/drone/pr Build is passing Details
2021-04-12 17:06:10 -07:00
erinn 0224372a66 sort and update contact list
continuous-integration/drone/pr Build is passing Details
2021-04-12 17:04:21 -07:00
Sarah Jamie Lewis bd7bccb9d2 Merge pull request 'update ui on block' (#22) from erinn237 into trunk
continuous-integration/drone/push Build is passing Details
Reviewed-on: #22
2021-04-12 15:44:37 -07:00
erinn 91b3d19cf0 fixes #237
continuous-integration/drone/pr Build is passing Details
2021-04-12 15:27:53 -07:00
Sarah Jamie Lewis 17bd2da4c4 Upgrade Cwtch / Tapir / Connectivity / Logging + API Call
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2021-04-09 20:05:59 -07:00
Sarah Jamie Lewis b3cbd5905b Merge pull request 'approve contacts, send messages' (#20) from sendagain into trunk
continuous-integration/drone/push Build is passing Details
Reviewed-on: #20
2021-04-09 19:51:48 -07:00
erinn c648f067af Merge branch 'trunk' of git.openprivacy.ca:flutter/libcwtch-go into sendagain
continuous-integration/drone/pr Build is passing Details
2021-04-09 19:41:37 -07:00
erinn 6e1cb20d66 mo wirin
continuous-integration/drone/pr Build is running Details
2021-04-09 19:31:05 -07:00
erinn 0c963172ff restoring sendmessage yay 2021-04-07 22:06:21 -07:00
Dan Ballard d8e21ee2c9 Merge pull request 'Adding Block/Allow Unknown Contact Global Setting' (#19) from peersettings into trunk
continuous-integration/drone/push Build is passing Details
Reviewed-on: #19
2021-04-06 15:13:22 -07:00
Sarah Jamie Lewis fe84c6ac47 Adding Block/Allow Unknown Contact Global Setting
continuous-integration/drone/pr Build is passing Details
2021-04-06 15:08:52 -07:00
Dan Ballard 6e9d423d58 drone.yml new drone format settings for email
continuous-integration/drone/push Build is passing Details
2021-03-30 14:04:17 -07:00
Dan Ballard 6e826691fa drone.yml new drone format settings for drone-gogs
continuous-integration/drone/push Build is passing Details
2021-03-29 16:55:42 -07:00
Dan Ballard 3ea5121998 Merge pull request 'Add Contact Flow' (#18) from peersettings into trunk
continuous-integration/drone/push Build is passing Details
Reviewed-on: #18
2021-03-29 15:39:35 -07:00
Sarah Jamie Lewis fd53fadef9 Add Contact Flow
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/pr Build is failing Details
2021-03-29 15:28:32 -07:00
Dan Ballard 2bfaaada54 drone.yml: update sha256 to exclude itself
continuous-integration/drone/push Build is passing Details
2021-03-26 18:01:35 -07:00
Dan Ballard 951a9cf10e gogs step use from_secret
continuous-integration/drone/push Build is passing Details
2021-03-25 17:40:28 -07:00
Dan Ballard 4967f5f3f8 drone.yml: deploy stage working and sha256
continuous-integration/drone/push Build is passing Details
2021-03-25 15:53:03 -07:00
Dan Ballard 3efb31e9f3 drone.yml: deploy stage debug
continuous-integration/drone/push Build is passing Details
2021-03-25 15:33:45 -07:00
Dan Ballard 3e6eaca789 drone.yml: deploy stage + notify-gogs on PR only
continuous-integration/drone/push Build is failing Details
2021-03-25 14:00:52 -07:00
Dan Ballard 77247bc4b9 drone.yml rely on trigger, rm most when
continuous-integration/drone/push Build is failing Details
2021-03-24 18:34:03 -07:00
Dan Ballard a0a836ebc6 drone.yml triggers to filter
continuous-integration/drone/push Build is passing Details
2021-03-24 18:32:00 -07:00
Dan Ballard d032211f27 drone.yml windows set GOPATH
continuous-integration/drone/push Build is passing Details
2021-03-24 18:14:49 -07:00
Dan Ballard 36e344053a Makefile: windows build. drone.yml: attempt windows build
continuous-integration/drone/push Build is passing Details
2021-03-24 16:57:53 -07:00
Dan Ballard da82e25575 drone.yml use official openpriv/android-go-mobile:2021.03
continuous-integration/drone/push Build is passing Details
2021-03-23 11:24:29 -07:00
Dan Ballard 15be89ac13 drone.yml: using hte new drone gomobile image we can cut all the needless instructions
continuous-integration/drone/push Build is passing Details
2021-03-22 18:14:21 -07:00
Dan Ballard f1c2b3094a drone.yml gomobile explode /go from GOPATH
continuous-integration/drone/push Build is passing Details
2021-03-22 18:04:00 -07:00
Dan Ballard f9d7230427 less gomobile related cleaning
continuous-integration/drone/push Build is passing Details
2021-03-22 11:48:17 -07:00
Dan Ballard bfafbbcd4a drone.yml: even more gomobile commands to make it work
continuous-integration/drone/push Build is passing Details
2021-03-19 14:56:02 -07:00
Dan Ballard a741aea975 Merge pull request 'Launch Peers on NewPeer + Set Name' (#16) from peersettings into trunk
continuous-integration/drone/push Build is failing Details
Reviewed-on: #16
2021-03-19 14:52:36 -07:00
Sarah Jamie Lewis 1fa86ca0ef Merge branch 'trunk' into peersettings
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/pr Build is failing Details
2021-03-19 14:51:07 -07:00
Dan Ballard 3b2c8a3d7b drone.yml: more gomobile commands to make it work
continuous-integration/drone/push Build is failing Details
2021-03-19 14:50:45 -07:00
Sarah Jamie Lewis faffb3e042 Dan Comments + Upgrade to Cwtch 0.6.0
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/pr Build is failing Details
2021-03-19 14:49:44 -07:00
Sarah Jamie Lewis 8072496afe Launch Peers on NewPeer + Set Name 2021-03-19 12:58:07 -07:00
Dan Ballard b678b3b77d drone.yml gomobile try gets
continuous-integration/drone/push Build is failing Details
2021-03-17 17:36:16 -07:00
Dan Ballard dac7d145d2 drone.yml wrong images
continuous-integration/drone/push Build is failing Details
2021-03-17 16:59:02 -07:00
Dan Ballard 3d1de1cd6e drone.yml new gomobile image
continuous-integration/drone/push Build is failing Details
2021-03-17 16:53:46 -07:00
Sarah Jamie Lewis e1d4eed7ac Merge pull request 'initialize message count' (#15) from rinnmar17 into trunk
continuous-integration/drone/push Build is failing Details
Reviewed-on: #15
2021-03-17 16:00:08 -07:00
erinn f3870163db Merge branch 'trunk' of git.openprivacy.ca:flutter/libcwtch-go into rinnmar17
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/pr Build is failing Details
2021-03-17 15:51:48 -07:00
erinn cf23b04f2d Merge pull request 'Save Peer History Settings' (#14) from peersettings into trunk
continuous-integration/drone/push Build is failing Details
Reviewed-on: #14
2021-03-17 15:49:39 -07:00
erinn e439c0954b initialize message counter 2021-03-17 15:42:09 -07:00
Sarah Jamie Lewis c18844a0d5 Save Peer History Settings
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/pr Build is failing Details
2021-03-17 15:39:59 -07:00
erinn 0677dc308b Merge pull request 'Peer Settings Pane Changes' (#13) from logging into trunk
continuous-integration/drone/push Build is failing Details
Reviewed-on: #13
2021-03-17 15:33:18 -07:00
erinn 2edaff73b5 Merge branch 'trunk' into logging
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/pr Build is failing Details
2021-03-17 15:24:45 -07:00
Sarah Jamie Lewis a7524d6c55 Peer Settings Pane Changes 2021-03-17 14:51:43 -07:00
Dan Ballard e9b6e10795 drone.yml keep vendors
continuous-integration/drone/push Build is failing Details
2021-03-17 13:41:52 -07:00
Dan Ballard d53d81f8d0 .drone.yml go get golint... not go1.16 yet
continuous-integration/drone/push Build is failing Details
2021-03-17 13:22:02 -07:00
Dan Ballard 68e60d438d fix govet 'error' and disable .drone.yml quality linting
continuous-integration/drone/push Build is failing Details
2021-03-17 13:16:27 -07:00
Dan Ballard 94792c5953 .drone.yml syntax fix
continuous-integration/drone/push Build is failing Details
2021-03-17 12:47:32 -07:00
Dan Ballard 8837a62808 Merge branch 'trunk' of git.openprivacy.ca:flutter/libcwtch-go into trunk 2021-03-17 12:43:36 -07:00
Dan Ballard f0870e4407 drone pipeline file test 2021-03-17 12:35:55 -07:00
erinn 1e983fadd4 Merge pull request 'Set default logging level and allow event bus configuration' (#12) from logging into trunk
Reviewed-on: #12
2021-03-17 12:22:17 -07:00
Sarah Jamie Lewis 64aeaad23a Set default logging level and allow event bus configuration 2021-03-17 12:14:44 -07:00
Dan Ballard eb06bdd19a Merge pull request 'unwrap eventprofileenvelope' (#11) from rinnmar16 into trunk
Reviewed-on: #11
2021-03-16 15:16:36 -07:00
erinn 1902944bc7 unwrap eventprofileenvelope 2021-03-16 15:16:04 -07:00
erinn 6e4547c8bb unwrap eventprofileenvelope 2021-03-16 15:05:44 -07:00
erinn b61094f5d4 Merge pull request 'Experimental Settings' (#10) from settings into trunk
Reviewed-on: #10
2021-03-16 13:58:29 -07:00
erinn d110e870ec Merge branch 'trunk' into settings 2021-03-16 13:58:01 -07:00
Sarah Jamie Lewis eb570745a5 Formatt + if/else 2021-03-16 13:55:50 -07:00
Sarah Jamie Lewis 3960244756 Fix condition on Accept 2021-03-16 13:54:31 -07:00
Dan Ballard 2359d8a2e4 Merge pull request 'contacts rewire' (#9) from contacts into trunk
Reviewed-on: #9
2021-03-16 13:49:11 -07:00
erinn 281f229410 undo bad refactor 2021-03-16 13:44:20 -07:00
erinn fe674e35fc undo bad refactor 2021-03-16 13:16:45 -07:00
Sarah Jamie Lewis c230746546 Experimental Settings 2021-03-16 12:54:25 -07:00
erinn 51c7b56438 dont list servers as contacts 2021-03-15 15:24:49 -07:00
erinn c2d5217c7a merge trunk 2021-03-12 04:25:54 -08:00
erinn 659cdce84c contact rewiring 2021-03-12 04:24:37 -08:00
erinn e3ee301a79 Merge pull request 'settings' (#8) from settings into trunk
Reviewed-on: #8
2021-03-12 04:20:29 -08:00
erinn cdb2b643a0 Merge branch 'trunk' into settings 2021-03-12 04:20:23 -08:00
erinn 5c7f4ab765 Merge pull request 'events 0.2' (#7) from contacts into trunk
Reviewed-on: #7
2021-03-12 04:19:44 -08:00
Sarah Jamie Lewis ef63736d74 Read/Write Global Settings 2021-03-10 09:41:09 -08:00
erinn 77a76b6bdb Merge branch 'contacts' of git.openprivacy.ca:flutter/libcwtch-go into contacts 2021-03-08 16:53:49 -08:00
Dan Ballard e55e182912 getAppBusEvents swallows empty strings resulting from 'errors' and non actionable events 2021-03-08 16:51:34 -08:00
Dan Ballard 8e78e64603 getAppBusEvents swallows empty strings resulting from 'errors' and non actionable events 2021-03-08 16:44:00 -08:00
Dan Ballard 877d4880d0 return empty string on err so dart doesnt fail to parse json of error string. it is Log.errorf ed 2021-03-05 11:15:31 -08:00
Dan Ballard 44655bb57b quality 2021-03-04 16:14:58 -08:00
Dan Ballard 87c7d7719b make event manager, clean event code out of lib.go, start handing some peer events and enrich for front end use 2021-03-04 16:14:10 -08:00
Dan Ballard 868ec3d203 Merge pull request 'SendProfileEvent' (#6) from add_profile_form into trunk
Reviewed-on: #6
2021-03-03 18:06:01 -08:00
Sarah Jamie Lewis b78d8d8832 Format 2021-03-03 18:05:22 -08:00
Sarah Jamie Lewis 7dfdb33c52 SendProfileEvent 2021-03-03 18:01:32 -08:00
erinn ecb657a7a5 more debug notes 2021-02-09 17:51:59 -08:00
erinn 60b5c82caa Update 'BUILDING_DEBUG.md' 2021-02-08 21:09:42 -08:00
erinn de4cd10d86 add debug notes 2021-02-08 21:08:30 -08:00
erinn 897b4ac435 Update 'go.mod' 2021-02-08 20:55:38 -08:00
erinn a9cb162d46 Update 'go.mod' 2021-02-08 20:54:19 -08:00
Sarah Jamie Lewis 69ba341c82 Merge pull request 'properly handle NewPeer event as per qt ui' (#5) from newPeer into trunk
Reviewed-on: #5
2021-02-08 20:20:32 -08:00
Dan Ballard 8cb17c5ec1 properly handle NewPeer event as per qt ui 2021-02-05 16:31:03 -08:00
erinn da1058f1a8 experimenting with adding additional info to appbus events 2021-02-03 05:22:52 -08:00
Dan Ballard 5d0bcf0846 Merge pull request 'full gomobile and ffi support + new GetMessages' (#3) from gomobile into trunk
Reviewed-on: #3
2021-01-28 12:25:39 -08:00
Dan Ballard dd9f60a9ac Merge pull request 'Unlock Profiles' (#4) from unlock into gomobile
Reviewed-on: #4
2021-01-26 17:22:50 -08:00
Sarah Jamie Lewis 92f74b2dc6 Unlock Profiles 2021-01-26 14:38:06 -08:00
erinn 240c4d36e0 merging dan gomobile with erinn ui 2021-01-22 00:00:03 -08:00
Dan Ballard 9c7a22d472 getAppBusEvents for gomobile 2021-01-20 12:05:37 -08:00
Dan Ballard 120dc5f468 add GetMessages 2021-01-14 15:34:08 -08:00
Dan Ballard b70da91e5c make c_ function wrappers so can work with gomobile and ffi. change Start to take tor path as well 2021-01-12 21:46:33 -08:00
Sarah Jamie Lewis b49519eebb Merge pull request 'gomobile-sub' (#2) from gomobile-sub into trunk
Reviewed-on: #2
2021-01-11 13:27:56 -08:00
25 changed files with 2276 additions and 141 deletions

137
.drone.yml Normal file
View File

@ -0,0 +1,137 @@
---
kind: pipeline
type: docker
name: default
steps:
- name: fetch
image: golang
volumes:
- name: deps
path: /go
commands:
- wget https://git.openprivacy.ca/openprivacy/buildfiles/raw/master/tor/tor
- wget https://git.openprivacy.ca/openprivacy/buildfiles/raw/master/tor/torrc
- chmod a+x tor
- go get -u golang.org/x/lint/golint
- git fetch --tags
#- export GO111MODULE=on
#- go mod vendor
- go get
# TODO: upgrade to go1.16, remove mod/vendor, add go install for 1.16
- echo `git describe --tags` > VERSION
- echo `date +%G-%m-%d-%H-%M` > BUILDDATE
- name: quality
image: golang
volumes:
- name: deps
path: /go
commands:
- go list ./... | xargs go vet
- go list ./... | xargs golint
#Todo: fix all the lint errors and add `-set_exit_status` above to enforce linting
- name: build-linux
image: golang
volumes:
- name: deps
path: /go
commands:
- make linux
- name: build-android
image: openpriv/android-go-mobile:2021.03
volumes:
- name: deps
path: /go
commands:
- go mod download
- gomobile init
- make android
- name: build-windows
image: openpriv/mingw-go:2021.03
environment:
GOPATH: /go
volumes:
- name: deps
path: /go
commands:
- make windows
- name: deploy-buildfiles
image: kroniak/ssh-client
environment:
BUILDFILES_KEY:
from_secret: buildfiles_key
secrets: [gogs_account_token]
when:
event:
- push
- tag
status: [ success ]
commands:
- echo $BUILDFILES_KEY > ~/id_rsab64
- base64 -d ~/id_rsab64 > ~/id_rsa
- chmod 400 ~/id_rsa
- export DIR=libCwtch-go-`cat VERSION`-`cat BUILDDATE`
- mkdir $DIR
- mv libCwtch.so libCwtch.dll cwtch.aar cwtch-sources.jar libCwtch.h $DIR/
- cd $DIR
- find . -type f -exec sha256sum {} \; > ./../sha256s.txt
- mv ./../sha256s.txt .
- cd ..
- scp -r -o StrictHostKeyChecking=no -i ~/id_rsa $DIR buildfiles@openprivacy.ca:/home/buildfiles/buildfiles/
- name: gitea-release
image: plugins/gitea-release
when:
event: tag
settings:
api_key:
from_secret: gogs_account_token
base_url: https://git.openprivacy.ca
files:
- libCwtch.so
- libCwtch.dll
- cwtch.aar
- cwtch-sources.jar
- libCwtch.h
checksum:
- sha256
- sha512
- name: notify-email
image: drillster/drone-email
settings:
host: build.openprivacy.ca
port: 25
skip_verify: true
from: drone@openprivacy.ca
when:
status: [ failure ]
- name: notify-gogs
image: openpriv/drone-gogs
when:
event: pull_request
status: [ success, changed, failure ]
environment:
GOGS_ACCOUNT_TOKEN:
from_secret: gogs_account_token
settings:
gogs_url: https://git.openprivacy.ca
volumes:
# gopath where bin and pkg lives to persist across steps
- name: deps
temp: {}
trigger:
repo: flutter/libcwtch-go
branch: trunk
event:
- push
- pull_request
- tag

6
.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
.idea
cwtch-sources.jar
cwtch.aar
libCwtch.h
libCwtch.so
libCwtch.dll

32
BUILDING_DEBUG.md Normal file
View File

@ -0,0 +1,32 @@
*sometimes: rm -rf $GOPATH/pkg*
*sometimes: rm -rf /tmp/gomobile*
rm -rf vendor
go clean --modcache
go mod download
go get -u golang.org/x/mobile
go get -u golang.org/x/mobile/bind
gomobile clean
gomobile init
make android
# errors
```
# runtime/cgo
gcc_android.c:6:10: fatal error: android/log.h: No such file or directory
#include <android/log.h>
^~~~~~~~~~~~~~~
compilation terminated.```
```
*solution:* run `gomobile init`

View File

@ -8,6 +8,8 @@ linux: libCwtch.so
android: cwtch.aar
windows: libCwtch.dll
libCwtch.so: lib.go
./switch-ffi.sh
go build -buildmode c-shared -o libCwtch.so
@ -16,5 +18,9 @@ cwtch.aar: lib.go
./switch-gomobile.sh
gomobile bind -target android
libCwtch.dll: lib.go
./switch-ffi.sh
GOOS=windows GOARCH=amd64 CGO_ENABLED=1 CC=x86_64-w64-mingw32-gcc-win32 go build -buildmode c-shared -o libCwtch.dll
clean:
rm -f cwtch.aar cwtch_go.apk libCwtch.h libCwtch.so cwtch-sources.jar
rm -f cwtch.aar cwtch_go.apk libCwtch.h libCwtch.so cwtch-sources.jar libCwtch.dll

View File

@ -1,6 +1,14 @@
# Build Instructions...
go build -buildmode c-shared -o libCwtch.so
make linux
make android
# Using
LD_LIBRARY_PATH set to point to libCwtch.so
## Linux Desktop:
- `LD_LIBRARY_PATH` set to point to `libCwtch.so`
- or drop a symlink into `/usr/lib`
## Android
- copy `cwtch.aar` into `flutter_app/android/cwtch`

23
constants/attributes.go Normal file
View File

@ -0,0 +1,23 @@
package constants
const SchemaVersion = "schemaVersion"
const Name = "name"
const LastRead = "last-read"
const Picture = "picture"
const ShowBlocked = "show-blocked"
const ProfileTypeV1DefaultPassword = "v1-defaultPassword"
const ProfileTypeV1Password = "v1-userPassword"
// PeerOnline stores state on if the peer believes it is online
const PeerOnline = "peer-online"
const StateProfilePane = "state-profile-pane"
const StateSelectedConversation = "state-selected-conversation"
const StateSelectedProfileTime = "state-selected-profile-time"
// Settings
const BlockUnknownPeersSetting = "blockunknownpeers"
const LocaleSetting = "locale"
const ZoomSetting = "zoom"

5
constants/globals.go Normal file
View File

@ -0,0 +1,5 @@
package constants
// We offer "un-passworded" profiles but our storage encrypts everything with a password. We need an agreed upon
// password to use in that case, that the app case use behind the scenes to password and unlock with
const DefactoPasswordForUnencryptedProfiles = "be gay do crime"

View File

@ -0,0 +1,26 @@
package constants
import "cwtch.im/cwtch/event"
// The server manager defines its own events, most should be self-explanatory:
const (
NewServer = event.Type("NewServer")
// Force a UI update
ListServers = event.Type("ListServers")
// Takes an Onion, used to toggle off/on Server availability
StartServer = event.Type("StartServer")
StopServer = event.Type("StopServer")
// Takes an Onion and a AutoStartEnabled boolean
AutoStart = event.Type("AutoStart")
// Get the status of a particular server (takes an Onion)
CheckServerStatus = event.Type("CheckServerStatus")
ServerStatusUpdate = event.Type("ServerStatusUpdate")
)
const (
AutoStartEnabled = event.Field("AutoStartEnabled")
)

View File

@ -0,0 +1,41 @@
package contact
import (
"cwtch.im/cwtch/model"
"cwtch.im/cwtch/peer"
"git.openprivacy.ca/flutter/libcwtch-go/features"
"git.openprivacy.ca/openprivacy/connectivity/tor"
)
// Functionality groups some common UI triggered functions for contacts...
type Functionality struct {
}
const addContactPrefix = "addcontact"
const sendMessagePrefix = "sendmessage"
// FunctionalityGate returns contact.Functionality always
func FunctionalityGate(experimentMap map[string]bool) (*Functionality, error) {
return new(Functionality), nil
}
// SendMessage handles sending messages to contacts
func (pf *Functionality) SendMessage(peer peer.SendMessages, handle string, message string) features.Response {
eventID := peer.SendMessageToPeer(handle, message)
return features.ConstructResponse(sendMessagePrefix, eventID)
}
// HandleImportString handles contact import strings
func (pf *Functionality) HandleImportString(peer peer.ModifyContactsAndPeers, importString string) features.Response {
if tor.IsValidHostname(importString) {
if peer.GetContact(importString) == nil {
peer.AddContact(importString, importString, model.AuthApproved)
// Implicit Peer Attempt
peer.PeerWithOnion(importString)
return features.ConstructResponse(addContactPrefix, "success")
}
return features.ConstructResponse(addContactPrefix, "contact_already_exists")
}
return features.ConstructResponse(addContactPrefix, "invalid_import_string")
}

View File

@ -0,0 +1,124 @@
package contact
import (
"cwtch.im/cwtch/model"
"git.openprivacy.ca/flutter/libcwtch-go/features"
"testing"
)
const ValidHostname = "openpravyvc6spbd4flzn4g2iqu4sxzsizbtb5aqec25t76dnoo5w7yd"
type MockPeer struct {
hasContact bool
addContact bool
peerRequest bool
}
func (m MockPeer) BlockUnknownConnections() {
panic("should never be called")
}
func (m MockPeer) AllowUnknownConnections() {
panic("should never be called")
}
func (m MockPeer) GetContacts() []string {
panic("should never be called")
}
func (m MockPeer) GetContact(s string) *model.PublicProfile {
if m.hasContact {
return &(model.GenerateNewProfile("").PublicProfile)
}
return nil
}
func (m MockPeer) GetContactAttribute(s string, s2 string) (string, bool) {
panic("should never be called")
}
func (m *MockPeer) AddContact(nick, onion string, authorization model.Authorization) {
m.addContact = true
}
func (m MockPeer) SetContactAuthorization(s string, authorization model.Authorization) error {
panic("should never be called")
}
func (m MockPeer) SetContactAttribute(s string, s2 string, s3 string) {
panic("should never be called")
}
func (m MockPeer) DeleteContact(s string) {
panic("should never be called")
}
func (m *MockPeer) PeerWithOnion(s string) {
m.peerRequest = true
}
func (m MockPeer) JoinServer(s string) error {
panic("should never be called")
}
func TestContactFunctionality_InValidHostname(t *testing.T) {
cf, _ := FunctionalityGate(map[string]bool{})
peer := &MockPeer{
hasContact: false,
addContact: false,
peerRequest: false,
}
response := cf.HandleImportString(peer, "")
if peer.addContact || peer.peerRequest {
t.Fatalf("HandleImportString for a malformed import string should have no resulted in addContact or a peerRequest: %v", peer)
}
if response.Error() != features.ConstructResponse(addContactPrefix, "invalid_import_string").Error() {
t.Fatalf("Response to a successful import is malformed: %v", response)
}
}
func TestContactFunctionality_ValidHostnameExistingContact(t *testing.T) {
cf, _ := FunctionalityGate(map[string]bool{})
peer := &MockPeer{
hasContact: true,
addContact: false,
peerRequest: false,
}
response := cf.HandleImportString(peer, ValidHostname)
if peer.addContact || peer.peerRequest {
t.Fatalf("HandleImportString for a valid string should not call addContact or a peerRequest when the contact already exists: %v", peer)
}
if response.Error() != features.ConstructResponse(addContactPrefix, "contact_already_exists").Error() {
t.Fatalf("Response to a successful import is malformed: %v", response)
}
}
func TestContactFunctionality_ValidHostnameUnknownContact(t *testing.T) {
cf, _ := FunctionalityGate(map[string]bool{})
peer := &MockPeer{
hasContact: false,
addContact: false,
peerRequest: false,
}
response := cf.HandleImportString(peer, ValidHostname)
if peer.addContact && peer.peerRequest {
if response.Error() != features.ConstructResponse(addContactPrefix, "success").Error() {
t.Fatalf("Response to a successful import is malformed: %v", response)
}
} else {
t.Fatalf("HandleImportString for a valid import string should have resulted in addContact or a peerRequest: %v", peer)
}
}

View File

@ -0,0 +1,127 @@
package groups
import (
"cwtch.im/cwtch/event"
"cwtch.im/cwtch/model"
"cwtch.im/cwtch/peer"
"encoding/base64"
"fmt"
"git.openprivacy.ca/flutter/libcwtch-go/features"
"git.openprivacy.ca/openprivacy/log"
"strings"
)
const serverPrefix = "server:"
const tofuBundlePrefix = "tofubundle:"
const groupPrefix = "torv3"
const groupExperiment = "tapir-groups-experiment"
const importBundlePrefix = "importBundle"
const (
// ServerList is a json encoded list of servers
ServerList = event.Field("ServerList")
)
const (
// UpdateServerInfo is an event containing a ProfileOnion and a ServerList
UpdateServerInfo = event.Type("UpdateServerInfo")
)
// ReadServerInfo is a meta-interface for reading information about servers..
type ReadServerInfo interface {
peer.ReadContacts
peer.ReadServers
}
// GroupFunctionality provides experiment gated server functionality
type GroupFunctionality struct {
}
// ExperimentGate returns GroupFunctionality if the experiment is enabled, and an error otherwise.
func ExperimentGate(experimentMap map[string]bool) (*GroupFunctionality, error) {
if experimentMap[groupExperiment] {
return new(GroupFunctionality), nil
}
return nil, fmt.Errorf("gated by %v", groupExperiment)
}
// SendMessage is a deprecated api
func (gf *GroupFunctionality) SendMessage(peer peer.CwtchPeer, handle string, message string) (string, error) {
// TODO this auto accepting behaviour needs some thinking through
if !peer.GetGroup(handle).Accepted {
err := peer.AcceptInvite(handle)
if err != nil {
log.Errorf("tried to mark a nonexistent group as existed. bad!")
return "", err
}
}
return peer.SendMessageToGroupTracked(handle, message)
}
// ValidPrefix returns true if an import string contains a prefix that indicates it contains information about a
// server or a group
func (gf *GroupFunctionality) ValidPrefix(importString string) bool {
return strings.HasPrefix(importString, tofuBundlePrefix) || strings.HasPrefix(importString, serverPrefix) || strings.HasPrefix(importString, groupPrefix)
}
// GetServerInfoList compiles all the information the UI might need regarding all servers..
func (gf *GroupFunctionality) GetServerInfoList(profile ReadServerInfo) []Server {
var servers []Server
for _, server := range profile.GetServers() {
servers = append(servers, gf.GetServerInfo(server, profile))
}
return servers
}
// GetServerInfo compiles all the information the UI might need regarding a particular server including any verified
// cryptographic keys
func (gf *GroupFunctionality) GetServerInfo(serverOnion string, profile peer.ReadContacts) Server {
serverInfo := profile.GetContact(serverOnion)
keyTypes := []model.KeyType{model.KeyTypeServerOnion, model.KeyTypeTokenOnion, model.KeyTypePrivacyPass}
var serverKeys []ServerKey
for _, keyType := range keyTypes {
if key, has := serverInfo.GetAttribute(string(keyType)); has {
serverKeys = append(serverKeys, ServerKey{Type: string(keyType), Key: key})
}
}
return Server{Onion: serverOnion, Status: serverInfo.State, Keys: serverKeys}
}
// HandleImportString handles import strings for groups and servers
func (gf *GroupFunctionality) HandleImportString(peer peer.CwtchPeer, importString string) error {
if strings.HasPrefix(importString, tofuBundlePrefix) {
bundle := strings.Split(importString, "||")
if len(bundle) == 2 {
err := gf.HandleImportString(peer, bundle[0][len(tofuBundlePrefix):])
// if the server import failed then abort the whole process..
if !strings.HasSuffix(err.Error(), "success") {
return features.ConstructResponse(importBundlePrefix, err.Error())
}
return gf.HandleImportString(peer, bundle[1])
}
} else if strings.HasPrefix(importString, serverPrefix) {
// Server Key Bundles are prefixed with
bundle, err := base64.StdEncoding.DecodeString(importString[len(serverPrefix):])
if err == nil {
if err = peer.AddServer(string(bundle)); err != nil {
return features.ConstructResponse(importBundlePrefix, err.Error())
}
return features.ConstructResponse(importBundlePrefix, "success")
}
return features.ConstructResponse(importBundlePrefix, err.Error())
} else if strings.HasPrefix(importString, groupPrefix) {
//eg: torv3JFDWkXExBsZLkjvfkkuAxHsiLGZBk0bvoeJID9ItYnU=EsEBCiBhOWJhZDU1OTQ0NWI3YmM2N2YxYTM5YjkzMTNmNTczNRIgpHeNaG+6jy750eDhwLO39UX4f2xs0irK/M3P6mDSYQIaOTJjM2ttb29ibnlnaGoyenc2cHd2N2Q1N3l6bGQ3NTNhdW8zdWdhdWV6enB2ZmFrM2FoYzRiZHlkCiJAdVSSVgsksceIfHe41OJu9ZFHO8Kwv3G6F5OK3Hw4qZ6hn6SiZjtmJlJezoBH0voZlCahOU7jCOg+dsENndZxAA==
if gid, err := peer.ImportGroup(importString); err != nil {
return features.ConstructResponse(importBundlePrefix, err.Error())
} else {
// Auto accept the group here.
if peer.AcceptInvite(gid) != nil {
log.Errorf("Error accepting invite: %v", err)
}
return features.ConstructResponse(importBundlePrefix, "success")
}
}
return features.ConstructResponse(importBundlePrefix, "invalid_group_invite_prefix")
}

View File

@ -0,0 +1,39 @@
package groups
import "testing"
func TestGroupFunctionality_ValidPrefix(t *testing.T) {
gf, _ := ExperimentGate(map[string]bool{groupExperiment: true})
if gf.ValidPrefix("torv3blahblahblah") == false {
t.Fatalf("torv3 should be a valid prefix")
}
if gf.ValidPrefix("tofubundle:32432423||3242342") == false {
t.Fatalf("tofubundle should be a valid prefix")
}
if gf.ValidPrefix("server:23541233t") == false {
t.Fatalf("server should be a valid prefix")
}
if gf.ValidPrefix("alice!24234") == true {
t.Fatalf("alice should be an invalid predix")
}
}
func TestGroupFunctionality_IsEnabled(t *testing.T) {
_, err := ExperimentGate(map[string]bool{})
if err == nil {
t.Fatalf("group functionality should be disabled")
}
_, err = ExperimentGate(map[string]bool{groupExperiment: true})
if err != nil {
t.Fatalf("group functionality should be enabled")
}
_, err = ExperimentGate(map[string]bool{groupExperiment: false})
if err == nil {
t.Fatalf("group functionality should be disabled")
}
}

12
features/groups/server.go Normal file
View File

@ -0,0 +1,12 @@
package groups
type ServerKey struct {
Type string `json:"type"`
Key string `json:"key"`
}
type Server struct {
Onion string `json:"onion"`
Status string `json:"status"`
Keys []ServerKey `json:"keys"`
}

13
features/response.go Normal file
View File

@ -0,0 +1,13 @@
package features
import "errors"
// Response is a wrapper to better semantically convey the response type...
type Response error
const errorSeparator = "."
// ConstructResponse is a helper function for creating Response structures.
func ConstructResponse(prefix string, error string) Response {
return errors.New(prefix + errorSeparator + error)
}

8
go.mod
View File

@ -1,10 +1,10 @@
module git.openprivacy.ca/libCwtch-go
module git.openprivacy.ca/flutter/libcwtch-go
go 1.15
require (
cwtch.im/cwtch v0.5.1
git.openprivacy.ca/openprivacy/connectivity v1.3.3
cwtch.im/cwtch v0.8.11
git.openprivacy.ca/openprivacy/connectivity v1.4.4
git.openprivacy.ca/openprivacy/log v1.0.2
golang.org/x/mobile v0.0.0-20201217150744-e6ae53a27f4f // indirect
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 // indirect
)

71
go.sum
View File

@ -1,91 +1,74 @@
cwtch.im v0.4.11 h1:2FdbstCo8a2pSoM9+FnH9H8IL8TYpNgEDA6+vcstqWI=
cwtch.im/cwtch v0.5.1 h1:84foD/HBebPbA4gUEwp+feakeHkD3Di53Q3FnSbqDMM=
cwtch.im/cwtch v0.5.1/go.mod h1:snHZIZwRQPAZG2LRZsN5SpAIbeR597VJoDS+KHm7q9w=
cwtch.im/tapir v0.2.1 h1:t1YJB9q5sV1A9xwiiwL6WVfw3dwQWLoecunuzT1PQtw=
cwtch.im/tapir v0.2.1/go.mod h1:xzzZ28adyUXNkYL1YodcHsAiTt3IJ8Loc29YVn9mIEQ=
cwtch.im/cwtch v0.8.11 h1:nUrd6srjLxInSJ0q1JdmRBhN4RRlZRL+2vIyE/AXkfY=
cwtch.im/cwtch v0.8.11/go.mod h1:D9dtO+WnKqdmufKSfFeFlUYaxLTfE/RtqVe1OD0kiKc=
git.openprivacy.ca/cwtch.im/tapir v0.4.3 h1:sctSfUXHDIqaHfJPDl+5lHtmoEJolQiHTcHZGAe5Qc4=
git.openprivacy.ca/cwtch.im/tapir v0.4.3/go.mod h1:10qEaib5x021zgyZ/97JKWsEpedH5+Vfy2CvB2V+08E=
git.openprivacy.ca/openprivacy/bine v0.0.4 h1:CO7EkGyz+jegZ4ap8g5NWRuDHA/56KKvGySR6OBPW+c=
git.openprivacy.ca/openprivacy/bine v0.0.4/go.mod h1:13ZqhKyqakDsN/ZkQkIGNULsmLyqtXc46XBcnuXm/mU=
git.openprivacy.ca/openprivacy/connectivity v1.2.0/go.mod h1:B7vzuVmChJtSKoh0ezph5vu6DQ0gIk0zHUNG6IgXCcA=
git.openprivacy.ca/openprivacy/connectivity v1.3.3 h1:OKHZ/pzY95+UNOhF74DisSYPh7lULtjbxFQnK9r6cAk=
git.openprivacy.ca/openprivacy/connectivity v1.3.3/go.mod h1:DL9QitHjpyNspMUe3wjIej9gFgDK2FdRKP2JE4+7T90=
git.openprivacy.ca/openprivacy/log v1.0.0/go.mod h1:gGYK8xHtndRLDymFtmjkG26GaMQNgyhioNS82m812Iw=
git.openprivacy.ca/openprivacy/log v1.0.1/go.mod h1:gGYK8xHtndRLDymFtmjkG26GaMQNgyhioNS82m812Iw=
git.openprivacy.ca/openprivacy/connectivity v1.4.4 h1:11M3akVCyy/luuhMpZTM1r9Jayl7IHD944Bxsn2FDpU=
git.openprivacy.ca/openprivacy/connectivity v1.4.4/go.mod h1:JVRCIdL+lAG6ohBFWiKeC/MN42nnC0sfFszR9XG6vPQ=
git.openprivacy.ca/openprivacy/log v1.0.2 h1:HLP4wsw4ljczFAelYnbObIs821z+jgMPCe8uODPnGQM=
git.openprivacy.ca/openprivacy/log v1.0.2/go.mod h1:gGYK8xHtndRLDymFtmjkG26GaMQNgyhioNS82m812Iw=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cretz/bine v0.1.1-0.20200124154328-f9f678b84cca/go.mod h1:6PF6fWAvYtwjRGkAuDEJeWNOv3a2hUouSP/yRYXmvHw=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gordonklaus/ineffassign v0.0.0-20200309095847-7953dde2c7bf/go.mod h1:cuNKsD1zp2v6XfE/orVX2QE1LC+i254ceGcVeDT3pTU=
github.com/gtank/merlin v0.1.1 h1:eQ90iG7K9pOhtereWsmyRJ6RAwcP4tHTDBHXNg+u5is=
github.com/gtank/merlin v0.1.1/go.mod h1:T86dnYJhcGOh5BjZFCJWTDeTK7XW8uE+E21Cy/bIQ+s=
github.com/gtank/ristretto255 v0.1.2 h1:JEqUCPA1NvLq5DwYtuzigd7ss8fwbYay9fi4/5uMzcc=
github.com/gtank/ristretto255 v0.1.2/go.mod h1:Ph5OpO6c7xKUGROZfWVLiJf9icMDwUeIvY4OmlYW69o=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/mimoo/StrobeGo v0.0.0-20181016162300-f8f6d4d2b643 h1:hLDRPB66XQT/8+wG9WsDpiCvZf1yKO7sz7scAjSlBa0=
github.com/mimoo/StrobeGo v0.0.0-20181016162300-f8f6d4d2b643/go.mod h1:43+3pMjjKimDBf5Kr4ZFNGbLql1zKkbImw+fZbw3geM=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/struCoder/pidusage v0.1.3/go.mod h1:pWBlW3YuSwRl6h7R5KbvA4N8oOqe9LjaKW5CwT1SPjI=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
go.etcd.io/bbolt v1.3.4 h1:hi1bXHMVrlQh6WwxAy+qZCV/SYIlqo+Ushwdpa4tAKg=
go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200204104054-c9f3fb736b72/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200206161412-a0c6ece9d31a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee h1:4yd7jl+vXjalO5ztz6Vc1VADv+S/80LGJmyl1ROJ2AI=
golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20201217150744-e6ae53a27f4f h1:kgfVkAEEQXXQ0qc6dH7n6y37NAYmTFmz0YRwrRjgxKw=
golang.org/x/mobile v0.0.0-20201217150744-e6ae53a27f4f/go.mod h1:skQtrUTUwhdJvXM/2KKJzY8pDgNr9I/FOMqDVRPBUS4=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb h1:mUVeFHoDKis5nxCAzoAi7E8Ghb86EXh/RK6wtvJIqRY=
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 h1:RqytpXGR1iVNX7psjB3ff8y7sNFinVFvkx1c8SjBkio=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200625195345-7480c7b4547d h1:V1BGE5ZHrUIYZYNEm0i7jrPwSo3ks0HSn1TrartSqME=
golang.org/x/tools v0.0.0-20200625195345-7480c7b4547d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

755
lib.go
View File

@ -1,4 +1,5 @@
//package cwtch
package main
import "C"
@ -6,9 +7,19 @@ import (
"crypto/rand"
"cwtch.im/cwtch/app"
"cwtch.im/cwtch/event"
"cwtch.im/cwtch/model"
"cwtch.im/cwtch/model/attr"
"cwtch.im/cwtch/peer"
"encoding/json"
"fmt"
"git.openprivacy.ca/flutter/libcwtch-go/constants"
contact "git.openprivacy.ca/flutter/libcwtch-go/features/contacts"
"git.openprivacy.ca/flutter/libcwtch-go/features/groups"
"git.openprivacy.ca/flutter/libcwtch-go/utils"
"git.openprivacy.ca/openprivacy/connectivity"
"runtime"
"strconv"
"strings"
"encoding/base64"
"git.openprivacy.ca/openprivacy/connectivity/tor"
@ -20,20 +31,80 @@ import (
"time"
)
const (
// ProfileOnion is an event field that contains the handle for a given profile.
// todo: this should probably be moved back into Cwtch, and renamed ProfileHandle (onions are too tor-specific)
ProfileOnion = event.Field("ProfileOnion")
)
var application app.Application
var eventHandler *utils.EventHandler
var acnQueue event.Queue
var contactEventsQueue event.Queue
var globalACN connectivity.ACN
//export HelloWorld
func HelloWorld(a C.int, b C.int) C.int {
return a+b
// ChatMessage API currently not officially documented, see
// https://git.openprivacy.ca/cwtch.im/secure-development-handbook/issues/3
// for latest updates for now
//
// A ChatMessage is the application-layer Cwtch message, delivered to the UI
// as serialized json.
type ChatMessage struct {
O int `json:"o"`
D string `json:"d"`
}
//export StartCwtch
func StartCwtch(dir_c *C.char, len C.int) {
//export c_StartCwtch
func c_StartCwtch(dir_c *C.char, len C.int, tor_c *C.char, torLen C.int) int8 {
dir := C.GoStringN(dir_c, len)
log.SetLevel(log.LevelDebug)
log.Infof("Loading Cwtch Directory %v", dir)
tor := C.GoStringN(tor_c, torLen)
return int8(StartCwtch(dir, tor))
}
// StartCwtch starts cwtch in the library and initlaizes all data structures
// GetAppbusEvents is always safe to use
// the rest of functions are unsafe until the CwtchStarted event has been received indicating StartCwtch has completed
// returns:
// 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 {
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" {
fh, err := os.Open(torPath)
if err != nil {
log.Errorf("%v", err)
log.Errorf("failed to stat tor, skipping StartCwtch(). potentially normal if the app was reinstalled or the device was restarted; this workorder should get canceled soon")
return 1
}
_ = fh.Close()
}
go _startCwtch(appDir, torPath)
return 0
}
func _startCwtch(appDir string, torPath string) {
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..
os.MkdirAll(path.Join(appDir), 0700)
utils.InitGlobalSettingsFile(appDir, constants.DefactoPasswordForUnencryptedProfiles)
log.Infof("Loading Cwtch Directory %v and tor path: %v", appDir, torPath)
mrand.Seed(int64(time.Now().Nanosecond()))
port := mrand.Intn(1000) + 9600
@ -46,124 +117,628 @@ func StartCwtch(dir_c *C.char, len C.int) {
panic(err)
}
log.Infof("making directory %v", dir)
os.MkdirAll(path.Join(dir, "/.tor","tor"),0700)
tor.NewTorrc().WithSocksPort(port).WithOnionTrafficOnly().WithControlPort(controlPort).WithHashedPassword(base64.StdEncoding.EncodeToString(key)).Build(filepath.Join(dir, ".tor", "tor", "torrc"))
acn, err := tor.NewTorACNWithAuth(path.Join(dir, "/.tor"), "", controlPort, tor.HashedPasswordAuthenticator{base64.StdEncoding.EncodeToString(key)})
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"))
acn, err := tor.NewTorACNWithAuth(path.Join(appDir, "/.tor"), torPath, controlPort, tor.HashedPasswordAuthenticator{Password: base64.StdEncoding.EncodeToString(key)})
if err != nil {
log.Errorf("\nError connecting to Tor: %v\n", err)
log.Errorf("\nError connecting to Tor replacing with ErrorACN: %v\n", err)
eventHandler.PublishAppEvent(event.NewEventList(utils.CwtchStartError, event.Error, err))
return
}
//acn.WaitTillBootstrapped()
newApp := app.NewApp(acn, dir)
id := mrand.Int31()
globalACN = acn
newApp := app.NewApp(acn, appDir)
acnQueue = event.NewQueue()
newApp.GetPrimaryBus().Subscribe(event.ACNStatus, acnQueue)
newApp.GetPrimaryBus().Subscribe(utils.UpdateGlobalSettings, acnQueue)
newApp.GetPrimaryBus().Subscribe(utils.SetLoggingLevel, acnQueue)
newApp.GetPrimaryBus().Subscribe(event.AppError, acnQueue)
eventHandler.HandleApp(newApp)
peer.DefaultEventsToHandle = []event.Type{
event.EncryptedGroupMessage,
event.NewMessageFromPeer,
event.PeerAcknowledgement,
event.NewGroupInvite,
event.PeerError,
event.SendMessageToPeerError,
event.SendMessageToGroupError,
event.NewGetValMessageFromPeer,
event.PeerStateChange,
event.NewRetValMessageFromPeer,
event.NewGroupInvite,
event.ServerStateChange,
event.ProtocolEngineStopped,
event.RetryServerRequest,
}
newApp.LoadProfiles("be gay do crime")
newApp.LaunchPeers()
settings := utils.ReadGlobalSettings()
settingsJson, _ := json.Marshal(settings)
newApp.LoadProfiles(constants.DefactoPasswordForUnencryptedProfiles)
application = newApp
log.Infof("Providing Handle %v", id)
// Send global settings to the UI...
application.GetPrimaryBus().Publish(event.NewEvent(utils.UpdateGlobalSettings, map[event.Field]string{event.Data: string(settingsJson)}))
log.Infof("libcwtch-go application launched")
application.GetPrimaryBus().Publish(event.NewEvent(utils.CwtchStarted, map[event.Field]string{}))
application.QueryACNVersion()
}
//export ACNEvents
func ACNEvents() *C.char {
select {
case myevent := <- acnQueue.OutChan():
return C.CString(fmt.Sprintf("%v", myevent))
default:
return C.CString("")
//export c_ReconnectCwtchForeground
func c_ReconnectCwtchForeground() {
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
}
// populate profile list
peerList := application.ListPeers()
for onion := range peerList {
eventHandler.Push(event.NewEvent(event.NewPeer, map[event.Field]string{event.Identity: onion, event.Created: event.False, "Reload": event.True}))
}
for onion := range peerList {
// fix peerpeercontact message counts
contactList := application.GetPeer(onion).GetContacts()
for _, handle := range contactList {
totalMessages := application.GetPeer(onion).GetContact(handle).Timeline.Len() + len(application.GetPeer(onion).GetContact(handle).UnacknowledgedMessages)
eventHandler.Push(event.NewEvent(event.MessageCounterResync, map[event.Field]string{
event.Identity: onion,
event.RemotePeer: handle,
event.Data: strconv.Itoa(totalMessages),
}))
}
// fix peergroupcontact message counts
groupList := application.GetPeer(onion).GetGroups()
for _, groupID := range groupList {
totalMessages := application.GetPeer(onion).GetGroup(groupID).Timeline.Len() + len(application.GetPeer(onion).GetGroup(groupID).UnacknowledgedMessages)
eventHandler.Push(event.NewEvent(event.MessageCounterResync, map[event.Field]string{
event.Identity: onion,
event.GroupID: groupID,
event.Data: strconv.Itoa(totalMessages),
}))
}
}
application.GetPrimaryBus().Publish(event.NewEvent(utils.CwtchStarted, map[event.Field]string{}))
application.QueryACNStatus()
application.QueryACNVersion()
}
//export c_SendAppEvent
// A generic method for Rebroadcasting App Events from a UI
func c_SendAppEvent(json_ptr *C.char, json_len C.int) {
eventJson := C.GoStringN(json_ptr, json_len)
SendAppEvent(eventJson)
}
// SendAppEvent is a generic method for Rebroadcasting App Events from a UI
func SendAppEvent(eventJson string) {
// Convert the Event Json back to a typed Event Struct, this will make the
// rest of the logic nicer.
var new_event event.Event
json.Unmarshal([]byte(eventJson), &new_event)
log.Infof("Event: %v", new_event)
// We need to update the local cache
// Ideally I think this would be pusgit hed back into Cwtch
switch new_event.EventType {
case utils.UpdateGlobalSettings:
var globalSettings utils.GlobalSettings
err := json.Unmarshal([]byte(new_event.Data[event.Data]), &globalSettings)
if err != nil {
log.Errorf("Error Unmarshalling Settings %v [%v]", err, new_event.Data[event.Data])
}
log.Debugf("New Settings %v", globalSettings)
utils.WriteGlobalSettings(globalSettings)
// Group Experiment Refresh
groupHandler, err := groups.ExperimentGate(utils.ReadGlobalSettings().Experiments)
if err == nil {
for profileOnion := range application.ListPeers() {
serverListForOnion := groupHandler.GetServerInfoList(application.GetPeer(profileOnion))
serversListBytes, _ := json.Marshal(serverListForOnion)
eventHandler.Push(event.NewEvent(groups.UpdateServerInfo, map[event.Field]string{"ProfileOnion": profileOnion, groups.ServerList: string(serversListBytes)}))
}
}
// Explicitly toggle blocking/unblocking of unknown connections for profiles
// that have been loaded.
if utils.ReadGlobalSettings().BlockUnknownConnections {
for onion := range application.ListPeers() {
application.GetPeer(onion).BlockUnknownConnections()
}
} else {
for onion := range application.ListPeers() {
application.GetPeer(onion).AllowUnknownConnections()
}
}
case utils.SetLoggingLevel:
_, warn := new_event.Data[utils.Warn]
_, error := new_event.Data[utils.Error]
_, debug := new_event.Data[utils.Debug]
_, info := new_event.Data[utils.Info]
// Assign logging level in priority order. The highest logging level wins in the
// event of multiple fields.
if info {
log.SetLevel(log.LevelInfo)
} else if warn {
log.SetLevel(log.LevelWarn)
} else if error {
log.SetLevel(log.LevelError)
} else if debug {
log.SetLevel(log.LevelDebug)
}
default: // do nothing
}
}
//export NextEvent
func NextEvent() {
}
//export GetProfiles
func GetProfiles() *C.char {
profiles := application.ListPeers()
jsonBytes,_ := json.Marshal(profiles)
return C.CString(string(jsonBytes))
}
type Contact struct {
Name string `json:"name"`
Onion string `json:"onion"`
Status string `json:"status"`
}
//export GetContacts
func GetContacts(onion_ptr *C.char, onion_len C.int) *C.char {
//export c_SendProfileEvent
// A generic method for Rebroadcasting Profile Events from a UI
func c_SendProfileEvent(onion_ptr *C.char, onion_len C.int, json_ptr *C.char, json_len C.int) {
onion := C.GoStringN(onion_ptr, onion_len)
log.Infof("Get Contacts for %v", onion)
mypeer := application.GetPeer(onion)
eventJson := C.GoStringN(json_ptr, json_len)
SendProfileEvent(onion, eventJson)
}
contactEventsQueue = event.NewQueue()
application.GetEventBus(onion).Subscribe(event.PeerStateChange, contactEventsQueue)
const (
AddContact = event.Type("AddContact")
ImportString = event.Field("ImportString")
)
var contacts []Contact
for _,contact := range mypeer.GetContacts() {
contactInfo := mypeer.GetContact(contact)
log.Infof("contactInfo %v", contactInfo)
contacts = append(contacts, Contact{Name: contactInfo.Name, Onion: contactInfo.Onion, Status: contactInfo.State})
// SendProfileEvent is a generic method for Rebroadcasting Profile Events from a UI
func SendProfileEvent(onion string, eventJson string) {
// Convert the Event Json back to a typed Event Struct, this will make the
// rest of the logic nicer.
var new_event event.Event
json.Unmarshal([]byte(eventJson), &new_event)
log.Infof("Event: %v %v", onion, new_event)
// Get the correct Peer
peer := application.GetPeer(onion)
if peer == nil {
return
}
bytes,_ := json.Marshal(contacts)
return C.CString(string(bytes))
}
// We need to update the local cache
// Ideally I think this would be pushed back into Cwtch
switch new_event.EventType {
case AddContact:
// Peer Functionality is Always Enabled, so we forgo the existence check...
// TODO: Combine with GroupFunctionality to make a meta-handleimportstring that can do both!
pf, _ := contact.FunctionalityGate(utils.ReadGlobalSettings().Experiments)
err := pf.HandleImportString(peer, new_event.Data[ImportString])
eventHandler.Push(event.NewEvent(event.AppError, map[event.Field]string{event.Data: err.Error()}))
case event.SetAttribute:
peer.SetAttribute(new_event.Data[event.Key], new_event.Data[event.Data])
case event.SetPeerAttribute:
peer.SetContactAttribute(new_event.Data[event.RemotePeer], new_event.Data[event.Key], new_event.Data[event.Data])
case event.SetPeerAuthorization:
peer.SetContactAuthorization(new_event.Data[event.RemotePeer], model.Authorization(new_event.Data[event.Authorization]))
//export SelectProfile
func SelectProfile(onion_ptr *C.char, onion_len C.int) *C.char {
onion := C.GoStringN(onion_ptr, onion_len)
log.Infof("Select Profile: %v", onion)
contactEventsQueue = event.NewQueue()
application.GetEventBus(onion).Subscribe(event.PeerStateChange, contactEventsQueue)
return C.CString("")
}
//export ContactEvents
func ContactEvents() *C.char {
select {
case myevent := <- contactEventsQueue.OutChan():
return C.CString(fmt.Sprintf("%v", myevent))
// If approved (e.g. after an unblock) we want to kick off peering again...
if model.Authorization(new_event.Data[event.Authorization]) == model.AuthApproved {
peer.PeerWithOnion(new_event.Data[event.RemotePeer])
}
default:
return C.CString("")
// rebroadcast catch all
log.Infof("Received Event %v for %v but no libCwtch handler found, relaying the event directly", new_event, onion)
application.GetEventBus(onion).Publish(new_event)
}
}
//export NumMessages
func NumMessages(profile_ptr *C.char, profile_len C.int, handle_ptr *C.char, handle_len C.int) (n C.int) {
//export c_GetAppBusEvent
func c_GetAppBusEvent() *C.char {
return C.CString(GetAppBusEvent())
}
// GetAppBusEvent blocks until an event
func GetAppBusEvent() string {
log.Debugf("appbusevent called")
for eventHandler == nil {
log.Debugf("waiting for eventHandler != nil")
time.Sleep(time.Second)
}
var json = ""
for json == "" {
log.Debugf("waiting for json != ''")
json = eventHandler.GetNextEvent()
}
log.Debugf("appbusevent: %v", json)
return json
}
type Profile struct {
Name string `json:"name"`
Onion string `json:"onion"`
ImagePath string `json:"imagePath"`
}
//export c_CreateProfile
func c_CreateProfile(nick_ptr *C.char, nick_len C.int, pass_ptr *C.char, pass_len C.int) {
CreateProfile(C.GoStringN(nick_ptr, nick_len), C.GoStringN(pass_ptr, pass_len))
}
func CreateProfile(nick, pass string) {
if pass == constants.DefactoPasswordForUnencryptedProfiles {
application.CreateTaggedPeer(nick, pass, constants.ProfileTypeV1DefaultPassword)
} else {
application.CreateTaggedPeer(nick, pass, constants.ProfileTypeV1Password)
}
}
//export c_LoadProfiles
func c_LoadProfiles(passwordPtr *C.char, passwordLen C.int) {
LoadProfiles(C.GoStringN(passwordPtr, passwordLen))
}
func LoadProfiles(pass string) {
application.LoadProfiles(pass)
}
//export c_ContactEvents
func c_ContactEvents() *C.char {
return C.CString(ContactEvents())
}
func ContactEvents() string {
select {
case myevent := <-contactEventsQueue.OutChan():
return fmt.Sprintf("%v", myevent)
default:
return ""
}
}
//export c_AcceptContact
func c_AcceptContact(profilePtr *C.char, profileLen C.int, handlePtr *C.char, handleLen C.int) {
AcceptContact(C.GoStringN(profilePtr, profileLen), C.GoStringN(handlePtr, handleLen))
}
// AcceptContact takes in a profileOnion and a handle to either a group or a peer and authorizes the handle
// for further action (e.g. messaging / connecting to the server / joining the group etc.)
func AcceptContact(profileOnion string, handle string) {
profile := application.GetPeer(profileOnion)
profileHandler := utils.NewPeerHelper(profile)
if profileHandler.IsGroup(handle) {
profile.AcceptInvite(handle)
} else {
err := profile.SetContactAuthorization(handle, model.AuthApproved)
if err == nil {
eventHandler.Push(event.NewEvent(event.PeerStateChange, map[event.Field]string{
ProfileOnion: profileOnion,
event.RemotePeer: handle,
"authorization": string(model.AuthApproved),
}))
} else {
log.Errorf("error accepting contact: %s", err.Error())
}
}
}
//export c_RejectInvite
func c_RejectInvite(profilePtr *C.char, profileLen C.int, handlePtr *C.char, handleLen C.int) {
RejectInvite(C.GoStringN(profilePtr, profileLen), C.GoStringN(handlePtr, handleLen))
}
// RejectInvite rejects a group invite
func RejectInvite(profileOnion string, handle string) {
log.Debugf("rejecting invite %v for %v", handle, profileOnion)
profile := application.GetPeer(profileOnion)
profileHandler := utils.NewPeerHelper(profile)
if profileHandler.IsGroup(handle) {
profile.RejectInvite(handle)
log.Debugf("successfully rejected invite %v for %v", handle, profileOnion)
}
}
//export c_BlockContact
func c_BlockContact(profilePtr *C.char, profileLen C.int, handlePtr *C.char, handleLen C.int) {
BlockContact(C.GoStringN(profilePtr, profileLen), C.GoStringN(handlePtr, handleLen))
}
func BlockContact(profile, handle string) {
err := application.GetPeer(profile).SetContactAuthorization(handle, model.AuthBlocked)
if err == nil {
eventHandler.Push(event.NewEvent(event.PeerStateChange, map[event.Field]string{
ProfileOnion: profile,
event.RemotePeer: handle,
"authorization": string(model.AuthBlocked),
}))
} else {
log.Errorf("error blocking contact: %s", err.Error())
}
}
//export c_UpdateMessageFlags
func c_UpdateMessageFlags(profile_ptr *C.char, profile_len C.int, handle_ptr *C.char, handle_len C.int, mIdx C.int, message_flags C.ulong) {
profile := C.GoStringN(profile_ptr, profile_len)
handle := C.GoStringN(handle_ptr, handle_len)
n = C.int(len(application.GetPeer(profile).GetContact(handle).Timeline.Messages))
log.Infof("NumMessagse(%s, %s) = %d", profile, handle, n)
return
UpdateMessageFlags(profile, handle, int(mIdx), int64(message_flags))
}
//export GetMessage
func GetMessage(profile_ptr *C.char, profile_len C.int, handle_ptr *C.char, handle_len C.int, message_index C.int) *C.char {
// UpdateMessageFlags sets the messages flags on a given message for a given profile.
// gomobile doesn't support uint64...so here we are....
func UpdateMessageFlags(profileOnion, handle string, mIdx int, flags int64) {
profile := application.GetPeer(profileOnion)
if profile != nil {
profile.UpdateMessageFlags(handle, mIdx, uint64(flags))
} else {
log.Errorf("called updatemessageflags with invalid profile onion")
}
}
//export c_GetMessage
func c_GetMessage(profile_ptr *C.char, profile_len C.int, handle_ptr *C.char, handle_len C.int, message_index C.int) *C.char {
profile := C.GoStringN(profile_ptr, profile_len)
handle := C.GoStringN(handle_ptr, handle_len)
message := application.GetPeer(profile).GetContact(handle).Timeline.Messages[message_index]
bytes,_ := json.Marshal(message)
log.Infof("GetMessage(%s, %s, %d) = %s", profile, handle, message_index, string(bytes))
return C.CString(string(bytes))
return C.CString(GetMessage(profile, handle, int(message_index)))
}
// EnhancedMessage wraps a Cwtch model.Message with some additional data to reduce calls from the UI.
type EnhancedMessage struct {
model.Message
ContactImage string
}
func GetMessage(profileOnion, handle string, message_index int) string {
profile := application.GetPeer(profileOnion)
ph := utils.NewPeerHelper(profile)
var message EnhancedMessage
if ph.IsGroup(handle) {
if len(profile.GetGroup(handle).Timeline.Messages) > message_index {
message.Message = profile.GetGroup(handle).Timeline.Messages[message_index]
message.ContactImage = ph.GetProfilePic(message.Message.PeerID)
} else {
// Message Index Request exceeded Timeline, most likely reason is this is a request for an
// unacknowledged sent message (it can take a many seconds for a message to be confirmed in the worst
// case).
offset := message_index - len(profile.GetGroup(handle).Timeline.Messages)
if len(profile.GetGroup(handle).UnacknowledgedMessages) > offset {
message.Message = profile.GetGroup(handle).UnacknowledgedMessages[offset]
message.ContactImage = ph.GetProfilePic(message.Message.PeerID)
} else {
log.Errorf("Couldn't find message in timeline or unacked messages, probably transient threading issue, but logging for visibility..")
}
}
} else {
if message_index < len(profile.GetContact(handle).Timeline.Messages) {
message.Message = profile.GetContact(handle).Timeline.Messages[message_index]
message.ContactImage = ph.GetProfilePic(handle)
} else {
log.Errorf("peerpeercontact getmessage out of range; sending counter resync just in case")
eventHandler.Push(event.NewEvent(event.MessageCounterResync, map[event.Field]string{
event.Identity: profileOnion,
event.RemotePeer: handle,
event.Data: strconv.Itoa(len(profile.GetContact(handle).Timeline.Messages)),
}))
}
}
bytes, _ := json.Marshal(message)
return string(bytes)
}
//export c_SendMessage
func c_SendMessage(profile_ptr *C.char, profile_len C.int, handle_ptr *C.char, handle_len C.int, msg_ptr *C.char, msg_len C.int) {
profile := C.GoStringN(profile_ptr, profile_len)
handle := C.GoStringN(handle_ptr, handle_len)
msg := C.GoStringN(msg_ptr, msg_len)
SendMessage(profile, handle, msg)
}
func SendMessage(profileOnion, handle, msg string) {
profile := application.GetPeer(profileOnion)
ph := utils.NewPeerHelper(profile)
if ph.IsGroup(handle) {
groupHandler, err := groups.ExperimentGate(utils.ReadGlobalSettings().Experiments)
if err == nil {
groupHandler.SendMessage(profile, handle, msg)
}
} else {
contactHandler, _ := contact.FunctionalityGate(utils.ReadGlobalSettings().Experiments)
contactHandler.SendMessage(profile, handle, msg)
}
}
//export c_SendInvitation
func c_SendInvitation(profile_ptr *C.char, profile_len C.int, handle_ptr *C.char, handle_len C.int, target_ptr *C.char, target_len C.int) {
profile := C.GoStringN(profile_ptr, profile_len)
handle := C.GoStringN(handle_ptr, handle_len)
target := C.GoStringN(target_ptr, target_len)
SendInvitation(profile, handle, target)
}
// Send an invitation from `profileOnion` to contact `handle` (peer or group)
// asking them to add the contact `target` (also peer or group).
// For groups, the profile must already have `target` as a contact.
func SendInvitation(profileOnion, handle, target string) {
profile := application.GetPeer(profileOnion)
ph := utils.NewPeerHelper(profile)
var invite ChatMessage
if ph.IsGroup(target) {
bundle, _ := profile.GetContact(profile.GetGroup(target).GroupServer).GetAttribute(string(model.BundleType))
inviteStr, err := profile.GetGroup(target).Invite()
if err == nil {
invite = ChatMessage{O: 101, D: fmt.Sprintf("tofubundle:server:%s||%s", base64.StdEncoding.EncodeToString([]byte(bundle)), inviteStr)}
}
} else {
invite = ChatMessage{O: 100, D: target}
}
inviteBytes, err := json.Marshal(invite)
if err != nil {
log.Errorf("malformed invite: %v", err)
} else {
SendMessage(profileOnion, handle, string(inviteBytes))
}
}
//export c_ResetTor
func c_ResetTor() {
ResetTor()
}
func ResetTor() {
globalACN.Restart()
}
//export c_CreateGroup
func c_CreateGroup(profile_ptr *C.char, profile_len C.int, server_ptr *C.char, server_len C.int, name_ptr *C.char, name_len C.int) {
profile := C.GoStringN(profile_ptr, profile_len)
server := C.GoStringN(server_ptr, server_len)
name := C.GoStringN(name_ptr, name_len)
CreateGroup(profile, server, name)
}
// CreateGroup takes in a profile and server in addition to a name and creates a new group.
func CreateGroup(profile string, server string, name string) {
peer := application.GetPeer(profile)
_, err := groups.ExperimentGate(utils.ReadGlobalSettings().Experiments)
if err == nil {
gid, _, err := peer.StartGroup(server)
if err == nil {
log.Debugf("created group %v on %v: $v", profile, server, gid)
// set the group name
peer.SetGroupAttribute(gid, attr.GetLocalScope("name"), name)
} else {
log.Errorf("error creating group or %v on server %v: %v", profile, server, err)
}
}
}
//export c_DeleteProfile
func c_DeleteProfile(profile_ptr *C.char, profile_len C.int, password_ptr *C.char, password_len C.int) {
profile := C.GoStringN(profile_ptr, profile_len)
password := C.GoStringN(password_ptr, password_len)
DeleteProfile(profile, password)
}
// DeleteProfile deletes a profile given the right password
func DeleteProfile(profile string, password string) {
// allow a blank password to delete "unencrypted" accounts...
if password == "" {
password = constants.DefactoPasswordForUnencryptedProfiles
}
application.DeletePeer(profile, password)
}
//export c_LeaveConversation
func c_LeaveConversation(profile_ptr *C.char, profile_len C.int, contact_ptr *C.char, contact_len C.int) {
profile := C.GoStringN(profile_ptr, profile_len)
contact := C.GoStringN(contact_ptr, contact_len)
LeaveConversation(profile, contact)
}
// LeaveConversation forces profile to leave the peer
func LeaveConversation(profile string, contact string) {
peer := application.GetPeer(profile)
peer.DeleteContact(contact)
}
//export c_LeaveGroup
func c_LeaveGroup(profile_ptr *C.char, profile_len C.int, group_ptr *C.char, group_len C.int) {
profile := C.GoStringN(profile_ptr, profile_len)
groupID := C.GoStringN(group_ptr, group_len)
LeaveGroup(profile, groupID)
}
// LeaveGroup forces profile to leave the group groupID
func LeaveGroup(profile string, groupID string) {
peer := application.GetPeer(profile)
_, err := groups.ExperimentGate(utils.ReadGlobalSettings().Experiments)
if err == nil {
peer.DeleteGroup(groupID)
}
}
//export c_ImportBundle
func c_ImportBundle(profile_ptr *C.char, profile_len C.int, bundle_ptr *C.char, bundle_len C.int) {
profile := C.GoStringN(profile_ptr, profile_len)
name := C.GoStringN(bundle_ptr, bundle_len)
ImportBundle(profile, name)
}
// ImportBundle takes in a handle to a profile and an invite string which could have one of many
// different formats (e.g. a peer address, a group invite, a server key bundle, or a combination)
func ImportBundle(profileOnion string, bundle string) {
profile := application.GetPeer(profileOnion)
peerHandler, _ := contact.FunctionalityGate(utils.ReadGlobalSettings().Experiments)
response := peerHandler.HandleImportString(profile, bundle)
if strings.Contains(response.Error(), "invalid_import_string") {
groupHandler, err := groups.ExperimentGate(utils.ReadGlobalSettings().Experiments)
if err == nil {
response = groupHandler.HandleImportString(profile, bundle)
eventHandler.Push(event.NewEvent(event.AppError, map[event.Field]string{event.Data: response.Error()}))
// We might have added a new server, so refresh the server list...
serverListForOnion := groupHandler.GetServerInfoList(profile)
serversListBytes, _ := json.Marshal(serverListForOnion)
eventHandler.Push(event.NewEvent(groups.UpdateServerInfo, map[event.Field]string{"ProfileOnion": profileOnion, groups.ServerList: string(serversListBytes)}))
return
}
}
eventHandler.Push(event.NewEvent(event.AppError, map[event.Field]string{event.Data: response.Error()}))
}
//export c_SetGroupAttribute
func c_SetGroupAttribute(profile_ptr *C.char, profile_len C.int, group_ptr *C.char, group_len C.int, key_ptr *C.char, key_len C.int, val_ptr *C.char, val_len C.int) {
profileOnion := C.GoStringN(profile_ptr, profile_len)
groupHandle := C.GoStringN(group_ptr, group_len)
key := C.GoStringN(key_ptr, key_len)
value := C.GoStringN(val_ptr, val_len)
SetGroupAttribute(profileOnion, groupHandle, key, value)
}
// SetGroupAttribute provides a wrapper around profile.SetGroupAttribute, gated by global experiments...
func SetGroupAttribute(profileOnion string, groupHandle string, key string, value string) {
profile := application.GetPeer(profileOnion)
_, err := groups.ExperimentGate(utils.ReadGlobalSettings().Experiments)
if err == nil {
profile.SetGroupAttribute(groupHandle, key, value)
}
}
//export c_ShutdownCwtch
func c_ShutdownCwtch() {
ShutdownCwtch()
}
// ShutdownCwtch is a safe way to shutdown any active cwtch applications and associated ACNs
func ShutdownCwtch() {
if application != nil && globalACN != nil {
// Kill the isolate
eventHandler.Push(event.NewEvent(event.Shutdown, map[event.Field]string{}))
// Allow for the shutdown events to go through and then purge everything else...
log.Infof("Shutting Down Application...")
application.Shutdown()
log.Infof("Shutting Down ACN...")
globalACN.Close()
log.Infof("Library Shutdown Complete!")
// do not remove - important for state checks elsewhere
application = nil
globalACN = nil
eventHandler = nil
}
}
// Leave as is, needed by ffi
func main() {}
func main() {}

24
quality.sh Executable file
View File

@ -0,0 +1,24 @@
#!/bin/sh
echo "Checking code quality (you want to see no output here)"
echo ""
echo "Vetting:"
go list ./... | xargs go vet
echo ""
echo "Linting:"
go list ./... | xargs golint
echo "Time to format"
gofmt -l -s -w .
# ineffassign (https://github.com/gordonklaus/ineffassign)
echo "Checking for ineffectual assignment of errors (unchecked errors...)"
ineffassign .
# misspell (https://github.com/client9/misspell/cmd/misspell)
echo "Checking for misspelled words..."
misspell . | grep -v "vendor/" | grep -v "go.sum" | grep -v ".idea"

15
utils/contacts.go Normal file
View File

@ -0,0 +1,15 @@
package utils
type Contact struct {
Name string `json:"name"`
Onion string `json:"onion"`
Status string `json:"status"`
Picture string `json:"picture"`
Authorization string `json:"authorization"`
SaveHistory string `json:"saveConversationHistory"`
Messages int `json:"numMessages"`
Unread int `json:"numUnread"`
LastMessage string `json:"lastMsgTime"`
IsGroup bool `json:"isGroup"`
GroupServer string `json:"groupServer"`
}

379
utils/eventHandler.go Normal file
View File

@ -0,0 +1,379 @@
package utils
import (
"cwtch.im/cwtch/app"
"cwtch.im/cwtch/app/plugins"
"cwtch.im/cwtch/model"
"cwtch.im/cwtch/model/attr"
"cwtch.im/cwtch/protocol/connections"
"encoding/json"
"git.openprivacy.ca/flutter/libcwtch-go/constants"
"git.openprivacy.ca/flutter/libcwtch-go/features/groups"
"git.openprivacy.ca/openprivacy/log"
"strconv"
)
import "cwtch.im/cwtch/event"
type EventProfileEnvelope struct {
Event event.Event
Profile string
}
type EventHandler struct {
app app.Application
appBusQueue event.Queue
profileEvents chan EventProfileEnvelope
}
func NewEventHandler() *EventHandler {
eh := &EventHandler{app: nil, appBusQueue: event.NewQueue(), profileEvents: make(chan EventProfileEnvelope)}
return eh
}
// PublishAppEvent is a way for libCwtch-go to publish an event for consumption by a UI before a Cwtch app has been initialized
// Main use: to signal an error before a cwtch app could be created
func (eh *EventHandler) PublishAppEvent(event event.Event) {
eh.appBusQueue.Publish(event)
}
func (eh *EventHandler) HandleApp(application app.Application) {
eh.app = application
application.GetPrimaryBus().Subscribe(event.NewPeer, eh.appBusQueue)
application.GetPrimaryBus().Subscribe(event.PeerError, eh.appBusQueue)
application.GetPrimaryBus().Subscribe(event.PeerDeleted, eh.appBusQueue)
application.GetPrimaryBus().Subscribe(event.Shutdown, eh.appBusQueue)
application.GetPrimaryBus().Subscribe(event.AppError, eh.appBusQueue)
application.GetPrimaryBus().Subscribe(event.ACNStatus, eh.appBusQueue)
application.GetPrimaryBus().Subscribe(event.ReloadDone, eh.appBusQueue)
application.GetPrimaryBus().Subscribe(event.ACNVersion, eh.appBusQueue)
application.GetPrimaryBus().Subscribe(UpdateGlobalSettings, eh.appBusQueue)
application.GetPrimaryBus().Subscribe(CwtchStarted, eh.appBusQueue)
}
func (eh *EventHandler) GetNextEvent() string {
appChan := eh.appBusQueue.OutChan()
select {
case e := <-appChan:
return eh.handleAppBusEvent(&e)
case ev := <-eh.profileEvents:
return eh.handleProfileEvent(&ev)
}
}
// 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)
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)
if e.Data["Reload"] != event.True {
eh.startHandlingPeer(onion)
}
tag,isTagged := profile.GetAttribute(app.AttributeTag)
if isTagged {
e.Data[app.AttributeTag] = tag
} else {
// Assume encrypted for non-tagged profiles - this isn't always true, but all post-beta profiles
// are tagged on creation.
e.Data[app.AttributeTag] = constants.ProfileTypeV1Password
}
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))
}
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.Debugf("contactsJson %v", e.Data["ContactsJson"])
}
}
json, _ := json.Marshal(e)
return string(json)
}
// 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 {
/*
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
return ""
}
}
}
}
json, _ := json.Marshal(unwrap(ev))
return string(json)
}
func unwrap(original *EventProfileEnvelope) *event.Event {
unwrapped := &original.Event
unwrapped.Data["ProfileOnion"] = original.Profile
return unwrapped
}
func (eh *EventHandler) startHandlingPeer(onion string) {
eventBus := eh.app.GetEventBus(onion)
q := event.NewQueue()
eventBus.Subscribe(event.NewMessageFromPeer, q)
eventBus.Subscribe(event.PeerAcknowledgement, q)
eventBus.Subscribe(event.DeleteContact, q)
eventBus.Subscribe(event.AppError, q)
eventBus.Subscribe(event.IndexedAcknowledgement, q)
eventBus.Subscribe(event.IndexedFailure, q)
eventBus.Subscribe(event.NewMessageFromGroup, q)
eventBus.Subscribe(event.GroupCreated, q)
eventBus.Subscribe(event.NewGroup, q)
eventBus.Subscribe(event.AcceptGroupInvite, q)
eventBus.Subscribe(event.SetGroupAttribute, q)
eventBus.Subscribe(event.DeleteGroup, q)
eventBus.Subscribe(event.SendMessageToGroupError, q)
eventBus.Subscribe(event.SendMessageToPeerError, q)
eventBus.Subscribe(event.ServerStateChange, q)
eventBus.Subscribe(event.PeerStateChange, q)
eventBus.Subscribe(event.PeerCreated, q)
eventBus.Subscribe(event.NetworkStatus, q)
eventBus.Subscribe(event.ChangePasswordSuccess, q)
eventBus.Subscribe(event.ChangePasswordError, q)
eventBus.Subscribe(event.NewRetValMessageFromPeer, q)
eventBus.Subscribe(event.SetAttribute, q)
go eh.forwardProfileMessages(onion, q)
}
func (eh *EventHandler) forwardProfileMessages(onion string, q event.Queue) {
log.Infof("Launching Forwarding Goroutine for %v", onion)
// TODO: graceful shutdown, via an injected event of special QUIT type exiting loop/go routine
for {
e := q.Next()
ev := EventProfileEnvelope{Event: e, Profile: onion}
eh.profileEvents <- ev
if ev.Event.EventType == event.Shutdown {
return
}
}
}
func (eh *EventHandler) Push(newEvent event.Event) {
eh.appBusQueue.Publish(newEvent)
}

34
utils/imageType.go Normal file
View File

@ -0,0 +1,34 @@
package utils
import "encoding/json"
// Image types we support
const (
// TypeImageDistro is a reletive path to any of the distributed images in cwtch/ui in the assets folder
TypeImageDistro = "distro"
// TypeImageComposition will be an face image composed of a recipe of parts like faceType, eyeType, etc
TypeImageComposition = "composition"
)
type image struct {
Val string
T string
}
func NewImage(val, t string) *image {
return &image{val, t}
}
func StringToImage(str string) (*image, error) {
var img image
err := json.Unmarshal([]byte(str), &img)
if err != nil {
return nil, err
}
return &img, nil
}
func ImageToString(img *image) string {
bytes, _ := json.Marshal(img)
return string(bytes)
}

18
utils/logging.go Normal file
View File

@ -0,0 +1,18 @@
package utils
import "cwtch.im/cwtch/event"
// An event to set the logging level dynamically from the UI
const (
SetLoggingLevel = event.Type("SetLoggingLevel")
)
// Logging Levels as Event Fields. Note: Unlike most event we don't cae about
// the *value* of the field, only the presence. If more than one of these fields is
// present in a single SetLoggingLevel event then the highest logging level is used. INFO < WARN < ERROR < DEBUG
const (
Warn = event.Field("Warn")
Error = event.Field("Error")
Debug = event.Field("Debug")
Info = event.Field("Info")
)

369
utils/manager.go Normal file
View File

@ -0,0 +1,369 @@
package utils
import (
"cwtch.im/cwtch/model"
"cwtch.im/cwtch/model/attr"
"cwtch.im/cwtch/peer"
"cwtch.im/cwtch/protocol/connections"
"errors"
"git.openprivacy.ca/flutter/libcwtch-go/constants"
"git.openprivacy.ca/openprivacy/log"
"strconv"
"strings"
"time"
)
type PeerHelper struct {
peer peer.CwtchPeer
}
func NewPeerHelper(profile peer.CwtchPeer) *PeerHelper {
return &PeerHelper{profile}
}
func (p *PeerHelper) IsGroup(id string) bool {
return len(id) == 32 && !p.IsServer(id)
}
func (p *PeerHelper) IsPeer(id string) bool {
return len(id) == 56 && !p.IsServer(id)
}
// Check if the id is associated with a contact with a KeyTypeServerOnion attribute (which indicates that this
// is a server, not a regular contact or a group
func (p *PeerHelper) IsServer(id string) bool {
_, ok := p.peer.GetContactAttribute(id, string(model.KeyTypeServerOnion))
return ok
}
/*
func getOrDefault(id, key string, defaultVal string) string {
var val string
var ok bool
if IsGroup(id) {
val, ok = the.Peer.GetGroupAttribute(id, key)
} else {
val, ok = the.Peer.GetContactAttribute(id, key)
}
if ok {
return val
} else {
return defaultVal
}
}*/
func (p *PeerHelper) GetWithSetDefault(id string, key string, defaultVal string) string {
var val string
var ok bool
if p.IsGroup(id) {
val, ok = p.peer.GetGroupAttribute(id, key)
} else {
val, ok = p.peer.GetContactAttribute(id, key)
}
if !ok {
val = defaultVal
if p.IsGroup(id) {
p.peer.SetGroupAttribute(id, key, defaultVal)
} else {
p.peer.SetContactAttribute(id, key, defaultVal)
}
}
return val
}
func (p *PeerHelper) GetNick(id string) string {
if p.IsGroup(id) {
nick, exists := p.peer.GetGroupAttribute(id, attr.GetLocalScope(constants.Name))
if !exists || nick == "" || nick == id {
nick, exists = p.peer.GetGroupAttribute(id, attr.GetPeerScope(constants.Name))
if !exists {
nick = "[" + id + "]"
}
}
return nick
} else {
nick, exists := p.peer.GetContactAttribute(id, attr.GetLocalScope(constants.Name))
if !exists || nick == "" || nick == id {
nick, exists = p.peer.GetContactAttribute(id, attr.GetPeerScope(constants.Name))
if !exists {
nick = "[" + id + "]"
// re-request
p.peer.SendGetValToPeer(id, attr.PublicScope, constants.Name)
}
}
return nick
}
}
// InitLastReadTime checks and gets the Attributable's LastRead time or sets it to now
func (p *PeerHelper) InitLastReadTime(id string) time.Time {
nowStr, _ := time.Now().MarshalText()
lastReadAttr := p.GetWithSetDefault(id, attr.GetLocalScope(constants.LastRead), string(nowStr))
var lastRead time.Time
lastRead.UnmarshalText([]byte(lastReadAttr))
return lastRead
}
// GetProfilePic returns a string path to an image to display for hte given peer/group id
func (p *PeerHelper) GetProfilePic(id string) string {
if p.IsGroup(id) {
if picVal, exists := p.peer.GetGroupAttribute(id, attr.GetLocalScope(constants.Picture)); exists {
pic, err := StringToImage(picVal)
if err == nil {
return GetPicturePath(pic)
}
}
if picVal, exists := p.peer.GetGroupAttribute(id, attr.GetPeerScope(constants.Picture)); exists {
pic, err := StringToImage(picVal)
if err == nil {
return GetPicturePath(pic)
}
}
return GetPicturePath(NewImage(RandomGroupImage(id), TypeImageDistro))
} else {
if picVal, exists := p.peer.GetContactAttribute(id, attr.GetLocalScope(constants.Picture)); exists {
pic, err := StringToImage(picVal)
if err == nil {
return GetPicturePath(pic)
}
}
if picVal, exists := p.peer.GetContactAttribute(id, attr.GetPeerScope(constants.Picture)); exists {
pic, err := StringToImage(picVal)
if err == nil {
return GetPicturePath(pic)
}
}
return RandomProfileImage(id)
}
}
// a lot of pics were stored full path + uri. remove all this to the relative path in images/
// fix for storing full paths introduced 2019.12
func profilePicRelativize(filename string) string {
parts := strings.Split(filename, "qml/images")
return parts[len(parts)-1]
}
func GetPicturePath(pic *image) string {
switch pic.T {
case TypeImageDistro:
return profilePicRelativize(pic.Val)
default:
log.Errorf("Unhandled profile picture type of %v\n", pic.T)
return ""
}
}
func (p *PeerHelper) CountUnread(messages []model.Message, lastRead time.Time) int {
count := 0
for i := len(messages) - 1; i >= 0; i-- {
if messages[i].Timestamp.After(lastRead) || messages[i].Timestamp.Equal(lastRead) {
count++
} else {
break
}
}
return count
}
func getLastMessageTime(tl *model.Timeline) int {
if len(tl.Messages) == 0 {
return 0
}
return int(tl.Messages[len(tl.Messages)-1].Timestamp.Unix())
}
/*
// AddProfile adds a new profile to the UI
func AddProfile(gcd *GrandCentralDispatcher, handle string) {
p := the.CwtchApp.GetPeer(handle)
if p != nil {
nick, exists := p.GetAttribute(attr.GetPublicScope(constants.Name))
if !exists {
nick = handle
}
picVal, ok := p.GetAttribute(attr.GetPublicScope(constants.Picture))
if !ok {
picVal = ImageToString(NewImage(RandomProfileImage(handle), TypeImageDistro))
}
pic, err := StringToImage(picVal)
if err != nil {
pic = NewImage(RandomProfileImage(handle), TypeImageDistro)
}
picPath := getPicturePath(pic)
tag, _ := p.GetAttribute(app.AttributeTag)
online, _ := p.GetAttribute(attr.GetLocalScope(constants.PeerOnline))
log.Debugf("AddProfile %v %v %v %v %v\n", handle, nick, picPath, tag, online)
gcd.AddProfile(handle, nick, picPath, tag, online == event.True)
}
}*/
/*
type manager struct {
gcd *GrandCentralDispatcher
profile string
}
// Manager is a middleware helper for entities like peer event listeners wishing to trigger ui changes (via the gcd)
// each manager is for one profile/peer
// manager takes minimal arguments and builds the full struct of data (usually pulled from a cwtch peer) required to call the GCD to perform the ui action
// manager also performs call filtering based on UI state: users of manager can safely always call it on events and not have to worry about weather the relevant ui is active
// ie: you can always safely call AddMessage even if in the ui a different profile is selected. manager will check with gcd, and if the correct conditions are not met, it will not call on gcd to update the ui incorrectly
type Manager interface {
Acknowledge(handle, mID string)
AddContact(Handle string)
AddSendMessageError(peer string, signature string, err string)
AddMessage(handle string, from string, message string, fromMe bool, messageID string, timestamp time.Time, Acknowledged bool)
ReloadProfiles()
UpdateContactDisplayName(handle string)
UpdateContactPicture(handle string)
UpdateContactStatus(handle string, status int, loading bool)
UpdateContactAttribute(handle, key, value string)
ChangePasswordResponse(error bool)
AboutToAddMessage()
MessageJustAdded()
StoreAndNotify(peer.CwtchPeer, string, string, time.Time, string)
UpdateNetworkStatus(online bool)
}
// NewManager returns a new Manager interface for a profile to the gcd
func NewManager(profile string, gcd *GrandCentralDispatcher) Manager {
return &manager{gcd: gcd, profile: profile}
}
*/
// EnrichNewPeer populates required data for use by frontend
// uiManager.AddContact(onion)
// (handle string, displayName string, image string, badge int, status int, authorization string, loading bool, lastMsgTime int)
func EnrichNewPeer(handle string, ph *PeerHelper, ev *EventProfileEnvelope) error {
log.Infof("Enriching New Peer %v", handle)
if ph.IsGroup(handle) {
group := ph.peer.GetGroup(handle)
if group != nil {
lastRead := ph.InitLastReadTime(group.GroupID)
ev.Event.Data["unread"] = strconv.Itoa(ph.CountUnread(group.Timeline.GetMessages(), lastRead))
ev.Event.Data["picture"] = ph.GetProfilePic(handle)
ev.Event.Data["nick"] = ph.GetNick(handle)
ev.Event.Data["status"] = strconv.Itoa(int(connections.ConnectionStateToType()[group.State]))
ev.Event.Data["authorization"] = string(model.AuthApproved)
ev.Event.Data["loading"] = "false"
ev.Event.Data["lastMsgTime"] = strconv.Itoa(getLastMessageTime(&group.Timeline))
}
} else if ph.IsPeer(handle) {
contact := ph.peer.GetContact(handle)
if contact != nil {
lastRead := ph.InitLastReadTime(contact.Onion)
ev.Event.Data["unread"] = strconv.Itoa(ph.CountUnread(contact.Timeline.GetMessages(), lastRead))
ev.Event.Data["numMessages"] = strconv.Itoa(contact.Timeline.Len())
ev.Event.Data["picture"] = ph.GetProfilePic(handle)
ev.Event.Data["nick"] = ph.GetNick(handle)
// TODO Replace this if with a better flow that separates New Contacts and Peering Updates
if contact.State == "" {
// Will be disconnected to start
ev.Event.Data["status"] = connections.ConnectionStateName[connections.DISCONNECTED]
} else {
ev.Event.Data["status"] = contact.State
}
ev.Event.Data["authorization"] = string(contact.Authorization)
ev.Event.Data["loading"] = "false"
ev.Event.Data["lastMsgTime"] = strconv.Itoa(getLastMessageTime(&contact.Timeline))
} else {
log.Errorf("Failed to find contact: %v", handle)
}
} else {
// could be a server?
log.Debugf("sorry, unable to handle AddContact(%v)", handle)
return errors.New("not a peer or group")
}
return nil
}
/*
// AddSendMessageError adds an error not and icon to a message in a conversation in the ui for the message identified by the peer/sig combo
func (this *manager) AddSendMessageError(peer string, signature string, err string) {
this.gcd.DoIfProfile(this.profile, func() {
this.gcd.DoIfConversation(peer, func() {
log.Debugf("Received Error Sending Message: %v", err)
// FIXME: Sometimes, for the first Peer message we send our error beats our message to the UI
time.Sleep(time.Second * 1)
this.gcd.GroupSendError(signature, err)
})
})
}
func (this *manager) AboutToAddMessage() {
this.gcd.TimelineInterface.AddMessage(this.gcd.TimelineInterface.num())
}
func (this *manager) MessageJustAdded() {
this.gcd.TimelineInterface.RequestEIR()
}*/
/*
// AddMessage adds a message to the message pane for the supplied conversation if it is active
func (this *manager) AddMessage(handle string, from string, message string, fromMe bool, messageID string, timestamp time.Time, Acknowledged bool) {
this.gcd.DoIfProfile(this.profile, func() {
this.gcd.DoIfConversation(handle, func() {
updateLastReadTime(handle)
// If the message is not from the user then add it, otherwise, just acknowledge.
if !fromMe || !Acknowledged {
this.gcd.TimelineInterface.AddMessage(this.gcd.TimelineInterface.num() - 1)
this.gcd.TimelineInterface.RequestEIR()
} else {
this.gcd.Acknowledged(messageID)
}
})
this.gcd.IncContactUnreadCount(handle)
})
if !fromMe {
this.gcd.Notify(handle)
}
}
func (this *manager) ReloadProfiles() {
this.gcd.reloadProfileList()
}
// UpdateContactDisplayName updates a contact's display name in the contact list and conversations
func (this *manager) UpdateContactDisplayName(handle string) {
this.gcd.DoIfProfile(this.profile, func() {
this.gcd.UpdateContactDisplayName(handle, GetNick(handle))
})
}
// UpdateContactPicture updates a contact's picture in the contact list and conversations
func (this *manager) UpdateContactPicture(handle string) {
this.gcd.DoIfProfile(this.profile, func() {
this.gcd.UpdateContactPicture(handle, GetProfilePic(handle))
})
}
// UpdateContactAttribute update's a contacts attribute in the ui
func (this *manager) UpdateContactAttribute(handle, key, value string) {
this.gcd.DoIfProfile(this.profile, func() {
this.gcd.UpdateContactAttribute(handle, key, value)
})
}
func (this *manager) ChangePasswordResponse(error bool) {
this.gcd.ChangePasswordResponse(error)
}
func (this *manager) UpdateNetworkStatus(online bool) {
this.gcd.UpdateProfileNetworkStatus(this.profile, online)
}
*/

110
utils/settings.go Normal file
View File

@ -0,0 +1,110 @@
package utils
import (
"cwtch.im/cwtch/event"
"cwtch.im/cwtch/storage/v1"
"encoding/json"
"git.openprivacy.ca/openprivacy/log"
"io/ioutil"
"os"
"path"
)
const (
CwtchStarted = event.Type("CwtchStarted")
CwtchStartError = event.Type("CwtchStartError")
UpdateGlobalSettings = event.Type("UpdateGlobalSettings")
)
var GlobalSettingsFile v1.FileStore
const GlobalSettingsFilename = "ui.globals"
const saltFile = "SALT"
type GlobalSettings struct {
Locale string
Theme string
PreviousPid int64
ExperimentsEnabled bool
Experiments map[string]bool
BlockUnknownConnections bool
StateRootPane int
FirstTime bool
UIColumnModePortrait string
UIColumnModeLandscape string
}
var DefaultGlobalSettings = GlobalSettings{
Locale: "en",
Theme: "dark",
PreviousPid: -1,
ExperimentsEnabled: false,
Experiments: make(map[string]bool),
StateRootPane: 0,
FirstTime: true,
BlockUnknownConnections: false,
UIColumnModePortrait: "DualpaneMode.Single",
UIColumnModeLandscape: "DualpaneMode.CopyPortrait",
}
func InitGlobalSettingsFile(directory string, password string) error {
var key [32]byte
salt, err := ioutil.ReadFile(path.Join(directory, saltFile))
if err != nil {
log.Infof("Could not find salt file: %v (creating a new settings file)", err)
var newSalt [128]byte
key, newSalt, err = v1.CreateKeySalt(password)
if err != nil {
log.Errorf("Could not initialize salt: %v", err)
return err
}
os.Mkdir(directory, 0700)
err := ioutil.WriteFile(path.Join(directory, saltFile), newSalt[:], 0600)
if err != nil {
log.Errorf("Could not write salt file: %v", err)
return err
}
} else {
key = v1.CreateKey(password, salt)
}
GlobalSettingsFile = v1.NewFileStore(directory, GlobalSettingsFilename, key)
log.Infof("initialized global settings file: %v", GlobalSettingsFile)
return nil
}
func ReadGlobalSettings() *GlobalSettings {
settings := DefaultGlobalSettings
if GlobalSettingsFile == nil {
log.Errorf("Global Settings File was not Initialized Properly")
return &settings
}
settingsBytes, err := GlobalSettingsFile.Read()
if err != nil {
log.Infof("Could not read global ui settings: %v (assuming this is a first time app deployment...)", err)
return &settings //firstTime = true
}
err = json.Unmarshal(settingsBytes, &settings)
if err != nil {
log.Errorf("Could not parse global ui settings: %v\n", err)
// TODO if settings is corrupted, we probably want to alert the UI.
return &settings //firstTime = true
}
log.Debugf("Settings: %#v", settings)
return &settings
}
func WriteGlobalSettings(globalSettings GlobalSettings) {
bytes, _ := json.Marshal(globalSettings)
// override first time setting
globalSettings.FirstTime = true
err := GlobalSettingsFile.Write(bytes)
if err != nil {
log.Errorf("Could not write global ui settings: %v\n", err)
}
}

29
utils/utils.go Normal file
View File

@ -0,0 +1,29 @@
package utils
import (
"encoding/base32"
"encoding/hex"
"git.openprivacy.ca/openprivacy/log"
"strings"
)
// temporary until we do real picture selection
func RandomProfileImage(onion string) string {
choices := []string{"001-centaur", "002-kraken", "003-dinosaur", "004-tree-1", "005-hand", "006-echidna", "007-robot", "008-mushroom", "009-harpy", "010-phoenix", "011-dragon-1", "012-devil", "013-troll", "014-alien", "015-minotaur", "016-madre-monte", "017-satyr", "018-karakasakozou", "019-pirate", "020-werewolf", "021-scarecrow", "022-valkyrie", "023-curupira", "024-loch-ness-monster", "025-tree", "026-cerberus", "027-gryphon", "028-mermaid", "029-vampire", "030-goblin", "031-yeti", "032-leprechaun", "033-medusa", "034-chimera", "035-elf", "036-hydra", "037-cyclops", "038-pegasus", "039-narwhal", "040-woodcutter", "041-zombie", "042-dragon", "043-frankenstein", "044-witch", "045-fairy", "046-genie", "047-pinocchio", "048-ghost", "049-wizard", "050-unicorn"}
barr, err := base32.StdEncoding.DecodeString(strings.ToUpper(onion))
if err != nil || len(barr) != 35 {
log.Errorf("error: %v %v %v\n", onion, err, barr)
return "extra/openprivacy.png"
}
return "profiles/" + choices[int(barr[33])%len(choices)] + ".png"
}
func RandomGroupImage(handle string) string {
choices := []string{"001-borobudur", "002-opera-house", "003-burj-al-arab", "004-chrysler", "005-acropolis", "006-empire-state-building", "007-temple", "008-indonesia-1", "009-new-zealand", "010-notre-dame", "011-space-needle", "012-seoul", "013-mosque", "014-milan", "015-statue", "016-pyramid", "017-cologne", "018-brandenburg-gate", "019-berlin-cathedral", "020-hungarian-parliament", "021-buckingham", "022-thailand", "023-independence", "024-angkor-wat", "025-vaticano", "026-christ-the-redeemer", "027-colosseum", "028-golden-gate-bridge", "029-sphinx", "030-statue-of-liberty", "031-cradle-of-humankind", "032-istanbul", "033-london-eye", "034-sagrada-familia", "035-tower-bridge", "036-burj-khalifa", "037-washington", "038-big-ben", "039-stonehenge", "040-white-house", "041-ahu-tongariki", "042-capitol", "043-eiffel-tower", "044-church-of-the-savior-on-spilled-blood", "045-arc-de-triomphe", "046-windmill", "047-louvre", "048-torii-gate", "049-petronas", "050-matsumoto-castle", "051-fuji", "052-temple-of-heaven", "053-pagoda", "054-chichen-itza", "055-forbidden-city", "056-merlion", "057-great-wall-of-china", "058-taj-mahal", "059-pisa", "060-indonesia"}
barr, err := hex.DecodeString(handle)
if err != nil || len(barr) == 0 {
log.Errorf("error: %v %v %v\n", handle, err, barr)
return "extra/openprivacy.png"
}
return "servers/" + choices[int(barr[0])%len(choices)] + ".png"
}