Compare commits

..

254 Commits

Author SHA1 Message Date
Michael Kirk
5c3b30104a Merge branch 'mkirk/moving-repo' 2017-07-21 15:53:34 -04:00
Michael Kirk
1499840de5 update README and podspec to direct upgrading users to proper source
// FREEBIE
2017-07-21 15:09:14 -04:00
Matthew Chen
d98c07ecff Merge branch 'charlesmchen/countryNameVsiOS8' 2017-07-20 09:14:49 -04:00
Matthew Chen
493aaca242 Avoid nil country names on iOS 8.
// FREEBIE
2017-07-19 14:21:54 -04:00
Matthew Chen
e20d63240b Merge branch 'mkirk/cleanup-with-transaction' 2017-07-14 09:26:35 -04:00
Michael Kirk
faeb7100b9 use existing transaction in cleanup
// FREEBIE
2017-07-14 09:26:28 -04:00
Matthew Chen
fe0f01daec Merge branch 'charlesmchen/pasteVoiceMessages' 2017-07-13 17:22:18 -04:00
Matthew Chen
c255504959 Fix copy and paste of voice messages.
// FREEBIE
2017-07-13 14:31:12 -04:00
Matthew Chen
640ec13b2e Merge branch 'charlesmchen/orphanCleanup' 2017-07-12 12:13:14 -04:00
Matthew Chen
6fa3fac4ae Fix broken tests.
// FREEBIE
2017-07-12 12:12:59 -04:00
Matthew Chen
7a50d6b996 Fix broken tests.
// FREEBIE
2017-07-12 12:12:59 -04:00
Matthew Chen
762f915179 Respond to CR.
// FREEBIE
2017-07-12 12:12:59 -04:00
Matthew Chen
96da091e9b Run orphan cleanup on startup.
// FREEBIE
2017-07-12 12:12:59 -04:00
Michael Kirk
9115a1f973 Merge branch 'mkirk/update-ci-gems' 2017-07-12 11:36:38 -04:00
Michael Kirk
57b90e1462 update ci gems
// FREEBIE
2017-07-12 11:36:33 -04:00
Michael Kirk
e0f805f80f Merge branch 'mkirk/fix-persistence-upgrade' 2017-07-11 15:26:11 -04:00
Michael Kirk
ab6c1fb3b8 Fix persist view for upgrade scenarios
// FREEBIE
2017-07-11 13:22:23 -04:00
Michael Kirk
9d3175b5cf Merge branch 'mkirk/cleanup-logging' 2017-07-11 13:19:51 -04:00
Michael Kirk
957979585c remove unhelpful logging
// FREEBIE
2017-07-10 12:37:03 -05:00
Michael Kirk
8361ffb818 Merge branch 'mkirk/persist-thread-view' 2017-07-10 11:52:45 -05:00
Michael Kirk
ea681a61e2 persist thread view
// FREEBIE
2017-07-10 11:52:37 -05:00
Matthew Chen
c1e1247eff Merge branch 'charlesmchen/identityManagerVsStartup' 2017-07-06 14:38:52 -04:00
Matthew Chen
92276157dc Don’t sync verification state until app has finished becoming active.
// FREEBIE
2017-07-06 12:34:08 -04:00
Michael Kirk
2216c2d413 Merge pull request #295 from WhisperSystems/mkirk/fix-tests
fix tests
2017-07-06 10:28:28 -05:00
Michael Kirk
a23b4871e0 fix tests
// FREEBIE
2017-07-06 09:37:12 -05:00
Michael Kirk
72e893d5f7 Merge commit 'e24f18320d3aefe87d2532c9f0520348c4598cb2' 2017-07-06 09:08:02 -05:00
Matthew Chen
e24f18320d Merge branch 'charlesmchen/modelConnection' into hotfix/2.13.3.0 2017-07-05 19:06:30 -04:00
Matthew Chen
58fb86b8e0 Use a dedicated connection for model reads & writes.
// FREEBIE
2017-07-05 18:54:48 -04:00
Matthew Chen
065017cafd Merge branch 'charlesmchen/sharedReadAndWriteConnections' into hotfix/2.13.3.0 2017-07-05 17:23:03 -04:00
Matthew Chen
daae31d30e Modify TSStorageManager to use separate shared read and write connections.
// FREEBIE
2017-07-05 16:58:39 -04:00
Michael Kirk
8714a8f37c Merge branch 'mkirk/no-sync-to-unregistered-number' 2017-07-05 13:00:35 -05:00
Michael Kirk
deff1fa4e7 FIX: verifiying unregistered user prints "no longer registered" error on
every launch

// FREEBIE
2017-07-05 12:59:53 -05:00
Michael Kirk
1fc5f77286 Merge branch 'mkirk/declined-call-notification' 2017-07-05 12:42:51 -05:00
Michael Kirk
fd625dff50 remove default case for better compiler warnings
// FREEBIE
2017-07-05 12:42:21 -05:00
Michael Kirk
89f86c4fd2 call type for declined call
// FREEBIE
2017-07-03 13:45:17 -05:00
Michael Kirk
04ef06ce95 Merge branch 'mkirk/fix-unread-badge' 2017-06-30 10:21:30 -10:00
Michael Kirk
f59779c118 message manager updates badge count
// FREEBIE
2017-06-30 06:13:26 -10:00
Michael Kirk
85fe68d3c4 Fix typo after rename
// FREEBIE
2017-06-28 19:09:04 -10:00
Michael Kirk
d6c5497f64 Merge branch 'mkirk/fix-tests' 2017-06-28 18:40:37 -10:00
Michael Kirk
0b33ef6161 try fastlane scan, since build is timing out.
// FREEBIE
2017-06-28 18:30:17 -10:00
Michael Kirk
acf31db4b3 bump travis image to 8.3
// FREEBIE
2017-06-28 18:04:25 -10:00
Michael Kirk
a8ea2428c6 gemfile for consistent build on dev/CI
// FREEBIE
2017-06-28 17:42:24 -10:00
Michael Kirk
605db6b788 Fix up deleteAttachments method since making attachmentFolder
dispatchOnce

// FREEBIE
2017-06-28 17:07:11 -10:00
Michael Kirk
1d71ca5e50 Fix some more tests.
Adapt to IdentityManager refactor
Adapt to DisappearingMessages refactor

// FREEBIE
2017-06-28 16:18:13 -10:00
Michael Kirk
8f9af85cca prefer hacks in test to hacks in code.
Adding a completion handler would be a bit gnarly since we're snaking
through a pretty long method chain.

// FREEBIE
2017-06-28 08:09:23 -10:00
Michael Kirk
1b9aae2ea6 CR: renaming var to be more general
// FREEBIE
2017-06-28 07:32:31 -10:00
Michael Kirk
e652dff4b4 Allow override of singleton enforcement in test app
We frequently build multiple instances of "singletons" for the purpose
of testing.

// FREEBIE
2017-06-28 07:30:02 -10:00
Michael Kirk
edf5852e82 set-expiration dispatches *sync* so we can test it.
// FREEBIE
2017-06-28 07:30:02 -10:00
Michael Kirk
1fb9fa79df Fix up some more tests.
// FREEBIE
2017-06-28 07:30:02 -10:00
Michael Kirk
13c5bdb8c5 fix disappearing messages test
// FREEBIE
2017-06-28 07:30:02 -10:00
Michael Kirk
2addb9e81d Fixed test build. Some tests still failing.
Executed 85 tests, with 22 failures (17 unexpected) in 7.416 (8.531) seconds

// FREEBIE
2017-06-28 07:30:02 -10:00
Michael Kirk
c711b4a66d Merge branch 'mkirk/key-version' 2017-06-23 15:16:17 -04:00
Michael Kirk
b2f9abbc7c transmit key version byte
// FREEBIE
2017-06-23 15:12:51 -04:00
Matthew Chen
14c64239a3 Merge branch 'charlesmchen/archiveSessionsOnSSQ' 2017-06-22 18:18:49 -04:00
Matthew Chen
e9219743ff Archive sessions on the 'session store' queue.
// FREEBIE
2017-06-22 18:14:21 -04:00
Michael Kirk
793a7449b0 Merge branch 'mkirk/archive-sessions-on-id-change' 2017-06-22 17:24:05 -04:00
Michael Kirk
5da7dc1fd3 Archive sessions upon identity change.
Session clearing must be in IdentityKeyStore implementation

Trying to do it in the protocol will not work with multiple devices,
because we'll only archive the session of the first sending device.

// FREEBIE
2017-06-22 17:23:57 -04:00
Matthew Chen
07e0291377 Merge branch 'charlesmchen/diskUsage' 2017-06-22 17:17:00 -04:00
Matthew Chen
e8e7b6bcba Add creation timestamp to attachment streams.
// FREEBIE
2017-06-22 17:09:36 -04:00
Michael Kirk
8fda18a8e3 Merge branch 'mkirk/verification-sync' 2017-06-22 15:19:59 -04:00
Michael Kirk
5a2169fa78 Don't sync verification until NullMessage succeeds. This better mirrors
existing sync message behavior

// FREEBIE
2017-06-22 15:19:46 -04:00
Michael Kirk
4c22f371a9 better comment on strange padding
// FREEBIE
2017-06-22 15:19:46 -04:00
Michael Kirk
6dea4c9fef fix padding calculation
// FREEBIE
2017-06-22 15:19:46 -04:00
Michael Kirk
a5660f4db4 cleanup ahead of PR
synchronize/dispatch when dequeuing verification sync messages

// FREEBIE
2017-06-22 15:19:46 -04:00
Michael Kirk
d927cba5ce don't sync verified state when we have never recorded the recipients
identity

e.g. every contact you have which is a signal user whom you've never
messaged will not have a recorded RecipientIdentity

// FREEBIE
2017-06-22 15:19:46 -04:00
Michael Kirk
badaa54327 sync all verification states with contact sync
// FREEBIE
2017-06-22 15:19:46 -04:00
Michael Kirk
12bfae10ed All sync messages should have 1-512 random padding
// FREEBIE
2017-06-22 15:19:46 -04:00
Michael Kirk
35ee92f38f send null message when syncing verification state
// FREEBIE
2017-06-22 15:19:46 -04:00
Michael Kirk
99cd8fc27d Log when receiving null message
// FREEBIE
2017-06-22 15:19:46 -04:00
Michael Kirk
f653bc36a8 sync verification state with contacts
// FREEBIE
2017-06-22 15:19:46 -04:00
Michael Kirk
48b3f498a9 WIP: adapt to verification proto changes
// FREEBIE
2017-06-22 15:19:46 -04:00
Michael Kirk
f526a372c9 proto update
// FREEBIE
2017-06-22 15:19:46 -04:00
Michael Kirk
6566ea694c no need to send sync messages when only 1 device
// FREEBIE
2017-06-22 15:19:46 -04:00
Matthew Chen
8b04e2a880 Merge branch 'charlesmchen/logOverflow' 2017-06-22 14:27:30 -04:00
Matthew Chen
ed369436fb Reduce chattiness of logs; increase log file sizes.
// FREEBIE
2017-06-22 13:24:53 -04:00
Matthew Chen
b946badd97 [SSK] Reduce chattiness of logs; increase log file sizes.
// FREEBIE
2017-06-22 10:10:30 -04:00
Matthew Chen
4609c508ec Merge branch 'charlesmchen/fixCFail' 2017-06-21 17:45:00 -04:00
Matthew Chen
30961cf2a5 Fix OWSCFail() macro.
// FREEBIE
2017-06-21 17:44:51 -04:00
Matthew Chen
4eacfe768f Merge branch 'charlesmchen/verificationSyncVsUI' 2017-06-21 17:43:20 -04:00
Matthew Chen
d8b34f6302 Ensure verification UI is updated to reflect incoming verification state sync messages.
// FREEBIE
2017-06-21 16:29:20 -04:00
Matthew Chen
2315ab79de Merge branch 'charlesmchen/attachmentStreamUpgradePerf' 2017-06-21 10:35:33 -04:00
Matthew Chen
e86e175ceb Respond to CR.
// FREEBIE
2017-06-21 10:35:25 -04:00
Matthew Chen
beb4ed71e7 Respond to CR.
// FREEBIE
2017-06-21 09:58:19 -04:00
Matthew Chen
cf65cc3be5 Improve perf of attachment stream file path upgrade.
// FREEBIE
2017-06-20 17:39:33 -04:00
Matthew Chen
ed249840c6 Merge branch 'charlesmchen/slowLaunchRelease' 2017-06-20 13:17:32 -04:00
Matthew Chen
27e5c836b7 Refine observation of async registration completion.
// FREEBIE
2017-06-20 12:04:59 -04:00
Matthew Chen
7af758bc67 Merge branch 'charlesmchen/enableVerificationStateSync' 2017-06-19 19:47:25 -04:00
Matthew Chen
07bec72f61 Enable verification state sync.
// FREEBIE
2017-06-19 19:46:28 -04:00
Michael Kirk
5110c5892d Merge branch 'mkirk/verification-key-length' 2017-06-19 17:26:33 -04:00
Michael Kirk
e746c6b7ec append/remove key type as necessary to fix verification syncing
// FREEBIE
2017-06-19 15:47:13 -04:00
Michael Kirk
2059bb4966 Merge branch 'WhisperSystems-mkirk/fix-verification-crash' 2017-06-19 15:33:55 -04:00
Michael Kirk
742e0d3c97 message sending must be on main thread
include description for verification sync messages

// FREEBIE
2017-06-19 10:57:13 -04:00
Matthew Chen
c0cb153f29 Merge branch 'charlesmchen/holidayCodeReviewOmnibus' 2017-06-17 13:46:32 -04:00
Matthew Chen
5d7c012b5c Respond to CR.
// FREEBIE
2017-06-17 13:41:29 -04:00
Matthew Chen
d80e42e0a0 Respond to post-holiday code reviews.
// FREEBIE
2017-06-17 13:37:59 -04:00
Matthew Chen
18e6a1b1cb Respond to post-holiday code reviews.
// FREEBIE
2017-06-17 13:37:59 -04:00
Matthew Chen
a9bac8bce7 Merge branch 'charlesmchen/syncVerificationRedux' 2017-06-17 13:34:41 -04:00
Matthew Chen
498c0ef681 Respond to CR.
// FREEBIE
2017-06-17 13:34:26 -04:00
Matthew Chen
44e1f4a14a Rework verification state sync per latest proto schema.
// FREEBIE
2017-06-16 12:22:49 -04:00
Matthew Chen
26e6aab071 Merge branch 'charlesmchen/lastAppLaunchCompletedVersion' 2017-06-16 12:05:00 -04:00
Matthew Chen
8ef118f5da Cache the attachments folder in TSAttachmentStream.
// FREEBIE
2017-06-16 11:17:12 -04:00
Matthew Chen
b9f9b6a0c3 Add isFirstLaunch method to AppVersion.
// FREEBIE
2017-06-16 10:59:48 -04:00
Matthew Chen
32e4eb2a42 Add a “last app completed launch” version.
// FREEBIE
2017-06-16 10:52:25 -04:00
Matthew Chen
7379e6a679 Merge branch 'charlesmchen/attachmentStreamUpgradesVsSerialQueue' 2017-06-16 10:11:35 -04:00
Matthew Chen
470cee0e11 Upgrade attachment streams on a serial queue.
// FREEBIE
2017-06-15 17:29:37 -04:00
Matthew Chen
09513fc1c3 Merge branch 'charlesmchen/databaseViewsVsStartupTime' 2017-06-15 15:44:26 -04:00
Matthew Chen
22109d719b Respond to CR.
// FREEBIE
2017-06-15 15:36:21 -04:00
Matthew Chen
0f96341059 Avoid crashing on startup due to database view creation.
* Substitute unread view for unseen view until unseen view is ready.
* Register as many views as possible async.
* Perform blocking, safe migrations before async registration of views.

// FREEBIE
2017-06-15 13:44:02 -04:00
Matthew Chen
bbc7c44c93 Use transactions in the jobs.
// FREEBIE
2017-06-15 13:44:02 -04:00
Matthew Chen
96dc0e4fdb Merge branch 'charlesmchen/removeBlockingPref' 2017-06-14 14:37:47 -04:00
Matthew Chen
d53db17447 Remove “block on safety number changes” setting in preferences.
// FREEBIE
2017-06-14 14:36:05 -04:00
Matthew Chen
f999c4abb5 Merge branch 'charlesmchen/databaseViewsVsPerf2' 2017-06-14 12:34:37 -04:00
Matthew Chen
0c503c379a Reduce number of database views.
// FREEBIE.
2017-06-14 12:26:07 -04:00
Matthew Chen
d8199a444f Merge branch 'charlesmchen/databaseViewsVsPerf' 2017-06-14 09:53:40 -04:00
Matthew Chen
bf07a8401e Remove an unnecessary database view.
// FREEBIE.
2017-06-14 09:50:36 -04:00
Matthew Chen
1f1410ffae Merge branch 'charlesmchen/groupCreationErrors' 2017-06-13 15:35:28 -04:00
Matthew Chen
3598cc18fa Ensure message sends only succeed or fail once.
// FREEBIE
2017-06-13 15:08:27 -04:00
Matthew Chen
e1439a54dc Add “group creation failed” error message.
// FREEBIE
2017-06-13 15:08:10 -04:00
Matthew Chen
857fb535d9 Merge branch 'charlesmchen/expirationVsCalls' 2017-06-13 13:39:14 -04:00
Matthew Chen
26836a5725 Skip expiration for calls.
// FREEBIE
2017-06-13 13:38:42 -04:00
Matthew Chen
7052b97c72 Merge branch 'charlesmchen/callsStuckOnConnecting' 2017-06-13 11:15:17 -04:00
Matthew Chen
42cef65de4 Improve logging around incoming messages.
// FREEBIE
2017-06-13 10:50:42 -04:00
Matthew Chen
9168512058 Merge branch 'charlesmchen/readReceiptsVsOlderMessages2' 2017-06-12 14:45:30 -04:00
Matthew Chen
dfab38b941 Rework how messages are marked read.
// FREEBIE
2017-06-12 14:44:33 -04:00
Matthew Chen
5d1a33b5fc Merge branch 'charlesmchen/readReceiptsVsOlderMessages' 2017-06-12 14:37:30 -04:00
Matthew Chen
4a028d32b1 Filter messages shown in the home view.
// FREEBIE
2017-06-12 13:33:12 -04:00
Matthew Chen
dcbb72d851 Filter messages shown in the home view.
// FREEBIE
2017-06-12 13:30:04 -04:00
Matthew Chen
5e50711412 Don’t update expiration for messages twice.
// FREEBIE
2017-06-12 13:29:17 -04:00
Matthew Chen
dc9a2253d5 Rework how messages are marked read.
// FREEBIE
2017-06-12 12:28:23 -04:00
Matthew Chen
c5c4643782 Rework how messages are marked read.
// FREEBIE
2017-06-12 11:51:25 -04:00
Matthew Chen
49f1180431 Merge branch 'mkirk/remove-legacy-message-sending' 2017-06-12 09:23:34 -04:00
Michael Kirk
c29549c213 remove legacy message sending
// FREEBIE
2017-06-12 09:23:27 -04:00
Matthew Chen
13a119b4b6 Merge branch 'charlesmchen/refineVerification' 2017-06-10 14:23:21 -04:00
Matthew Chen
f324327880 Don’t update home view sort order in response to dynamic interactions or verification state changes.
// FREEBIE
2017-06-10 14:13:23 -04:00
Matthew Chen
1052915c1b We only want to create change messages in response to user activity, on any of their devices.
// FREEBIE
2017-06-10 14:05:53 -04:00
Matthew Chen
12aed7a4a1 Merge branch 'charlesmchen/defaultVerificationMessageDescription' 2017-06-09 11:42:04 -04:00
Matthew Chen
fbc1bad881 Add a default description for verification state messages.
// FREEBIE
2017-06-09 11:28:26 -04:00
Michael Kirk
8bd0281254 Merge branch 'mkirk/archive-not-delete' 2017-06-08 17:55:55 -04:00
Michael Kirk
0df5ea3ee5 CR: continue to delete session when receiving an EndSession
// FREEBIE
2017-06-08 16:18:45 -04:00
Michael Kirk
cfd9b84e65 Remove redundant missing-session check.
It already happens in decrypt/processException

// FREEBIE
2017-06-08 15:45:01 -04:00
Michael Kirk
1db9c8b344 prefer archiving vs deleting sessions.
This gives us a little resiliency to handling messages out of order
across key change.

We still *always* print SN when they change.
And we still verify that the session used for encrypt/decrypt is the trusted
session

// FREEBIE
2017-06-08 15:45:01 -04:00
Matthew Chen
f2f654af19 Merge branch 'charlesmchen/verificationStateChangeMessages' 2017-06-08 10:40:31 -04:00
Matthew Chen
ca04d912db Don't actually transmit any verification state sync messages until we finalize the proto schema changes.
// FREEBIE
2017-06-08 10:40:17 -04:00
Matthew Chen
841271dc6c Respond to CR.
// FREEBIE
2017-06-08 10:40:17 -04:00
Matthew Chen
fdd172bda3 Add verification state change messages.
// FREEBIE
2017-06-08 10:40:17 -04:00
Matthew Chen
eed6377910 Add verification state change messages.
// FREEBIE
2017-06-08 10:40:17 -04:00
Michael Kirk
fba94754a6 Merge branch 'mkirk/redundant-sn-changes' 2017-06-08 10:31:29 -04:00
Michael Kirk
7fc73e2ba1 include recipient name in error message
// FREEBIE
2017-06-08 10:31:10 -04:00
Michael Kirk
94fb7d50e8 CR: add comment
// FREEBIE
2017-06-08 10:15:41 -04:00
Michael Kirk
dfd0f8073d When failing to send to new identity, save it.
Remove creation of "old style" blocking SN alerts

// FREEBIE
2017-06-07 23:22:21 -04:00
Matthew Chen
edc6578b9b Merge branch 'charlesmchen/syncVerificationState' 2017-06-07 17:50:21 -04:00
Matthew Chen
0e11566824 Respond to CR.
// FREEBIE
2017-06-07 17:48:12 -04:00
Matthew Chen
e27e55ca99 Fix a typo.
// FREEBIE
2017-06-07 15:36:14 -04:00
Matthew Chen
5acb209427 Sync verification state.
// FREEBIE
2017-06-07 15:16:45 -04:00
Matthew Chen
3c2835d318 Sync verification state.
// FREEBIE
2017-06-07 15:16:45 -04:00
Matthew Chen
07ac17fd39 Sync verification state.
// FREEBIE
2017-06-07 15:16:45 -04:00
Matthew Chen
90d671924f Sync verification state.
// FREEBIE
2017-06-07 15:16:45 -04:00
Matthew Chen
89b1da7666 Sync verification state.
// FREEBIE
2017-06-07 15:16:44 -04:00
Matthew Chen
b2425ddf9a Sync verification state.
// FREEBIE
2017-06-07 15:16:44 -04:00
Michael Kirk
33df1fb6c0 Merge branch 'mkirk/create-missed-call-notification-in-thread' 2017-06-07 15:02:08 -04:00
Michael Kirk
90087c34fa Create an interaction when missing a call due to identity change
// FREEBIE
2017-06-07 15:02:04 -04:00
Michael Kirk
435f13f2ff Merge branch 'mkirk/avoid-deadlock-on-unknown-session' 2017-06-07 14:44:51 -04:00
Michael Kirk
0c2a1ff89f avoid deadlock on unknown session
// FREEBIE
2017-06-07 12:36:17 -04:00
Michael Kirk
d475c58348 Merge branch 'mkirk/identityManager' 2017-06-07 10:11:18 -04:00
Michael Kirk
81d36a4652 code cleanup per code review
// FREEBIE
2017-06-07 10:11:01 -04:00
Michael Kirk
4a73ab2851 trust matching first known key, regardless of how old it is
// FREEBIE
2017-06-07 09:19:09 -04:00
Michael Kirk
1603e8bfbf more specific asserts, clean up logging
// FREEBIE
2017-06-07 09:18:34 -04:00
Michael Kirk
e408250ef2 Fix crash when messaging user for the first time
We trust on first use, so if there is no identity, no need to compare
key.

// FREEBIE
2017-06-07 08:54:26 -04:00
Michael Kirk
167961a45f restore call-back for changed identities
// FREEBIE
2017-06-07 08:31:40 -04:00
Matthew Chen
12b5c2c26a Sync verification state.
// FREEBIE
2017-06-06 17:46:31 -04:00
Matthew Chen
8a26883876 Fix crash.
// FREEBIE
2017-06-06 17:43:13 -04:00
Matthew Chen
fcc17ca869 Respond to CR.
// FREEBIE
2017-06-06 16:10:10 -04:00
Michael Kirk
ecf7ef61f4 update identity manager names
// FREEBIE
2017-06-06 15:43:54 -04:00
Matthew Chen
d9acaced2a Clean up ahead of PR.
// FREEBIE
2017-06-06 15:32:43 -04:00
Matthew Chen
f1d85c2a95 Clean up ahead of PR.
// FREEBIE
2017-06-06 15:16:09 -04:00
Matthew Chen
559c389f9f Sketch out OWSIdentityManager.
// FREEBIE
2017-06-06 15:01:11 -04:00
Matthew Chen
702f7677c6 Sketch out OWSIdentityManager.
// FREEBIE
2017-06-06 14:12:50 -04:00
Matthew Chen
0fcd0afd17 Sketch out OWSVerificationManager.
// FREEBIE
2017-06-06 12:08:39 -04:00
Matthew Chen
9154cc46f0 Sketch out OWSVerificationManager.
// FREEBIE
2017-06-06 12:07:49 -04:00
Matthew Chen
43d1aa49dc Merge branch 'charlesmchen/formatFailMessages' 2017-06-06 09:57:32 -04:00
Matthew Chen
69e40cdf1c Format fail messages.
// FREEBIE
2017-06-06 09:51:47 -04:00
Matthew Chen
72fb925af5 Merge branch 'charlesmchen/reworkSystemMessages' 2017-06-06 09:49:18 -04:00
Matthew Chen
f63c85f5d5 Rework and unify the system messages.
// FREEBIE
2017-06-06 09:49:09 -04:00
Michael Kirk
0ad794dfd9 Merge branch 'mkirk/better-envelope-logging' 2017-06-05 09:31:20 -04:00
Michael Kirk
ab378f79b4 better message receipt logging
// FREEBIE
2017-06-05 09:31:12 -04:00
Matthew Chen
01f2911462 Merge branch 'charlesmchen/obsoleteNotification' 2017-06-02 10:32:02 -04:00
Matthew Chen
7c78d62a0d Remove obsolete TSUIDatabaseConnectionDidUpdateNotification notification.
// FREEBIE
2017-06-02 10:02:27 -04:00
Michael Kirk
05a96008e3 Merge branch 'mkirk/reject-unseen-id-calls' 2017-06-01 13:20:37 -07:00
Michael Kirk
ebd4800e21 return unseen identity rather than bool
This turns out to be more versitile for the client app

// FREEBIE
2017-05-31 17:40:59 -07:00
Michael Kirk
e10cc0c180 determine if recipient identity change is unseen
// FREEBIE
2017-05-31 16:51:11 -07:00
Matthew Chen
cd7a172b94 Revert "Remove obsolete TSUIDatabaseConnectionDidUpdateNotification notification."
This reverts commit f2fb2cb9dc.

// FREEBIE
2017-05-31 17:42:49 -04:00
Matthew Chen
f2fb2cb9dc Remove obsolete TSUIDatabaseConnectionDidUpdateNotification notification.
// FREEBIE
2017-05-31 17:42:21 -04:00
Michael Kirk
0c46288cf9 Merge branch 'mkirk/dedicated-session-connection' 2017-05-31 11:32:57 -07:00
Michael Kirk
806a64ee53 Store session as Immutable to be clear about when it's mutated.
// FREEBIE
2017-05-31 11:32:52 -07:00
Michael Kirk
29e86901e7 Do not cache session objects
Ensure that any uncommitted session mutation doesn't hang around.

// FREEBIE
2017-05-31 11:32:52 -07:00
Matthew Chen
07b54039b2 Merge branch 'charlesmchen/incomingAndOutgoingDatabaseViews' 2017-05-31 10:04:19 -04:00
Matthew Chen
32d97bc6c7 Respond to CR.
// FREEBIE
2017-05-31 10:02:04 -04:00
Matthew Chen
09f7a9df4e Add incoming and outgoing message database views.
// FREEBIE
2017-05-31 09:57:34 -04:00
Matthew Chen
888943a047 Merge branch 'charlesmchen/cleanupTimerUsage' 2017-05-31 09:54:00 -04:00
Matthew Chen
2b197197be Clean up timer usage.
// FREEBIE
2017-05-31 09:53:30 -04:00
Matthew Chen
0a3e75ee8c Merge branch 'charlesmchen/fixMarkAsRead' 2017-05-31 09:50:30 -04:00
Matthew Chen
e889f49e3b Fix “mark as read” logic.
// FREEBIE
2017-05-30 11:01:54 -04:00
Matthew Chen
6acfab6a50 Merge branch 'charlesmchen/refineUnseenIndicator' 2017-05-30 10:10:54 -04:00
Matthew Chen
a5bebaf862 Respond to CR.
// FREEBIE
2017-05-30 10:05:13 -04:00
Matthew Chen
0eff3625c9 Respond to CR.
// FREEBIE
2017-05-30 10:05:13 -04:00
Matthew Chen
31e216519a Respond to CR.
* Add [TSInteraction compareForSorting].
* Add a separate database view for safety number changes.

// FREEBIE
2017-05-30 10:05:13 -04:00
Matthew Chen
7c5a11b221 Changes for unseen indicator.
* Create separate database views for “unseen” and “unread” messages.
* Add “unseen tracking” to info and error messages.
* Rationalize “timestamp” vs. “receipt timestamp”.
* Ensure microsecond precision for interaction sorting.
* Add OWSFail() macros.

// FREEBIE
2017-05-30 10:05:13 -04:00
Matthew Chen
32d5e52142 DRY up the creation of database views.
// FREEBIE
2017-05-30 10:05:13 -04:00
Matthew Chen
6e94b3cccb Add a database view for dynamic interactions.
// FREEBIE
2017-05-30 10:05:13 -04:00
Matthew Chen
c7cc023541 Merge branch 'charlesmchen/cacheAccountNames' 2017-05-30 09:51:16 -04:00
Matthew Chen
66927f206d Cache display names for accounts.
// FREEBIE
2017-05-30 09:40:47 -04:00
Michael Kirk
33a2b05dca Merge branch 'mkirk/remove-unnecessary-notifications' 2017-05-26 15:43:47 -07:00
Michael Kirk
42b35bb897 Don't notify for some error messages
Specifically:

- Don't play alert when building block offer

- Don't play/show redundant alert/lockscreen notifications for id changes
  e.g.: if you share 100 groups with Bob, and Bob's SN change, you only
  need *one* notification, even though we display the message in 100
  group threads + 1 contact thread.

// FREEBIE
2017-05-26 15:43:35 -07:00
Michael Kirk
0eef7ccb8f Merge branch 'mkirk/confirm-send' 2017-05-26 15:34:01 -07:00
Michael Kirk
09d7d8c027 Given a recipient id, returns any unconfirmed identity
// FREEBIE
2017-05-26 15:32:09 -07:00
Michael Kirk
0201fa34ce Merge branch 'mkirk/profile-request' 2017-05-26 09:47:59 -07:00
Michael Kirk
5df67c8e5c move constant per code review
// FREEBIE
2017-05-26 09:47:55 -07:00
Michael Kirk
fe075d2f77 Support for profile fetching so we can display SN changes upon entering
a thread

// FREEBIE
2017-05-26 09:47:55 -07:00
Matthew Chen
b89d16ea90 Merge branch 'charlesmchen/messageViewPerf2_' 2017-05-26 11:18:11 -04:00
Matthew Chen
ef9303dd03 Rename audio duration and image size methods in TSAttachmentStream.
// FREEBIE
2017-05-26 11:18:02 -04:00
Michael Kirk
12c45b8a47 Merge branch 'mkirk/log-error-on-send-failure' 2017-05-25 08:36:57 -07:00
Michael Kirk
3be70e9719 log error on failure
// FREEBIE
2017-05-25 08:36:45 -07:00
Michael Kirk
d782904d15 Merge branch 'mkirk/safety-numbers' 2017-05-24 18:05:36 -07:00
Michael Kirk
4a6a02c009 Ensure updates don't clobber
// FREEBIE
2017-05-24 18:01:48 -07:00
Michael Kirk
8ee57d9132 save identity to legacy identity store so we can switch versions while
testing

// FREEBIE
2017-05-24 18:01:48 -07:00
Michael Kirk
0001b6c493 Code style per code review, no functional changes
// FREEBIE
2017-05-24 18:01:48 -07:00
Michael Kirk
f2f3acb897 IdentityKeyStore changes
1) Always accept keys from incoming messages

2) Block sending only if it's a recent change, or if always
   block is enabled

// FREEBIE
2017-05-24 18:01:48 -07:00
Matthew Chen
0a8c4203ea Merge branch 'charlesmchen/socketManagerAssert' 2017-05-24 17:44:38 -04:00
Matthew Chen
07bf3b9af5 Remove invalid assert in socket manager.
// FREEBIE
2017-05-24 15:03:36 -04:00
Matthew Chen
289fd4f0cc Merge branch 'charlesmchen/messageViewPerf2' 2017-05-24 09:59:03 -04:00
Matthew Chen
fe796d6c52 Cache image size and audio duration on attachments.
// FREEBIE
2017-05-23 09:39:54 -04:00
Matthew Chen
d612358256 Merge branch 'charlesmchen/manualCensorshipCircumvention' 2017-05-23 09:26:23 -04:00
Matthew Chen
58edbdfbd9 Let users manually specify the domain fronting country.
// FREEBIE
2017-05-22 20:33:26 -04:00
Matthew Chen
98ff7e5aba Add support for manually activating censorship circumvention.
// FREEBIE
2017-05-22 20:33:26 -04:00
Matthew Chen
d3fc5e4ab8 Rework how the views observe socket state.
// FREEBIE
2017-05-22 20:33:26 -04:00
Matthew Chen
45b947dc04 Rework how the views observe socket state.
// FREEBIE
2017-05-22 20:33:26 -04:00
Matthew Chen
2171cd1d96 Add support for manually activating censorship circumvention.
// FREEBIE
2017-05-22 20:33:26 -04:00
Matthew Chen
cbeafac20e Merge branch 'charlesmchen/addToContactsOffer' 2017-05-22 18:24:55 -04:00
Matthew Chen
66b8d54015 “Add to contacts” offer.
// FREEBIE
2017-05-22 18:19:56 -04:00
Matthew Chen
485af7e817 Merge branch 'charlesmchen/localPhoneNumberCountryVsContactParsing' 2017-05-22 18:15:58 -04:00
Matthew Chen
fcc7eb6565 Try the country code for the local phone number when parsing phone numbers.
// FREEBIE
2017-05-19 22:30:49 -04:00
Matthew Chen
dd14510355 Merge branch 'charlesmchen/pinYapDatabase' 2017-05-19 18:04:36 -04:00
Matthew Chen
4837a9d371 Pin YapDatabase to v2.9.3 to avoid v.3.x.
// FREEBIE
2017-05-19 17:56:14 -04:00
Matthew Chen
2439752c20 Merge branch 'charlesmchen/attachmentFilenames' 2017-05-19 17:33:53 -04:00
Matthew Chen
df17403ec9 Respond to CR.
// FREEBIE
2017-05-19 17:33:17 -04:00
Matthew Chen
c955b189f7 Respond to CR.
// FREEBIE
2017-05-19 17:20:58 -04:00
Matthew Chen
9fb1012c6e Persist attachment file paths.
// FREEBIE
2017-05-19 17:20:58 -04:00
Matthew Chen
c75769d407 Rename attachment source filename property.
// FREEBIE
2017-05-19 17:20:58 -04:00
Michael Kirk
52097864f1 Merge branch 'mkirk/show-name-in-sn-change' 2017-05-19 16:38:09 -04:00
Michael Kirk
92d72b3fc8 make nonatomic per code review
// FREEBIE
2017-05-19 16:37:16 -04:00
Michael Kirk
47b1c31b50 Contact Name in SN changed notifications
// FREEBIE
2017-05-19 11:44:05 -04:00
Michael Kirk
e212fdf2cf Merge branch 'mkirk/group-sn-changes' 2017-05-19 11:40:56 -04:00
Michael Kirk
fcbfde3874 nonblocking SN change notifications don't percolate group threads to the
top unless there is a message in that thread.

Otherwise the user is overwhelmed by a bunch of group threads with
redundant notifications. The notifications will be there if the user
later enters the threads. The affect is that only the thread with the
relevant message will pop to the top.

// FREEBIE
2017-05-19 11:40:44 -04:00
Michael Kirk
4becd43973 "Bob's SN changed" displayed in every group containing Bob
// FREEBIE
2017-05-19 11:40:44 -04:00
153 changed files with 5929 additions and 1678 deletions

1
.gitignore vendored
View File

@ -17,6 +17,7 @@ DerivedData
*.hmap
*.ipa
*.xcuserstate
xcshareddata
Pods/

View File

@ -1,17 +1,17 @@
language: objective-c
osx_image: xcode8
osx_image: xcode8.3
env:
-EARLY_START_SIMULATOR=1 # early starting simulator reduces false negatives due to test timeouts
before_install:
- brew update
- gem install cocoapods xcpretty --no-ri --no-rdoc
- pod repo update --silent
- bundle
- bundle exec pod repo update --silent
after_failure:
- sleep 10 # This prevents the occasional output truncation that happens when piping to xcpretty.
script: make
script: make scan_test

View File

@ -11,3 +11,19 @@ target 'TSKitiOSTestApp' do
end
end
post_install do |installer|
installer.pods_project.targets.each do |target|
if target.to_s == "SignalServiceKit"
puts "--[!] Disabling singleton enforcement for SSK."
target.build_configurations.each do |config|
existing_definitions = config.build_settings['GCC_PREPROCESSOR_DEFINITIONS']
if existing_definitions == nil || existing.length == 0
existing_definitions = "$(inheritied)"
end
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] = "#{existing_definitions} SSK_BUILDING_FOR_TESTS=1"
config.build_settings['OTHER_SWIFT_FLAGS'] = ['$(inherited)', '-DSSK_BUILDING_FOR_TESTS']
end
end
end
end

View File

@ -17,6 +17,7 @@ PODS:
- AFNetworking/NSURLSession
- AxolotlKit (0.8.1):
- 25519 (~> 2.0.1)
- CocoaLumberjack
- HKDFKit (~> 0.0.3)
- ProtocolBuffers (~> 1.9.8)
- CocoaLumberjack (2.4.0):
@ -28,12 +29,11 @@ PODS:
- CocoaLumberjack/Extensions (2.4.0):
- CocoaLumberjack/Default
- HKDFKit (0.0.3)
- libPhoneNumber-iOS (0.9.4)
- libPhoneNumber-iOS (0.9.10)
- Mantle (2.1.0):
- Mantle/extobjc (= 2.1.0)
- Mantle/extobjc (2.1.0)
- ProtocolBuffers (1.9.11)
- Reachability (3.2)
- SAMKeychain (1.5.2)
- SignalServiceKit (0.9.0):
- '25519'
@ -45,7 +45,7 @@ PODS:
- SAMKeychain
- SocketRocket
- TwistedOakCollapsingFutures
- YapDatabase/SQLCipher
- YapDatabase/SQLCipher (~> 2.9.3)
- SocketRocket (0.5.1)
- SQLCipher/common (3.4.1)
- SQLCipher/fts (3.4.1):
@ -53,54 +53,53 @@ PODS:
- TwistedOakCollapsingFutures (1.0.0):
- UnionFind (~> 1.0)
- UnionFind (1.0.1)
- YapDatabase/SQLCipher (2.9.2):
- YapDatabase/SQLCipher/Core (= 2.9.2)
- YapDatabase/SQLCipher/Extensions (= 2.9.2)
- YapDatabase/SQLCipher/Core (2.9.2):
- YapDatabase/SQLCipher (2.9.3):
- YapDatabase/SQLCipher/Core (= 2.9.3)
- YapDatabase/SQLCipher/Extensions (= 2.9.3)
- YapDatabase/SQLCipher/Core (2.9.3):
- CocoaLumberjack (~> 2)
- SQLCipher/fts
- YapDatabase/SQLCipher/Extensions (2.9.2):
- YapDatabase/SQLCipher/Extensions (2.9.3):
- YapDatabase/SQLCipher/Core
- YapDatabase/SQLCipher/Extensions/ActionManager (= 2.9.2)
- YapDatabase/SQLCipher/Extensions/CloudKit (= 2.9.2)
- YapDatabase/SQLCipher/Extensions/ConnectionProxy (= 2.9.2)
- YapDatabase/SQLCipher/Extensions/CrossProcessNotification (= 2.9.2)
- YapDatabase/SQLCipher/Extensions/FilteredViews (= 2.9.2)
- YapDatabase/SQLCipher/Extensions/FullTextSearch (= 2.9.2)
- YapDatabase/SQLCipher/Extensions/Hooks (= 2.9.2)
- YapDatabase/SQLCipher/Extensions/Relationships (= 2.9.2)
- YapDatabase/SQLCipher/Extensions/RTreeIndex (= 2.9.2)
- YapDatabase/SQLCipher/Extensions/SearchResults (= 2.9.2)
- YapDatabase/SQLCipher/Extensions/SecondaryIndex (= 2.9.2)
- YapDatabase/SQLCipher/Extensions/Views (= 2.9.2)
- YapDatabase/SQLCipher/Extensions/ActionManager (2.9.2):
- Reachability (~> 3)
- YapDatabase/SQLCipher/Extensions/ActionManager (= 2.9.3)
- YapDatabase/SQLCipher/Extensions/CloudKit (= 2.9.3)
- YapDatabase/SQLCipher/Extensions/ConnectionProxy (= 2.9.3)
- YapDatabase/SQLCipher/Extensions/CrossProcessNotification (= 2.9.3)
- YapDatabase/SQLCipher/Extensions/FilteredViews (= 2.9.3)
- YapDatabase/SQLCipher/Extensions/FullTextSearch (= 2.9.3)
- YapDatabase/SQLCipher/Extensions/Hooks (= 2.9.3)
- YapDatabase/SQLCipher/Extensions/Relationships (= 2.9.3)
- YapDatabase/SQLCipher/Extensions/RTreeIndex (= 2.9.3)
- YapDatabase/SQLCipher/Extensions/SearchResults (= 2.9.3)
- YapDatabase/SQLCipher/Extensions/SecondaryIndex (= 2.9.3)
- YapDatabase/SQLCipher/Extensions/Views (= 2.9.3)
- YapDatabase/SQLCipher/Extensions/ActionManager (2.9.3):
- YapDatabase/SQLCipher/Core
- YapDatabase/SQLCipher/Extensions/Views
- YapDatabase/SQLCipher/Extensions/CloudKit (2.9.2):
- YapDatabase/SQLCipher/Extensions/CloudKit (2.9.3):
- YapDatabase/SQLCipher/Core
- YapDatabase/SQLCipher/Extensions/ConnectionProxy (2.9.2):
- YapDatabase/SQLCipher/Extensions/ConnectionProxy (2.9.3):
- YapDatabase/SQLCipher/Core
- YapDatabase/SQLCipher/Extensions/CrossProcessNotification (2.9.2):
- YapDatabase/SQLCipher/Extensions/CrossProcessNotification (2.9.3):
- YapDatabase/SQLCipher/Core
- YapDatabase/SQLCipher/Extensions/FilteredViews (2.9.2):
- YapDatabase/SQLCipher/Extensions/FilteredViews (2.9.3):
- YapDatabase/SQLCipher/Core
- YapDatabase/SQLCipher/Extensions/Views
- YapDatabase/SQLCipher/Extensions/FullTextSearch (2.9.2):
- YapDatabase/SQLCipher/Extensions/FullTextSearch (2.9.3):
- YapDatabase/SQLCipher/Core
- YapDatabase/SQLCipher/Extensions/Hooks (2.9.2):
- YapDatabase/SQLCipher/Extensions/Hooks (2.9.3):
- YapDatabase/SQLCipher/Core
- YapDatabase/SQLCipher/Extensions/Relationships (2.9.2):
- YapDatabase/SQLCipher/Extensions/Relationships (2.9.3):
- YapDatabase/SQLCipher/Core
- YapDatabase/SQLCipher/Extensions/RTreeIndex (2.9.2):
- YapDatabase/SQLCipher/Extensions/RTreeIndex (2.9.3):
- YapDatabase/SQLCipher/Core
- YapDatabase/SQLCipher/Extensions/SearchResults (2.9.2):
- YapDatabase/SQLCipher/Extensions/SearchResults (2.9.3):
- YapDatabase/SQLCipher/Core
- YapDatabase/SQLCipher/Extensions/FullTextSearch
- YapDatabase/SQLCipher/Extensions/Views
- YapDatabase/SQLCipher/Extensions/SecondaryIndex (2.9.2):
- YapDatabase/SQLCipher/Extensions/SecondaryIndex (2.9.3):
- YapDatabase/SQLCipher/Core
- YapDatabase/SQLCipher/Extensions/Views (2.9.2):
- YapDatabase/SQLCipher/Extensions/Views (2.9.3):
- YapDatabase/SQLCipher/Core
DEPENDENCIES:
@ -112,13 +111,13 @@ EXTERNAL SOURCES:
AxolotlKit:
:git: https://github.com/WhisperSystems/SignalProtocolKit.git
SignalServiceKit:
:path: "../../SignalServiceKit.podspec"
:path: ../../SignalServiceKit.podspec
SocketRocket:
:git: https://github.com/facebook/SocketRocket.git
CHECKOUT OPTIONS:
AxolotlKit:
:commit: a3c843cc8a423c5924c663490978f81dba34d04e
:commit: 28afe5c1dbcfdea73d147e464c53d191d1e3ea50
:git: https://github.com/WhisperSystems/SignalProtocolKit.git
SocketRocket:
:commit: 877ac7438be3ad0b45ef5ca3969574e4b97112bf
@ -127,21 +126,20 @@ CHECKOUT OPTIONS:
SPEC CHECKSUMS:
'25519': dc4bad7e2dbcbf1efa121068a705a44cd98c80fc
AFNetworking: 5e0e199f73d8626b11e79750991f5d173d1f8b67
AxolotlKit: 240c7d761e4b1be9c6de78ebec498aaeedc978f4
AxolotlKit: a9530d6835baae0f204b1f6b9dd79b7901176f0d
CocoaLumberjack: aa9dcab71bdf9eaf2a63bbd9ddc87863efe45457
HKDFKit: c058305d6f64b84f28c50bd7aa89574625bcb62a
libPhoneNumber-iOS: 63bab980d1fc9783d82d955800ac9d7c1d81fde3
libPhoneNumber-iOS: f721ae4d5854bce60934f9fb9b0b28e8e68913cb
Mantle: 2fa750afa478cd625a94230fbf1c13462f29395b
ProtocolBuffers: d509225eb2ea43d9582a59e94348fcf86e2abd65
Reachability: 33e18b67625424e47b6cde6d202dce689ad7af96
SAMKeychain: 1865333198217411f35327e8da61b43de79b635b
SignalServiceKit: 59a79a51b89b963ba94db30cc99ed5212da0bb9f
SignalServiceKit: 2ad8d86da055e24ac3ea0354ec1d4b13251af28f
SocketRocket: dbb1554b8fc288ef8ef370d6285aeca7361be31e
SQLCipher: 43d12c0eb9c57fb438749618fc3ce0065509a559
TwistedOakCollapsingFutures: f359b90f203e9ab13dfb92c9ff41842a7fe1cd0c
UnionFind: c33be5adb12983981d6e827ea94fc7f9e370f52d
YapDatabase: b1e43555a34a5298e23a045be96817a5ef0da58f
YapDatabase: cd911121580ff16675f65ad742a9eb0ab4d9e266
PODFILE CHECKSUM: 1a7633963dbcaa43f298949d83c42c1cd1dce940
PODFILE CHECKSUM: a0f4507b6b4e6f9da3250901b06187a67236e083
COCOAPODS: 1.2.0
COCOAPODS: 1.2.1

View File

@ -457,7 +457,7 @@
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n";
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n";
showEnvVarsInLog = 0;
};
276B029791E679B0E87877B7 /* [CP] Copy Pods Resources */ = {
@ -532,7 +532,7 @@
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n";
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */

View File

@ -28,7 +28,7 @@
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "1D0826C97B09E58C83B3C228FBC535FA"
BlueprintIdentifier = "FF3F51F81980908EDE1836B76AA3A1EC"
BuildableName = "libSignalServiceKit.a"
BlueprintName = "SignalServiceKit"
ReferencedContainer = "container:Pods/Pods.xcodeproj">

5
Gemfile Normal file
View File

@ -0,0 +1,5 @@
source 'https://rubygems.org'
gem 'fastlane'
gem 'cocoapods'

189
Gemfile.lock Normal file
View File

@ -0,0 +1,189 @@
GEM
remote: https://rubygems.org/
specs:
CFPropertyList (2.3.5)
activesupport (4.2.9)
i18n (~> 0.7)
minitest (~> 5.1)
thread_safe (~> 0.3, >= 0.3.4)
tzinfo (~> 1.1)
addressable (2.5.1)
public_suffix (~> 2.0, >= 2.0.2)
babosa (1.0.2)
claide (1.0.2)
cocoapods (1.2.1)
activesupport (>= 4.0.2, < 5)
claide (>= 1.0.1, < 2.0)
cocoapods-core (= 1.2.1)
cocoapods-deintegrate (>= 1.0.1, < 2.0)
cocoapods-downloader (>= 1.1.3, < 2.0)
cocoapods-plugins (>= 1.0.0, < 2.0)
cocoapods-search (>= 1.0.0, < 2.0)
cocoapods-stats (>= 1.0.0, < 2.0)
cocoapods-trunk (>= 1.2.0, < 2.0)
cocoapods-try (>= 1.1.0, < 2.0)
colored2 (~> 3.1)
escape (~> 0.0.4)
fourflusher (~> 2.0.1)
gh_inspector (~> 1.0)
molinillo (~> 0.5.7)
nap (~> 1.0)
ruby-macho (~> 1.1)
xcodeproj (>= 1.4.4, < 2.0)
cocoapods-core (1.2.1)
activesupport (>= 4.0.2, < 5)
fuzzy_match (~> 2.0.4)
nap (~> 1.0)
cocoapods-deintegrate (1.0.1)
cocoapods-downloader (1.1.3)
cocoapods-plugins (1.0.0)
nap
cocoapods-search (1.0.0)
cocoapods-stats (1.0.0)
cocoapods-trunk (1.2.0)
nap (>= 0.8, < 2.0)
netrc (= 0.7.8)
cocoapods-try (1.1.0)
colored (1.2)
colored2 (3.1.2)
commander-fastlane (4.4.5)
highline (~> 1.7.2)
declarative (0.0.9)
declarative-option (0.1.0)
domain_name (0.5.20170404)
unf (>= 0.0.5, < 1.0.0)
dotenv (2.2.1)
escape (0.0.4)
excon (0.57.1)
faraday (0.12.1)
multipart-post (>= 1.2, < 3)
faraday-cookie_jar (0.0.6)
faraday (>= 0.7.4)
http-cookie (~> 1.0.0)
faraday_middleware (0.11.0.1)
faraday (>= 0.7.4, < 1.0)
fastimage (2.1.0)
fastlane (2.46.0)
CFPropertyList (>= 2.3, < 3.0.0)
addressable (>= 2.3, < 3.0.0)
babosa (>= 1.0.2, < 2.0.0)
bundler (>= 1.12.0, < 2.0.0)
colored
commander-fastlane (>= 4.4.5, < 5.0.0)
dotenv (>= 2.1.1, < 3.0.0)
excon (>= 0.45.0, < 1.0.0)
faraday (~> 0.9)
faraday-cookie_jar (~> 0.0.6)
faraday_middleware (~> 0.9)
fastimage (>= 2.1.0, < 3.0.0)
gh_inspector (>= 1.0.1, < 2.0.0)
google-api-client (>= 0.12.0, < 0.13.0)
highline (>= 1.7.2, < 2.0.0)
json (< 3.0.0)
mini_magick (~> 4.5.1)
multi_json
multi_xml (~> 0.5)
multipart-post (~> 2.0.0)
plist (>= 3.1.0, < 4.0.0)
rubyzip (>= 1.1.0, < 2.0.0)
security (= 0.1.3)
slack-notifier (>= 1.3, < 2.0.0)
terminal-notifier (>= 1.6.2, < 2.0.0)
terminal-table (>= 1.4.5, < 2.0.0)
tty-screen (~> 0.5.0)
word_wrap (~> 1.0.0)
xcodeproj (>= 1.4.4, < 2.0.0)
xcpretty (>= 0.2.4, < 1.0.0)
xcpretty-travis-formatter (>= 0.0.3)
fourflusher (2.0.1)
fuzzy_match (2.0.4)
gh_inspector (1.0.3)
google-api-client (0.12.0)
addressable (~> 2.5, >= 2.5.1)
googleauth (~> 0.5)
httpclient (>= 2.8.1, < 3.0)
mime-types (~> 3.0)
representable (~> 3.0)
retriable (>= 2.0, < 4.0)
googleauth (0.5.1)
faraday (~> 0.9)
jwt (~> 1.4)
logging (~> 2.0)
memoist (~> 0.12)
multi_json (~> 1.11)
os (~> 0.9)
signet (~> 0.7)
highline (1.7.8)
http-cookie (1.0.3)
domain_name (~> 0.5)
httpclient (2.8.3)
i18n (0.8.6)
json (2.1.0)
jwt (1.5.6)
little-plugger (1.1.4)
logging (2.2.2)
little-plugger (~> 1.1)
multi_json (~> 1.10)
memoist (0.16.0)
mime-types (3.1)
mime-types-data (~> 3.2015)
mime-types-data (3.2016.0521)
mini_magick (4.5.1)
minitest (5.10.2)
molinillo (0.5.7)
multi_json (1.12.1)
multi_xml (0.6.0)
multipart-post (2.0.0)
nanaimo (0.2.3)
nap (1.1.0)
netrc (0.7.8)
os (0.9.6)
plist (3.3.0)
public_suffix (2.0.5)
representable (3.0.4)
declarative (< 0.1.0)
declarative-option (< 0.2.0)
uber (< 0.2.0)
retriable (3.0.2)
rouge (2.0.7)
ruby-macho (1.1.0)
rubyzip (1.2.1)
security (0.1.3)
signet (0.7.3)
addressable (~> 2.3)
faraday (~> 0.9)
jwt (~> 1.5)
multi_json (~> 1.10)
slack-notifier (1.5.1)
terminal-notifier (1.8.0)
terminal-table (1.8.0)
unicode-display_width (~> 1.1, >= 1.1.1)
thread_safe (0.3.6)
tty-screen (0.5.0)
tzinfo (1.2.3)
thread_safe (~> 0.1)
uber (0.1.0)
unf (0.1.4)
unf_ext
unf_ext (0.0.7.4)
unicode-display_width (1.3.0)
word_wrap (1.0.0)
xcodeproj (1.5.0)
CFPropertyList (~> 2.3.3)
claide (>= 1.0.2, < 2.0)
colored2 (~> 3.1)
nanaimo (~> 0.2.3)
xcpretty (0.2.8)
rouge (~> 2.0.7)
xcpretty-travis-formatter (0.0.4)
xcpretty (~> 0.2, >= 0.0.7)
PLATFORMS
ruby
DEPENDENCIES
cocoapods
fastlane
BUNDLED WITH
1.14.6

View File

@ -14,9 +14,11 @@ default: test
test: pod_install retest
scan_test: pod_install scan
pod_install:
cd $(WORKING_DIR) && \
pod install
bundle exec pod install
build: pod_install
cd $(WORKING_DIR) && \
@ -28,6 +30,9 @@ retest: optional_early_start_simulator
-destination '${BUILD_DESTINATION}' \
test | xcpretty
scan:
bundle exec fastlane scan
clean:
cd $(WORKING_DIR) && \
$(XCODE_BUILD) \

View File

@ -1,3 +1,27 @@
# SignalServiceKit has Moved
Per https://github.com/WhisperSystems/Signal-iOS/pull/2341 we've moved
the SignalServiceKit codebase into the primary Signal-iOS repository at
https://github.com/WhisperSystems/Signal-iOS. As such, this repository
will no longer be updated.
Don't worry - we will continue to make updates to SignalServiceKit, and
you can continue to use it in your projects as before. The only
difference is where the code lives.
If you are using Cocoapods, staying up to date is as simple as modifying
a line in your Podfile from this:
```
- pod 'SignalServiceKit', git: 'https://github.com/WhisperSystems/SignalServiceKit.git'
```
To this:
```
+ pod 'SignalServiceKit', git: 'https://github.com/WhisperSystems/Signal-iOS.git'
```
# SignalServiceKit
SignalServiceKit is an Objective-C library for communicating with the Signal

View File

@ -17,15 +17,37 @@ An Objective-C library for communicating with the Signal messaging service.
s.homepage = "https://github.com/WhisperSystems/SignalServiceKit"
s.license = 'GPLv3'
s.author = { "Frederic Jacobs" => "github@fredericjacobs.com" }
s.author = { "Whisper Systems" => "ios@whispersystems.com" }
s.source = { :git => "https://github.com/WhisperSystems/SignalServiceKit.git", :tag => s.version.to_s }
s.social_media_url = 'https://twitter.com/FredericJacobs'
s.social_media_url = 'https://twitter.com/WhipserSystems'
deprecation_message = <<EOS
installing SignalServiceKit via the Signal-iOS repository.
To get future updates, point your Podfile at the new location. Simply change this:
pod 'SignalServiceKit', git: 'https://github.com/WhisperSystems/SignalServiceKit.git'
To this:
pod 'SignalServiceKit', git: 'https://github.com/WhisperSystems/Signal-iOS.git'
Sorry for the disruption!
EOS
s.deprecated_in_favor_of = deprecation_message
s.platform = :ios, '8.0'
#s.ios.deployment_target = '8.0'
#s.osx.deployment_target = '10.9'
s.requires_arc = true
s.source_files = 'src/**/*.{h,m,mm}'
# By not including any actual files, upgrading users will see
# that they need to point upgrades to the new source at
# https://github.com/WhisperSystems/Signal-iOS
# Details in README.md
s.source_files = 'README.md'
s.resources = ['src/Security/PinningCertificate/textsecure.cer',
'src/Security/PinningCertificate/GIAG2.crt']
@ -37,7 +59,7 @@ An Objective-C library for communicating with the Signal messaging service.
s.dependency 'AFNetworking'
s.dependency 'AxolotlKit'
s.dependency 'Mantle'
s.dependency 'YapDatabase/SQLCipher'
s.dependency 'YapDatabase/SQLCipher', '~> 2.9.3'
s.dependency 'SocketRocket'
s.dependency 'libPhoneNumber-iOS'
s.dependency 'SAMKeychain'

1
fastlane/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
test_output

8
fastlane/Scanfile Normal file
View File

@ -0,0 +1,8 @@
# For more information about this configuration visit
# https://github.com/fastlane/fastlane/tree/master/scan#scanfile
workspace "Example/TSKitiOSTestApp/TSKitiOSTestApp.xcworkspace"
scheme "TSKitiOSTestApp"
devices ["iPhone SE"]

View File

@ -34,6 +34,24 @@ message Content {
optional DataMessage dataMessage = 1;
optional SyncMessage syncMessage = 2;
optional CallMessage callMessage = 3;
optional NullMessage nullMessage = 4;
}
message NullMessage {
optional bytes padding = 1;
}
message Verified {
enum State {
DEFAULT = 0;
VERIFIED = 1;
UNVERIFIED = 2;
}
optional string destination = 1;
optional bytes identityKey = 2;
optional State state = 3;
optional bytes nullMessage = 4;
}
message CallMessage {
@ -130,6 +148,8 @@ message SyncMessage {
optional Request request = 4;
repeated Read read = 5;
optional Blocked blocked = 6;
optional Verified verified = 7;
optional bytes padding = 8;
}
message AttachmentPointer {
@ -172,6 +192,7 @@ message ContactDetails {
optional string name = 2;
optional Avatar avatar = 3;
optional string color = 4;
optional Verified verified = 5;
}
message GroupDetails {

View File

@ -10,6 +10,7 @@ NS_ASSUME_NONNULL_BEGIN
extern NSString *const TSRegistrationErrorDomain;
extern NSString *const TSRegistrationErrorUserInfoHTTPStatus;
extern NSString *const kNSNotificationName_RegistrationStateDidChange;
extern NSString *const kNSNotificationName_LocalNumberDidChange;
@class TSNetworkManager;
@class TSStorageManager;

View File

@ -18,11 +18,12 @@ NS_ASSUME_NONNULL_BEGIN
NSString *const TSRegistrationErrorDomain = @"TSRegistrationErrorDomain";
NSString *const TSRegistrationErrorUserInfoHTTPStatus = @"TSHTTPStatus";
NSString *const kNSNotificationName_RegistrationStateDidChange = @"kNSNotificationName_RegistrationStateDidChange";
NSString *const kNSNotificationName_LocalNumberDidChange = @"kNSNotificationName_LocalNumberDidChange";
@interface TSAccountManager ()
@property (nullable, nonatomic, retain) NSString *phoneNumberAwaitingVerification;
@property (nonatomic, strong, readonly) TSStorageManager *storageManager;
@property (nonatomic, nullable) NSString *phoneNumberAwaitingVerification;
@property (nonatomic, readonly) TSStorageManager *storageManager;
@end
@ -58,6 +59,15 @@ NSString *const kNSNotificationName_RegistrationStateDidChange = @"kNSNotificati
return sharedInstance;
}
- (void)setPhoneNumberAwaitingVerification:(NSString *_Nullable)phoneNumberAwaitingVerification
{
_phoneNumberAwaitingVerification = phoneNumberAwaitingVerification;
[[NSNotificationCenter defaultCenter] postNotificationName:kNSNotificationName_LocalNumberDidChange
object:nil
userInfo:nil];
}
+ (BOOL)isRegistered {
return [TSStorageManager localNumber] ? YES : NO;
}

View File

@ -4,6 +4,7 @@
#import "TSPreKeyManager.h"
#import "NSURLSessionDataTask+StatusCode.h"
#import "OWSIdentityManager.h"
#import "TSNetworkManager.h"
#import "TSRegisterSignedPrekeyRequest.h"
#import "TSStorageHeaders.h"
@ -127,11 +128,11 @@ static const CGFloat kSignedPreKeyUpdateFailureMaxFailureDuration = 10 * 24 * 60
RefreshPreKeysMode modeCopy = mode;
TSStorageManager *storageManager = [TSStorageManager sharedManager];
ECKeyPair *identityKeyPair = [storageManager identityKeyPair];
ECKeyPair *identityKeyPair = [[OWSIdentityManager sharedManager] identityKeyPair];
if (!identityKeyPair) {
[storageManager generateNewIdentityKey];
identityKeyPair = [storageManager identityKeyPair];
[[OWSIdentityManager sharedManager] generateNewIdentityKey];
identityKeyPair = [[OWSIdentityManager sharedManager] identityKeyPair];
// Switch modes if necessary.
modeCopy = RefreshPreKeysMode_SignedAndOneTime;
@ -152,11 +153,12 @@ static const CGFloat kSignedPreKeyUpdateFailureMaxFailureDuration = 10 * 24 * 60
// Store the new one-time keys immediately, before they are sent to the
// service to prevent race conditions and other edge cases.
[storageManager storePreKeyRecords:preKeys];
request = [[TSRegisterPrekeysRequest alloc] initWithPrekeyArray:preKeys
identityKey:[storageManager identityKeyPair].publicKey
signedPreKeyRecord:signedPreKey
preKeyLastResort:lastResortPreKey];
request = [[TSRegisterPrekeysRequest alloc]
initWithPrekeyArray:preKeys
identityKey:identityKeyPair.publicKey
signedPreKeyRecord:signedPreKey
preKeyLastResort:lastResortPreKey];
} else {
description = @"just signed prekey";
request = [[TSRegisterSignedPrekeyRequest alloc] initWithSignedPreKeyRecord:signedPreKey];

View File

@ -1,19 +0,0 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "TSYapDatabaseObject.h"
NS_ASSUME_NONNULL_BEGIN
@interface TSPrivacyPreferences : TSYapDatabaseObject
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)sharedInstance;
@property BOOL shouldBlockOnIdentityChange;
@end
NS_ASSUME_NONNULL_END

View File

@ -1,48 +0,0 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "TSPrivacyPreferences.h"
NS_ASSUME_NONNULL_BEGIN
NSString *const TSPrivacyPreferencesSingletonKey = @"TSPrivacyPreferences";
@implementation TSPrivacyPreferences
+ (instancetype)sharedInstance
{
static TSPrivacyPreferences *sharedInstance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [self fetchObjectWithUniqueID:TSPrivacyPreferencesSingletonKey];
if (!sharedInstance) {
sharedInstance = [[self alloc] initDefault];
}
});
return sharedInstance;
}
- (instancetype)initDefault
{
return [self initWithShouldBlockOnIdentityChange:YES];
}
- (instancetype)initWithShouldBlockOnIdentityChange:(BOOL)shouldBlockOnIdentityChange
{
self = [super initWithUniqueId:TSPrivacyPreferencesSingletonKey];
if (!self) {
return self;
}
_shouldBlockOnIdentityChange = shouldBlockOnIdentityChange;
OWSSingletonAssert();
return self;
}
@end
NS_ASSUME_NONNULL_END

View File

@ -244,13 +244,12 @@ NS_ASSUME_NONNULL_BEGIN
- (NSArray<NSString *> *)textSecureIdentifiers {
__block NSMutableArray *identifiers = [NSMutableArray array];
[[TSStorageManager sharedManager]
.dbConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
for (PhoneNumber *number in self.parsedPhoneNumbers) {
if ([SignalRecipient recipientWithTextSecureIdentifier:number.toE164 withTransaction:transaction]) {
[identifiers addObject:number.toE164];
}
}
[[TSStorageManager sharedManager].dbReadConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
for (PhoneNumber *number in self.parsedPhoneNumbers) {
if ([SignalRecipient recipientWithTextSecureIdentifier:number.toE164 withTransaction:transaction]) {
[identifiers addObject:number.toE164];
}
}
}];
return [identifiers copy];
}

View File

@ -122,11 +122,11 @@ NS_ASSUME_NONNULL_BEGIN
}
NSMutableSet *recipientIds = [NSMutableSet set];
[[TSStorageManager sharedManager]
.dbConnection readWithBlock:^(YapDatabaseReadTransaction *_Nonnull transaction) {
NSArray *allRecipientKeys = [transaction allKeysInCollection:[SignalRecipient collection]];
[recipientIds addObjectsFromArray:allRecipientKeys];
}];
[[TSStorageManager sharedManager].dbReadConnection
readWithBlock:^(YapDatabaseReadTransaction *_Nonnull transaction) {
NSArray *allRecipientKeys = [transaction allKeysInCollection:[SignalRecipient collection]];
[recipientIds addObjectsFromArray:allRecipientKeys];
}];
NSMutableSet<NSString *> *allContacts = [[abPhoneNumbers setByAddingObjectsFromSet:recipientIds] mutableCopy];
@ -135,7 +135,7 @@ NS_ASSUME_NONNULL_BEGIN
[recipientIds minusSet:matchedIds];
// Cleaning up unregistered identifiers
[[TSStorageManager sharedManager].dbConnection
[[TSStorageManager sharedManager].dbReadWriteConnection
readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
for (NSString *identifier in recipientIds) {
SignalRecipient *recipient =
@ -185,23 +185,22 @@ NS_ASSUME_NONNULL_BEGIN
}
// Insert or update contact attributes
[[TSStorageManager sharedManager]
.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
for (NSString *identifier in attributesForIdentifier) {
SignalRecipient *recipient =
[SignalRecipient recipientWithTextSecureIdentifier:identifier withTransaction:transaction];
if (!recipient) {
recipient = [[SignalRecipient alloc] initWithTextSecureIdentifier:identifier
relay:nil];
}
[[TSStorageManager sharedManager].dbReadWriteConnection
readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
for (NSString *identifier in attributesForIdentifier) {
SignalRecipient *recipient =
[SignalRecipient recipientWithTextSecureIdentifier:identifier withTransaction:transaction];
if (!recipient) {
recipient = [[SignalRecipient alloc] initWithTextSecureIdentifier:identifier relay:nil];
}
NSDictionary *attributes = [attributesForIdentifier objectForKey:identifier];
NSDictionary *attributes = [attributesForIdentifier objectForKey:identifier];
recipient.relay = attributes[@"relay"];
recipient.relay = attributes[@"relay"];
[recipient saveWithTransaction:transaction];
}
}];
[recipient saveWithTransaction:transaction];
}
}];
success([NSSet setWithArray:attributesForIdentifier.allKeys]);
}

View File

@ -22,7 +22,6 @@ static NSString *const RPDefaultsKeyPhoneNumberCanonical = @"RPDefaultsKeyPhoneN
NBPhoneNumber *number = [phoneUtil parse:text defaultRegion:regionCode error:&parseError];
if (parseError) {
DDLogDebug(@"Issue while parsing number: %@", [parseError description]);
return nil;
}
@ -165,10 +164,24 @@ static NSString *const RPDefaultsKeyPhoneNumberCanonical = @"RPDefaultsKeyPhoneN
// (ISO 3166-1 alpha-2).
NSNumber *callingCodeForLocalNumber = [[PhoneNumber phoneNumberFromE164:clientPhoneNumber] getCountryCode];
if (callingCodeForLocalNumber != nil) {
tryParsingWithCountryCode([NSString stringWithFormat:@"+%@%@",
callingCodeForLocalNumber,
sanitizedString],
[self defaultRegionCode]);
NSString *callingCodePrefix = [NSString stringWithFormat:@"+%@", callingCodeForLocalNumber];
tryParsingWithCountryCode(
[callingCodePrefix stringByAppendingString:sanitizedString], [self defaultRegionCode]);
// Try to determine what the country code is for the local phone number
// and also try parsing the phone number using that country code if it
// differs from the device's region code.
//
// For example, a French person living in Italy might have an
// Italian phone number but use French region/language for their
// phone. They're likely to have both Italian and French contacts.
NSString *localCountryCode =
[PhoneNumberUtil.sharedUtil probableCountryCodeForCallingCode:callingCodePrefix];
if (localCountryCode && ![localCountryCode isEqualToString:[self defaultRegionCode]]) {
tryParsingWithCountryCode(
[callingCodePrefix stringByAppendingString:sanitizedString], localCountryCode);
}
}
}

View File

@ -85,6 +85,9 @@
NSDictionary *countryCodeComponent = @{NSLocaleCountryCode : countryCode};
NSString *identifier = [NSLocale localeIdentifierFromComponents:countryCodeComponent];
NSString *country = [NSLocale.currentLocale displayNameForKey:NSLocaleIdentifier value:identifier];
if (country.length < 1) {
country = [NSLocale.systemLocale displayNameForKey:NSLocaleIdentifier value:identifier];
}
return country;
}

View File

@ -3,8 +3,8 @@
//
#import "SignalRecipient.h"
#import "OWSIdentityManager.h"
#import "TSStorageHeaders.h"
#import "TSStorageManager+IdentityKeyStore.h"
NS_ASSUME_NONNULL_BEGIN
@ -37,7 +37,7 @@ NS_ASSUME_NONNULL_BEGIN
+ (nullable instancetype)recipientWithTextSecureIdentifier:(NSString *)textSecureIdentifier
{
__block SignalRecipient *recipient;
[self.dbConnection readWithBlock:^(YapDatabaseReadTransaction *_Nonnull transaction) {
[self.dbReadConnection readWithBlock:^(YapDatabaseReadTransaction *_Nonnull transaction) {
recipient = [self recipientWithTextSecureIdentifier:textSecureIdentifier withTransaction:transaction];
}];
return recipient;

View File

@ -36,6 +36,11 @@ NS_ASSUME_NONNULL_BEGIN
*/
- (nullable NSString *)contactIdentifier;
/**
* @returns recipientId for each recipient in the thread
*/
@property (nonatomic, readonly) NSArray<NSString *> *recipientIdentifiers;
#if TARGET_OS_IOS
/**
@ -67,7 +72,6 @@ NS_ASSUME_NONNULL_BEGIN
- (BOOL)hasSafetyNumbers;
- (void)markAllAsRead;
- (void)markAllAsReadWithTransaction:(YapDatabaseReadWriteTransaction *)transaction;
/**

View File

@ -6,6 +6,7 @@
#import "OWSReadTracking.h"
#import "TSDatabaseView.h"
#import "TSIncomingMessage.h"
#import "TSInfoMessage.h"
#import "TSInteraction.h"
#import "TSInvalidIdentityKeyReceivingErrorMessage.h"
#import "TSOutgoingMessage.h"
@ -84,6 +85,12 @@ NS_ASSUME_NONNULL_BEGIN
return nil;
}
- (NSArray<NSString *> *)recipientIdentifiers
{
NSAssert(FALSE, @"Should be implemented in subclasses");
return @[];
}
- (nullable UIImage *)image
{
return nil;
@ -124,7 +131,7 @@ NS_ASSUME_NONNULL_BEGIN
*/
- (void)enumerateInteractionsUsingBlock:(void (^)(TSInteraction *interaction))block
{
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[self.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[self enumerateInteractionsWithTransaction:transaction
usingBlock:^(
TSInteraction *interaction, YapDatabaseReadTransaction *transaction) {
@ -165,7 +172,7 @@ NS_ASSUME_NONNULL_BEGIN
- (NSUInteger)numberOfInteractions
{
__block NSUInteger count;
[[self dbConnection] readWithBlock:^(YapDatabaseReadTransaction *_Nonnull transaction) {
[[self dbReadConnection] readWithBlock:^(YapDatabaseReadTransaction *_Nonnull transaction) {
YapDatabaseViewTransaction *interactionsByThread = [transaction ext:TSMessageDatabaseViewExtensionName];
count = [interactionsByThread numberOfItemsInGroup:self.uniqueId];
}];
@ -183,6 +190,24 @@ NS_ASSUME_NONNULL_BEGIN
return hasUnread;
}
- (NSArray<id<OWSReadTracking>> *)unseenMessagesWithTransaction:(YapDatabaseReadTransaction *)transaction
{
NSMutableArray<id<OWSReadTracking>> *messages = [NSMutableArray new];
[[TSDatabaseView unseenDatabaseViewExtension:transaction]
enumerateRowsInGroup:self.uniqueId
usingBlock:^(
NSString *collection, NSString *key, id object, id metadata, NSUInteger index, BOOL *stop) {
if (![object conformsToProtocol:@protocol(OWSReadTracking)]) {
OWSFail(@"%@ Unexpected object in unseen messages: %@", self.tag, object);
return;
}
[messages addObject:(id<OWSReadTracking>)object];
}];
return [messages copy];
}
- (NSArray<id<OWSReadTracking> > *)unreadMessagesWithTransaction:(YapDatabaseReadTransaction *)transaction
{
NSMutableArray<id<OWSReadTracking> > *messages = [NSMutableArray new];
@ -200,36 +225,45 @@ NS_ASSUME_NONNULL_BEGIN
return [messages copy];
}
- (NSArray<id<OWSReadTracking> > *)unreadMessages
{
__block NSArray<id<OWSReadTracking> > *messages;
[self.dbConnection readWithBlock:^(YapDatabaseReadTransaction *_Nonnull transaction) {
messages = [self unreadMessagesWithTransaction:transaction];
}];
return messages;
}
- (void)markAllAsReadWithTransaction:(YapDatabaseReadWriteTransaction *)transaction
{
for (id<OWSReadTracking> message in [self unreadMessagesWithTransaction:transaction]) {
[message markAsReadLocallyWithTransaction:transaction];
for (id<OWSReadTracking> message in [self unseenMessagesWithTransaction:transaction]) {
[message markAsReadWithTransaction:transaction sendReadReceipt:YES updateExpiration:YES];
}
}
- (void)markAllAsRead
{
for (id<OWSReadTracking> message in [self unreadMessages]) {
[message markAsReadLocally];
}
// Just to be defensive, we'll also check for unread messages.
OWSAssert([self unseenMessagesWithTransaction:transaction].count < 1);
}
- (TSInteraction *) lastInteraction {
__block TSInteraction *last;
[TSStorageManager.sharedManager.dbConnection readWithBlock:^(YapDatabaseReadTransaction *transaction){
[TSStorageManager.sharedManager.dbReadConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
last = [[transaction ext:TSMessageDatabaseViewExtensionName] lastObjectInGroup:self.uniqueId];
}];
return (TSInteraction *)last;
return last;
}
- (TSInteraction *)lastInteractionForInbox
{
__block TSInteraction *last = nil;
[TSStorageManager.sharedManager.dbReadConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
[[transaction ext:TSMessageDatabaseViewExtensionName]
enumerateRowsInGroup:self.uniqueId
withOptions:NSEnumerationReverse
usingBlock:^(
NSString *collection, NSString *key, id object, id metadata, NSUInteger index, BOOL *stop) {
OWSAssert([object isKindOfClass:[TSInteraction class]]);
TSInteraction *interaction = (TSInteraction *)object;
if ([TSThread shouldInteractionAppearInInbox:interaction]) {
last = interaction;
*stop = YES;
}
}];
}];
return last;
}
- (NSDate *)lastMessageDate {
@ -241,16 +275,49 @@ NS_ASSUME_NONNULL_BEGIN
}
- (NSString *)lastMessageLabel {
if (self.lastInteraction == nil) {
TSInteraction *interaction = self.lastInteractionForInbox;
if (interaction == nil) {
return @"";
} else {
return [self lastInteraction].description;
return interaction.description;
}
}
- (void)updateWithLastMessage:(TSInteraction *)lastMessage transaction:(YapDatabaseReadWriteTransaction *)transaction {
NSDate *lastMessageDate = [lastMessage receiptDateForSorting];
// Returns YES IFF the interaction should show up in the inbox as the last message.
+ (BOOL)shouldInteractionAppearInInbox:(TSInteraction *)interaction
{
OWSAssert(interaction);
if (interaction.isDynamicInteraction) {
return NO;
}
if ([interaction isKindOfClass:[TSErrorMessage class]]) {
TSErrorMessage *errorMessage = (TSErrorMessage *)interaction;
if (errorMessage.errorType == TSErrorMessageNonBlockingIdentityChange) {
// Otherwise all group threads with the recipient will percolate to the top of the inbox, even though
// there was no meaningful interaction.
return NO;
}
} else if ([interaction isKindOfClass:[TSInfoMessage class]]) {
TSInfoMessage *infoMessage = (TSInfoMessage *)interaction;
if (infoMessage.messageType == TSInfoMessageVerificationStateChange) {
return NO;
}
}
return YES;
}
- (void)updateWithLastMessage:(TSInteraction *)lastMessage transaction:(YapDatabaseReadWriteTransaction *)transaction {
OWSAssert(lastMessage);
OWSAssert(transaction);
if (![self.class shouldInteractionAppearInInbox:lastMessage]) {
return;
}
NSDate *lastMessageDate = [lastMessage dateForSorting];
if (!_lastMessageDate || [lastMessageDate timeIntervalSinceDate:self.lastMessageDate] > 0) {
_lastMessageDate = lastMessageDate;
@ -330,7 +397,7 @@ NS_ASSUME_NONNULL_BEGIN
- (void)updateWithMutedUntilDate:(NSDate *)mutedUntilDate
{
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[self.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[self applyChangeToSelfAndLatestThread:transaction
changeBlock:^(TSThread *thread) {
[thread setMutedUntilDate:mutedUntilDate];

View File

@ -6,7 +6,7 @@
#import "ContactsManagerProtocol.h"
#import "ContactsUpdater.h"
#import "NotificationsProtocol.h"
#import "TSStorageManager+identityKeyStore.h"
#import "OWSIdentityManager.h"
#import "TextSecureKitEnv.h"
#import <YapDatabase/YapDatabaseConnection.h>
#import <YapDatabase/YapDatabaseTransaction.h>
@ -20,6 +20,8 @@ NS_ASSUME_NONNULL_BEGIN
- (instancetype)initWithContactId:(NSString *)contactId {
NSString *uniqueIdentifier = [[self class] threadIdFromContactId:contactId];
OWSAssert(contactId.length > 0);
self = [super initWithUniqueId:uniqueIdentifier];
return self;
@ -29,7 +31,8 @@ NS_ASSUME_NONNULL_BEGIN
transaction:(YapDatabaseReadWriteTransaction *)transaction
relay:(nullable NSString *)relay
{
OWSAssert(contactId);
OWSAssert(contactId.length > 0);
SignalRecipient *recipient =
[SignalRecipient recipientWithTextSecureIdentifier:contactId withTransaction:transaction];
@ -55,6 +58,8 @@ NS_ASSUME_NONNULL_BEGIN
+ (instancetype)getOrCreateThreadWithContactId:(NSString *)contactId
transaction:(YapDatabaseReadWriteTransaction *)transaction {
OWSAssert(contactId.length > 0);
TSContactThread *thread =
[self fetchObjectWithUniqueID:[self threadIdFromContactId:contactId] transaction:transaction];
@ -68,8 +73,10 @@ NS_ASSUME_NONNULL_BEGIN
+ (instancetype)getOrCreateThreadWithContactId:(NSString *)contactId
{
OWSAssert(contactId.length > 0);
__block TSContactThread *thread;
[[self dbConnection] readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[[self dbReadWriteConnection] readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
thread = [self getOrCreateThreadWithContactId:contactId transaction:transaction];
}];
@ -80,13 +87,18 @@ NS_ASSUME_NONNULL_BEGIN
return [[self class] contactIdFromThreadId:self.uniqueId];
}
- (NSArray<NSString *> *)recipientIdentifiers
{
return @[self.contactIdentifier];
}
- (BOOL)isGroupThread {
return false;
}
- (BOOL)hasSafetyNumbers
{
return !![self.storageManager identityKeyForRecipientId:self.contactIdentifier];
return !![[OWSIdentityManager sharedManager] identityKeyForRecipientId:self.contactIdentifier];
}
- (NSString *)name

View File

@ -1,5 +1,6 @@
// Created by Frederic Jacobs on 16/11/14.
// Copyright (c) 2014 Open Whisper Systems. All rights reserved.
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "TSGroupModel.h"
#import "TSThread.h"
@ -22,6 +23,9 @@ NS_ASSUME_NONNULL_BEGIN
+ (NSString *)threadIdFromGroupId:(NSData *)groupId;
// all group threads containing recipient as a member
+ (NSArray<TSGroupThread *> *)groupThreadsWithRecipientId:(NSString *)recipientId;
- (void)updateAvatarWithAttachmentStream:(TSAttachmentStream *)attachmentStream;
@end

View File

@ -1,10 +1,12 @@
// Created by Frederic Jacobs on 16/11/14.
// Copyright (c) 2014 Open Whisper Systems. All rights reserved.
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "TSGroupThread.h"
#import "NSData+Base64.h"
#import "SignalRecipient.h"
#import "TSAttachmentStream.h"
#import <SignalServiceKit/TSAccountManager.h>
#import <YapDatabase/YapDatabaseConnection.h>
#import <YapDatabase/YapDatabaseTransaction.h>
@ -16,6 +18,13 @@ NS_ASSUME_NONNULL_BEGIN
- (instancetype)initWithGroupModel:(TSGroupModel *)groupModel
{
OWSAssert(groupModel);
OWSAssert(groupModel.groupId.length > 0);
OWSAssert(groupModel.groupMemberIds.count > 0);
for (NSString *recipientId in groupModel.groupMemberIds) {
OWSAssert(recipientId.length > 0);
}
NSString *uniqueIdentifier = [[self class] threadIdFromGroupId:groupModel.groupId];
self = [super initWithUniqueId:uniqueIdentifier];
if (!self) {
@ -29,6 +38,8 @@ NS_ASSUME_NONNULL_BEGIN
- (instancetype)initWithGroupIdData:(NSData *)groupId
{
OWSAssert(groupId.length > 0);
TSGroupModel *groupModel = [[TSGroupModel alloc] initWithTitle:nil memberIds:nil image:nil groupId:groupId];
self = [self initWithGroupModel:groupModel];
@ -41,11 +52,16 @@ NS_ASSUME_NONNULL_BEGIN
+ (instancetype)threadWithGroupModel:(TSGroupModel *)groupModel transaction:(YapDatabaseReadTransaction *)transaction
{
OWSAssert(groupModel);
OWSAssert(groupModel.groupId.length > 0);
return [self fetchObjectWithUniqueID:[self threadIdFromGroupId:groupModel.groupId] transaction:transaction];
}
+ (instancetype)getOrCreateThreadWithGroupIdData:(NSData *)groupId
{
OWSAssert(groupId.length > 0);
TSGroupThread *thread = [self fetchObjectWithUniqueID:[self threadIdFromGroupId:groupId]];
if (!thread) {
thread = [[self alloc] initWithGroupIdData:groupId];
@ -56,6 +72,9 @@ NS_ASSUME_NONNULL_BEGIN
+ (instancetype)getOrCreateThreadWithGroupModel:(TSGroupModel *)groupModel
transaction:(YapDatabaseReadWriteTransaction *)transaction {
OWSAssert(groupModel);
OWSAssert(groupModel.groupId.length > 0);
TSGroupThread *thread =
[self fetchObjectWithUniqueID:[self threadIdFromGroupId:groupModel.groupId] transaction:transaction];
@ -68,8 +87,11 @@ NS_ASSUME_NONNULL_BEGIN
+ (instancetype)getOrCreateThreadWithGroupModel:(TSGroupModel *)groupModel
{
OWSAssert(groupModel);
OWSAssert(groupModel.groupId.length > 0);
__block TSGroupThread *thread;
[[self dbConnection] readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[[self dbReadWriteConnection] readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
thread = [self getOrCreateThreadWithGroupModel:groupModel transaction:transaction];
}];
return thread;
@ -77,14 +99,57 @@ NS_ASSUME_NONNULL_BEGIN
+ (NSString *)threadIdFromGroupId:(NSData *)groupId
{
OWSAssert(groupId.length > 0);
return [TSGroupThreadPrefix stringByAppendingString:[groupId base64EncodedString]];
}
+ (NSData *)groupIdFromThreadId:(NSString *)threadId
{
OWSAssert(threadId.length > 0);
return [NSData dataFromBase64String:[threadId substringWithRange:NSMakeRange(1, threadId.length - 1)]];
}
- (NSArray<NSString *> *)recipientIdentifiers
{
NSMutableArray<NSString *> *groupMemberIds = [self.groupModel.groupMemberIds mutableCopy];
if (groupMemberIds == nil) {
return @[];
}
[groupMemberIds removeObject:[TSAccountManager localNumber]];
return [groupMemberIds copy];
}
// Group and Contact threads share a collection, this is a convenient way to enumerate *just* the group threads
+ (void)enumerateGroupThreadsUsingBlock:(void (^)(TSGroupThread *groupThread, BOOL *stop))block
{
[self enumerateCollectionObjectsUsingBlock:^(id obj, BOOL *stop) {
if ([obj isKindOfClass:[TSGroupThread class]]) {
block((TSGroupThread *)obj, stop);
}
}];
}
// @returns all threads to which the recipient is a member.
//
// @note If this becomes a hotspot we can extract into a YapDB View.
// As is, the number of groups should be small (dozens, *maybe* hundreds), and we only enumerate them upon SN changes.
+ (NSArray<TSGroupThread *> *)groupThreadsWithRecipientId:(NSString *)recipientId
{
NSMutableArray<TSGroupThread *> *groupThreads = [NSMutableArray new];
[self enumerateGroupThreadsUsingBlock:^(TSGroupThread *_Nonnull groupThread, BOOL *_Nonnull stop) {
if ([groupThread.groupModel.groupMemberIds containsObject:recipientId]) {
[groupThreads addObject:groupThread];
}
}];
return [groupThreads copy];
}
- (BOOL)isGroupThread
{
return true;

View File

@ -27,7 +27,7 @@ NS_ASSUME_NONNULL_BEGIN
return self;
}
- (OWSSignalServiceProtosSyncMessage *)buildSyncMessage
- (OWSSignalServiceProtosSyncMessageBuilder *)syncMessageBuilder
{
OWSSignalServiceProtosSyncMessageBlockedBuilder *blockedPhoneNumbersBuilder =
[OWSSignalServiceProtosSyncMessageBlockedBuilder new];
@ -35,7 +35,7 @@ NS_ASSUME_NONNULL_BEGIN
OWSSignalServiceProtosSyncMessageBuilder *syncMessageBuilder = [OWSSignalServiceProtosSyncMessageBuilder new];
[syncMessageBuilder setBlocked:[blockedPhoneNumbersBuilder build]];
return [syncMessageBuilder build];
return syncMessageBuilder;
}
@end

View File

@ -7,10 +7,12 @@
NS_ASSUME_NONNULL_BEGIN
@class SignalAccount;
@class OWSRecipientIdentity;
@interface OWSContactsOutputStream : OWSChunkedOutputStream
- (void)writeSignalAccount:(SignalAccount *)signalAccount;
- (void)writeSignalAccount:(SignalAccount *)signalAccount
recipientIdentity:(nullable OWSRecipientIdentity *)recipientIdentity;
@end

View File

@ -5,6 +5,8 @@
#import "OWSContactsOutputStream.h"
#import "Contact.h"
#import "MIMETypeUtil.h"
#import "NSData+keyVersionByte.h"
#import "OWSRecipientIdentity.h"
#import "OWSSignalServiceProtos.pb.h"
#import "SignalAccount.h"
#import <ProtocolBuffers/CodedOutputStream.h>
@ -14,14 +16,23 @@ NS_ASSUME_NONNULL_BEGIN
@implementation OWSContactsOutputStream
- (void)writeSignalAccount:(SignalAccount *)signalAccount
recipientIdentity:(nullable OWSRecipientIdentity *)recipientIdentity
{
OWSAssert(signalAccount);
OWSAssert(signalAccount.contact);
OWSSignalServiceProtosContactDetailsBuilder *contactBuilder = [OWSSignalServiceProtosContactDetailsBuilder new];
[contactBuilder setName:signalAccount.contact.fullName];
[contactBuilder setNumber:signalAccount.recipientId];
if (recipientIdentity != nil) {
OWSSignalServiceProtosVerifiedBuilder *verifiedBuilder = [OWSSignalServiceProtosVerifiedBuilder new];
verifiedBuilder.destination = recipientIdentity.recipientId;
verifiedBuilder.identityKey = [recipientIdentity.identityKey prependKeyType];
verifiedBuilder.state = OWSVerificationStateToProtoState(recipientIdentity.verificationState);
contactBuilder.verifiedBuilder = verifiedBuilder;
}
NSData *avatarPng;
if (signalAccount.contact.image) {
OWSSignalServiceProtosContactDetailsAvatarBuilder *avatarBuilder =

View File

@ -1,4 +1,6 @@
// Copyright © 2016 Open Whisper Systems. All rights reserved.
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "OWSReadReceipt.h"
#import <YapDatabase/YapDatabase.h>
@ -104,7 +106,7 @@ NSString *const OWSReadReceiptColumnSenderId = @"senderId";
stringWithFormat:@"WHERE %@ = ? AND %@ = ?", OWSReadReceiptColumnSenderId, OWSReadReceiptColumnTimestamp];
YapDatabaseQuery *query = [YapDatabaseQuery queryWithFormat:queryFormat, senderId, @(timestamp)];
[[self dbConnection] readWithBlock:^(YapDatabaseReadTransaction *_Nonnull transaction) {
[[self dbReadConnection] readWithBlock:^(YapDatabaseReadTransaction *_Nonnull transaction) {
[[transaction ext:OWSReadReceiptIndexOnSenderIdAndTimestamp]
enumerateKeysAndObjectsMatchingQuery:query
usingBlock:^(NSString *collection, NSString *key, id object, BOOL *stop) {

View File

@ -1,4 +1,6 @@
// Copyright © 2016 Open Whisper Systems. All rights reserved.
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "OWSReadReceiptsMessage.h"
#import "OWSReadReceipt.h"
@ -26,7 +28,7 @@ NS_ASSUME_NONNULL_BEGIN
return self;
}
- (OWSSignalServiceProtosSyncMessage *)buildSyncMessage
- (OWSSignalServiceProtosSyncMessageBuilder *)syncMessageBuilder
{
OWSSignalServiceProtosSyncMessageBuilder *syncMessageBuilder = [OWSSignalServiceProtosSyncMessageBuilder new];
for (OWSReadReceipt *readReceipt in self.readReceipts) {
@ -37,7 +39,7 @@ NS_ASSUME_NONNULL_BEGIN
[syncMessageBuilder addRead:[readProtoBuilder build]];
}
return [syncMessageBuilder build];
return syncMessageBuilder;
}
@end

View File

@ -7,7 +7,10 @@
#import "OWSReadReceipt.h"
#import "OWSSignalServiceProtos.pb.h"
#import "TSContactThread.h"
#import "TSDatabaseView.h"
#import "TSIncomingMessage.h"
#import "TSStorageManager.h"
#import <YapDatabase/YapDatabaseConnection.h>
NS_ASSUME_NONNULL_BEGIN
@ -82,14 +85,61 @@ NSString *const OWSReadReceiptsProcessorMarkedMessageAsReadNotification =
TSIncomingMessage *message =
[TSIncomingMessage findMessageWithAuthorId:readReceipt.senderId timestamp:readReceipt.timestamp];
if (message) {
[message markAsReadFromReadReceipt];
[OWSDisappearingMessagesJob setExpirationForMessage:message expirationStartedAt:readReceipt.timestamp];
OWSAssert(message.thread);
// Mark all unread messages in this thread that are older than message specified in the read
// receipt.
NSMutableArray<id<OWSReadTracking>> *interactionsToMarkAsRead = [NSMutableArray new];
// Always mark the message specified by the read receipt as read.
[interactionsToMarkAsRead addObject:message];
[self.storageManager.dbReadWriteConnection readWriteWithBlock:^(
YapDatabaseReadWriteTransaction *transaction) {
[[TSDatabaseView unseenDatabaseViewExtension:transaction]
enumerateRowsInGroup:message.uniqueThreadId
usingBlock:^(NSString *collection,
NSString *key,
id object,
id metadata,
NSUInteger index,
BOOL *stop) {
TSInteraction *interaction = object;
if (interaction.timestampForSorting > message.timestampForSorting) {
*stop = YES;
return;
}
id<OWSReadTracking> possiblyRead = (id<OWSReadTracking>)object;
OWSAssert(!possiblyRead.read);
[interactionsToMarkAsRead addObject:possiblyRead];
}];
for (id<OWSReadTracking> interaction in interactionsToMarkAsRead) {
// * Don't send a read receipt in response to a read receipt.
// * Don't update expiration; we'll do that in the next statement.
[interaction markAsReadWithTransaction:transaction sendReadReceipt:NO updateExpiration:NO];
if ([interaction isKindOfClass:[TSMessage class]]) {
TSMessage *otherMessage = (TSMessage *)interaction;
// Update expiration using the timestamp from the readReceipt.
[OWSDisappearingMessagesJob setExpirationForMessage:otherMessage
expirationStartedAt:readReceipt.timestamp];
// Fire event that will cancel any pending notifications for this message.
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter]
postNotificationName:OWSReadReceiptsProcessorMarkedMessageAsReadNotification
object:otherMessage];
});
}
}
}];
// If it was previously saved, no need to keep it around any longer.
[readReceipt remove];
[[NSNotificationCenter defaultCenter]
postNotificationName:OWSReadReceiptsProcessorMarkedMessageAsReadNotification
object:message];
} else {
DDLogDebug(@"%@ Received read receipt for an unknown message. Saving it for later.", self.tag);
[readReceipt save];

View File

@ -0,0 +1,24 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "OWSOutgoingSyncMessage.h"
#import "OWSRecipientIdentity.h"
NS_ASSUME_NONNULL_BEGIN
@interface OWSVerificationStateSyncMessage : OWSOutgoingSyncMessage
- (instancetype)initWithVerificationState:(OWSVerificationState)verificationState
identityKey:(NSData *)identityKey
verificationForRecipientId:(NSString *)recipientId;
// This is a clunky name, but we want to differentiate it from `recipientIdentifier` inherited from `TSOutgoingMessage`
@property (nonatomic, readonly) NSString *verificationForRecipientId;
@property (nonatomic, readonly) size_t paddingBytesLength;
@property (nonatomic, readonly) size_t unpaddedVerifiedLength;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,101 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "OWSVerificationStateSyncMessage.h"
#import "Cryptography.h"
#import "OWSIdentityManager.h"
#import "OWSSignalServiceProtos.pb.h"
NS_ASSUME_NONNULL_BEGIN
#pragma mark -
@interface OWSVerificationStateSyncMessage ()
@property (nonatomic, readonly) OWSVerificationState verificationState;
@property (nonatomic, readonly) NSData *identityKey;
@end
#pragma mark -
@implementation OWSVerificationStateSyncMessage
- (instancetype)initWithVerificationState:(OWSVerificationState)verificationState
identityKey:(NSData *)identityKey
verificationForRecipientId:(NSString *)verificationForRecipientId
{
OWSAssert(identityKey.length == kIdentityKeyLength);
OWSAssert(verificationForRecipientId.length > 0);
// we only sync user's marking as un/verified. Never sync the conflicted state, the sibling device
// will figure that out on it's own.
OWSAssert(verificationState != OWSVerificationStateNoLongerVerified);
self = [super init];
if (!self) {
return self;
}
_verificationState = verificationState;
_identityKey = identityKey;
_verificationForRecipientId = verificationForRecipientId;
// This sync message should be 1-512 bytes longer than the corresponding NullMessage
// we store this values so the corresponding NullMessage can subtract it from the total length.
_paddingBytesLength = arc4random_uniform(512) + 1;
return self;
}
- (OWSSignalServiceProtosSyncMessageBuilder *)syncMessageBuilder
{
OWSAssert(self.identityKey.length == kIdentityKeyLength);
OWSAssert(self.verificationForRecipientId.length > 0);
// we only sync user's marking as un/verified. Never sync the conflicted state, the sibling device
// will figure that out on it's own.
OWSAssert(self.verificationState != OWSVerificationStateNoLongerVerified);
OWSSignalServiceProtosSyncMessageBuilder *syncMessageBuilder = [OWSSignalServiceProtosSyncMessageBuilder new];
OWSSignalServiceProtosVerifiedBuilder *verifiedBuilder = [OWSSignalServiceProtosVerifiedBuilder new];
verifiedBuilder.destination = self.verificationForRecipientId;
verifiedBuilder.identityKey = self.identityKey;
verifiedBuilder.state = OWSVerificationStateToProtoState(self.verificationState);
OWSAssert(self.paddingBytesLength != 0);
// We add the same amount of padding in the VerificationStateSync message and it's coresponding NullMessage so that
// the sync message is indistinguishable from an outgoing Sent transcript corresponding to the NullMessage. We pad
// the NullMessage so as to obscure it's content. The sync message (like all sync messages) will be *additionally*
// padded by the superclass while being sent. The end result is we send a NullMessage of a non-distinct size, and a
// verification sync which is ~1-512 bytes larger then that.
verifiedBuilder.nullMessage = [Cryptography generateRandomBytes:self.paddingBytesLength];
syncMessageBuilder.verifiedBuilder = verifiedBuilder;
return syncMessageBuilder;
}
- (size_t)unpaddedVerifiedLength
{
OWSAssert(self.identityKey.length == kIdentityKeyLength);
OWSAssert(self.verificationForRecipientId.length > 0);
// we only sync user's marking as un/verified. Never sync the conflicted state, the sibling device
// will figure that out on it's own.
OWSAssert(self.verificationState != OWSVerificationStateNoLongerVerified);
OWSSignalServiceProtosVerifiedBuilder *verifiedBuilder = [OWSSignalServiceProtosVerifiedBuilder new];
verifiedBuilder.destination = self.verificationForRecipientId;
verifiedBuilder.identityKey = self.identityKey;
verifiedBuilder.state = OWSVerificationStateToProtoState(self.verificationState);
return [verifiedBuilder build].data.length;
}
@end
NS_ASSUME_NONNULL_END

View File

@ -92,7 +92,7 @@ static const CGFloat kAttachmentDownloadProgressTheta = 0.001f;
digest:digest
contentType:attachmentProto.contentType
relay:relay
filename:attachmentProto.fileName
sourceFilename:attachmentProto.fileName
attachmentType:attachmentType];
[attachmentIds addObject:pointer.uniqueId];

View File

@ -31,20 +31,20 @@ typedef NS_ENUM(NSUInteger, TSAttachmentType) {
@property (atomic, readwrite) BOOL isDownloaded;
@property (nonatomic) TSAttachmentType attachmentType;
// Represents the "nominal" filename sent or received in the protos,
// Represents the "source" filename sent or received in the protos,
// not the filename on disk.
@property (nonatomic, readonly, nullable) NSString *filename;
@property (nonatomic, readonly, nullable) NSString *sourceFilename;
// This constructor is used for new instances of TSAttachmentPointer,
// i.e. undownloaded incoming attachments.
- (instancetype)initWithServerId:(UInt64)serverId
encryptionKey:(NSData *)encryptionKey
contentType:(NSString *)contentType
filename:(nullable NSString *)filename;
sourceFilename:(nullable NSString *)sourceFilename;
// This constructor is used for new instances of TSAttachmentStream
// that represent new, un-uploaded outgoing attachments.
- (instancetype)initWithContentType:(NSString *)contentType filename:(nullable NSString *)filename;
- (instancetype)initWithContentType:(NSString *)contentType sourceFilename:(nullable NSString *)sourceFilename;
// This constructor is used for new instances of TSAttachmentStream
// that represent downloaded incoming attachments.

View File

@ -22,7 +22,7 @@ NSUInteger const TSAttachmentSchemaVersion = 3;
- (instancetype)initWithServerId:(UInt64)serverId
encryptionKey:(NSData *)encryptionKey
contentType:(NSString *)contentType
filename:(nullable NSString *)filename
sourceFilename:(nullable NSString *)sourceFilename
{
self = [super init];
if (!self) {
@ -33,14 +33,14 @@ NSUInteger const TSAttachmentSchemaVersion = 3;
_encryptionKey = encryptionKey;
_contentType = contentType;
_attachmentSchemaVersion = TSAttachmentSchemaVersion;
_filename = filename;
_sourceFilename = sourceFilename;
return self;
}
// This constructor is used for new instances of TSAttachmentStream
// that represent new, un-uploaded outgoing attachments.
- (instancetype)initWithContentType:(NSString *)contentType filename:(nullable NSString *)filename
- (instancetype)initWithContentType:(NSString *)contentType sourceFilename:(nullable NSString *)sourceFilename
{
self = [super init];
if (!self) {
@ -49,7 +49,7 @@ NSUInteger const TSAttachmentSchemaVersion = 3;
_contentType = contentType;
_attachmentSchemaVersion = TSAttachmentSchemaVersion;
_filename = filename;
_sourceFilename = sourceFilename;
return self;
}
@ -67,7 +67,7 @@ NSUInteger const TSAttachmentSchemaVersion = 3;
_serverId = pointer.serverId;
_encryptionKey = pointer.encryptionKey;
_contentType = pointer.contentType;
_filename = pointer.filename;
_sourceFilename = pointer.sourceFilename;
_attachmentSchemaVersion = TSAttachmentSchemaVersion;
return self;
@ -85,6 +85,12 @@ NSUInteger const TSAttachmentSchemaVersion = 3;
_attachmentSchemaVersion = TSAttachmentSchemaVersion;
}
if (!_sourceFilename) {
// renamed _filename to _sourceFilename
_sourceFilename = [coder decodeObjectForKey:@"filename"];
OWSAssert(!_sourceFilename || [_sourceFilename isKindOfClass:[NSString class]]);
}
return self;
}
@ -107,9 +113,9 @@ NSUInteger const TSAttachmentSchemaVersion = 3;
return [NSString stringWithFormat:@"📽 %@", attachmentString];
} else if ([MIMETypeUtil isAudio:self.contentType]) {
// a missing filename is the legacy way to determin if an audio attachment is a voice note vs. other arbitrary
// audio attachments.
if (self.isVoiceMessage || !self.filename || self.filename.length == 0) {
// a missing filename is the legacy way to determine if an audio attachment is
// a voice note vs. other arbitrary audio attachments.
if (self.isVoiceMessage || !self.sourceFilename || self.sourceFilename.length == 0) {
attachmentString = NSLocalizedString(@"ATTACHMENT_TYPE_VOICE_MESSAGE",
@"Short text label for a voice message attachment, used for thread preview and on lockscreen");
return [NSString stringWithFormat:@"🎤 %@", attachmentString];

View File

@ -24,7 +24,7 @@ typedef NS_ENUM(NSUInteger, TSAttachmentPointerState) {
digest:(nullable NSData *)digest
contentType:(NSString *)contentType
relay:(NSString *)relay
filename:(nullable NSString *)filename
sourceFilename:(nullable NSString *)sourceFilename
attachmentType:(TSAttachmentType)attachmentType NS_DESIGNATED_INITIALIZER;
@property (nonatomic, readonly) NSString *relay;

View File

@ -30,10 +30,10 @@ NS_ASSUME_NONNULL_BEGIN
digest:(nullable NSData *)digest
contentType:(NSString *)contentType
relay:(NSString *)relay
filename:(nullable NSString *)filename
sourceFilename:(nullable NSString *)sourceFilename
attachmentType:(TSAttachmentType)attachmentType
{
self = [super initWithServerId:serverId encryptionKey:key contentType:contentType filename:filename];
self = [super initWithServerId:serverId encryptionKey:key contentType:contentType sourceFilename:sourceFilename];
if (!self) {
return self;
}

View File

@ -10,12 +10,15 @@
NS_ASSUME_NONNULL_BEGIN
@class TSAttachmentPointer;
@class YapDatabaseReadWriteTransaction;
@interface TSAttachmentStream : TSAttachment
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithContentType:(NSString *)contentType filename:(NSString *)filename NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithContentType:(NSString *)contentType
sourceFilename:(nullable NSString *)sourceFilename NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithPointer:(TSAttachmentPointer *)pointer NS_DESIGNATED_INITIALIZER;
- (nullable instancetype)initWithCoder:(NSCoder *)coder NS_DESIGNATED_INITIALIZER;
// Though now required, `digest` may be null for pre-existing records or from
// messages received from other clients
@ -24,6 +27,8 @@ NS_ASSUME_NONNULL_BEGIN
// This only applies for attachments being uploaded.
@property (atomic) BOOL isUploaded;
@property (nonatomic, readonly) NSDate *creationTimestamp;
#if TARGET_OS_IPHONE
- (nullable UIImage *)image;
#endif
@ -32,14 +37,21 @@ NS_ASSUME_NONNULL_BEGIN
- (BOOL)isImage;
- (BOOL)isVideo;
- (BOOL)isAudio;
- (nullable NSString *)filePath;
- (nullable NSURL *)mediaURL;
- (nullable NSString *)filePath;
- (nullable NSData *)readDataFromFileWithError:(NSError **)error;
- (BOOL)writeData:(NSData *)data error:(NSError **)error;
+ (void)deleteAttachments;
+ (NSString *)attachmentsFolder;
+ (NSUInteger)numberOfItemsInAttachmentsFolder;
- (CGSize)imageSizeWithTransaction:(YapDatabaseReadWriteTransaction *)transaction;
- (CGSize)imageSizeWithoutTransaction;
- (CGFloat)audioDurationSecondsWithTransaction:(YapDatabaseReadWriteTransaction *)transaction;
- (CGFloat)audioDurationSecondsWithoutTransaction;
@end

View File

@ -6,15 +6,32 @@
#import "MIMETypeUtil.h"
#import "TSAttachmentPointer.h"
#import <AVFoundation/AVFoundation.h>
#import <ImageIO/ImageIO.h>
#import <YapDatabase/YapDatabase.h>
#import <YapDatabase/YapDatabaseTransaction.h>
NS_ASSUME_NONNULL_BEGIN
@interface TSAttachmentStream ()
// We only want to generate the file path for this attachment once, so that
// changes in the file path generation logic don't break existing attachments.
@property (nullable, nonatomic) NSString *localRelativeFilePath;
// These properties should only be accessed on the main thread.
@property (nullable, nonatomic) NSNumber *cachedImageWidth;
@property (nullable, nonatomic) NSNumber *cachedImageHeight;
@property (nullable, nonatomic) NSNumber *cachedAudioDurationSeconds;
@end
#pragma mark -
@implementation TSAttachmentStream
- (instancetype)initWithContentType:(NSString *)contentType filename:(NSString *)filename
- (instancetype)initWithContentType:(NSString *)contentType sourceFilename:(nullable NSString *)sourceFilename
{
self = [super initWithContentType:contentType filename:filename];
self = [super initWithContentType:contentType sourceFilename:sourceFilename];
if (!self) {
return self;
}
@ -24,6 +41,9 @@ NS_ASSUME_NONNULL_BEGIN
// state, but this constructor is used only for new outgoing
// attachments which haven't been uploaded yet.
_isUploaded = NO;
_creationTimestamp = [NSDate new];
[self ensureFilePath];
return self;
}
@ -43,6 +63,27 @@ NS_ASSUME_NONNULL_BEGIN
// attachments which don't need to be uploaded.
_isUploaded = YES;
self.attachmentType = pointer.attachmentType;
_creationTimestamp = [NSDate new];
[self ensureFilePath];
return self;
}
- (nullable instancetype)initWithCoder:(NSCoder *)coder
{
self = [super initWithCoder:coder];
if (!self) {
return self;
}
// OWS105AttachmentFilePaths will ensure the file path is saved if necessary.
[self ensureFilePath];
// OWS105AttachmentFilePaths will ensure the creation timestamp is saved if necessary.
if (!_creationTimestamp) {
_creationTimestamp = [NSDate new];
}
return self;
}
@ -60,82 +101,137 @@ NS_ASSUME_NONNULL_BEGIN
}
}
#pragma mark - TSYapDatabaseModel overrides
- (void)removeWithTransaction:(YapDatabaseReadWriteTransaction *)transaction
- (void)ensureFilePath
{
[super removeWithTransaction:transaction];
[self removeFile];
if (self.localRelativeFilePath) {
return;
}
NSString *attachmentsFolder = [[self class] attachmentsFolder];
NSString *filePath = [MIMETypeUtil filePathForAttachment:self.uniqueId
ofMIMEType:self.contentType
sourceFilename:self.sourceFilename
inFolder:attachmentsFolder];
if (!filePath) {
DDLogError(@"%@ Could not generate path for attachment.", self.tag);
OWSAssert(0);
return;
}
if (![filePath hasPrefix:attachmentsFolder]) {
DDLogError(@"%@ Attachment paths should all be in the attachments folder.", self.tag);
OWSAssert(0);
return;
}
NSString *localRelativeFilePath = [filePath substringFromIndex:attachmentsFolder.length];
if (localRelativeFilePath.length < 1) {
DDLogError(@"%@ Empty local relative attachment paths.", self.tag);
OWSAssert(0);
return;
}
self.localRelativeFilePath = localRelativeFilePath;
OWSAssert(self.filePath);
}
#pragma mark - File Management
- (nullable NSData *)readDataFromFileWithError:(NSError **)error
{
return [NSData dataWithContentsOfFile:self.filePath options:0 error:error];
*error = nil;
NSString *_Nullable filePath = self.filePath;
if (!filePath) {
DDLogError(@"%@ Missing path for attachment.", self.tag);
OWSAssert(0);
return nil;
}
return [NSData dataWithContentsOfFile:filePath options:0 error:error];
}
- (BOOL)writeData:(NSData *)data error:(NSError **)error
{
DDLogInfo(@"%@ Created file at %@", self.tag, self.filePath);
return [data writeToFile:self.filePath options:0 error:error];
*error = nil;
NSString *_Nullable filePath = self.filePath;
if (!filePath) {
DDLogError(@"%@ Missing path for attachment.", self.tag);
OWSAssert(0);
return NO;
}
DDLogInfo(@"%@ Writing attachment to file: %@", self.tag, filePath);
return [data writeToFile:filePath options:0 error:error];
}
+ (NSString *)attachmentsFolder
{
NSString *documentsPath =
[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
NSString *attachmentFolder = [documentsPath stringByAppendingFormat:@"/Attachments"];
static NSString *attachmentsFolder = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSString *documentsPath =
[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
attachmentsFolder = [documentsPath stringByAppendingFormat:@"/Attachments"];
NSError *error = nil;
[[NSFileManager defaultManager] createDirectoryAtPath:attachmentFolder
withIntermediateDirectories:YES
attributes:nil
error:&error];
if (error) {
DDLogError(@"Failed to create attachments directory: %@", error);
}
BOOL isDirectory;
BOOL exists = [[NSFileManager defaultManager] fileExistsAtPath:attachmentsFolder isDirectory:&isDirectory];
if (exists) {
OWSAssert(isDirectory);
return attachmentFolder;
}
+ (NSUInteger)numberOfItemsInAttachmentsFolder
{
NSError *error;
NSUInteger count =
[[[NSFileManager defaultManager] contentsOfDirectoryAtPath:[self attachmentsFolder] error:&error] count];
if (error) {
DDLogError(@"Unable to count attachments in attachments folder. Error: %@", error);
}
return count;
DDLogInfo(@"Attachments directory already exists");
} else {
NSError *error = nil;
[[NSFileManager defaultManager] createDirectoryAtPath:attachmentsFolder
withIntermediateDirectories:YES
attributes:nil
error:&error];
if (error) {
DDLogError(@"Failed to create attachments directory: %@", error);
}
}
});
return attachmentsFolder;
}
- (nullable NSString *)filePath
{
return [MIMETypeUtil filePathForAttachment:self.uniqueId
ofMIMEType:self.contentType
filename:self.filename
inFolder:[[self class] attachmentsFolder]];
if (!self.localRelativeFilePath) {
OWSAssert(0);
return nil;
}
return [[[self class] attachmentsFolder] stringByAppendingPathComponent:self.localRelativeFilePath];
}
- (nullable NSURL *)mediaURL
{
NSString *filePath = self.filePath;
return filePath ? [NSURL fileURLWithPath:filePath] : nil;
NSString *_Nullable filePath = self.filePath;
if (!filePath) {
DDLogError(@"%@ Missing path for attachment.", self.tag);
OWSAssert(0);
return nil;
}
return [NSURL fileURLWithPath:filePath];
}
- (void)removeFile
- (void)removeFileWithTransaction:(YapDatabaseReadWriteTransaction *)transaction
{
NSString *_Nullable filePath = self.filePath;
if (!filePath) {
DDLogError(@"%@ Missing path for attachment.", self.tag);
OWSAssert(0);
return;
}
NSError *error;
[[NSFileManager defaultManager] removeItemAtPath:[self filePath] error:&error];
[[NSFileManager defaultManager] removeItemAtPath:filePath error:&error];
if (error) {
DDLogError(@"%@ remove file errored with: %@", self.tag, error);
}
}
- (void)removeWithTransaction:(YapDatabaseReadWriteTransaction *)transaction
{
[super removeWithTransaction:transaction];
[self removeFileWithTransaction:transaction];
}
- (BOOL)isAnimated {
return [MIMETypeUtil isAnimated:self.contentType];
}
@ -154,17 +250,26 @@ NS_ASSUME_NONNULL_BEGIN
- (nullable UIImage *)image
{
if ([self isVideo] || [self isAudio]) {
if ([self isVideo]) {
return [self videoThumbnail];
} else if ([self isImage] || [self isAnimated]) {
NSURL *_Nullable mediaUrl = [self mediaURL];
if (!mediaUrl) {
return nil;
}
return [UIImage imageWithData:[NSData dataWithContentsOfURL:mediaUrl]];
} else {
// [self isAnimated] || [self isImage]
return [UIImage imageWithData:[NSData dataWithContentsOfURL:[self mediaURL]]];
return nil;
}
}
- (nullable UIImage *)videoThumbnail
{
AVURLAsset *asset = [[AVURLAsset alloc] initWithURL:[NSURL fileURLWithPath:self.filePath] options:nil];
NSURL *_Nullable mediaUrl = [self mediaURL];
if (!mediaUrl) {
return nil;
}
AVURLAsset *asset = [[AVURLAsset alloc] initWithURL:mediaUrl options:nil];
AVAssetImageGenerator *generate = [[AVAssetImageGenerator alloc] initWithAsset:asset];
generate.appliesPreferredTrackTransform = YES;
NSError *err = NULL;
@ -176,10 +281,191 @@ NS_ASSUME_NONNULL_BEGIN
+ (void)deleteAttachments
{
NSError *error;
[[NSFileManager defaultManager] removeItemAtPath:[self attachmentsFolder] error:&error];
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *fileURL = [NSURL fileURLWithPath:self.attachmentsFolder];
NSArray<NSURL *> *contents =
[fileManager contentsOfDirectoryAtURL:fileURL includingPropertiesForKeys:nil options:0 error:&error];
if (error) {
DDLogError(@"Failed to delete attachment folder with error: %@", error.debugDescription);
OWSFail(@"failed to get contents of attachments folder: %@ with error: %@", self.attachmentsFolder, error);
return;
}
for (NSURL *url in contents) {
NSError *deletionError;
[fileManager removeItemAtURL:url error:&deletionError];
if (deletionError) {
OWSFail(@"failed to remove item at path: %@ with error: %@", url, deletionError);
// continue to try to delete remaining items.
}
}
return;
}
- (CGSize)calculateImageSize
{
if ([self isVideo]) {
return [self videoThumbnail].size;
} else if ([self isImage] || [self isAnimated]) {
NSURL *_Nullable mediaUrl = [self mediaURL];
if (!mediaUrl) {
return CGSizeZero;
}
// With CGImageSource we avoid loading the whole image into memory.
CGImageSourceRef source = CGImageSourceCreateWithURL((CFURLRef)mediaUrl, NULL);
if (!source) {
OWSAssert(0);
return CGSizeZero;
}
NSDictionary *options = @{
(NSString *)kCGImageSourceShouldCache : @(NO),
};
NSDictionary *properties
= (__bridge_transfer NSDictionary *)CGImageSourceCopyPropertiesAtIndex(source, 0, (CFDictionaryRef)options);
CGSize imageSize = CGSizeZero;
if (properties) {
NSNumber *width = properties[(NSString *)kCGImagePropertyPixelWidth];
NSNumber *height = properties[(NSString *)kCGImagePropertyPixelHeight];
if (width && height) {
imageSize = CGSizeMake(width.floatValue, height.floatValue);
} else {
OWSAssert(0);
}
}
CFRelease(source);
return imageSize;
} else {
return CGSizeZero;
}
}
- (CGSize)ensureCachedImageSizeWithTransaction:(YapDatabaseReadWriteTransaction *_Nullable)transaction
{
OWSAssert([NSThread isMainThread]);
if (self.cachedImageWidth && self.cachedImageHeight) {
return CGSizeMake(self.cachedImageWidth.floatValue, self.cachedImageHeight.floatValue);
}
CGSize imageSize = [self calculateImageSize];
self.cachedImageWidth = @(imageSize.width);
self.cachedImageHeight = @(imageSize.height);
void (^updateDataStore)() = ^(YapDatabaseReadWriteTransaction *transaction) {
OWSAssert(transaction);
NSString *collection = [[self class] collection];
TSAttachmentStream *latestInstance = [transaction objectForKey:self.uniqueId inCollection:collection];
if (latestInstance) {
latestInstance.cachedImageWidth = @(imageSize.width);
latestInstance.cachedImageHeight = @(imageSize.height);
[latestInstance saveWithTransaction:transaction];
} else {
// This message has not yet been saved; do nothing.
OWSAssert(0);
}
};
if (transaction) {
updateDataStore(transaction);
} else {
[self.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
updateDataStore(transaction);
}];
}
return imageSize;
}
- (CGSize)imageSizeWithTransaction:(YapDatabaseReadWriteTransaction *)transaction
{
OWSAssert([NSThread isMainThread]);
OWSAssert(transaction);
return [self ensureCachedImageSizeWithTransaction:transaction];
}
- (CGSize)imageSizeWithoutTransaction
{
OWSAssert([NSThread isMainThread]);
return [self ensureCachedImageSizeWithTransaction:nil];
}
- (CGFloat)calculateAudioDurationSeconds
{
OWSAssert([NSThread isMainThread]);
OWSAssert([self isAudio]);
NSError *error;
AVAudioPlayer *audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:self.mediaURL error:&error];
if (error && [error.domain isEqualToString:NSOSStatusErrorDomain]
&& (error.code == kAudioFileInvalidFileError || error.code == kAudioFileStreamError_InvalidFile)) {
// Ignore "invalid audio file" errors.
return 0.f;
}
OWSAssert(!error);
if (!error) {
return (CGFloat)[audioPlayer duration];
} else {
return 0;
}
}
- (CGFloat)ensureCachedAudioDurationSecondsWithTransaction:(YapDatabaseReadWriteTransaction *_Nullable)transaction
{
OWSAssert([NSThread isMainThread]);
if (self.cachedAudioDurationSeconds) {
return self.cachedAudioDurationSeconds.floatValue;
}
CGFloat audioDurationSeconds = [self calculateAudioDurationSeconds];
self.cachedAudioDurationSeconds = @(audioDurationSeconds);
void (^updateDataStore)() = ^(YapDatabaseReadWriteTransaction *transaction) {
OWSAssert(transaction);
NSString *collection = [[self class] collection];
TSAttachmentStream *latestInstance = [transaction objectForKey:self.uniqueId inCollection:collection];
if (latestInstance) {
latestInstance.cachedAudioDurationSeconds = @(audioDurationSeconds);
[latestInstance saveWithTransaction:transaction];
} else {
// This message has not yet been saved; do nothing.
OWSAssert(0);
}
};
if (transaction) {
updateDataStore(transaction);
} else {
[self.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
updateDataStore(transaction);
}];
}
return audioDurationSeconds;
}
- (CGFloat)audioDurationSecondsWithTransaction:(YapDatabaseReadWriteTransaction *)transaction
{
OWSAssert([NSThread isMainThread]);
OWSAssert(transaction);
return [self ensureCachedAudioDurationSecondsWithTransaction:transaction];
}
- (CGFloat)audioDurationSecondsWithoutTransaction
{
OWSAssert([NSThread isMainThread]);
return [self ensureCachedAudioDurationSecondsWithTransaction:nil];
}
#pragma mark - Logging

View File

@ -1,4 +1,6 @@
// Copyright © 2016 Open Whisper Systems. All rights reserved.
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "OWSOutgoingSentMessageTranscript.h"
#import "OWSSignalServiceProtos.pb.h"
@ -37,7 +39,7 @@ NS_ASSUME_NONNULL_BEGIN
return self;
}
- (OWSSignalServiceProtosSyncMessage *)buildSyncMessage
- (OWSSignalServiceProtosSyncMessageBuilder *)syncMessageBuilder
{
OWSSignalServiceProtosSyncMessageBuilder *syncMessageBuilder = [OWSSignalServiceProtosSyncMessageBuilder new];
@ -49,7 +51,7 @@ NS_ASSUME_NONNULL_BEGIN
[syncMessageBuilder setSentBuilder:sentBuilder];
return [syncMessageBuilder build];
return syncMessageBuilder;
}
@end

View File

@ -1,7 +1,10 @@
// Copyright © 2016 Open Whisper Systems. All rights reserved.
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "OWSOutgoingSyncMessage.h"
#import "OWSSignalServiceProtos.pb.h"
#import "Cryptography.h"
NS_ASSUME_NONNULL_BEGIN
@ -19,18 +22,22 @@ NS_ASSUME_NONNULL_BEGIN
return NO;
}
- (BOOL)isLegacyMessage
{
return NO;
}
// This method should not be overridden, since we want to add random padding to *every* sync message
- (OWSSignalServiceProtosSyncMessage *)buildSyncMessage
{
NSAssert(NO, @"buildSyncMessage must be overridden in subclass");
OWSSignalServiceProtosSyncMessageBuilder *builder = [self syncMessageBuilder];
// Add a random 1-512 bytes to obscure sync message type
size_t paddingBytesLength = arc4random_uniform(512) + 1;
builder.padding = [Cryptography generateRandomBytes:paddingBytesLength];
return [builder build];
}
// e.g.
OWSSignalServiceProtosSyncMessageBuilder *syncMessageBuilder = [OWSSignalServiceProtosSyncMessageBuilder new];
return [syncMessageBuilder build];
- (OWSSignalServiceProtosSyncMessageBuilder *)syncMessageBuilder
{
OWSFail(@"Abstract method should be overridden in subclass.");
return [OWSSignalServiceProtosSyncMessageBuilder new];
}
- (NSData *)buildPlainTextData

View File

@ -1,4 +1,6 @@
// Copyright © 2016 Open Whisper Systems. All rights reserved.
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "OWSOutgoingSyncMessage.h"
@ -6,10 +8,13 @@ NS_ASSUME_NONNULL_BEGIN
@class YapDatabaseReadWriteTransaction;
@protocol ContactsManagerProtocol;
@class OWSIdentityManager;
@interface OWSSyncContactsMessage : OWSOutgoingSyncMessage
- (instancetype)initWithContactsManager:(id<ContactsManagerProtocol>)contactsManager;
- (instancetype)initWithContactsManager:(id<ContactsManagerProtocol>)contactsManager
identityManager:(OWSIdentityManager *)identityManager;
- (NSData *)buildPlainTextAttachmentData;
@end

View File

@ -8,6 +8,7 @@
#import "NSDate+millisecondTimeStamp.h"
#import "OWSContactsOutputStream.h"
#import "OWSSignalServiceProtos.pb.h"
#import "OWSIdentityManager.h"
#import "SignalAccount.h"
#import "TSAttachment.h"
#import "TSAttachmentStream.h"
@ -17,12 +18,14 @@ NS_ASSUME_NONNULL_BEGIN
@interface OWSSyncContactsMessage ()
@property (nonatomic, readonly) id<ContactsManagerProtocol> contactsManager;
@property (nonatomic, readonly) OWSIdentityManager *identityManager;
@end
@implementation OWSSyncContactsMessage
- (instancetype)initWithContactsManager:(id<ContactsManagerProtocol>)contactsManager
identityManager:(OWSIdentityManager *)identityManager
{
self = [super initWithTimestamp:[NSDate ows_millisecondTimeStamp]];
if (!self) {
@ -30,11 +33,12 @@ NS_ASSUME_NONNULL_BEGIN
}
_contactsManager = contactsManager;
_identityManager = identityManager;
return self;
}
- (OWSSignalServiceProtosSyncMessage *)buildSyncMessage
- (OWSSignalServiceProtosSyncMessageBuilder *)syncMessageBuilder
{
if (self.attachmentIds.count != 1) {
DDLogError(@"expected sync contact message to have exactly one attachment, but found %lu",
@ -53,7 +57,7 @@ NS_ASSUME_NONNULL_BEGIN
OWSSignalServiceProtosSyncMessageBuilder *syncMessageBuilder = [OWSSignalServiceProtosSyncMessageBuilder new];
[syncMessageBuilder setContactsBuilder:contactsBuilder];
return [syncMessageBuilder build];
return syncMessageBuilder;
}
- (NSData *)buildPlainTextAttachmentData
@ -66,7 +70,9 @@ NS_ASSUME_NONNULL_BEGIN
OWSContactsOutputStream *contactsOutputStream = [OWSContactsOutputStream streamWithOutputStream:dataOutputStream];
for (SignalAccount *signalAccount in self.contactsManager.signalAccounts) {
[contactsOutputStream writeSignalAccount:signalAccount];
OWSRecipientIdentity *recipientIdentity = [self.identityManager recipientIdentityForRecipientId:signalAccount.recipientId];
[contactsOutputStream writeSignalAccount:signalAccount recipientIdentity:recipientIdentity];
}
[contactsOutputStream flush];

View File

@ -19,7 +19,7 @@ NS_ASSUME_NONNULL_BEGIN
return [super initWithTimestamp:[NSDate ows_millisecondTimeStamp]];
}
- (OWSSignalServiceProtosSyncMessage *)buildSyncMessage
- (OWSSignalServiceProtosSyncMessageBuilder *)syncMessageBuilder
{
if (self.attachmentIds.count != 1) {
@ -37,7 +37,7 @@ NS_ASSUME_NONNULL_BEGIN
OWSSignalServiceProtosSyncMessageBuilder *syncMessageBuilder = [OWSSignalServiceProtosSyncMessageBuilder new];
[syncMessageBuilder setGroupsBuilder:groupsBuilder];
return [syncMessageBuilder build];
return syncMessageBuilder;
}
- (NSData *)buildPlainTextAttachmentData

View File

@ -0,0 +1,26 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "OWSRecipientIdentity.h"
#import "TSInfoMessage.h"
NS_ASSUME_NONNULL_BEGIN
@class TSThread;
@interface OWSVerificationStateChangeMessage : TSInfoMessage
@property (nonatomic, readonly) NSString *recipientId;
@property (nonatomic, readonly) OWSVerificationState verificationState;
@property (nonatomic, readonly) BOOL isLocalChange;
- (instancetype)initWithTimestamp:(uint64_t)timestamp
thread:(TSThread *)thread
recipientId:(NSString *)recipientId
verificationState:(OWSVerificationState)verificationState
isLocalChange:(BOOL)isLocalChange;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,34 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "OWSVerificationStateChangeMessage.h"
#import "OWSDisappearingMessagesConfiguration.h"
NS_ASSUME_NONNULL_BEGIN
@implementation OWSVerificationStateChangeMessage
- (instancetype)initWithTimestamp:(uint64_t)timestamp
thread:(TSThread *)thread
recipientId:(NSString *)recipientId
verificationState:(OWSVerificationState)verificationState
isLocalChange:(BOOL)isLocalChange
{
OWSAssert(recipientId.length > 0);
self = [super initWithTimestamp:timestamp inThread:thread messageType:TSInfoMessageVerificationStateChange];
if (!self) {
return self;
}
_recipientId = recipientId;
_verificationState = verificationState;
_isLocalChange = isLocalChange;
return self;
}
@end
NS_ASSUME_NONNULL_END

View File

@ -2,31 +2,42 @@
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "OWSReadTracking.h"
#import "OWSSignalServiceProtos.pb.h"
#import "TSMessage.h"
@interface TSErrorMessage : TSMessage
NS_ASSUME_NONNULL_BEGIN
@interface TSErrorMessage : TSMessage <OWSReadTracking>
typedef NS_ENUM(int32_t, TSErrorMessageType) {
TSErrorMessageNoSession,
TSErrorMessageWrongTrustedIdentityKey,
TSErrorMessageWrongTrustedIdentityKey, // DEPRECATED: We no longer create TSErrorMessageWrongTrustedIdentityKey, but
// persisted legacy messages could exist indefinitly.
TSErrorMessageInvalidKeyException,
TSErrorMessageMissingKeyId, // unused
TSErrorMessageInvalidMessage,
TSErrorMessageDuplicateMessage,
TSErrorMessageDuplicateMessage, // unused
TSErrorMessageInvalidVersion,
TSErrorMessageNonBlockingIdentityChange,
TSErrorMessageUnknownContactBlockOffer,
TSErrorMessageGroupCreationFailed,
};
- (instancetype)initWithCoder:(NSCoder *)coder NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithTimestamp:(uint64_t)timestamp
inThread:(TSThread *)thread
failedMessageType:(TSErrorMessageType)errorMessageType NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithTimestamp:(uint64_t)timestamp
inThread:(TSThread *)thread
messageBody:(NSString *)body
failedMessageType:(TSErrorMessageType)errorMessageType
recipientId:(nullable NSString *)recipientId NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithTimestamp:(uint64_t)timestamp
inThread:(TSThread *)thread
failedMessageType:(TSErrorMessageType)errorMessageType;
- (instancetype)initWithTimestamp:(uint64_t)timestamp
inThread:(nullable TSThread *)thread
messageBody:(nullable NSString *)body
attachmentIds:(NSArray<NSString *> *)attachmentIds
expiresInSeconds:(uint32_t)expiresInSeconds
expireStartedAt:(uint64_t)expireStartedAt NS_UNAVAILABLE;
@ -43,6 +54,11 @@ typedef NS_ENUM(int32_t, TSErrorMessageType) {
+ (instancetype)missingSessionWithEnvelope:(OWSSignalServiceProtosEnvelope *)envelope
withTransaction:(YapDatabaseReadWriteTransaction *)transaction;
+ (instancetype)nonblockingIdentityChangeInThread:(TSThread *)thread recipientId:(NSString *)recipientId;
@property (nonatomic, readonly) TSErrorMessageType errorType;
@property (nullable, nonatomic, readonly) NSString *recipientId;
@end
NS_ASSUME_NONNULL_END

View File

@ -3,22 +3,57 @@
//
#import "TSErrorMessage.h"
#import "NotificationsProtocol.h"
#import "ContactsManagerProtocol.h"
#import "NSDate+millisecondTimeStamp.h"
#import "TSContactThread.h"
#import "TSErrorMessage_privateConstructor.h"
#import "TSMessagesManager.h"
#import "TextSecureKitEnv.h"
#import <YapDatabase/YapDatabaseConnection.h>
NS_ASSUME_NONNULL_BEGIN
NSUInteger TSErrorMessageSchemaVersion = 1;
@interface TSErrorMessage ()
@property (nonatomic, getter=wasRead) BOOL read;
@property (nonatomic, readonly) NSUInteger errorMessageSchemaVersion;
@end
#pragma mark -
@implementation TSErrorMessage
- (instancetype)initWithCoder:(NSCoder *)coder
{
return [super initWithCoder:coder];
self = [super initWithCoder:coder];
if (!self) {
return self;
}
if (self.errorMessageSchemaVersion < 1) {
_read = YES;
}
_errorMessageSchemaVersion = TSErrorMessageSchemaVersion;
return self;
}
- (instancetype)initWithTimestamp:(uint64_t)timestamp
inThread:(TSThread *)thread
failedMessageType:(TSErrorMessageType)errorMessageType
{
return [self initWithTimestamp:timestamp inThread:thread failedMessageType:errorMessageType recipientId:nil];
}
- (instancetype)initWithTimestamp:(uint64_t)timestamp
inThread:(TSThread *)thread
failedMessageType:(TSErrorMessageType)errorMessageType
recipientId:(nullable NSString *)recipientId
{
self = [super initWithTimestamp:timestamp
inThread:thread
@ -32,14 +67,8 @@
}
_errorType = errorMessageType;
// TODO: Move this out of model class.
//
// For now, dispatch async to ensure we're not inside a transaction
// and thereby avoid deadlock.
TSErrorMessage *errorMessage = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[[TextSecureKitEnv sharedEnv].notificationsManager notifyUserForErrorMessage:errorMessage inThread:thread];
});
_recipientId = recipientId;
_errorMessageSchemaVersion = TSErrorMessageSchemaVersion;
return self;
}
@ -68,10 +97,27 @@
return NSLocalizedString(@"ERROR_MESSAGE_INVALID_KEY_EXCEPTION", @"");
case TSErrorMessageWrongTrustedIdentityKey:
return NSLocalizedString(@"ERROR_MESSAGE_WRONG_TRUSTED_IDENTITY_KEY", @"");
case TSErrorMessageNonBlockingIdentityChange:
return NSLocalizedString(@"ERROR_MESSAGE_NON_BLOCKING_IDENTITY_CHANGE", @"");
case TSErrorMessageNonBlockingIdentityChange: {
if (self.recipientId) {
NSString *messageFormat = NSLocalizedString(@"ERROR_MESSAGE_NON_BLOCKING_IDENTITY_CHANGE_FORMAT",
@"Shown when signal users safety numbers changed, embeds the user's {{name or phone number}}");
NSString *recipientDisplayName =
[[TextSecureKitEnv sharedEnv].contactsManager displayNameForPhoneIdentifier:self.recipientId];
return [NSString stringWithFormat:messageFormat, recipientDisplayName];
} else {
// recipientId will be nil for legacy errors
return NSLocalizedString(
@"ERROR_MESSAGE_NON_BLOCKING_IDENTITY_CHANGE", @"Shown when signal users safety numbers changed");
}
break;
}
case TSErrorMessageUnknownContactBlockOffer:
return NSLocalizedString(@"UNKNOWN_CONTACT_BLOCK_OFFER", nil);
return NSLocalizedString(@"UNKNOWN_CONTACT_BLOCK_OFFER",
@"Message shown in conversation view that offers to block an unknown user.");
case TSErrorMessageGroupCreationFailed:
return NSLocalizedString(@"GROUP_CREATION_FAILED",
@"Message shown in conversation view that indicates there were issues with group creation.");
default:
return NSLocalizedString(@"ERROR_MESSAGE_UNKNOWN_ERROR", @"");
break;
@ -109,4 +155,51 @@
[[self alloc] initWithEnvelope:envelope withTransaction:transaction failedMessageType:TSErrorMessageNoSession];
}
+ (instancetype)nonblockingIdentityChangeInThread:(TSThread *)thread recipientId:(NSString *)recipientId
{
return [[self alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp]
inThread:thread
failedMessageType:TSErrorMessageNonBlockingIdentityChange
recipientId:recipientId];
}
#pragma mark - OWSReadTracking
- (BOOL)shouldAffectUnreadCounts
{
return NO;
}
- (void)markAsReadWithTransaction:(YapDatabaseReadWriteTransaction *)transaction
sendReadReceipt:(BOOL)sendReadReceipt
updateExpiration:(BOOL)updateExpiration
{
OWSAssert(transaction);
if (_read) {
return;
}
DDLogDebug(@"%@ marking as read uniqueId: %@ which has timestamp: %llu", self.tag, self.uniqueId, self.timestamp);
_read = YES;
[self saveWithTransaction:transaction];
[self touchThreadWithTransaction:transaction];
// Ignore sendReadReceipt and updateExpiration; they don't apply to error messages.
}
#pragma mark - Logging
+ (NSString *)tag
{
return [NSString stringWithFormat:@"[%@]", self.class];
}
- (NSString *)tag
{
return self.class.tag;
}
@end
NS_ASSUME_NONNULL_END

View File

@ -107,21 +107,6 @@ extern NSString *const TSIncomingMessageWasReadOnThisDeviceNotification;
// This will be 0 for messages created before we were tracking sourceDeviceId
@property (nonatomic, readonly) UInt32 sourceDeviceId;
@property (nonatomic, readonly, getter=wasRead) BOOL read;
/*
* Marks a message as having been read on this device (as opposed to responding to a remote read receipt).
*
*/
- (void)markAsReadLocally;
// TODO possible to remove?
- (void)markAsReadLocallyWithTransaction:(YapDatabaseReadWriteTransaction *)transaction;
/**
* Similar to markAsReadWithTransaction, but doesn't send out read receipts.
* Used for *responding* to a remote read receipt.
*/
- (void)markAsReadFromReadReceipt;
@end

View File

@ -3,6 +3,8 @@
//
#import "TSIncomingMessage.h"
#import "OWSDisappearingMessagesConfiguration.h"
#import "OWSDisappearingMessagesJob.h"
#import "TSContactThread.h"
#import "TSDatabaseSecondaryIndexes.h"
#import "TSGroupThread.h"
@ -12,11 +14,24 @@ NS_ASSUME_NONNULL_BEGIN
NSString *const TSIncomingMessageWasReadOnThisDeviceNotification = @"TSIncomingMessageWasReadOnThisDeviceNotification";
@interface TSIncomingMessage ()
@property (nonatomic, getter=wasRead) BOOL read;
@end
#pragma mark -
@implementation TSIncomingMessage
- (instancetype)initWithCoder:(NSCoder *)coder
{
return [super initWithCoder:coder];
self = [super initWithCoder:coder];
if (!self) {
return self;
}
return self;
}
- (instancetype)initWithTimestamp:(uint64_t)timestamp
@ -57,15 +72,13 @@ NSString *const TSIncomingMessageWasReadOnThisDeviceNotification = @"TSIncomingM
_sourceDeviceId = sourceDeviceId;
_read = NO;
OWSAssert(self.receivedAtDate);
return self;
}
+ (nullable instancetype)findMessageWithAuthorId:(NSString *)authorId timestamp:(uint64_t)timestamp
{
__block TSIncomingMessage *foundMessage;
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[self.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
// In theory we could build a new secondaryIndex for (authorId,timestamp), but in practice there should
// be *very* few (millisecond) timestamps with multiple authors.
[TSDatabaseSecondaryIndexes
@ -97,37 +110,40 @@ NSString *const TSIncomingMessageWasReadOnThisDeviceNotification = @"TSIncomingM
return foundMessage;
}
- (void)markAsReadFromReadReceipt
#pragma mark - OWSReadTracking
- (BOOL)shouldAffectUnreadCounts
{
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[self markAsReadWithoutNotificationWithTransaction:transaction];
}];
return YES;
}
- (void)markAsReadLocallyWithTransaction:(YapDatabaseReadWriteTransaction *)transaction
- (void)markAsReadWithTransaction:(YapDatabaseReadWriteTransaction *)transaction
sendReadReceipt:(BOOL)sendReadReceipt
updateExpiration:(BOOL)updateExpiration
{
[self markAsReadWithoutNotificationWithTransaction:transaction];
[[NSNotificationCenter defaultCenter] postNotificationName:TSIncomingMessageWasReadOnThisDeviceNotification
object:self];
}
OWSAssert(transaction);
- (void)markAsReadLocally
{
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[self markAsReadWithoutNotificationWithTransaction:transaction];
}];
// Notification must happen outside of the transaction, else we'll likely crash when the notification receiver
// tries to do anything with the DB.
[[NSNotificationCenter defaultCenter] postNotificationName:TSIncomingMessageWasReadOnThisDeviceNotification
object:self];
}
if (_read) {
return;
}
- (void)markAsReadWithoutNotificationWithTransaction:(YapDatabaseReadWriteTransaction *)transaction
{
DDLogInfo(@"%@ marking as read uniqueId: %@ which has timestamp: %llu", self.tag, self.uniqueId, self.timestamp);
DDLogDebug(@"%@ marking as read uniqueId: %@ which has timestamp: %llu", self.tag, self.uniqueId, self.timestamp);
_read = YES;
[self saveWithTransaction:transaction];
[self touchThreadWithTransaction:transaction];
if (updateExpiration) {
[OWSDisappearingMessagesJob setExpirationForMessage:self];
}
if (sendReadReceipt) {
// Notification must happen outside of the transaction, else we'll likely crash when the notification receiver
// tries to do anything with the DB.
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:TSIncomingMessageWasReadOnThisDeviceNotification
object:self];
});
}
}
#pragma mark - Logging

View File

@ -2,19 +2,23 @@
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "OWSReadTracking.h"
#import "TSMessage.h"
NS_ASSUME_NONNULL_BEGIN
@interface TSInfoMessage : TSMessage
@interface TSInfoMessage : TSMessage <OWSReadTracking>
typedef NS_ENUM(NSInteger, TSInfoMessageType) {
TSInfoMessageTypeSessionDidEnd,
TSInfoMessageUserNotRegistered,
// TSInfoMessageTypeUnsupportedMessage appears to be obsolete.
TSInfoMessageTypeUnsupportedMessage,
TSInfoMessageTypeGroupUpdate,
TSInfoMessageTypeGroupQuit,
TSInfoMessageTypeDisappearingMessagesUpdate
TSInfoMessageTypeDisappearingMessagesUpdate,
TSInfoMessageAddToContactsOffer,
TSInfoMessageVerificationStateChange,
};
+ (instancetype)userNotRegisteredMessageInThread:(TSThread *)thread;

View File

@ -2,16 +2,40 @@
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "NSDate+millisecondTimeStamp.h"
#import "TSInfoMessage.h"
#import "NSDate+millisecondTimeStamp.h"
#import <YapDatabase/YapDatabaseConnection.h>
NS_ASSUME_NONNULL_BEGIN
NSUInteger TSInfoMessageSchemaVersion = 1;
@interface TSInfoMessage ()
@property (nonatomic, getter=wasRead) BOOL read;
@property (nonatomic, readonly) NSUInteger infoMessageSchemaVersion;
@end
#pragma mark -
@implementation TSInfoMessage
- (instancetype)initWithCoder:(NSCoder *)coder
{
return [super initWithCoder:coder];
self = [super initWithCoder:coder];
if (!self) {
return self;
}
if (self.infoMessageSchemaVersion < 1) {
_read = YES;
}
_infoMessageSchemaVersion = TSInfoMessageSchemaVersion;
return self;
}
- (instancetype)initWithTimestamp:(uint64_t)timestamp
@ -30,6 +54,7 @@ NS_ASSUME_NONNULL_BEGIN
}
_messageType = infoMessage;
_infoMessageSchemaVersion = TSInfoMessageSchemaVersion;
return self;
}
@ -64,6 +89,12 @@ NS_ASSUME_NONNULL_BEGIN
return NSLocalizedString(@"GROUP_YOU_LEFT", nil);
case TSInfoMessageTypeGroupUpdate:
return _customMessage != nil ? _customMessage : NSLocalizedString(@"GROUP_UPDATED", nil);
case TSInfoMessageAddToContactsOffer:
return NSLocalizedString(@"ADD_TO_CONTACTS_OFFER",
@"Message shown in conversation view that offers to add an unknown user to your phone's contacts.");
case TSInfoMessageVerificationStateChange:
return NSLocalizedString(@"VERIFICATION_STATE_CHANGE_GENERIC",
@"Generic message indicating that verification state changed for a given user.");
default:
break;
}
@ -71,6 +102,43 @@ NS_ASSUME_NONNULL_BEGIN
return @"Unknown Info Message Type";
}
#pragma mark - OWSReadTracking
- (BOOL)shouldAffectUnreadCounts
{
return NO;
}
- (void)markAsReadWithTransaction:(YapDatabaseReadWriteTransaction *)transaction
sendReadReceipt:(BOOL)sendReadReceipt
updateExpiration:(BOOL)updateExpiration
{
OWSAssert(transaction);
if (_read) {
return;
}
DDLogDebug(@"%@ marking as read uniqueId: %@ which has timestamp: %llu", self.tag, self.uniqueId, self.timestamp);
_read = YES;
[self saveWithTransaction:transaction];
[self touchThreadWithTransaction:transaction];
// Ignore sendReadReceipt and updateExpiration; they don't apply to info messages.
}
#pragma mark - Logging
+ (NSString *)tag
{
return [NSString stringWithFormat:@"[%@]", self.class];
}
- (NSString *)tag
{
return self.class.tag;
}
@end
NS_ASSUME_NONNULL_END

View File

@ -16,7 +16,6 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, readonly) TSThread *thread;
@property (nonatomic, readonly) uint64_t timestamp;
- (NSDate *)date;
- (NSString *)description;
/**
@ -33,7 +32,17 @@ NS_ASSUME_NONNULL_BEGIN
+ (instancetype)interactionForTimestamp:(uint64_t)timestamp
withTransaction:(YapDatabaseReadWriteTransaction *)transaction;
- (nullable NSDate *)receiptDateForSorting;
- (NSDate *)dateForSorting;
- (uint64_t)timestampForSorting;
- (NSComparisonResult)compareForSorting:(TSInteraction *)other;
// "Dynamic" interactions are not messages or static events (like
// info messages, error messages, etc.). They are interactions
// created, updated and deleted by the views.
//
// These include block offers, "add to contact" offers,
// unseen message indicators, etc.
- (BOOL)isDynamicInteraction;
@end

View File

@ -3,6 +3,7 @@
//
#import "TSInteraction.h"
#import "NSDate+millisecondTimeStamp.h"
#import "TSDatabaseSecondaryIndexes.h"
#import "TSStorageManager+messageIDs.h"
#import "TSThread.h"
@ -52,7 +53,6 @@ NS_ASSUME_NONNULL_BEGIN
return self;
}
#pragma mark Thread
- (TSThread *)thread
@ -72,11 +72,6 @@ NS_ASSUME_NONNULL_BEGIN
return self.timestamp;
}
- (NSDate *)date {
uint64_t seconds = self.timestamp / 1000;
return [NSDate dateWithTimeIntervalSince1970:seconds];
}
+ (NSString *)stringFromTimeStamp:(uint64_t)timestamp {
return [[NSNumber numberWithUnsignedLongLong:timestamp] stringValue];
}
@ -88,9 +83,30 @@ NS_ASSUME_NONNULL_BEGIN
return [myNumber unsignedLongLongValue];
}
- (nullable NSDate *)receiptDateForSorting
- (NSDate *)dateForSorting
{
return self.date;
return [NSDate ows_dateWithMillisecondsSince1970:self.timestampForSorting];
}
- (uint64_t)timestampForSorting
{
return self.timestamp;
}
- (NSComparisonResult)compareForSorting:(TSInteraction *)other
{
OWSAssert(other);
uint64_t timestamp1 = self.timestampForSorting;
uint64_t timestamp2 = other.timestampForSorting;
if (timestamp1 > timestamp2) {
return NSOrderedDescending;
} else if (timestamp1 < timestamp2) {
return NSOrderedAscending;
} else {
return NSOrderedSame;
}
}
- (NSString *)description {
@ -109,6 +125,11 @@ NS_ASSUME_NONNULL_BEGIN
[fetchedThread updateWithLastMessage:self transaction:transaction];
}
- (BOOL)isDynamicInteraction
{
return NO;
}
@end
NS_ASSUME_NONNULL_END

View File

@ -21,9 +21,6 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, readonly) uint64_t expiresAt;
@property (nonatomic, readonly) BOOL isExpiringMessage;
@property (nonatomic, readonly) BOOL shouldStartExpireTimer;
// _DO NOT_ access this property directly. You almost certainly
// want to use receiptDateForSorting instead.
@property (nonatomic, readonly) NSDate *receivedAtDate;
- (instancetype)initWithTimestamp:(uint64_t)timestamp;

View File

@ -35,6 +35,13 @@ static const NSUInteger OWSMessageSchemaVersion = 3;
*/
@property (nonatomic, readonly) NSUInteger schemaVersion;
// The timestamp property is populated by the envelope,
// which is created by the sender.
//
// We typically want to order messages locally by when
// they were received & decrypted, not by when they were sent.
@property (nonatomic, readonly) uint64_t receivedAtTimestamp;
@end
#pragma mark -
@ -104,7 +111,7 @@ static const NSUInteger OWSMessageSchemaVersion = 3;
_expiresInSeconds = expiresInSeconds;
_expireStartedAt = expireStartedAt;
[self updateExpiresAt];
_receivedAtDate = [NSDate date];
_receivedAtTimestamp = [NSDate ows_millisecondTimeStamp];
return self;
}
@ -133,10 +140,16 @@ static const NSUInteger OWSMessageSchemaVersion = 3;
_attachmentIds = [NSMutableArray new];
}
if (!_receivedAtDate) {
// TSIncomingMessage.receivedAt has been superceded by TSMessage.receivedAtDate.
NSDate *receivedAt = [coder decodeObjectForKey:@"receivedAt"];
_receivedAtDate = receivedAt;
if (_receivedAtTimestamp == 0) {
// Upgrade from the older "receivedAtDate" and "receivedAt" properties if
// necessary.
NSDate *receivedAtDate = [coder decodeObjectForKey:@"receivedAtDate"];
if (!receivedAtDate) {
receivedAtDate = [coder decodeObjectForKey:@"receivedAt"];
}
if (receivedAtDate) {
_receivedAtTimestamp = [NSDate ows_millisecondsSince1970ForDate:receivedAtDate];
}
}
_schemaVersion = OWSMessageSchemaVersion;
@ -224,10 +237,19 @@ static const NSUInteger OWSMessageSchemaVersion = 3;
return self.expiresInSeconds > 0;
}
- (nullable NSDate *)receiptDateForSorting
- (uint64_t)timestampForSorting
{
// Prefer receivedAtDate if set, otherwise fallback to date.
return self.receivedAtDate ?: self.date;
if ([self shouldUseReceiptDateForSorting] && self.receivedAtTimestamp > 0) {
return self.receivedAtTimestamp;
} else {
OWSAssert(self.timestamp > 0);
return self.timestamp;
}
}
- (BOOL)shouldUseReceiptDateForSorting
{
return YES;
}
#pragma mark - Logging

View File

@ -89,7 +89,7 @@ typedef NS_ENUM(NSInteger, TSGroupMetaMessage) {
@property (atomic, readonly) BOOL hasSyncedTranscript;
@property (atomic, readonly) NSString *customMessage;
@property (atomic, readonly) NSString *mostRecentFailureText;
// A map of attachment id-to-filename.
// A map of attachment id-to-"source" filename.
@property (nonatomic, readonly) NSMutableDictionary<NSString *, NSString *> *attachmentFilenameMap;
@property (atomic, readonly) TSGroupMetaMessage groupMetaMessage;
@ -97,13 +97,6 @@ typedef NS_ENUM(NSInteger, TSGroupMetaMessage) {
// If set, this group message should only be sent to a single recipient.
@property (atomic, readonly) NSString *singleGroupRecipient;
/**
* Whether the message should be serialized as a modern aka Content, or the old style legacy message.
* Sync and Call messsages must be sent as Content, but other old style DataMessage payloads should be
* sent as legacy message until we're confident no significant number of legacy clients exist in the wild.
*/
@property (nonatomic, readonly) BOOL isLegacyMessage;
@property (nonatomic, readonly) BOOL isVoiceMessage;
/**

View File

@ -186,8 +186,6 @@ NSString *const kTSOutgoingMessageSentRecipientAll = @"kTSOutgoingMessageSentRec
_attachmentFilenameMap = [NSMutableDictionary new];
OWSAssert(self.receivedAtDate);
return self;
}
@ -247,7 +245,7 @@ NSString *const kTSOutgoingMessageSentRecipientAll = @"kTSOutgoingMessageSentRec
{
OWSAssert(error);
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[self.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[self applyChangeToSelfAndLatestOutgoingMessage:transaction
changeBlock:^(TSOutgoingMessage *message) {
[message setMessageState:TSOutgoingMessageStateUnsent];
@ -256,19 +254,9 @@ NSString *const kTSOutgoingMessageSentRecipientAll = @"kTSOutgoingMessageSentRec
}];
}
//- (void)updateWithMessageState:(TSOutgoingMessageState)messageState
//{
// [self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
// [self applyChangeToSelfAndLatestOutgoingMessage:transaction
// changeBlock:^(TSOutgoingMessage *message) {
// [message setMessageState:messageState];
// }];
// }];
//}
- (void)updateWithMessageState:(TSOutgoingMessageState)messageState
{
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[self.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[self updateWithMessageState:messageState transaction:transaction];
}];
}
@ -277,7 +265,7 @@ NSString *const kTSOutgoingMessageSentRecipientAll = @"kTSOutgoingMessageSentRec
transaction:(YapDatabaseReadWriteTransaction *)transaction
{
OWSAssert(transaction);
[self applyChangeToSelfAndLatestOutgoingMessage:transaction
changeBlock:^(TSOutgoingMessage *message) {
[message setMessageState:messageState];
@ -286,7 +274,7 @@ NSString *const kTSOutgoingMessageSentRecipientAll = @"kTSOutgoingMessageSentRec
- (void)updateWithHasSyncedTranscript:(BOOL)hasSyncedTranscript
{
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[self.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[self applyChangeToSelfAndLatestOutgoingMessage:transaction
changeBlock:^(TSOutgoingMessage *message) {
[message setHasSyncedTranscript:hasSyncedTranscript];
@ -307,7 +295,7 @@ NSString *const kTSOutgoingMessageSentRecipientAll = @"kTSOutgoingMessageSentRec
- (void)updateWithCustomMessage:(NSString *)customMessage
{
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[self.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[self updateWithCustomMessage:customMessage transaction:transaction];
}];
}
@ -324,14 +312,14 @@ NSString *const kTSOutgoingMessageSentRecipientAll = @"kTSOutgoingMessageSentRec
- (void)updateWithWasDelivered
{
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[self.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[self updateWithWasDeliveredWithTransaction:transaction];
}];
}
- (void)updateWithWasSentAndDelivered
{
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[self.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[self applyChangeToSelfAndLatestOutgoingMessage:transaction
changeBlock:^(TSOutgoingMessage *message) {
[message setMessageState:TSOutgoingMessageStateSentToService];
@ -409,7 +397,7 @@ NSString *const kTSOutgoingMessageSentRecipientAll = @"kTSOutgoingMessageSentRec
- (void)updateWithSentRecipient:(NSString *)contactId
{
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[self.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[self updateWithSentRecipient:contactId transaction:transaction];
}];
}
@ -453,8 +441,8 @@ NSString *const kTSOutgoingMessageSentRecipientAll = @"kTSOutgoingMessageSentRec
if (!attachmentWasGroupAvatar) {
NSMutableArray *attachments = [NSMutableArray new];
for (NSString *attachmentId in self.attachmentIds) {
NSString *filename = self.attachmentFilenameMap[attachmentId];
[attachments addObject:[self buildAttachmentProtoForAttachmentId:attachmentId filename:filename]];
NSString *sourceFilename = self.attachmentFilenameMap[attachmentId];
[attachments addObject:[self buildAttachmentProtoForAttachmentId:attachmentId filename:sourceFilename]];
}
[builder setAttachmentsArray:attachments];
}
@ -467,16 +455,12 @@ NSString *const kTSOutgoingMessageSentRecipientAll = @"kTSOutgoingMessageSentRec
return [[self dataMessageBuilder] build];
}
// For legacy message this is a serialized DataMessage.
// For modern messages, e.g. Sync and Call messages, this is a serialized Contact
- (NSData *)buildPlainTextData
{
return [[self buildDataMessage] data];
}
OWSSignalServiceProtosContentBuilder *contentBuilder = [OWSSignalServiceProtosContentBuilder new];
contentBuilder.dataMessage = [self buildDataMessage];
- (BOOL)isLegacyMessage
{
return YES;
return [[contentBuilder build] data];
}
- (BOOL)shouldSyncTranscript

View File

@ -1,9 +1,5 @@
//
// TSInvalidIdentityKeyErrorMessage.m
// Signal
//
// Created by Frederic Jacobs on 15/02/15.
// Copyright (c) 2015 Open Whisper Systems. All rights reserved.
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "TSInvalidIdentityKeyErrorMessage.h"

View File

@ -4,11 +4,11 @@
#import "TSInvalidIdentityKeyReceivingErrorMessage.h"
#import "OWSFingerprint.h"
#import "OWSIdentityManager.h"
#import "TSContactThread.h"
#import "TSDatabaseView.h"
#import "TSErrorMessage_privateConstructor.h"
#import "TSMessagesManager.h"
#import "TSStorageManager+IdentityKeyStore.h"
#import "TSStorageManager.h"
#import <AxolotlKit/NSData+keyVersionByte.h>
#import <AxolotlKit/PreKeyWhisperMessage.h>
@ -81,7 +81,7 @@ NS_ASSUME_NONNULL_BEGIN
// Saving a new identity mutates the session store so it must happen on the sessionStoreQueue
dispatch_async([OWSDispatch sessionStoreQueue], ^{
[[TSStorageManager sharedManager] saveRemoteIdentity:newKey recipientId:self.envelope.source];
[[OWSIdentityManager sharedManager] saveRemoteIdentity:newKey recipientId:self.envelope.source];
dispatch_async(dispatch_get_main_queue(), ^{
// Decrypt this and any old messages for the newly accepted key

View File

@ -1,9 +1,5 @@
//
// TSInvalidIdentityKeySendingErrorMessage.h
// Signal
//
// Created by Frederic Jacobs on 15/02/15.
// Copyright (c) 2015 Open Whisper Systems. All rights reserved.
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "TSInvalidIdentityKeyErrorMessage.h"
@ -19,12 +15,6 @@ extern NSString *TSInvalidRecipientKey;
@interface TSInvalidIdentityKeySendingErrorMessage : TSInvalidIdentityKeyErrorMessage
+ (instancetype)untrustedKeyWithOutgoingMessage:(TSOutgoingMessage *)outgoingMessage
inThread:(TSThread *)thread
forRecipient:(NSString *)recipientId
preKeyBundle:(PreKeyBundle *)preKeyBundle;
@property (nonatomic, readonly) NSString *recipientId;
@property (nonatomic, readonly) NSString *messageId;
@end

View File

@ -4,12 +4,12 @@
#import "TSInvalidIdentityKeySendingErrorMessage.h"
#import "OWSFingerprint.h"
#import "OWSIdentityManager.h"
#import "PreKeyBundle+jsonDict.h"
#import "SignalRecipient.h"
#import "TSContactThread.h"
#import "TSErrorMessage_privateConstructor.h"
#import "TSOutgoingMessage.h"
#import "TSStorageManager+IdentityKeyStore.h"
#import <AxolotlKit/NSData+keyVersionByte.h>
NS_ASSUME_NONNULL_BEGIN
@ -32,34 +32,26 @@ NSString *TSInvalidRecipientKey = @"TSInvalidRecipientKey";
{
self = [super initWithTimestamp:message.timestamp
inThread:thread
failedMessageType:TSErrorMessageWrongTrustedIdentityKey];
failedMessageType:TSErrorMessageWrongTrustedIdentityKey
recipientId:recipientId];
if (self) {
_messageId = message.uniqueId;
_preKeyBundle = preKeyBundle;
_recipientId = recipientId;
}
return self;
}
+ (instancetype)untrustedKeyWithOutgoingMessage:(TSOutgoingMessage *)outgoingMessage
inThread:(TSThread *)thread
forRecipient:(NSString *)recipientId
preKeyBundle:(PreKeyBundle *)preKeyBundle
{
TSInvalidIdentityKeySendingErrorMessage *message = [[self alloc] initWithOutgoingMessage:outgoingMessage
inThread:thread
forRecipient:recipientId
preKeyBundle:preKeyBundle];
return message;
}
- (void)acceptNewIdentityKey
{
// Shouldn't really get here, since we're no longer creating blocking SN changes.
// But there may still be some old unaccepted SN errors in the wild that need to be accepted.
OWSFail(@"accepting new identity key is deprecated.");
// Saving a new identity mutates the session store so it must happen on the sessionStoreQueue
dispatch_async([OWSDispatch sessionStoreQueue], ^{
[[TSStorageManager sharedManager] saveRemoteIdentity:self.newIdentityKey recipientId:self.recipientId];
[[OWSIdentityManager sharedManager] saveRemoteIdentity:self.newIdentityKey recipientId:self.recipientId];
});
}

View File

@ -0,0 +1,17 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "TSInfoMessage.h"
NS_ASSUME_NONNULL_BEGIN
@interface OWSAddToContactsOfferMessage : TSInfoMessage
+ (instancetype)addToContactsOfferMessage:(uint64_t)timestamp thread:(TSThread *)thread contactId:(NSString *)contactId;
@property (nonatomic, readonly) NSString *contactId;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,49 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "OWSAddToContactsOfferMessage.h"
NS_ASSUME_NONNULL_BEGIN
@interface OWSAddToContactsOfferMessage ()
@property (nonatomic) NSString *contactId;
@end
#pragma mark -
@implementation OWSAddToContactsOfferMessage
+ (instancetype)addToContactsOfferMessage:(uint64_t)timestamp thread:(TSThread *)thread contactId:(NSString *)contactId
{
return [[OWSAddToContactsOfferMessage alloc] initWithTimestamp:timestamp thread:thread contactId:contactId];
}
- (instancetype)initWithTimestamp:(uint64_t)timestamp thread:(TSThread *)thread contactId:(NSString *)contactId
{
self = [super initWithTimestamp:timestamp inThread:thread messageType:TSInfoMessageAddToContactsOffer];
if (self) {
_contactId = contactId;
}
return self;
}
- (BOOL)shouldUseReceiptDateForSorting
{
// Use the timestamp, not the "received at" timestamp to sort,
// since we're creating these interactions after the fact and back-dating them.
return NO;
}
- (BOOL)isDynamicInteraction
{
return YES;
}
@end
NS_ASSUME_NONNULL_END

View File

@ -11,6 +11,7 @@
NS_ASSUME_NONNULL_BEGIN
NSString *const kNSNotificationName_BlockedPhoneNumbersDidChange = @"kNSNotificationName_BlockedPhoneNumbersDidChange";
NSString *const kOWSBlockingManager_BlockedPhoneNumbersCollection = @"kOWSBlockingManager_BlockedPhoneNumbersCollection";
// This key is used to persist the current "blocked phone numbers" state.
NSString *const kOWSBlockingManager_BlockedPhoneNumbersKey = @"kOWSBlockingManager_BlockedPhoneNumbersKey";
@ -25,7 +26,7 @@ NSString *const kOWSBlockingManager_SyncedBlockedPhoneNumbersKey = @"kOWSBlockin
// We don't store the phone numbers as instances of PhoneNumber to avoid
// consistency issues between clients, but these should all be valid e164
// phone numbers.
@property (nonatomic, readonly) NSMutableSet<NSString *> *blockedPhoneNumberSet;
@property (atomic, readonly) NSMutableSet<NSString *> *blockedPhoneNumberSet;
@end
@ -74,6 +75,19 @@ NSString *const kOWSBlockingManager_SyncedBlockedPhoneNumbersKey = @"kOWSBlockin
return self;
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)observeNotifications
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(applicationDidBecomeActive:)
name:UIApplicationDidBecomeActiveNotification
object:nil];
}
- (void)addBlockedPhoneNumber:(NSString *)phoneNumber
{
OWSAssert(phoneNumber.length > 0);
@ -82,7 +96,7 @@ NSString *const kOWSBlockingManager_SyncedBlockedPhoneNumbersKey = @"kOWSBlockin
@synchronized(self)
{
[self lazyLoadBlockedPhoneNumbersIfNecessary];
[self ensureLazyInitialization];
if ([_blockedPhoneNumberSet containsObject:phoneNumber]) {
// Ignore redundant changes.
@ -103,7 +117,7 @@ NSString *const kOWSBlockingManager_SyncedBlockedPhoneNumbersKey = @"kOWSBlockin
@synchronized(self)
{
[self lazyLoadBlockedPhoneNumbersIfNecessary];
[self ensureLazyInitialization];
if (![_blockedPhoneNumberSet containsObject:phoneNumber]) {
// Ignore redundant changes.
@ -124,7 +138,7 @@ NSString *const kOWSBlockingManager_SyncedBlockedPhoneNumbersKey = @"kOWSBlockin
@synchronized(self)
{
[self lazyLoadBlockedPhoneNumbersIfNecessary];
[self ensureLazyInitialization];
NSSet *newSet = [NSSet setWithArray:blockedPhoneNumbers];
if ([_blockedPhoneNumberSet isEqualToSet:newSet]) {
@ -141,7 +155,7 @@ NSString *const kOWSBlockingManager_SyncedBlockedPhoneNumbersKey = @"kOWSBlockin
{
@synchronized(self)
{
[self lazyLoadBlockedPhoneNumbersIfNecessary];
[self ensureLazyInitialization];
return [_blockedPhoneNumberSet.allObjects sortedArrayUsingSelector:@selector(compare:)];
}
@ -189,7 +203,7 @@ NSString *const kOWSBlockingManager_SyncedBlockedPhoneNumbersKey = @"kOWSBlockin
}
// This method should only be called from within a synchronized block.
- (void)lazyLoadBlockedPhoneNumbersIfNecessary
- (void)ensureLazyInitialization
{
if (_blockedPhoneNumberSet) {
// _blockedPhoneNumberSet has already been loaded, abort.
@ -201,6 +215,15 @@ NSString *const kOWSBlockingManager_SyncedBlockedPhoneNumbersKey = @"kOWSBlockin
inCollection:kOWSBlockingManager_BlockedPhoneNumbersCollection];
_blockedPhoneNumberSet = [[NSMutableSet alloc] initWithArray:(blockedPhoneNumbers ?: [NSArray new])];
[self syncBlockedPhoneNumbersIfNecessary];
[self observeNotifications];
}
// This method should only be called from within a synchronized block.
- (void)syncBlockedPhoneNumbersIfNecessary
{
OWSAssert(_blockedPhoneNumberSet);
// If we haven't yet successfully synced the current "blocked phone numbers" changes,
// try again to sync now.
NSArray<NSString *> *syncedBlockedPhoneNumbers =
@ -210,7 +233,7 @@ NSString *const kOWSBlockingManager_SyncedBlockedPhoneNumbersKey = @"kOWSBlockin
if (![_blockedPhoneNumberSet isEqualToSet:syncedBlockedPhoneNumberSet]) {
DDLogInfo(@"%@ retrying sync of blocked phone numbers", self.tag);
dispatch_async(dispatch_get_main_queue(), ^{
[self sendBlockedPhoneNumbersMessage:blockedPhoneNumbers];
[self sendBlockedPhoneNumbersMessage:self.blockedPhoneNumbers];
});
}
}
@ -231,8 +254,6 @@ NSString *const kOWSBlockingManager_SyncedBlockedPhoneNumbersKey = @"kOWSBlockin
}
failure:^(NSError *error) {
DDLogError(@"%@ Failed to send blocked phone numbers sync message with error: %@", self.tag, error);
// TODO: We might want to retry more often than just app launch.
}];
}
@ -246,6 +267,18 @@ NSString *const kOWSBlockingManager_SyncedBlockedPhoneNumbersKey = @"kOWSBlockin
inCollection:kOWSBlockingManager_BlockedPhoneNumbersCollection];
}
#pragma mark - Notifications
- (void)applicationDidBecomeActive:(NSNotification *)notification
{
OWSAssert([NSThread isMainThread]);
@synchronized(self)
{
[self syncBlockedPhoneNumbersIfNecessary];
}
}
#pragma mark - Logging
+ (NSString *)tag

View File

@ -1,38 +1,38 @@
// Created by Michael Kirk on 9/23/16.
// Copyright © 2016 Open Whisper Systems. All rights reserved.
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
NS_ASSUME_NONNULL_BEGIN
@class TSStorageManager;
@class TSMessage;
@class TSThread;
@class YapDatabaseReadTransaction;
@interface OWSDisappearingMessagesFinder : NSObject
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithStorageManager:(TSStorageManager *)storageManager NS_DESIGNATED_INITIALIZER;
+ (instancetype)defaultInstance;
- (void)enumerateExpiredMessagesWithBlock:(void (^_Nonnull)(TSMessage *message))block;
- (void)enumerateUnstartedExpiringMessagesInThread:(TSThread *)thread block:(void (^_Nonnull)(TSMessage *message))block;
- (void)enumerateExpiredMessagesWithBlock:(void (^_Nonnull)(TSMessage *message))block
transaction:(YapDatabaseReadTransaction *)transaction;
- (void)enumerateUnstartedExpiringMessagesInThread:(TSThread *)thread
block:(void (^_Nonnull)(TSMessage *message))block
transaction:(YapDatabaseReadTransaction *)transaction;
/**
* @return
* uint64_t millisecond timestamp wrapped in a number. Retrieve with `unsignedLongLongvalue`.
* or nil if there are no upcoming expired messages
*/
- (nullable NSNumber *)nextExpirationTimestamp;
- (nullable NSNumber *)nextExpirationTimestampWithTransaction:(YapDatabaseReadTransaction *_Nonnull)transaction;
/**
* Database extensions required for class to work.
*/
- (void)asyncRegisterDatabaseExtensions;
+ (void)asyncRegisterDatabaseExtensions:(TSStorageManager *)storageManager;
/**
* Only use the sync version for testing, generally we'll want to register extensions async
*/
- (void)blockingRegisterDatabaseExtensions;
+ (void)blockingRegisterDatabaseExtensions:(TSStorageManager *)storageManager;
@end

View File

@ -1,5 +1,6 @@
// Created by Michael Kirk on 9/23/16.
// Copyright © 2016 Open Whisper Systems. All rights reserved.
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "OWSDisappearingMessagesFinder.h"
#import "NSDate+millisecondTimeStamp.h"
@ -17,40 +18,13 @@ static NSString *const OWSDisappearingMessageFinderThreadIdColumn = @"thread_id"
static NSString *const OWSDisappearingMessageFinderExpiresAtColumn = @"expires_at";
static NSString *const OWSDisappearingMessageFinderExpiresAtIndex = @"index_messages_on_expires_at_and_thread_id_v2";
@interface OWSDisappearingMessagesFinder ()
@property (nonatomic, readonly) TSStorageManager *storageManager;
@property (nonatomic, readonly) YapDatabaseConnection *dbConnection;
@end
@implementation OWSDisappearingMessagesFinder
- (instancetype)initWithStorageManager:(TSStorageManager *)storageManager
{
self = [super init];
if (!self) {
return self;
}
_storageManager = storageManager;
_dbConnection = [storageManager newDatabaseConnection];
return self;
}
+ (instancetype)defaultInstance
{
static OWSDisappearingMessagesFinder *defaultInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
defaultInstance = [[self alloc] initWithStorageManager:[TSStorageManager sharedManager]];
});
return defaultInstance;
}
- (NSArray<NSString *> *)fetchUnstartedExpiringMessageIdsInThread:(TSThread *)thread
transaction:(YapDatabaseReadTransaction *_Nonnull)transaction
{
OWSAssert(transaction);
NSMutableArray<NSString *> *messageIds = [NSMutableArray new];
NSString *formattedString = [NSString stringWithFormat:@"WHERE %@ = 0 AND %@ = \"%@\"",
OWSDisappearingMessageFinderExpiresAtColumn,
@ -58,19 +32,19 @@ static NSString *const OWSDisappearingMessageFinderExpiresAtIndex = @"index_mess
thread.uniqueId];
YapDatabaseQuery *query = [YapDatabaseQuery queryWithFormat:formattedString];
[self.dbConnection readWithBlock:^(YapDatabaseReadTransaction *_Nonnull transaction) {
[[transaction ext:OWSDisappearingMessageFinderExpiresAtIndex]
enumerateKeysMatchingQuery:query
usingBlock:^void(NSString *collection, NSString *key, BOOL *stop) {
[messageIds addObject:key];
}];
}];
[[transaction ext:OWSDisappearingMessageFinderExpiresAtIndex]
enumerateKeysMatchingQuery:query
usingBlock:^void(NSString *collection, NSString *key, BOOL *stop) {
[messageIds addObject:key];
}];
return [messageIds copy];
}
- (NSArray<NSString *> *)fetchExpiredMessageIds
- (NSArray<NSString *> *)fetchExpiredMessageIdsWithTransaction:(YapDatabaseReadTransaction *_Nonnull)transaction
{
OWSAssert(transaction);
NSMutableArray<NSString *> *messageIds = [NSMutableArray new];
uint64_t now = [NSDate ows_millisecondTimeStamp];
@ -80,33 +54,31 @@ static NSString *const OWSDisappearingMessageFinderExpiresAtIndex = @"index_mess
OWSDisappearingMessageFinderExpiresAtColumn,
now];
YapDatabaseQuery *query = [YapDatabaseQuery queryWithFormat:formattedString];
[self.dbConnection readWithBlock:^(YapDatabaseReadTransaction *_Nonnull transaction) {
[[transaction ext:OWSDisappearingMessageFinderExpiresAtIndex]
enumerateKeysMatchingQuery:query
usingBlock:^void(NSString *collection, NSString *key, BOOL *stop) {
[messageIds addObject:key];
}];
}];
[[transaction ext:OWSDisappearingMessageFinderExpiresAtIndex]
enumerateKeysMatchingQuery:query
usingBlock:^void(NSString *collection, NSString *key, BOOL *stop) {
[messageIds addObject:key];
}];
return [messageIds copy];
}
- (nullable NSNumber *)nextExpirationTimestamp
- (nullable NSNumber *)nextExpirationTimestampWithTransaction:(YapDatabaseReadTransaction *)transaction
{
OWSAssert(transaction);
NSString *formattedString = [NSString stringWithFormat:@"WHERE %@ > 0 ORDER BY %@ ASC",
OWSDisappearingMessageFinderExpiresAtColumn,
OWSDisappearingMessageFinderExpiresAtColumn];
YapDatabaseQuery *query = [YapDatabaseQuery queryWithFormat:formattedString];
__block TSMessage *firstMessage;
[self.dbConnection readWithBlock:^(YapDatabaseReadTransaction *_Nonnull transaction) {
[[transaction ext:OWSDisappearingMessageFinderExpiresAtIndex]
enumerateKeysAndObjectsMatchingQuery:query
usingBlock:^void(NSString *collection, NSString *key, id object, BOOL *stop) {
firstMessage = (TSMessage *)object;
*stop = YES;
}];
}];
[[transaction ext:OWSDisappearingMessageFinderExpiresAtIndex]
enumerateKeysAndObjectsMatchingQuery:query
usingBlock:^void(NSString *collection, NSString *key, id object, BOOL *stop) {
firstMessage = (TSMessage *)object;
*stop = YES;
}];
if (firstMessage && firstMessage.expiresAt > 0) {
return [NSNumber numberWithUnsignedLongLong:firstMessage.expiresAt];
@ -115,10 +87,15 @@ static NSString *const OWSDisappearingMessageFinderExpiresAtIndex = @"index_mess
return nil;
}
- (void)enumerateUnstartedExpiringMessagesInThread:(TSThread *)thread block:(void (^_Nonnull)(TSMessage *message))block
- (void)enumerateUnstartedExpiringMessagesInThread:(TSThread *)thread
block:(void (^_Nonnull)(TSMessage *message))block
transaction:(YapDatabaseReadTransaction *)transaction
{
for (NSString *expiringMessageId in [self fetchUnstartedExpiringMessageIdsInThread:thread]) {
TSMessage *_Nullable message = [TSMessage fetchObjectWithUniqueID:expiringMessageId];
OWSAssert(transaction);
for (NSString *expiringMessageId in
[self fetchUnstartedExpiringMessageIdsInThread:thread transaction:transaction]) {
TSMessage *_Nullable message = [TSMessage fetchObjectWithUniqueID:expiringMessageId transaction:transaction];
if ([message isKindOfClass:[TSMessage class]]) {
block(message);
} else {
@ -132,23 +109,30 @@ static NSString *const OWSDisappearingMessageFinderExpiresAtIndex = @"index_mess
* We don't want to instantiate potentially many messages at once.
*/
- (NSArray<TSMessage *> *)fetchUnstartedExpiringMessagesInThread:(TSThread *)thread
transaction:(YapDatabaseReadTransaction *)transaction
{
OWSAssert(transaction);
NSMutableArray<TSMessage *> *messages = [NSMutableArray new];
[self enumerateUnstartedExpiringMessagesInThread:thread
block:^(TSMessage *_Nonnull message) {
block:^(TSMessage *message) {
[messages addObject:message];
}];
}
transaction:transaction];
return [messages copy];
}
- (void)enumerateExpiredMessagesWithBlock:(void (^_Nonnull)(TSMessage *message))block
transaction:(YapDatabaseReadTransaction *)transaction
{
OWSAssert(transaction);
// Since we can't directly mutate the enumerated expired messages, we store only their ids in hopes of saving a
// little memory and then enumerate the (larger) TSMessage objects one at a time.
for (NSString *expiredMessageId in [self fetchExpiredMessageIds]) {
TSMessage *_Nullable message = [TSMessage fetchObjectWithUniqueID:expiredMessageId];
for (NSString *expiredMessageId in [self fetchExpiredMessageIdsWithTransaction:transaction]) {
TSMessage *_Nullable message = [TSMessage fetchObjectWithUniqueID:expiredMessageId transaction:transaction];
if ([message isKindOfClass:[TSMessage class]]) {
block(message);
} else {
@ -161,19 +145,22 @@ static NSString *const OWSDisappearingMessageFinderExpiresAtIndex = @"index_mess
* Don't use this in production. Useful for testing.
* We don't want to instantiate potentially many messages at once.
*/
- (NSArray<TSMessage *> *)fetchExpiredMessages
- (NSArray<TSMessage *> *)fetchExpiredMessagesWithTransaction:(YapDatabaseReadTransaction *)transaction
{
OWSAssert(transaction);
NSMutableArray<TSMessage *> *messages = [NSMutableArray new];
[self enumerateExpiredMessagesWithBlock:^(TSMessage *_Nonnull message) {
[self enumerateExpiredMessagesWithBlock:^(TSMessage *message) {
[messages addObject:message];
}];
}
transaction:transaction];
return [messages copy];
}
#pragma mark - YapDatabaseExtension
- (YapDatabaseSecondaryIndex *)indexDatabaseExtension
+ (YapDatabaseSecondaryIndex *)indexDatabaseExtension
{
YapDatabaseSecondaryIndexSetup *setup = [YapDatabaseSecondaryIndexSetup new];
[setup addColumn:OWSDisappearingMessageFinderExpiresAtColumn withType:YapDatabaseSecondaryIndexTypeInteger];
@ -202,23 +189,23 @@ static NSString *const OWSDisappearingMessageFinderExpiresAtIndex = @"index_mess
}
// Useful for tests, don't use in app startup path because it's slow.
- (void)blockingRegisterDatabaseExtensions
+ (void)blockingRegisterDatabaseExtensions:(TSStorageManager *)storageManager
{
[self.storageManager.database registerExtension:[self indexDatabaseExtension]
withName:OWSDisappearingMessageFinderExpiresAtIndex];
[storageManager.database registerExtension:[self indexDatabaseExtension]
withName:OWSDisappearingMessageFinderExpiresAtIndex];
}
- (void)asyncRegisterDatabaseExtensions
+ (void)asyncRegisterDatabaseExtensions:(TSStorageManager *)storageManager
{
[self.storageManager.database asyncRegisterExtension:[self indexDatabaseExtension]
withName:OWSDisappearingMessageFinderExpiresAtIndex
completionBlock:^(BOOL ready) {
if (ready) {
DDLogDebug(@"%@ completed registering extension async.", self.tag);
} else {
DDLogError(@"%@ failed registering extension async.", self.tag);
}
}];
[storageManager.database asyncRegisterExtension:[self indexDatabaseExtension]
withName:OWSDisappearingMessageFinderExpiresAtIndex
completionBlock:^(BOOL ready) {
if (ready) {
DDLogDebug(@"%@ completed registering extension async.", self.tag);
} else {
DDLogError(@"%@ failed registering extension async.", self.tag);
}
}];
}
#pragma mark - Logging

View File

@ -17,7 +17,10 @@ NS_ASSUME_NONNULL_BEGIN
@interface OWSDisappearingMessagesJob ()
// This property should only be accessed on the serialQueue.
@property (nonatomic, readonly) TSStorageManager *storageManager;
@property (nonatomic, readonly) YapDatabaseConnection *databaseConnection;
@property (nonatomic, readonly) OWSDisappearingMessagesFinder *disappearingMessagesFinder;
// These three properties should only be accessed on the main thread.
@ -48,7 +51,9 @@ NS_ASSUME_NONNULL_BEGIN
return self;
}
_disappearingMessagesFinder = [[OWSDisappearingMessagesFinder alloc] initWithStorageManager:storageManager];
_storageManager = storageManager;
_databaseConnection = storageManager.newDatabaseConnection;
_disappearingMessagesFinder = [OWSDisappearingMessagesFinder new];
OWSSingletonAssert();
@ -79,26 +84,32 @@ NS_ASSUME_NONNULL_BEGIN
return queue;
}
// This method should only be called on the serialQueue.
- (void)run
{
uint64_t now = [NSDate ows_millisecondTimeStamp];
__block uint expirationCount = 0;
[self.disappearingMessagesFinder enumerateExpiredMessagesWithBlock:^(TSMessage *message) {
// sanity check
if (message.expiresAt > now) {
DDLogError(@"%@ Refusing to remove message which doesn't expire until: %lld", self.tag, message.expiresAt);
return;
}
[self.databaseConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) {
[self.disappearingMessagesFinder enumerateExpiredMessagesWithBlock:^(TSMessage *message) {
// sanity check
if (message.expiresAt > now) {
DDLogError(
@"%@ Refusing to remove message which doesn't expire until: %lld", self.tag, message.expiresAt);
return;
}
DDLogDebug(@"%@ Removing message which expired at: %lld", self.tag, message.expiresAt);
[message remove];
expirationCount++;
DDLogDebug(@"%@ Removing message which expired at: %lld", self.tag, message.expiresAt);
[message removeWithTransaction:transaction];
expirationCount++;
}
transaction:transaction];
}];
DDLogDebug(@"%@ Removed %u expired messages", self.tag, expirationCount);
}
// This method should only be called on the serialQueue.
- (void)runLoop
{
DDLogVerbose(@"%@ Run", self.tag);
@ -106,7 +117,11 @@ NS_ASSUME_NONNULL_BEGIN
[self run];
uint64_t now = [NSDate ows_millisecondTimeStamp];
NSNumber *nextExpirationTimestampNumber = [self.disappearingMessagesFinder nextExpirationTimestamp];
__block NSNumber *nextExpirationTimestampNumber;
[self.databaseConnection readWithBlock:^(YapDatabaseReadTransaction *_Nonnull transaction) {
nextExpirationTimestampNumber =
[self.disappearingMessagesFinder nextExpirationTimestampWithTransaction:transaction];
}];
if (!nextExpirationTimestampNumber) {
// In theory we could kill the loop here. It should resume when the next expiring message is saved,
// But this is a safeguard for any race conditions that exist while running the job as a new message is saved.
@ -149,8 +164,20 @@ NS_ASSUME_NONNULL_BEGIN
});
}
// This method should only be called on the serialQueue.
- (void)setExpirationForMessage:(TSMessage *)message expirationStartedAt:(uint64_t)expirationStartedAt
{
[self.databaseConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) {
[self setExpirationForMessage:message expirationStartedAt:expirationStartedAt transaction:transaction];
}];
}
- (void)setExpirationForMessage:(TSMessage *)message
expirationStartedAt:(uint64_t)expirationStartedAt
transaction:(YapDatabaseReadWriteTransaction *_Nonnull)transaction
{
OWSAssert(transaction);
if (!message.isExpiringMessage) {
return;
}
@ -161,7 +188,7 @@ NS_ASSUME_NONNULL_BEGIN
// Don't clobber if multiple actions simultaneously triggered expiration.
if (message.expireStartedAt == 0 || message.expireStartedAt > expirationStartedAt) {
message.expireStartedAt = expirationStartedAt;
[message save];
[message saveWithTransaction:transaction];
}
// Necessary that the async expiration run happens *after* the message is saved with expiration configuration.
@ -175,19 +202,26 @@ NS_ASSUME_NONNULL_BEGIN
});
}
// This method should only be called on the serialQueue.
- (void)setExpirationsForThread:(TSThread *)thread
{
uint64_t now = [NSDate ows_millisecondTimeStamp];
[self.disappearingMessagesFinder
enumerateUnstartedExpiringMessagesInThread:thread
block:^(TSMessage *_Nonnull message) {
DDLogWarn(@"%@ Starting expiring message which should have already "
@"been started.",
self.tag);
// specify "now" in case D.M. have since been disabled, but we have
// existing unstarted expiring messages that still need to expire.
[self setExpirationForMessage:message expirationStartedAt:now];
}];
[self.databaseConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) {
[self.disappearingMessagesFinder
enumerateUnstartedExpiringMessagesInThread:thread
block:^(TSMessage *_Nonnull message) {
DDLogWarn(
@"%@ Starting expiring message which should have already "
@"been started.",
self.tag);
// specify "now" in case D.M. have since been disabled, but we have
// existing unstarted expiring messages that still need to expire.
[self setExpirationForMessage:message
expirationStartedAt:now
transaction:transaction];
}
transaction:transaction];
}];
}
+ (void)becomeConsistentWithConfigurationForMessage:(TSMessage *)message

View File

@ -36,33 +36,36 @@ static NSString *const OWSFailedAttachmentDownloadsJobAttachmentStateIndex = @"i
return self;
}
- (NSArray<NSString *> *)fetchAttemptingOutAttachmentIds:(YapDatabaseConnection *)dbConnection
- (NSArray<NSString *> *)fetchAttemptingOutAttachmentIdsWithTransaction:
(YapDatabaseReadWriteTransaction *_Nonnull)transaction
{
OWSAssert(transaction);
NSMutableArray<NSString *> *attachmentIds = [NSMutableArray new];
NSString *formattedString = [NSString stringWithFormat:@"WHERE %@ != %d",
OWSFailedAttachmentDownloadsJobAttachmentStateColumn,
(int)TSAttachmentPointerStateFailed];
YapDatabaseQuery *query = [YapDatabaseQuery queryWithFormat:formattedString];
[dbConnection readWithBlock:^(YapDatabaseReadTransaction *_Nonnull transaction) {
[[transaction ext:OWSFailedAttachmentDownloadsJobAttachmentStateIndex]
enumerateKeysMatchingQuery:query
usingBlock:^void(NSString *collection, NSString *key, BOOL *stop) {
[attachmentIds addObject:key];
}];
}];
[[transaction ext:OWSFailedAttachmentDownloadsJobAttachmentStateIndex]
enumerateKeysMatchingQuery:query
usingBlock:^void(NSString *collection, NSString *key, BOOL *stop) {
[attachmentIds addObject:key];
}];
return [attachmentIds copy];
}
- (void)enumerateAttemptingOutAttachmentsWithBlock:(void (^_Nonnull)(TSAttachmentPointer *attachment))block
transaction:(YapDatabaseReadWriteTransaction *_Nonnull)transaction
{
YapDatabaseConnection *dbConnection = [self.storageManager newDatabaseConnection];
OWSAssert(transaction);
// Since we can't directly mutate the enumerated attachments, we store only their ids in hopes
// of saving a little memory and then enumerate the (larger) TSAttachment objects one at a time.
for (NSString *attachmentId in [self fetchAttemptingOutAttachmentIds:dbConnection]) {
TSAttachmentPointer *_Nullable attachment = [TSAttachmentPointer fetchObjectWithUniqueID:attachmentId];
for (NSString *attachmentId in [self fetchAttemptingOutAttachmentIdsWithTransaction:transaction]) {
TSAttachmentPointer *_Nullable attachment =
[TSAttachmentPointer fetchObjectWithUniqueID:attachmentId transaction:transaction];
if ([attachment isKindOfClass:[TSAttachmentPointer class]]) {
block(attachment);
} else {
@ -74,15 +77,19 @@ static NSString *const OWSFailedAttachmentDownloadsJobAttachmentStateIndex = @"i
- (void)run
{
__block uint count = 0;
[self enumerateAttemptingOutAttachmentsWithBlock:^(TSAttachmentPointer *attachment) {
// sanity check
if (attachment.state != TSAttachmentPointerStateFailed) {
DDLogDebug(@"%@ marking attachment as failed", self.tag);
attachment.state = TSAttachmentPointerStateFailed;
[attachment save];
count++;
}
}];
[[self.storageManager newDatabaseConnection]
readWriteWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) {
[self enumerateAttemptingOutAttachmentsWithBlock:^(TSAttachmentPointer *attachment) {
// sanity check
if (attachment.state != TSAttachmentPointerStateFailed) {
DDLogDebug(@"%@ marking attachment as failed", self.tag);
attachment.state = TSAttachmentPointerStateFailed;
[attachment saveWithTransaction:transaction];
count++;
}
}
transaction:transaction];
}];
DDLogDebug(@"%@ Marked %u attachments as unsent", self.tag, count);
}

View File

@ -37,33 +37,36 @@ static NSString *const OWSFailedMessagesJobMessageStateIndex = @"index_outoing_m
return self;
}
- (NSArray<NSString *> *)fetchAttemptingOutMessageIds:(YapDatabaseConnection *)dbConnection
- (NSArray<NSString *> *)fetchAttemptingOutMessageIdsWithTransaction:
(YapDatabaseReadWriteTransaction *_Nonnull)transaction
{
OWSAssert(transaction);
NSMutableArray<NSString *> *messageIds = [NSMutableArray new];
NSString *formattedString = [NSString stringWithFormat:@"WHERE %@ == %d",
OWSFailedMessagesJobMessageStateColumn,
(int)TSOutgoingMessageStateAttemptingOut];
YapDatabaseQuery *query = [YapDatabaseQuery queryWithFormat:formattedString];
[dbConnection readWithBlock:^(YapDatabaseReadTransaction *_Nonnull transaction) {
[[transaction ext:OWSFailedMessagesJobMessageStateIndex]
enumerateKeysMatchingQuery:query
usingBlock:^void(NSString *collection, NSString *key, BOOL *stop) {
[messageIds addObject:key];
}];
}];
[[transaction ext:OWSFailedMessagesJobMessageStateIndex]
enumerateKeysMatchingQuery:query
usingBlock:^void(NSString *collection, NSString *key, BOOL *stop) {
[messageIds addObject:key];
}];
return [messageIds copy];
}
- (void)enumerateAttemptingOutMessagesWithBlock:(void (^_Nonnull)(TSOutgoingMessage *message))block
transaction:(YapDatabaseReadWriteTransaction *_Nonnull)transaction
{
YapDatabaseConnection *dbConnection = [self.storageManager newDatabaseConnection];
OWSAssert(transaction);
// Since we can't directly mutate the enumerated "attempting out" expired messages, we store only their ids in hopes
// of saving a little memory and then enumerate the (larger) TSMessage objects one at a time.
for (NSString *expiredMessageId in [self fetchAttemptingOutMessageIds:dbConnection]) {
TSOutgoingMessage *_Nullable message = [TSOutgoingMessage fetchObjectWithUniqueID:expiredMessageId];
for (NSString *expiredMessageId in [self fetchAttemptingOutMessageIdsWithTransaction:transaction]) {
TSOutgoingMessage *_Nullable message =
[TSOutgoingMessage fetchObjectWithUniqueID:expiredMessageId transaction:transaction];
if ([message isKindOfClass:[TSOutgoingMessage class]]) {
block(message);
} else {
@ -75,18 +78,26 @@ static NSString *const OWSFailedMessagesJobMessageStateIndex = @"index_outoing_m
- (void)run
{
__block uint count = 0;
[self enumerateAttemptingOutMessagesWithBlock:^(TSOutgoingMessage *message) {
// sanity check
OWSAssert(message.messageState == TSOutgoingMessageStateAttemptingOut);
if (message.messageState != TSOutgoingMessageStateAttemptingOut) {
DDLogError(@"%@ Refusing to mark as unsent message with state: %d", self.tag, (int)message.messageState);
return;
}
DDLogDebug(@"%@ marking message as unsent", self.tag);
[message updateWithMessageState:TSOutgoingMessageStateUnsent];
count++;
}];
[[self.storageManager newDatabaseConnection]
readWriteWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) {
[self enumerateAttemptingOutMessagesWithBlock:^(TSOutgoingMessage *message) {
// sanity check
OWSAssert(message.messageState == TSOutgoingMessageStateAttemptingOut);
if (message.messageState != TSOutgoingMessageStateAttemptingOut) {
DDLogError(
@"%@ Refusing to mark as unsent message with state: %d", self.tag, (int)message.messageState);
return;
}
DDLogDebug(@"%@ marking message as unsent: %@", self.tag, message.uniqueId);
[message updateWithMessageState:TSOutgoingMessageStateUnsent transaction:transaction];
OWSAssert(message.messageState == TSOutgoingMessageStateUnsent);
count++;
}
transaction:transaction];
}];
DDLogDebug(@"%@ Marked %u messages as unsent", self.tag, count);
}

View File

@ -0,0 +1,53 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "OWSRecipientIdentity.h"
#import <AxolotlKit/IdentityKeyStore.h>
NS_ASSUME_NONNULL_BEGIN
extern NSString *const TSStorageManagerTrustedKeysCollection;
// This notification will be fired whenever identities are created
// or their verification state changes.
extern NSString *const kNSNotificationName_IdentityStateDidChange;
// number of bytes in a signal identity key, excluding the key-type byte.
extern const NSUInteger kIdentityKeyLength;
@class OWSRecipientIdentity;
@class OWSSignalServiceProtosVerified;
// This class can be safely accessed and used from any thread.
@interface OWSIdentityManager : NSObject <IdentityKeyStore>
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)sharedManager;
- (void)generateNewIdentityKey;
- (nullable NSData *)identityKeyForRecipientId:(NSString *)recipientId;
- (void)setVerificationState:(OWSVerificationState)verificationState
identityKey:(NSData *)identityKey
recipientId:(NSString *)recipientId
isUserInitiatedChange:(BOOL)isUserInitiatedChange;
- (OWSVerificationState)verificationStateForRecipientId:(NSString *)recipientId;
- (nullable OWSRecipientIdentity *)recipientIdentityForRecipientId:(NSString *)recipientId;
/**
* @param recipientId unique stable identifier for the recipient, e.g. e164 phone number
* @returns nil if the recipient does not exist, or is trusted for sending
* else returns the untrusted recipient.
*/
- (nullable OWSRecipientIdentity *)untrustedIdentityForSendingToRecipientId:(NSString *)recipientId;
- (void)processIncomingSyncMessage:(OWSSignalServiceProtosVerified *)verified;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,779 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "OWSIdentityManager.h"
#import "NSDate+millisecondTimeStamp.h"
#import "NotificationsProtocol.h"
#import "OWSError.h"
#import "OWSMessageSender.h"
#import "OWSOutgoingNullMessage.h"
#import "OWSRecipientIdentity.h"
#import "OWSVerificationStateChangeMessage.h"
#import "OWSVerificationStateSyncMessage.h"
#import "TSAccountManager.h"
#import "TSContactThread.h"
#import "TSErrorMessage.h"
#import "TSGroupThread.h"
#import "TSStorageManager+keyingMaterial.h"
#import "TSStorageManager+sessionStore.h"
#import "TSStorageManager.h"
#import "TextSecureKitEnv.h"
#import <25519/Curve25519.h>
#import <AxolotlKit/NSData+keyVersionByte.h>
NS_ASSUME_NONNULL_BEGIN
// Storing our own identity key
NSString *const TSStorageManagerIdentityKeyStoreIdentityKey = @"TSStorageManagerIdentityKeyStoreIdentityKey";
NSString *const TSStorageManagerIdentityKeyStoreCollection = @"TSStorageManagerIdentityKeyStoreCollection";
// Storing recipients identity keys
NSString *const TSStorageManagerTrustedKeysCollection = @"TSStorageManagerTrustedKeysCollection";
NSString *const OWSIdentityManager_QueuedVerificationStateSyncMessages =
@"OWSIdentityManager_QueuedVerificationStateSyncMessages";
// Don't trust an identity for sending to unless they've been around for at least this long
const NSTimeInterval kIdentityKeyStoreNonBlockingSecondsThreshold = 5.0;
// The canonical key includes 32 bytes of identity material plus one byte specifying the key type
const NSUInteger kIdentityKeyLength = 33;
// Cryptographic operations do not use the "type" byte of the identity key, so, for legacy reasons we store just
// the identity material.
// TODO: migrate to storing the full 33 byte representation.
const NSUInteger kStoredIdentityKeyLength = 32;
NSString *const kNSNotificationName_IdentityStateDidChange = @"kNSNotificationName_IdentityStateDidChange";
@interface OWSIdentityManager ()
@property (nonatomic, readonly) TSStorageManager *storageManager;
@property (nonatomic, readonly) OWSMessageSender *messageSender;
@end
#pragma mark -
@implementation OWSIdentityManager
+ (instancetype)sharedManager
{
static OWSIdentityManager *sharedMyManager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedMyManager = [[self alloc] initDefault];
});
return sharedMyManager;
}
- (instancetype)initDefault
{
TSStorageManager *storageManager = [TSStorageManager sharedManager];
OWSMessageSender *messageSender = [TextSecureKitEnv sharedEnv].messageSender;
return [self initWithStorageManager:storageManager messageSender:messageSender];
}
- (instancetype)initWithStorageManager:(TSStorageManager *)storageManager
messageSender:(OWSMessageSender *)messageSender
{
self = [super init];
if (!self) {
return self;
}
OWSAssert(storageManager);
OWSAssert(messageSender);
_storageManager = storageManager;
_messageSender = messageSender;
OWSSingletonAssert();
[self observeNotifications];
return self;
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)observeNotifications
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(applicationDidBecomeActive:)
name:UIApplicationDidBecomeActiveNotification
object:nil];
}
- (void)generateNewIdentityKey
{
[self.storageManager setObject:[Curve25519 generateKeyPair]
forKey:TSStorageManagerIdentityKeyStoreIdentityKey
inCollection:TSStorageManagerIdentityKeyStoreCollection];
}
- (nullable NSData *)identityKeyForRecipientId:(NSString *)recipientId
{
@synchronized(self)
{
return [OWSRecipientIdentity fetchObjectWithUniqueID:recipientId].identityKey;
}
}
- (nullable ECKeyPair *)identityKeyPair
{
return [self.storageManager keyPairForKey:TSStorageManagerIdentityKeyStoreIdentityKey
inCollection:TSStorageManagerIdentityKeyStoreCollection];
}
- (int)localRegistrationId
{
return (int)[TSAccountManager getOrGenerateRegistrationId];
}
- (BOOL)saveRemoteIdentity:(NSData *)identityKey recipientId:(NSString *)recipientId
{
OWSAssert(identityKey.length == kStoredIdentityKeyLength);
OWSAssert(recipientId.length > 0);
@synchronized(self)
{
// Deprecated. We actually no longer use the TSStorageManagerTrustedKeysCollection for trust
// decisions, but it's desirable to try to keep it up to date with our trusted identitys
// while we're switching between versions, e.g. so we don't get into a state where we have a
// session for an identity not in our key store.
[self.storageManager setObject:identityKey
forKey:recipientId
inCollection:TSStorageManagerTrustedKeysCollection];
OWSRecipientIdentity *existingIdentity = [OWSRecipientIdentity fetchObjectWithUniqueID:recipientId];
if (existingIdentity == nil) {
DDLogInfo(@"%@ saving first use identity for recipient: %@", self.tag, recipientId);
[[[OWSRecipientIdentity alloc] initWithRecipientId:recipientId
identityKey:identityKey
isFirstKnownKey:YES
createdAt:[NSDate new]
verificationState:OWSVerificationStateDefault] save];
// Cancel any pending verification state sync messages for this recipient.
[self clearSyncMessageForRecipientId:recipientId];
[self fireIdentityStateChangeNotification];
return NO;
}
if (![existingIdentity.identityKey isEqual:identityKey]) {
OWSVerificationState verificationState;
switch (existingIdentity.verificationState) {
case OWSVerificationStateDefault:
verificationState = OWSVerificationStateDefault;
break;
case OWSVerificationStateVerified:
case OWSVerificationStateNoLongerVerified:
verificationState = OWSVerificationStateNoLongerVerified;
break;
}
DDLogInfo(@"%@ replacing identity for existing recipient: %@ (%@ -> %@)",
self.tag,
recipientId,
OWSVerificationStateToString(existingIdentity.verificationState),
OWSVerificationStateToString(verificationState));
[self createIdentityChangeInfoMessageForRecipientId:recipientId];
[[[OWSRecipientIdentity alloc] initWithRecipientId:recipientId
identityKey:identityKey
isFirstKnownKey:NO
createdAt:[NSDate new]
verificationState:verificationState] save];
dispatch_async([OWSDispatch sessionStoreQueue], ^{
[self.storageManager archiveAllSessionsForContact:recipientId];
});
// Cancel any pending verification state sync messages for this recipient.
[self clearSyncMessageForRecipientId:recipientId];
[self fireIdentityStateChangeNotification];
return YES;
}
return NO;
}
}
- (void)setVerificationState:(OWSVerificationState)verificationState
identityKey:(NSData *)identityKey
recipientId:(NSString *)recipientId
isUserInitiatedChange:(BOOL)isUserInitiatedChange
{
OWSAssert(identityKey.length == kStoredIdentityKeyLength);
OWSAssert(recipientId.length > 0);
@synchronized(self)
{
// Ensure a remote identity exists for this key. We may be learning about
// it for the first time.
[self saveRemoteIdentity:identityKey recipientId:recipientId];
OWSRecipientIdentity *recipientIdentity = [OWSRecipientIdentity fetchObjectWithUniqueID:recipientId];
if (recipientIdentity == nil) {
OWSFail(@"Missing expected identity: %@", recipientId);
return;
}
if (recipientIdentity.verificationState == verificationState) {
return;
}
DDLogInfo(@"%@ setVerificationState: %@ (%@ -> %@)",
self.tag,
recipientId,
OWSVerificationStateToString(recipientIdentity.verificationState),
OWSVerificationStateToString(verificationState));
[recipientIdentity updateWithVerificationState:verificationState];
if (isUserInitiatedChange) {
[self saveChangeMessagesForRecipientId:recipientId verificationState:verificationState isLocalChange:YES];
[self enqueueSyncMessageForVerificationStateForRecipientId:recipientId];
} else {
// Cancel any pending verification state sync messages for this recipient.
[self clearSyncMessageForRecipientId:recipientId];
}
}
[self fireIdentityStateChangeNotification];
}
- (OWSVerificationState)verificationStateForRecipientId:(NSString *)recipientId
{
OWSAssert(recipientId.length > 0);
@synchronized(self)
{
OWSRecipientIdentity *_Nullable currentIdentity = [OWSRecipientIdentity fetchObjectWithUniqueID:recipientId];
if (!currentIdentity) {
// We might not know the identity for this recipient yet.
return OWSVerificationStateDefault;
}
return currentIdentity.verificationState;
}
}
- (nullable OWSRecipientIdentity *)recipientIdentityForRecipientId:(NSString *)recipientId
{
OWSAssert(recipientId.length > 0);
@synchronized(self)
{
return [OWSRecipientIdentity fetchObjectWithUniqueID:recipientId];
}
}
- (nullable OWSRecipientIdentity *)untrustedIdentityForSendingToRecipientId:(NSString *)recipientId
{
OWSAssert(recipientId.length > 0);
@synchronized(self)
{
OWSRecipientIdentity *_Nullable recipientIdentity = [OWSRecipientIdentity fetchObjectWithUniqueID:recipientId];
if (recipientIdentity == nil) {
// trust on first use
return nil;
}
BOOL isTrusted = [self isTrustedIdentityKey:recipientIdentity.identityKey
recipientId:recipientId
direction:TSMessageDirectionOutgoing];
if (isTrusted) {
return nil;
} else {
return recipientIdentity;
}
}
}
- (void)fireIdentityStateChangeNotification
{
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:kNSNotificationName_IdentityStateDidChange
object:nil
userInfo:nil];
});
}
- (BOOL)isTrustedIdentityKey:(NSData *)identityKey
recipientId:(NSString *)recipientId
direction:(TSMessageDirection)direction
{
OWSAssert(identityKey.length == kStoredIdentityKeyLength);
OWSAssert(recipientId.length > 0);
OWSAssert(direction != TSMessageDirectionUnknown);
@synchronized(self)
{
if ([[self.storageManager localNumber] isEqualToString:recipientId]) {
if ([[self identityKeyPair].publicKey isEqualToData:identityKey]) {
return YES;
} else {
DDLogError(@"%@ Wrong identity: %@ for local key: %@, recipientId: %@",
self.tag,
identityKey,
[self identityKeyPair].publicKey,
recipientId);
OWSAssert(NO);
return NO;
}
}
switch (direction) {
case TSMessageDirectionIncoming: {
return YES;
}
case TSMessageDirectionOutgoing: {
OWSRecipientIdentity *existingIdentity = [OWSRecipientIdentity fetchObjectWithUniqueID:recipientId];
return [self isTrustedKey:identityKey forSendingToIdentity:existingIdentity];
}
default: {
DDLogError(@"%@ unexpected message direction: %ld", self.tag, (long)direction);
OWSAssert(NO);
return NO;
}
}
}
}
- (BOOL)isTrustedKey:(NSData *)identityKey forSendingToIdentity:(nullable OWSRecipientIdentity *)recipientIdentity
{
OWSAssert(identityKey.length == kStoredIdentityKeyLength);
@synchronized(self)
{
if (recipientIdentity == nil) {
return YES;
}
OWSAssert(recipientIdentity.identityKey.length == kStoredIdentityKeyLength);
if (![recipientIdentity.identityKey isEqualToData:identityKey]) {
DDLogWarn(@"%@ key mismatch for recipient: %@", self.tag, recipientIdentity.recipientId);
return NO;
}
if ([recipientIdentity isFirstKnownKey]) {
return YES;
}
switch (recipientIdentity.verificationState) {
case OWSVerificationStateDefault: {
BOOL isNew = (fabs([recipientIdentity.createdAt timeIntervalSinceNow])
< kIdentityKeyStoreNonBlockingSecondsThreshold);
if (isNew) {
DDLogWarn(@"%@ not trusting new identity for recipient: %@", self.tag, recipientIdentity.recipientId);
return NO;
} else {
return YES;
}
}
case OWSVerificationStateVerified:
return YES;
case OWSVerificationStateNoLongerVerified:
DDLogWarn(@"%@ not trusting no longer verified identity for recipient: %@", self.tag, recipientIdentity.recipientId);
return NO;
}
}
}
- (void)createIdentityChangeInfoMessageForRecipientId:(NSString *)recipientId
{
OWSAssert(recipientId.length > 0);
NSMutableArray<TSMessage *> *messages = [NSMutableArray new];
TSContactThread *contactThread = [TSContactThread getOrCreateThreadWithContactId:recipientId];
OWSAssert(contactThread != nil);
TSErrorMessage *errorMessage =
[TSErrorMessage nonblockingIdentityChangeInThread:contactThread recipientId:recipientId];
[messages addObject:errorMessage];
[[TextSecureKitEnv sharedEnv].notificationsManager notifyUserForErrorMessage:errorMessage inThread:contactThread];
for (TSGroupThread *groupThread in [TSGroupThread groupThreadsWithRecipientId:recipientId]) {
[messages addObject:[TSErrorMessage nonblockingIdentityChangeInThread:groupThread recipientId:recipientId]];
}
[self.storageManager.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
for (TSMessage *message in messages) {
[message saveWithTransaction:transaction];
}
}];
}
- (void)enqueueSyncMessageForVerificationStateForRecipientId:(NSString *)recipientId
{
OWSAssert(recipientId.length > 0);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
@synchronized(self)
{
[self.storageManager setObject:recipientId
forKey:recipientId
inCollection:OWSIdentityManager_QueuedVerificationStateSyncMessages];
}
dispatch_async(dispatch_get_main_queue(), ^{
[self tryToSyncQueuedVerificationStates];
});
});
}
- (void)tryToSyncQueuedVerificationStates
{
OWSAssert([NSThread isMainThread]);
if ([UIApplication sharedApplication].applicationState != UIApplicationStateActive) {
// Only try to sync if the app is active to avoid interfering with startup.
//
// applicationDidBecomeActive: will try to sync again when the app becomes active.
return;
}
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
@synchronized(self)
{
NSMutableArray<NSString *> *recipientIds = [NSMutableArray new];
[self.storageManager.dbReadWriteConnection readWriteWithBlock:^(
YapDatabaseReadWriteTransaction *transaction) {
[transaction enumerateKeysAndObjectsInCollection:OWSIdentityManager_QueuedVerificationStateSyncMessages
usingBlock:^(NSString *_Nonnull recipientId,
id _Nonnull object,
BOOL *_Nonnull stop) {
[recipientIds addObject:recipientId];
}];
}];
NSMutableArray<OWSVerificationStateSyncMessage *> *messages = [NSMutableArray new];
for (NSString *recipientId in recipientIds) {
OWSRecipientIdentity *recipientIdentity = [OWSRecipientIdentity fetchObjectWithUniqueID:recipientId];
if (!recipientIdentity) {
OWSFail(@"Could not load recipient identity for recipientId: %@", recipientId);
continue;
}
if (recipientIdentity.recipientId.length < 1) {
OWSFail(@"Invalid recipient identity for recipientId: %@", recipientId);
continue;
}
// Prepend key type for transit.
// TODO we should just be storing the key type so we don't have to juggle re-adding it.
NSData *identityKey = [recipientIdentity.identityKey prependKeyType];
if (identityKey.length != kIdentityKeyLength) {
OWSFail(@"Invalid recipient identitykey for recipientId: %@ key: %@", recipientId, identityKey);
continue;
}
if (recipientIdentity.verificationState == OWSVerificationStateNoLongerVerified) {
// We don't want to sync "no longer verified" state. Other clients can
// figure this out from the /profile/ endpoint, and this can cause data
// loss as a user's devices overwrite each other's verification.
OWSFail(@"Queue verification state had unexpected value: %@ recipientId: %@",
OWSVerificationStateToString(recipientIdentity.verificationState),
recipientId);
continue;
}
OWSVerificationStateSyncMessage *message = [[OWSVerificationStateSyncMessage alloc]
initWithVerificationState:recipientIdentity.verificationState
identityKey:identityKey
verificationForRecipientId:recipientIdentity.recipientId];
[messages addObject:message];
}
if (messages.count > 0) {
dispatch_async(dispatch_get_main_queue(), ^{
for (OWSVerificationStateSyncMessage *message in messages) {
[self sendSyncVerificationStateMessage:message];
}
});
}
}
});
}
- (void)sendSyncVerificationStateMessage:(OWSVerificationStateSyncMessage *)message
{
OWSAssert(message);
OWSAssert(message.verificationForRecipientId.length > 0);
OWSAssert([NSThread isMainThread]);
TSContactThread *contactThread = [TSContactThread getOrCreateThreadWithContactId:message.verificationForRecipientId];
// Send null message to appear as though we're sending a normal message to cover the sync messsage sent
// subsequently
OWSOutgoingNullMessage *nullMessage = [[OWSOutgoingNullMessage alloc] initWithContactThread:contactThread
verificationStateSyncMessage:message];
[self.messageSender sendMessage:nullMessage
success:^{
dispatch_async(dispatch_get_main_queue(), ^{
DDLogInfo(@"%@ Successfully sent verification state NullMessage", self.tag);
[self.messageSender sendMessage:message
success:^{
DDLogInfo(@"%@ Successfully sent verification state sync message", self.tag);
// Record that this verification state was successfully synced.
[self clearSyncMessageForRecipientId:message.verificationForRecipientId];
}
failure:^(NSError *error) {
DDLogError(
@"%@ Failed to send verification state sync message with error: %@", self.tag, error);
}];
});
}
failure:^(NSError *_Nonnull error) {
DDLogError(@"%@ Failed to send verification state NullMessage with error: %@", self.tag, error);
if (error.code == OWSErrorCodeNoSuchSignalRecipient) {
DDLogInfo(@"%@ Removing retries for syncing verification state, since user is no longer registered: %@",
self.tag,
message.verificationForRecipientId);
// Otherwise this will fail forever.
[self clearSyncMessageForRecipientId:message.verificationForRecipientId];
}
}];
}
- (void)clearSyncMessageForRecipientId:(NSString *)recipientId
{
OWSAssert(recipientId.length > 0);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
@synchronized(self)
{
[self.storageManager removeObjectForKey:recipientId
inCollection:OWSIdentityManager_QueuedVerificationStateSyncMessages];
}
});
}
- (void)processIncomingSyncMessage:(OWSSignalServiceProtosVerified *)verified
{
NSString *recipientId = verified.destination;
if (recipientId.length < 1) {
OWSFail(@"Verification state sync message missing recipientId.");
return;
}
NSData *rawIdentityKey = verified.identityKey;
if (rawIdentityKey.length != kIdentityKeyLength) {
OWSFail(@"Verification state sync message for recipient: %@ with malformed identityKey: %@",
recipientId,
rawIdentityKey);
return;
}
NSData *identityKey = [rawIdentityKey removeKeyType];
switch (verified.state) {
case OWSSignalServiceProtosVerifiedStateDefault:
[self tryToApplyVerificationStateFromSyncMessage:OWSVerificationStateDefault
recipientId:recipientId
identityKey:identityKey
overwriteOnConflict:NO];
break;
case OWSSignalServiceProtosVerifiedStateVerified:
[self tryToApplyVerificationStateFromSyncMessage:OWSVerificationStateVerified
recipientId:recipientId
identityKey:identityKey
overwriteOnConflict:YES];
break;
case OWSSignalServiceProtosVerifiedStateUnverified:
OWSFail(@"Verification state sync message for recipientId: %@ has unexpected value: %@.",
recipientId,
OWSVerificationStateToString(OWSVerificationStateNoLongerVerified));
return;
}
[self fireIdentityStateChangeNotification];
}
- (void)tryToApplyVerificationStateFromSyncMessage:(OWSVerificationState)verificationState
recipientId:(NSString *)recipientId
identityKey:(NSData *)identityKey
overwriteOnConflict:(BOOL)overwriteOnConflict
{
if (recipientId.length < 1) {
OWSFail(@"Verification state sync message missing recipientId.");
return;
}
if (identityKey.length != kStoredIdentityKeyLength) {
OWSFail(@"Verification state sync message missing identityKey: %@", recipientId);
return;
}
@synchronized(self)
{
OWSRecipientIdentity *_Nullable recipientIdentity = [OWSRecipientIdentity fetchObjectWithUniqueID:recipientId];
if (!recipientIdentity) {
// There's no existing recipient identity for this recipient.
// We should probably create one.
if (verificationState == OWSVerificationStateDefault) {
// There's no point in creating a new recipient identity just to
// set its verification state to default.
return;
}
// Ensure a remote identity exists for this key. We may be learning about
// it for the first time.
[self saveRemoteIdentity:identityKey recipientId:recipientId];
recipientIdentity = [OWSRecipientIdentity fetchObjectWithUniqueID:recipientId];
if (recipientIdentity == nil) {
OWSFail(@"Missing expected identity: %@", recipientId);
return;
}
if (![recipientIdentity.recipientId isEqualToString:recipientId]) {
OWSFail(@"recipientIdentity has unexpected recipientId: %@", recipientId);
return;
}
if (![recipientIdentity.identityKey isEqualToData:identityKey]) {
OWSFail(@"recipientIdentity has unexpected identityKey: %@", recipientId);
return;
}
if (recipientIdentity.verificationState == verificationState) {
return;
}
DDLogInfo(@"%@ setVerificationState: %@ (%@ -> %@)",
self.tag,
recipientId,
OWSVerificationStateToString(recipientIdentity.verificationState),
OWSVerificationStateToString(verificationState));
[recipientIdentity updateWithVerificationState:verificationState];
// No need to call [saveChangeMessagesForRecipientId:..] since this is
// a new recipient.
} else {
// There's an existing recipient identity for this recipient.
// We should update it.
if (![recipientIdentity.recipientId isEqualToString:recipientId]) {
OWSFail(@"recipientIdentity has unexpected recipientId: %@", recipientId);
return;
}
if (![recipientIdentity.identityKey isEqualToData:identityKey]) {
// The conflict case where we receive a verification sync message
// whose identity key disagrees with the local identity key for
// this recipient.
if (!overwriteOnConflict) {
DDLogWarn(@"recipientIdentity has non-matching identityKey: %@", recipientId);
return;
}
DDLogWarn(@"recipientIdentity has non-matching identityKey; overwriting: %@", recipientId);
[self saveRemoteIdentity:identityKey recipientId:recipientId];
recipientIdentity = [OWSRecipientIdentity fetchObjectWithUniqueID:recipientId];
if (recipientIdentity == nil) {
OWSFail(@"Missing expected identity: %@", recipientId);
return;
}
if (![recipientIdentity.recipientId isEqualToString:recipientId]) {
OWSFail(@"recipientIdentity has unexpected recipientId: %@", recipientId);
return;
}
if (![recipientIdentity.identityKey isEqualToData:identityKey]) {
OWSFail(@"recipientIdentity has unexpected identityKey: %@", recipientId);
return;
}
}
if (recipientIdentity.verificationState == verificationState) {
return;
}
[recipientIdentity updateWithVerificationState:verificationState];
[self saveChangeMessagesForRecipientId:recipientId verificationState:verificationState isLocalChange:NO];
}
}
}
// We only want to create change messages in response to user activity,
// on any of their devices.
- (void)saveChangeMessagesForRecipientId:(NSString *)recipientId
verificationState:(OWSVerificationState)verificationState
isLocalChange:(BOOL)isLocalChange
{
OWSAssert(recipientId.length > 0);
NSMutableArray<TSMessage *> *messages = [NSMutableArray new];
TSContactThread *contactThread = [TSContactThread getOrCreateThreadWithContactId:recipientId];
OWSAssert(contactThread);
[messages addObject:[[OWSVerificationStateChangeMessage alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp]
thread:contactThread
recipientId:recipientId
verificationState:verificationState
isLocalChange:isLocalChange]];
for (TSGroupThread *groupThread in [TSGroupThread groupThreadsWithRecipientId:recipientId]) {
[messages
addObject:[[OWSVerificationStateChangeMessage alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp]
thread:groupThread
recipientId:recipientId
verificationState:verificationState
isLocalChange:isLocalChange]];
}
[self.storageManager.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
for (TSMessage *message in messages) {
[message saveWithTransaction:transaction];
}
}];
}
#pragma mark - Notifications
- (void)applicationDidBecomeActive:(NSNotification *)notification
{
OWSAssert([NSThread isMainThread]);
// We want to defer this so that we never call this method until
// [UIApplicationDelegate applicationDidBecomeActive:] is complete.
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)1.f * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
[self tryToSyncQueuedVerificationStates];
});
}
#pragma mark - Logging
+ (NSString *)tag
{
return [NSString stringWithFormat:@"[%@]", self.class];
}
- (NSString *)tag
{
return self.class.tag;
}
@end
NS_ASSUME_NONNULL_END

View File

@ -4,8 +4,6 @@
#import "OWSIncomingMessageReadObserver.h"
#import "NSDate+millisecondTimeStamp.h"
#import "OWSDisappearingMessagesConfiguration.h"
#import "OWSDisappearingMessagesJob.h"
#import "OWSSendReadReceiptsJob.h"
#import "TSIncomingMessage.h"
@ -13,7 +11,7 @@ NS_ASSUME_NONNULL_BEGIN
@interface OWSIncomingMessageReadObserver ()
@property BOOL isObserving;
@property (nonatomic) BOOL isObserving;
@property (nonatomic, readonly) OWSSendReadReceiptsJob *sendReadReceiptsJob;
@end
@ -41,6 +39,8 @@ NS_ASSUME_NONNULL_BEGIN
- (void)startObserving
{
OWSAssert([NSThread isMainThread]);
if (self.isObserving) {
return;
}
@ -60,7 +60,6 @@ NS_ASSUME_NONNULL_BEGIN
}
TSIncomingMessage *message = (TSIncomingMessage *)notification.object;
[OWSDisappearingMessagesJob setExpirationForMessage:message];
[self.sendReadReceiptsJob runWith:message];
}

View File

@ -1,20 +0,0 @@
// Copyright © 2016 Open Whisper Systems. All rights reserved.
#import "OWSMessageServiceParams.h"
NS_ASSUME_NONNULL_BEGIN
/**
* Contstructs the per-device-message parameters used when submitting a message to
* the Signal Web Service. Using a legacy parameter format. Cannot be used for Sync messages.
*/
@interface OWSLegacyMessageServiceParams : OWSMessageServiceParams
- (instancetype)initWithType:(TSWhisperMessageType)type
recipientId:(NSString *)destination
device:(int)deviceId
body:(NSData *)body
registrationId:(int)registrationId;
@end
NS_ASSUME_NONNULL_END

View File

@ -1,33 +0,0 @@
// Copyright © 2016 Open Whisper Systems. All rights reserved.
#import "OWSLegacyMessageServiceParams.h"
NS_ASSUME_NONNULL_BEGIN
@implementation OWSLegacyMessageServiceParams
+ (NSDictionary *)JSONKeyPathsByPropertyKey
{
NSMutableDictionary *keys = [[super JSONKeyPathsByPropertyKey] mutableCopy];
[keys setObject:@"body" forKey:@"content"];
return [keys copy];
}
- (instancetype)initWithType:(TSWhisperMessageType)type
recipientId:(NSString *)destination
device:(int)deviceId
body:(NSData *)body
registrationId:(int)registrationId
{
self = [super initWithType:type recipientId:destination device:deviceId content:body registrationId:registrationId];
if (!self) {
return self;
}
return self;
}
@end
NS_ASSUME_NONNULL_END

View File

@ -71,7 +71,7 @@ NS_SWIFT_NAME(MessageSender)
*/
- (void)sendAttachmentData:(NSData *)attachmentData
contentType:(NSString *)contentType
filename:(nullable NSString *)filename
sourceFilename:(nullable NSString *)sourceFilename
inMessage:(TSOutgoingMessage *)outgoingMessage
success:(void (^)())successHandler
failure:(void (^)(NSError *error))failureHandler;
@ -86,14 +86,6 @@ NS_SWIFT_NAME(MessageSender)
success:(void (^)())successHandler
failure:(void (^)(NSError *error))failureHandler;
/**
* Resend a message to a select recipient in a thread when previous sending failed due to key error.
* e.g. If a key change prevents one recipient from receiving the message, we don't want to resend to the entire group.
*/
- (void)resendMessageFromKeyError:(TSInvalidIdentityKeySendingErrorMessage *)errorMessage
success:(void (^)())successHandler
failure:(void (^)(NSError *error))failureHandler;
- (void)handleMessageSentRemotely:(TSOutgoingMessage *)message sentAt:(uint64_t)sentAt;
/**

View File

@ -4,12 +4,13 @@
#import "OWSMessageSender.h"
#import "ContactsUpdater.h"
#import "NSData+keyVersionByte.h"
#import "NSData+messagePadding.h"
#import "OWSBlockingManager.h"
#import "OWSDevice.h"
#import "OWSDisappearingMessagesJob.h"
#import "OWSError.h"
#import "OWSLegacyMessageServiceParams.h"
#import "OWSIdentityManager.h"
#import "OWSMessageServiceParams.h"
#import "OWSOutgoingSentMessageTranscript.h"
#import "OWSOutgoingSyncMessage.h"
@ -26,7 +27,6 @@
#import "TSNetworkManager.h"
#import "TSOutgoingMessage.h"
#import "TSPreKeyManager.h"
#import "TSStorageManager+IdentityKeyStore.h"
#import "TSStorageManager+PreKeyStore.h"
#import "TSStorageManager+SignedPreKeyStore.h"
#import "TSStorageManager+keyingMaterial.h"
@ -192,8 +192,8 @@ NSUInteger const OWSSendMessageOperationMaxRetries = 4;
[message updateWithMessageState:TSOutgoingMessageStateSentToService];
DDLogDebug(@"%@ succeeded.", strongSelf.tag);
aSuccessHandler();
[strongSelf markAsComplete];
};
@ -208,6 +208,7 @@ NSUInteger const OWSSendMessageOperationMaxRetries = 4;
DDLogDebug(@"%@ failed with error: %@", strongSelf.tag, error);
aFailureHandler(error);
[strongSelf markAsComplete];
};
@ -279,10 +280,13 @@ NSUInteger const OWSSendMessageOperationMaxRetries = 4;
- (void)tryWithRemainingRetries:(NSUInteger)remainingRetries
{
DDLogDebug(@"%@ remainingRetries: %lu", self.tag, (unsigned long)remainingRetries);
// Use this flag to ensure a given operation only succeeds or fails once.
__block BOOL onceFlag = NO;
RetryableFailureHandler retryableFailureHandler = ^(NSError *_Nonnull error) {
DDLogInfo(@"%@ Sending failed.", self.tag);
DDLogInfo(@"%@ Sending failed. Remaining retries: %lu", self.tag, (unsigned long)remainingRetries);
OWSAssert(!onceFlag);
onceFlag = YES;
if (![error isRetryable] || [error isFatal]) {
DDLogInfo(@"%@ Skipping retry due to terminal error: %@", self.tag, error);
@ -299,14 +303,29 @@ NSUInteger const OWSSendMessageOperationMaxRetries = 4;
}
};
[self.messageSender attemptToSendMessage:self.message success:self.successHandler failure:retryableFailureHandler];
[self.messageSender attemptToSendMessage:self.message
success:^{
OWSAssert(!onceFlag);
onceFlag = YES;
self.successHandler();
}
failure:retryableFailureHandler];
}
- (void)markAsComplete
{
[self willChangeValueForKey:OWSSendMessageOperationKeyIsExecuting];
[self willChangeValueForKey:OWSSendMessageOperationKeyIsFinished];
self.operationState = OWSSendMessageOperationStateFinished;
// Ensure we call the success or failure handler exactly once.
@synchronized(self)
{
OWSAssert(self.operationState != OWSSendMessageOperationStateFinished);
self.operationState = OWSSendMessageOperationStateFinished;
}
[self didChangeValueForKey:OWSSendMessageOperationKeyIsExecuting];
[self didChangeValueForKey:OWSSendMessageOperationKeyIsFinished];
@ -428,8 +447,6 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
success:(void (^)())successHandler
failure:(RetryableFailureHandler)failureHandler
{
DDLogDebug(@"%@ sending message: %@", self.tag, message.debugDescription);
[self ensureAnyAttachmentsUploaded:message
success:^() {
[self deliverMessage:message
@ -450,7 +467,6 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
failure:(RetryableFailureHandler)failureHandler
{
if (!message.hasAttachments) {
DDLogDebug(@"%@ No attachments for message: %@", self.tag, message);
return successHandler();
}
@ -493,7 +509,7 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
[self sendAttachmentData:attachmentData
contentType:contentType
filename:nil
sourceFilename:nil
inMessage:message
success:successWithDeleteHandler
failure:failureWithDeleteHandler];
@ -501,7 +517,7 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
- (void)sendAttachmentData:(NSData *)data
contentType:(NSString *)contentType
filename:(nullable NSString *)filename
sourceFilename:(nullable NSString *)sourceFilename
inMessage:(TSOutgoingMessage *)message
success:(void (^)())successHandler
failure:(void (^)(NSError *error))failureHandler
@ -515,7 +531,7 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
dispatch_async([OWSDispatch attachmentsQueue], ^{
TSAttachmentStream *attachmentStream =
[[TSAttachmentStream alloc] initWithContentType:contentType filename:filename];
[[TSAttachmentStream alloc] initWithContentType:contentType sourceFilename:sourceFilename];
if (message.isVoiceMessage) {
attachmentStream.attachmentType = TSAttachmentTypeVoiceMessage;
}
@ -529,8 +545,8 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
[attachmentStream save];
[message.attachmentIds addObject:attachmentStream.uniqueId];
if (filename) {
message.attachmentFilenameMap[attachmentStream.uniqueId] = filename;
if (sourceFilename) {
message.attachmentFilenameMap[attachmentStream.uniqueId] = sourceFilename;
}
[message save];
@ -540,34 +556,6 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
});
}
- (void)resendMessageFromKeyError:(TSInvalidIdentityKeySendingErrorMessage *)errorMessage
success:(void (^)())successHandler
failure:(void (^)(NSError *error))failureHandler
{
AssertIsOnMainThread();
OWSAssert(errorMessage);
NSString *failedMessageId = errorMessage.messageId;
// Here we remove the existing error message because sending a new message will either
// 1.) succeed and create a new successful message in the thread or...
// 2.) fail and create a new identical error message in the thread.
[errorMessage remove];
// The failedMessageId might be nil for transient, unsaved outgoing messages.
// See [TSOutgoingMessage saveWithTransaction:] for details of which messages
// we do not save.
if (!failedMessageId) {
return;
}
TSOutgoingMessage *message = [TSOutgoingMessage fetchObjectWithUniqueID:failedMessageId];
OWSAssert(message);
return [self sendMessage:message success:successHandler failure:failureHandler];
}
- (NSArray<SignalRecipient *> *)getRecipients:(NSArray<NSString *> *)identifiers error:(NSError **)error
{
NSMutableArray<SignalRecipient *> *recipients = [NSMutableArray new];
@ -790,6 +778,7 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
// retryable errors.
if ([error isFatal]) {
failureHandler(error);
return;
}
if ([error isRetryable] && !firstRetryableError) {
@ -885,20 +874,55 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
} @catch (NSException *exception) {
deviceMessages = @[];
if ([exception.name isEqualToString:UntrustedIdentityKeyException]) {
[[TSInvalidIdentityKeySendingErrorMessage
untrustedKeyWithOutgoingMessage:message
inThread:thread
forRecipient:exception.userInfo[TSInvalidRecipientKey]
preKeyBundle:exception.userInfo[TSInvalidPreKeyBundleKey]] save];
NSError *error = OWSErrorWithCodeDescription(OWSErrorCodeUntrustedIdentityKey,
NSLocalizedString(@"FAILED_SENDING_BECAUSE_UNTRUSTED_IDENTITY_KEY",
@"action sheet header when re-sending message which failed because of untrusted identity keys"));
// This *can* happen under normal usage, but it should happen relatively rarely.
// We expect it to happen whenever Bob reinstalls, and Alice messages Bob before
// she can pull down his latest identity.
// If it's happening a lot, we should rethink our profile fetching strategy.
OWSAnalyticsInfo(@"Message send failed due to untrusted key.");
NSString *localizedErrorDescriptionFormat
= NSLocalizedString(@"FAILED_SENDING_BECAUSE_UNTRUSTED_IDENTITY_KEY",
@"action sheet header when re-sending message which failed because of untrusted identity keys");
NSString *localizedErrorDescription =
[NSString stringWithFormat:localizedErrorDescriptionFormat,
[self.contactsManager displayNameForPhoneIdentifier:recipient.recipientId]];
NSError *error = OWSErrorWithCodeDescription(OWSErrorCodeUntrustedIdentityKey, localizedErrorDescription);
// Key will continue to be unaccepted, so no need to retry. It'll only cause us to hit the Pre-Key request
// rate limit
[error setIsRetryable:NO];
// Avoid the "Too many failures with this contact" error rate limiting.
[error setIsFatal:YES];
return failureHandler(error);
PreKeyBundle *newKeyBundle = exception.userInfo[TSInvalidPreKeyBundleKey];
if (![newKeyBundle isKindOfClass:[PreKeyBundle class]]) {
OWSFail(@"%@ unexpected TSInvalidPreKeyBundleKey: %@", self.tag, newKeyBundle);
failureHandler(error);
return;
}
NSData *newIdentityKeyWithVersion = newKeyBundle.identityKey;
if (![newIdentityKeyWithVersion isKindOfClass:[NSData class]]) {
OWSFail(@"%@ unexpected TSInvalidRecipientKey: %@", self.tag, newIdentityKeyWithVersion);
failureHandler(error);
return;
}
// TODO migrate to storing the full 33 byte representation of the identity key.
if (newIdentityKeyWithVersion.length != kIdentityKeyLength) {
OWSFail(@"%@ unexpected key length: %lu", self.tag, (unsigned long)newIdentityKeyWithVersion.length);
failureHandler(error);
return;
}
NSData *newIdentityKey = [newIdentityKeyWithVersion removeKeyType];
[[OWSIdentityManager sharedManager] saveRemoteIdentity:newIdentityKey recipientId:recipient.recipientId];
failureHandler(error);
return;
}
if ([exception.name isEqualToString:OWSMessageSenderRateLimitedException]) {
@ -938,10 +962,7 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
});
}
failure:^(NSURLSessionDataTask *task, NSError *error) {
DDLogInfo(@"%@ sending to recipient: %@, failed with error: %@",
self.tag,
recipient.uniqueId,
message.debugDescription);
DDLogInfo(@"%@ sending to recipient: %@, failed with error: %@", self.tag, recipient.uniqueId, error);
[DDLog flushLog];
NSHTTPURLResponse *response = (NSHTTPURLResponse *)task.response;
@ -1140,7 +1161,9 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
inThread:(TSThread *)thread
{
NSMutableArray *messagesArray = [NSMutableArray arrayWithCapacity:recipient.devices.count];
NSData *plainText = [message buildPlainTextData];
DDLogDebug(@"%@ built message: %@ plainTextData.length: %lu", self.tag, [message class], plainText.length);
for (NSNumber *deviceNumber in recipient.devices) {
@try {
@ -1153,8 +1176,7 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
messageDict = [self encryptedMessageWithPlaintext:plainText
toRecipient:recipient.uniqueId
deviceId:deviceNumber
keyingStorage:[TSStorageManager sharedManager]
legacy:message.isLegacyMessage];
keyingStorage:[TSStorageManager sharedManager]];
} @catch (NSException *exception) {
encryptionException = exception;
}
@ -1187,7 +1209,6 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
toRecipient:(NSString *)identifier
deviceId:(NSNumber *)deviceNumber
keyingStorage:(TSStorageManager *)storage
legacy:(BOOL)isLegacymessage
{
if (![storage containsSession:identifier deviceId:[deviceNumber intValue]]) {
__block dispatch_semaphore_t sema = dispatch_semaphore_create(0);
@ -1228,7 +1249,7 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
SessionBuilder *builder = [[SessionBuilder alloc] initWithSessionStore:storage
preKeyStore:storage
signedPreKeyStore:storage
identityKeyStore:storage
identityKeyStore:[OWSIdentityManager sharedManager]
recipientId:identifier
deviceId:[deviceNumber intValue]];
@try {
@ -1251,7 +1272,7 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
SessionCipher *cipher = [[SessionCipher alloc] initWithSessionStore:storage
preKeyStore:storage
signedPreKeyStore:storage
identityKeyStore:storage
identityKeyStore:[OWSIdentityManager sharedManager]
recipientId:identifier
deviceId:[deviceNumber intValue]];
@ -1261,21 +1282,11 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
NSData *serializedMessage = encryptedMessage.serialized;
TSWhisperMessageType messageType = [self messageTypeForCipherMessage:encryptedMessage];
OWSMessageServiceParams *messageParams;
// DEPRECATED - Remove after all clients have been upgraded.
if (isLegacymessage) {
messageParams = [[OWSLegacyMessageServiceParams alloc] initWithType:messageType
recipientId:identifier
device:[deviceNumber intValue]
body:serializedMessage
registrationId:cipher.remoteRegistrationId];
} else {
messageParams = [[OWSMessageServiceParams alloc] initWithType:messageType
recipientId:identifier
device:[deviceNumber intValue]
content:serializedMessage
registrationId:cipher.remoteRegistrationId];
}
OWSMessageServiceParams *messageParams = [[OWSMessageServiceParams alloc] initWithType:messageType
recipientId:identifier
device:[deviceNumber intValue]
content:serializedMessage
registrationId:cipher.remoteRegistrationId];
NSError *error;
NSDictionary *jsonDict = [MTLJSONAdapter JSONDictionaryFromModel:messageParams error:&error];
@ -1316,6 +1327,7 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
}
}
// Called when the server indicates that the devices no longer exist - e.g. when the remote recipient has reinstalled.
- (void)handleStaleDevicesWithResponse:(NSData *)responseData
recipientId:(NSString *)identifier
completion:(void (^)())completionHandler

View File

@ -1,5 +1,6 @@
// Created by Michael Kirk on 12/1/16.
// Copyright © 2016 Open Whisper Systems. All rights reserved.
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "OWSOutgoingCallMessage.h"
#import "NSDate+millisecondTimeStamp.h"
@ -109,10 +110,6 @@ NS_ASSUME_NONNULL_BEGIN
return NO;
}
- (BOOL)isLegacyMessage
{
return NO;
}
//
///**
// * override thread accessor in superclass, since this model is never saved.

View File

@ -0,0 +1,19 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "TSOutgoingMessage.h"
NS_ASSUME_NONNULL_BEGIN
@class OWSVerificationStateSyncMessage;
@class TSContactThread;
@interface OWSOutgoingNullMessage : TSOutgoingMessage
- (instancetype)initWithContactThread:(TSContactThread *)contactThread
verificationStateSyncMessage:(OWSVerificationStateSyncMessage *)verificationStateSyncMessage;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,76 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "OWSOutgoingNullMessage.h"
#import "OWSSignalServiceProtos.pb.h"
#import "Cryptography.h"
#import "OWSVerificationStateSyncMessage.h"
#import "NSDate+millisecondTimeStamp.h"
#import "TSContactThread.h"
NS_ASSUME_NONNULL_BEGIN
@interface OWSOutgoingNullMessage ()
@property (nonatomic, readonly) OWSVerificationStateSyncMessage *verificationStateSyncMessage;
@end
@implementation OWSOutgoingNullMessage
- (instancetype)initWithContactThread:(TSContactThread *)contactThread
verificationStateSyncMessage:(OWSVerificationStateSyncMessage *)verificationStateSyncMessage
{
self = [super initWithTimestamp:[NSDate ows_millisecondTimeStamp]
inThread:contactThread];
if (!self) {
return self;
}
_verificationStateSyncMessage = verificationStateSyncMessage;
return self;
}
#pragma mark - override TSOutgoingMessage
- (NSData *)buildPlainTextData
{
OWSSignalServiceProtosContentBuilder *contentBuilder = [OWSSignalServiceProtosContentBuilder new];
OWSSignalServiceProtosNullMessageBuilder *nullMessageBuilder = [OWSSignalServiceProtosNullMessageBuilder new];
NSUInteger contentLength = self.verificationStateSyncMessage.unpaddedVerifiedLength;
OWSAssert(self.verificationStateSyncMessage.paddingBytesLength > 0);
// We add the same amount of padding in the VerificationStateSync message and it's coresponding NullMessage so that
// the sync message is indistinguishable from an outgoing Sent transcript corresponding to the NullMessage. We pad
// the NullMessage so as to obscure it's content. The sync message (like all sync messages) will be *additionally*
// padded by the superclass while being sent. The end result is we send a NullMessage of a non-distinct size, and a
// verification sync which is ~1-512 bytes larger then that.
contentLength += self.verificationStateSyncMessage.paddingBytesLength;
OWSAssert(contentLength > 0)
nullMessageBuilder.padding = [Cryptography generateRandomBytes:contentLength];
contentBuilder.nullMessage = [nullMessageBuilder build];
return [contentBuilder build].data;
}
- (BOOL)shouldSyncTranscript
{
return NO;
}
- (void)saveWithTransaction:(YapDatabaseReadWriteTransaction *)transaction
{
// No-op as we don't want to actually display this as an outgoing message in our thread.
return;
}
@end
NS_ASSUME_NONNULL_END

View File

@ -15,13 +15,15 @@
*/
@property (nonatomic, readonly, getter=wasRead) BOOL read;
/**
* Call when the user viewed the message/call on this device. "locally" as opposed to being notified via a read receipt
* sync message of a remote read.
*/
- (void)markAsReadLocally;
- (void)markAsReadLocallyWithTransaction:(YapDatabaseReadWriteTransaction *)transaction;
@property (nonatomic, readonly) NSString *uniqueThreadId;
- (BOOL)shouldAffectUnreadCounts;
/**
* Used for *responding* to a remote read receipt or in response to user activity.
*/
- (void)markAsReadWithTransaction:(YapDatabaseReadWriteTransaction *)transaction
sendReadReceipt:(BOOL)sendReadReceipt
updateExpiration:(BOOL)updateExpiration;
@end

View File

@ -36,6 +36,8 @@
@class OWSSignalServiceProtosGroupDetailsAvatar;
@class OWSSignalServiceProtosGroupDetailsAvatarBuilder;
@class OWSSignalServiceProtosGroupDetailsBuilder;
@class OWSSignalServiceProtosNullMessage;
@class OWSSignalServiceProtosNullMessageBuilder;
@class OWSSignalServiceProtosSyncMessage;
@class OWSSignalServiceProtosSyncMessageBlocked;
@class OWSSignalServiceProtosSyncMessageBlockedBuilder;
@ -50,6 +52,8 @@
@class OWSSignalServiceProtosSyncMessageRequestBuilder;
@class OWSSignalServiceProtosSyncMessageSent;
@class OWSSignalServiceProtosSyncMessageSentBuilder;
@class OWSSignalServiceProtosVerified;
@class OWSSignalServiceProtosVerifiedBuilder;
@class ObjectiveCFileOptions;
@class ObjectiveCFileOptionsBuilder;
@class PBDescriptorProto;
@ -107,6 +111,15 @@ typedef NS_ENUM(SInt32, OWSSignalServiceProtosEnvelopeType) {
BOOL OWSSignalServiceProtosEnvelopeTypeIsValidValue(OWSSignalServiceProtosEnvelopeType value);
NSString *NSStringFromOWSSignalServiceProtosEnvelopeType(OWSSignalServiceProtosEnvelopeType value);
typedef NS_ENUM(SInt32, OWSSignalServiceProtosVerifiedState) {
OWSSignalServiceProtosVerifiedStateDefault = 0,
OWSSignalServiceProtosVerifiedStateVerified = 1,
OWSSignalServiceProtosVerifiedStateUnverified = 2,
};
BOOL OWSSignalServiceProtosVerifiedStateIsValidValue(OWSSignalServiceProtosVerifiedState value);
NSString *NSStringFromOWSSignalServiceProtosVerifiedState(OWSSignalServiceProtosVerifiedState value);
typedef NS_ENUM(SInt32, OWSSignalServiceProtosDataMessageFlags) {
OWSSignalServiceProtosDataMessageFlagsEndSession = 1,
OWSSignalServiceProtosDataMessageFlagsExpirationTimerUpdate = 2,
@ -126,7 +139,7 @@ BOOL OWSSignalServiceProtosSyncMessageRequestTypeIsValidValue(OWSSignalServicePr
NSString *NSStringFromOWSSignalServiceProtosSyncMessageRequestType(OWSSignalServiceProtosSyncMessageRequestType value);
typedef NS_ENUM(SInt32, OWSSignalServiceProtosAttachmentPointerFlags) {
OWSSignalServiceProtosAttachmentPointerFlagsVoiceMessage = 1,
OWSSignalServiceProtosAttachmentPointerFlagsVoiceMessage = 1,
};
BOOL OWSSignalServiceProtosAttachmentPointerFlagsIsValidValue(OWSSignalServiceProtosAttachmentPointerFlags value);
@ -263,21 +276,26 @@ NSString *NSStringFromOWSSignalServiceProtosGroupContextType(OWSSignalServicePro
#define Content_dataMessage @"dataMessage"
#define Content_syncMessage @"syncMessage"
#define Content_callMessage @"callMessage"
#define Content_nullMessage @"nullMessage"
@interface OWSSignalServiceProtosContent : PBGeneratedMessage<GeneratedMessageProtocol> {
@private
BOOL hasDataMessage_:1;
BOOL hasSyncMessage_:1;
BOOL hasCallMessage_:1;
BOOL hasNullMessage_:1;
OWSSignalServiceProtosDataMessage* dataMessage;
OWSSignalServiceProtosSyncMessage* syncMessage;
OWSSignalServiceProtosCallMessage* callMessage;
OWSSignalServiceProtosNullMessage* nullMessage;
}
- (BOOL) hasDataMessage;
- (BOOL) hasSyncMessage;
- (BOOL) hasCallMessage;
- (BOOL) hasNullMessage;
@property (readonly, strong) OWSSignalServiceProtosDataMessage* dataMessage;
@property (readonly, strong) OWSSignalServiceProtosSyncMessage* syncMessage;
@property (readonly, strong) OWSSignalServiceProtosCallMessage* callMessage;
@property (readonly, strong) OWSSignalServiceProtosNullMessage* nullMessage;
+ (instancetype) defaultInstance;
- (instancetype) defaultInstance;
@ -334,6 +352,143 @@ NSString *NSStringFromOWSSignalServiceProtosGroupContextType(OWSSignalServicePro
- (OWSSignalServiceProtosContentBuilder*) setCallMessageBuilder:(OWSSignalServiceProtosCallMessageBuilder*) builderForValue;
- (OWSSignalServiceProtosContentBuilder*) mergeCallMessage:(OWSSignalServiceProtosCallMessage*) value;
- (OWSSignalServiceProtosContentBuilder*) clearCallMessage;
- (BOOL) hasNullMessage;
- (OWSSignalServiceProtosNullMessage*) nullMessage;
- (OWSSignalServiceProtosContentBuilder*) setNullMessage:(OWSSignalServiceProtosNullMessage*) value;
- (OWSSignalServiceProtosContentBuilder*) setNullMessageBuilder:(OWSSignalServiceProtosNullMessageBuilder*) builderForValue;
- (OWSSignalServiceProtosContentBuilder*) mergeNullMessage:(OWSSignalServiceProtosNullMessage*) value;
- (OWSSignalServiceProtosContentBuilder*) clearNullMessage;
@end
#define NullMessage_padding @"padding"
@interface OWSSignalServiceProtosNullMessage : PBGeneratedMessage<GeneratedMessageProtocol> {
@private
BOOL hasPadding_:1;
NSData* padding;
}
- (BOOL) hasPadding;
@property (readonly, strong) NSData* padding;
+ (instancetype) defaultInstance;
- (instancetype) defaultInstance;
- (BOOL) isInitialized;
- (void) writeToCodedOutputStream:(PBCodedOutputStream*) output;
- (OWSSignalServiceProtosNullMessageBuilder*) builder;
+ (OWSSignalServiceProtosNullMessageBuilder*) builder;
+ (OWSSignalServiceProtosNullMessageBuilder*) builderWithPrototype:(OWSSignalServiceProtosNullMessage*) prototype;
- (OWSSignalServiceProtosNullMessageBuilder*) toBuilder;
+ (OWSSignalServiceProtosNullMessage*) parseFromData:(NSData*) data;
+ (OWSSignalServiceProtosNullMessage*) parseFromData:(NSData*) data extensionRegistry:(PBExtensionRegistry*) extensionRegistry;
+ (OWSSignalServiceProtosNullMessage*) parseFromInputStream:(NSInputStream*) input;
+ (OWSSignalServiceProtosNullMessage*) parseFromInputStream:(NSInputStream*) input extensionRegistry:(PBExtensionRegistry*) extensionRegistry;
+ (OWSSignalServiceProtosNullMessage*) parseFromCodedInputStream:(PBCodedInputStream*) input;
+ (OWSSignalServiceProtosNullMessage*) parseFromCodedInputStream:(PBCodedInputStream*) input extensionRegistry:(PBExtensionRegistry*) extensionRegistry;
@end
@interface OWSSignalServiceProtosNullMessageBuilder : PBGeneratedMessageBuilder {
@private
OWSSignalServiceProtosNullMessage* resultNullMessage;
}
- (OWSSignalServiceProtosNullMessage*) defaultInstance;
- (OWSSignalServiceProtosNullMessageBuilder*) clear;
- (OWSSignalServiceProtosNullMessageBuilder*) clone;
- (OWSSignalServiceProtosNullMessage*) build;
- (OWSSignalServiceProtosNullMessage*) buildPartial;
- (OWSSignalServiceProtosNullMessageBuilder*) mergeFrom:(OWSSignalServiceProtosNullMessage*) other;
- (OWSSignalServiceProtosNullMessageBuilder*) mergeFromCodedInputStream:(PBCodedInputStream*) input;
- (OWSSignalServiceProtosNullMessageBuilder*) mergeFromCodedInputStream:(PBCodedInputStream*) input extensionRegistry:(PBExtensionRegistry*) extensionRegistry;
- (BOOL) hasPadding;
- (NSData*) padding;
- (OWSSignalServiceProtosNullMessageBuilder*) setPadding:(NSData*) value;
- (OWSSignalServiceProtosNullMessageBuilder*) clearPadding;
@end
#define Verified_destination @"destination"
#define Verified_identityKey @"identityKey"
#define Verified_state @"state"
#define Verified_nullMessage @"nullMessage"
@interface OWSSignalServiceProtosVerified : PBGeneratedMessage<GeneratedMessageProtocol> {
@private
BOOL hasDestination_:1;
BOOL hasIdentityKey_:1;
BOOL hasNullMessage_:1;
BOOL hasState_:1;
NSString* destination;
NSData* identityKey;
NSData* nullMessage;
OWSSignalServiceProtosVerifiedState state;
}
- (BOOL) hasDestination;
- (BOOL) hasIdentityKey;
- (BOOL) hasState;
- (BOOL) hasNullMessage;
@property (readonly, strong) NSString* destination;
@property (readonly, strong) NSData* identityKey;
@property (readonly) OWSSignalServiceProtosVerifiedState state;
@property (readonly, strong) NSData* nullMessage;
+ (instancetype) defaultInstance;
- (instancetype) defaultInstance;
- (BOOL) isInitialized;
- (void) writeToCodedOutputStream:(PBCodedOutputStream*) output;
- (OWSSignalServiceProtosVerifiedBuilder*) builder;
+ (OWSSignalServiceProtosVerifiedBuilder*) builder;
+ (OWSSignalServiceProtosVerifiedBuilder*) builderWithPrototype:(OWSSignalServiceProtosVerified*) prototype;
- (OWSSignalServiceProtosVerifiedBuilder*) toBuilder;
+ (OWSSignalServiceProtosVerified*) parseFromData:(NSData*) data;
+ (OWSSignalServiceProtosVerified*) parseFromData:(NSData*) data extensionRegistry:(PBExtensionRegistry*) extensionRegistry;
+ (OWSSignalServiceProtosVerified*) parseFromInputStream:(NSInputStream*) input;
+ (OWSSignalServiceProtosVerified*) parseFromInputStream:(NSInputStream*) input extensionRegistry:(PBExtensionRegistry*) extensionRegistry;
+ (OWSSignalServiceProtosVerified*) parseFromCodedInputStream:(PBCodedInputStream*) input;
+ (OWSSignalServiceProtosVerified*) parseFromCodedInputStream:(PBCodedInputStream*) input extensionRegistry:(PBExtensionRegistry*) extensionRegistry;
@end
@interface OWSSignalServiceProtosVerifiedBuilder : PBGeneratedMessageBuilder {
@private
OWSSignalServiceProtosVerified* resultVerified;
}
- (OWSSignalServiceProtosVerified*) defaultInstance;
- (OWSSignalServiceProtosVerifiedBuilder*) clear;
- (OWSSignalServiceProtosVerifiedBuilder*) clone;
- (OWSSignalServiceProtosVerified*) build;
- (OWSSignalServiceProtosVerified*) buildPartial;
- (OWSSignalServiceProtosVerifiedBuilder*) mergeFrom:(OWSSignalServiceProtosVerified*) other;
- (OWSSignalServiceProtosVerifiedBuilder*) mergeFromCodedInputStream:(PBCodedInputStream*) input;
- (OWSSignalServiceProtosVerifiedBuilder*) mergeFromCodedInputStream:(PBCodedInputStream*) input extensionRegistry:(PBExtensionRegistry*) extensionRegistry;
- (BOOL) hasDestination;
- (NSString*) destination;
- (OWSSignalServiceProtosVerifiedBuilder*) setDestination:(NSString*) value;
- (OWSSignalServiceProtosVerifiedBuilder*) clearDestination;
- (BOOL) hasIdentityKey;
- (NSData*) identityKey;
- (OWSSignalServiceProtosVerifiedBuilder*) setIdentityKey:(NSData*) value;
- (OWSSignalServiceProtosVerifiedBuilder*) clearIdentityKey;
- (BOOL) hasState;
- (OWSSignalServiceProtosVerifiedState) state;
- (OWSSignalServiceProtosVerifiedBuilder*) setState:(OWSSignalServiceProtosVerifiedState) value;
- (OWSSignalServiceProtosVerifiedBuilder*) clearState;
- (BOOL) hasNullMessage;
- (NSData*) nullMessage;
- (OWSSignalServiceProtosVerifiedBuilder*) setNullMessage:(NSData*) value;
- (OWSSignalServiceProtosVerifiedBuilder*) clearNullMessage;
@end
#define CallMessage_offer @"offer"
@ -832,6 +987,8 @@ NSString *NSStringFromOWSSignalServiceProtosGroupContextType(OWSSignalServicePro
#define SyncMessage_request @"request"
#define SyncMessage_read @"read"
#define SyncMessage_blocked @"blocked"
#define SyncMessage_verified @"verified"
#define SyncMessage_padding @"padding"
@interface OWSSignalServiceProtosSyncMessage : PBGeneratedMessage<GeneratedMessageProtocol> {
@private
BOOL hasSent_:1;
@ -839,11 +996,15 @@ NSString *NSStringFromOWSSignalServiceProtosGroupContextType(OWSSignalServicePro
BOOL hasGroups_:1;
BOOL hasRequest_:1;
BOOL hasBlocked_:1;
BOOL hasVerified_:1;
BOOL hasPadding_:1;
OWSSignalServiceProtosSyncMessageSent* sent;
OWSSignalServiceProtosSyncMessageContacts* contacts;
OWSSignalServiceProtosSyncMessageGroups* groups;
OWSSignalServiceProtosSyncMessageRequest* request;
OWSSignalServiceProtosSyncMessageBlocked* blocked;
OWSSignalServiceProtosVerified* verified;
NSData* padding;
NSMutableArray * readArray;
}
- (BOOL) hasSent;
@ -851,12 +1012,16 @@ NSString *NSStringFromOWSSignalServiceProtosGroupContextType(OWSSignalServicePro
- (BOOL) hasGroups;
- (BOOL) hasRequest;
- (BOOL) hasBlocked;
- (BOOL) hasVerified;
- (BOOL) hasPadding;
@property (readonly, strong) OWSSignalServiceProtosSyncMessageSent* sent;
@property (readonly, strong) OWSSignalServiceProtosSyncMessageContacts* contacts;
@property (readonly, strong) OWSSignalServiceProtosSyncMessageGroups* groups;
@property (readonly, strong) OWSSignalServiceProtosSyncMessageRequest* request;
@property (readonly, strong) NSArray<OWSSignalServiceProtosSyncMessageRead*> * read;
@property (readonly, strong) OWSSignalServiceProtosSyncMessageBlocked* blocked;
@property (readonly, strong) OWSSignalServiceProtosVerified* verified;
@property (readonly, strong) NSData* padding;
- (OWSSignalServiceProtosSyncMessageRead*)readAtIndex:(NSUInteger)index;
+ (instancetype) defaultInstance;
@ -1290,6 +1455,18 @@ NSString *NSStringFromOWSSignalServiceProtosGroupContextType(OWSSignalServicePro
- (OWSSignalServiceProtosSyncMessageBuilder*) setBlockedBuilder:(OWSSignalServiceProtosSyncMessageBlockedBuilder*) builderForValue;
- (OWSSignalServiceProtosSyncMessageBuilder*) mergeBlocked:(OWSSignalServiceProtosSyncMessageBlocked*) value;
- (OWSSignalServiceProtosSyncMessageBuilder*) clearBlocked;
- (BOOL) hasVerified;
- (OWSSignalServiceProtosVerified*) verified;
- (OWSSignalServiceProtosSyncMessageBuilder*) setVerified:(OWSSignalServiceProtosVerified*) value;
- (OWSSignalServiceProtosSyncMessageBuilder*) setVerifiedBuilder:(OWSSignalServiceProtosVerifiedBuilder*) builderForValue;
- (OWSSignalServiceProtosSyncMessageBuilder*) mergeVerified:(OWSSignalServiceProtosVerified*) value;
- (OWSSignalServiceProtosSyncMessageBuilder*) clearVerified;
- (BOOL) hasPadding;
- (NSData*) padding;
- (OWSSignalServiceProtosSyncMessageBuilder*) setPadding:(NSData*) value;
- (OWSSignalServiceProtosSyncMessageBuilder*) clearPadding;
@end
#define AttachmentPointer_id @"id"
@ -1309,7 +1486,7 @@ NSString *NSStringFromOWSSignalServiceProtosGroupContextType(OWSSignalServicePro
BOOL hasThumbnail_:1;
BOOL hasDigest_:1;
BOOL hasSize_:1;
BOOL hasFlags_ : 1;
BOOL hasFlags_:1;
UInt64 id;
NSString* contentType;
NSString* fileName;
@ -1326,7 +1503,7 @@ NSString *NSStringFromOWSSignalServiceProtosGroupContextType(OWSSignalServicePro
- (BOOL) hasThumbnail;
- (BOOL) hasDigest;
- (BOOL) hasFileName;
- (BOOL)hasFlags;
- (BOOL) hasFlags;
@property (readonly) UInt64 id;
@property (readonly, strong) NSString* contentType;
@property (readonly, strong) NSData* key;
@ -1406,10 +1583,10 @@ NSString *NSStringFromOWSSignalServiceProtosGroupContextType(OWSSignalServicePro
- (OWSSignalServiceProtosAttachmentPointerBuilder*) setFileName:(NSString*) value;
- (OWSSignalServiceProtosAttachmentPointerBuilder*) clearFileName;
- (BOOL)hasFlags;
- (UInt32)flags;
- (OWSSignalServiceProtosAttachmentPointerBuilder *)setFlags:(UInt32)value;
- (OWSSignalServiceProtosAttachmentPointerBuilder *)clearFlags;
- (BOOL) hasFlags;
- (UInt32) flags;
- (OWSSignalServiceProtosAttachmentPointerBuilder*) setFlags:(UInt32) value;
- (OWSSignalServiceProtosAttachmentPointerBuilder*) clearFlags;
@end
#define GroupContext_id @"id"
@ -1508,25 +1685,30 @@ NSString *NSStringFromOWSSignalServiceProtosGroupContextType(OWSSignalServicePro
#define ContactDetails_name @"name"
#define ContactDetails_avatar @"avatar"
#define ContactDetails_color @"color"
#define ContactDetails_verified @"verified"
@interface OWSSignalServiceProtosContactDetails : PBGeneratedMessage<GeneratedMessageProtocol> {
@private
BOOL hasNumber_:1;
BOOL hasName_:1;
BOOL hasColor_:1;
BOOL hasAvatar_:1;
BOOL hasVerified_:1;
NSString* number;
NSString* name;
NSString* color;
OWSSignalServiceProtosContactDetailsAvatar* avatar;
OWSSignalServiceProtosVerified* verified;
}
- (BOOL) hasNumber;
- (BOOL) hasName;
- (BOOL) hasAvatar;
- (BOOL) hasColor;
- (BOOL) hasVerified;
@property (readonly, strong) NSString* number;
@property (readonly, strong) NSString* name;
@property (readonly, strong) OWSSignalServiceProtosContactDetailsAvatar* avatar;
@property (readonly, strong) NSString* color;
@property (readonly, strong) OWSSignalServiceProtosVerified* verified;
+ (instancetype) defaultInstance;
- (instancetype) defaultInstance;
@ -1644,6 +1826,13 @@ NSString *NSStringFromOWSSignalServiceProtosGroupContextType(OWSSignalServicePro
- (NSString*) color;
- (OWSSignalServiceProtosContactDetailsBuilder*) setColor:(NSString*) value;
- (OWSSignalServiceProtosContactDetailsBuilder*) clearColor;
- (BOOL) hasVerified;
- (OWSSignalServiceProtosVerified*) verified;
- (OWSSignalServiceProtosContactDetailsBuilder*) setVerified:(OWSSignalServiceProtosVerified*) value;
- (OWSSignalServiceProtosContactDetailsBuilder*) setVerifiedBuilder:(OWSSignalServiceProtosVerifiedBuilder*) builderForValue;
- (OWSSignalServiceProtosContactDetailsBuilder*) mergeVerified:(OWSSignalServiceProtosVerified*) value;
- (OWSSignalServiceProtosContactDetailsBuilder*) clearVerified;
@end
#define GroupDetails_id @"id"

File diff suppressed because it is too large Load Diff

View File

@ -34,14 +34,16 @@ NS_ASSUME_NONNULL_BEGIN
return self;
}
- (nullable NSDate *)receiptDateForSorting
- (BOOL)shouldUseReceiptDateForSorting
{
// Always use date, since we're creating these interactions after the fact
// and back-dating them.
//
// By default [TSMessage receiptDateForSorting] will prefer to use receivedAtDate
// which is not back-dated.
return self.date;
// Use the timestamp, not the "received at" timestamp to sort,
// since we're creating these interactions after the fact and back-dating them.
return NO;
}
- (BOOL)isDynamicInteraction
{
return YES;
}
@end

View File

@ -16,6 +16,8 @@ typedef enum {
// These call types are used until the call connects.
RPRecentCallTypeOutgoingIncomplete,
RPRecentCallTypeIncomingIncomplete,
RPRecentCallTypeMissedBecauseOfChangedIdentity,
RPRecentCallTypeIncomingDeclined
} RPRecentCallType;
@interface TSCall : TSInteraction <OWSReadTracking>

View File

@ -13,13 +13,15 @@ NSUInteger TSCallCurrentSchemaVersion = 1;
@interface TSCall ()
@property (nonatomic, getter=wasRead) BOOL read;
@property (nonatomic, readonly) NSUInteger callSchemaVersion;
@end
@implementation TSCall
#pragma mark -
@synthesize read = _read;
@implementation TSCall
- (instancetype)initWithTimestamp:(uint64_t)timestamp
withCallNumber:(NSString *)contactNumber
@ -34,7 +36,7 @@ NSUInteger TSCallCurrentSchemaVersion = 1;
_callSchemaVersion = TSCallCurrentSchemaVersion;
_callType = callType;
if (_callType == RPRecentCallTypeMissed) {
if (_callType == RPRecentCallTypeMissed || _callType == RPRecentCallTypeMissedBecauseOfChangedIdentity) {
_read = NO;
} else {
_read = YES;
@ -72,27 +74,37 @@ NSUInteger TSCallCurrentSchemaVersion = 1;
return NSLocalizedString(@"OUTGOING_INCOMPLETE_CALL", @"");
case RPRecentCallTypeIncomingIncomplete:
return NSLocalizedString(@"INCOMING_INCOMPLETE_CALL", @"");
case RPRecentCallTypeMissedBecauseOfChangedIdentity:
return NSLocalizedString(@"INFO_MESSAGE_MISSED_CALL_DUE_TO_CHANGED_IDENITY", @"info message text shown in conversation view");
case RPRecentCallTypeIncomingDeclined:
return NSLocalizedString(@"INCOMING_DECLINED_CALL",
@"info message recorded in conversation history when local user declined a call");
}
}
#pragma mark - OWSReadTracking
- (void)markAsReadLocallyWithTransaction:(YapDatabaseReadWriteTransaction *)transaction
- (BOOL)shouldAffectUnreadCounts
{
DDLogInfo(@"%@ marking as read uniqueId: %@ which has timestamp: %llu", self.tag, self.uniqueId, self.timestamp);
_read = YES;
[self saveWithTransaction:transaction];
// redraw any thread-related unread count UI.
[self touchThreadWithTransaction:transaction];
return YES;
}
- (void)markAsReadLocally
- (void)markAsReadWithTransaction:(YapDatabaseReadWriteTransaction *)transaction
sendReadReceipt:(BOOL)sendReadReceipt
updateExpiration:(BOOL)updateExpiration
{
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) {
[self markAsReadLocallyWithTransaction:transaction];
}];
OWSAssert(transaction);
if (_read) {
return;
}
DDLogDebug(@"%@ marking as read uniqueId: %@ which has timestamp: %llu", self.tag, self.uniqueId, self.timestamp);
_read = YES;
[self saveWithTransaction:transaction];
[self touchThreadWithTransaction:transaction];
// Ignore sendReadReceipt and updateExpiration; they don't apply to calls.
}
#pragma mark - Methods
@ -107,7 +119,7 @@ NSUInteger TSCallCurrentSchemaVersion = 1;
_callType = callType;
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) {
[self.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) {
[self saveWithTransaction:transaction];
// redraw any thread-related unread count UI.

View File

@ -1,12 +1,13 @@
// Created by Frederic Jacobs.
// Copyright (c) 2014 Open Whisper Systems. All rights reserved.
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "TSYapDatabaseObject.h"
#import "ContactsManagerProtocol.h"
@interface TSGroupModel : TSYapDatabaseObject
@property (nonatomic, strong) NSMutableArray *groupMemberIds;
@property (nonatomic, strong) NSMutableArray<NSString *> *groupMemberIds;
@property (nonatomic, strong) NSString *groupName;
@property (nonatomic, strong) NSData *groupId;
@ -14,7 +15,7 @@
@property (nonatomic, strong) UIImage *groupImage;
- (instancetype)initWithTitle:(NSString *)title
memberIds:(NSMutableArray *)memberIds
memberIds:(NSMutableArray<NSString *> *)memberIds
image:(UIImage *)image
groupId:(NSData *)groupId;

View File

@ -1,5 +1,6 @@
// Created by Frederic Jacobs on 13/11/14.
// Copyright (c) 2014 Open Whisper Systems. All rights reserved.
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "TSGroupModel.h"
#import "FunctionalUtil.h"
@ -8,7 +9,7 @@
#if TARGET_OS_IOS
- (instancetype)initWithTitle:(NSString *)title
memberIds:(NSMutableArray *)memberIds
memberIds:(NSMutableArray<NSString *> *)memberIds
image:(UIImage *)image
groupId:(NSData *)groupId
{

View File

@ -50,6 +50,7 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, readonly) OWSMessageSender *messageSender;
@property (nonatomic, readonly) OWSIncomingMessageFinder *incomingMessageFinder;
@property (nonatomic, readonly) OWSBlockingManager *blockingManager;
@property (nonatomic, readonly) OWSIdentityManager *identityManager;
@end
@ -73,13 +74,16 @@ NS_ASSUME_NONNULL_BEGIN
id<ContactsManagerProtocol> contactsManager = [TextSecureKitEnv sharedEnv].contactsManager;
id<OWSCallMessageHandler> callMessageHandler = [TextSecureKitEnv sharedEnv].callMessageHandler;
ContactsUpdater *contactsUpdater = [ContactsUpdater sharedUpdater];
OWSIdentityManager *identityManager = [OWSIdentityManager sharedManager];
OWSMessageSender *messageSender = [TextSecureKitEnv sharedEnv].messageSender;
return [self initWithNetworkManager:networkManager
storageManager:storageManager
callMessageHandler:callMessageHandler
contactsManager:contactsManager
contactsUpdater:contactsUpdater
identityManager:identityManager
messageSender:messageSender];
}
@ -88,6 +92,7 @@ NS_ASSUME_NONNULL_BEGIN
callMessageHandler:(id<OWSCallMessageHandler>)callMessageHandler
contactsManager:(id<ContactsManagerProtocol>)contactsManager
contactsUpdater:(ContactsUpdater *)contactsUpdater
identityManager:(OWSIdentityManager *)identityManager
messageSender:(OWSMessageSender *)messageSender
{
self = [super init];
@ -101,6 +106,7 @@ NS_ASSUME_NONNULL_BEGIN
_callMessageHandler = callMessageHandler;
_contactsManager = contactsManager;
_contactsUpdater = contactsUpdater;
_identityManager = identityManager;
_messageSender = messageSender;
_dbConnection = storageManager.newDatabaseConnection;
@ -109,9 +115,24 @@ NS_ASSUME_NONNULL_BEGIN
OWSSingletonAssert();
[self startObserving];
return self;
}
- (void)startObserving
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(yapDatabaseModified:)
name:YapDatabaseModifiedNotification
object:nil];
}
- (void)yapDatabaseModified:(NSNotification *)notification
{
[self updateApplicationBadgeCount];
}
#pragma mark - Debugging
- (NSString *)descriptionForEnvelope:(OWSSignalServiceProtosEnvelope *)envelope
@ -165,6 +186,8 @@ NS_ASSUME_NONNULL_BEGIN
return [NSString stringWithFormat:@"<DataMessage: %@ />", [self descriptionForDataMessage:content.dataMessage]];
} else if (content.hasCallMessage) {
return [NSString stringWithFormat:@"<CallMessage: %@ />", content.callMessage];
} else if (content.hasNullMessage) {
return [NSString stringWithFormat:@"<NullMessage: %@ />", content.nullMessage];
} else {
OWSAssert(NO);
return @"UnknownContent";
@ -202,7 +225,7 @@ NS_ASSUME_NONNULL_BEGIN
*/
- (NSString *)descriptionForSyncMessage:(OWSSignalServiceProtosSyncMessage *)syncMessage
{
NSMutableString *description = [[NSMutableString alloc] initWithString:@"CallMessage: "];
NSMutableString *description = [NSMutableString new];
if (syncMessage.hasSent) {
[description appendString:@"SentTranscript"];
} else if (syncMessage.hasRequest) {
@ -219,6 +242,10 @@ NS_ASSUME_NONNULL_BEGIN
[description appendString:@"Blocked"];
} else if (syncMessage.read.count > 0) {
[description appendString:@"ReadReceipt"];
} else if (syncMessage.hasVerified) {
NSString *verifiedString =
[NSString stringWithFormat:@"Verification for: %@", syncMessage.verified.destination];
[description appendString:verifiedString];
} else {
// Shouldn't happen
OWSAssert(NO);
@ -263,7 +290,11 @@ NS_ASSUME_NONNULL_BEGIN
DDLogDebug(@"%@ handled secure message.", self.tag);
if (error) {
DDLogError(
@"%@ handling secure message failed with error: %@", self.tag, error);
@"%@ handling secure message from address: %@.%d failed with error: %@",
self.tag,
envelope.source,
(unsigned int)envelope.sourceDevice,
error);
}
completion();
}];
@ -273,10 +304,14 @@ NS_ASSUME_NONNULL_BEGIN
case OWSSignalServiceProtosEnvelopeTypePrekeyBundle: {
[self handlePreKeyBundleAsync:envelope
completion:^(NSError *_Nullable error) {
DDLogDebug(@"%@ handled pre-key bundle", self.tag);
DDLogDebug(@"%@ handled pre-key whisper message", self.tag);
if (error) {
DDLogError(
@"%@ handling pre-key bundle failed with error: %@", self.tag, error);
DDLogError(@"%@ handling pre-key whisper message from address: %@.%d failed "
@"with error: %@",
self.tag,
envelope.source,
(unsigned int)envelope.sourceDevice,
error);
}
completion();
}];
@ -328,17 +363,6 @@ NS_ASSUME_NONNULL_BEGIN
NSString *recipientId = messageEnvelope.source;
int deviceId = messageEnvelope.sourceDevice;
dispatch_async([OWSDispatch sessionStoreQueue], ^{
if (![storageManager containsSession:recipientId deviceId:deviceId]) {
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
TSErrorMessage *errorMessage =
[TSErrorMessage missingSessionWithEnvelope:messageEnvelope withTransaction:transaction];
[errorMessage saveWithTransaction:transaction];
}];
DDLogError(@"Skipping message envelope for unknown session.");
completion(nil);
return;
}
// DEPRECATED - Remove after all clients have been upgraded.
NSData *encryptedData
= messageEnvelope.hasContent ? messageEnvelope.content : messageEnvelope.legacyMessage;
@ -363,7 +387,7 @@ NS_ASSUME_NONNULL_BEGIN
SessionCipher *cipher = [[SessionCipher alloc] initWithSessionStore:storageManager
preKeyStore:storageManager
signedPreKeyStore:storageManager
identityKeyStore:storageManager
identityKeyStore:self.identityManager
recipientId:recipientId
deviceId:deviceId];
@ -415,7 +439,7 @@ NS_ASSUME_NONNULL_BEGIN
SessionCipher *cipher = [[SessionCipher alloc] initWithSessionStore:storageManager
preKeyStore:storageManager
signedPreKeyStore:storageManager
identityKeyStore:storageManager
identityKeyStore:self.identityManager
recipientId:recipientId
deviceId:deviceId];
@ -449,7 +473,7 @@ NS_ASSUME_NONNULL_BEGIN
sourceId:envelope.source
sourceDeviceId:envelope.sourceDevice];
if (duplicateEnvelope) {
DDLogInfo(@"%@ Ignoring previously received envelope with timestamp: %llu", self.tag, envelope.timestamp);
DDLogInfo(@"%@ Ignoring previously received envelope from %@.%d with timestamp: %llu", self.tag, envelope.source, (unsigned int)envelope.sourceDevice, envelope.timestamp);
return;
}
@ -462,6 +486,8 @@ NS_ASSUME_NONNULL_BEGIN
[self handleIncomingEnvelope:envelope withDataMessage:content.dataMessage];
} else if (content.hasCallMessage) {
[self handleIncomingEnvelope:envelope withCallMessage:content.callMessage];
} else if (content.hasNullMessage) {
DDLogInfo(@"%@ Received null message.", self.tag);
} else {
DDLogWarn(@"%@ Ignoring envelope. Content with no known payload", self.tag);
}
@ -499,7 +525,7 @@ NS_ASSUME_NONNULL_BEGIN
NSString *recipientId = incomingEnvelope.source;
__block TSThread *thread;
[[TSStorageManager sharedManager].dbConnection
[[TSStorageManager sharedManager].dbReadWriteConnection
readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
thread = [TSContactThread getOrCreateThreadWithContactId:recipientId transaction:transaction];
}];
@ -608,6 +634,8 @@ NS_ASSUME_NONNULL_BEGIN
return;
}
DDLogDebug(@"%@ incoming attachment message: %@", self.tag, createdMessage.debugDescription);
[attachmentsProcessor fetchAttachmentsForMessage:createdMessage
success:^(TSAttachmentStream *attachmentStream) {
DDLogDebug(
@ -646,7 +674,8 @@ NS_ASSUME_NONNULL_BEGIN
} else if (syncMessage.hasRequest) {
if (syncMessage.request.type == OWSSignalServiceProtosSyncMessageRequestTypeContacts) {
OWSSyncContactsMessage *syncContactsMessage =
[[OWSSyncContactsMessage alloc] initWithContactsManager:self.contactsManager];
[[OWSSyncContactsMessage alloc] initWithContactsManager:self.contactsManager
identityManager:self.identityManager];
[self.messageSender sendTemporaryAttachmentData:[syncContactsMessage buildPlainTextAttachmentData]
contentType:OWSMimeTypeApplicationOctetStream
@ -657,7 +686,6 @@ NS_ASSUME_NONNULL_BEGIN
failure:^(NSError *error) {
DDLogError(@"%@ Failed to send Contacts response syncMessage with error: %@", self.tag, error);
}];
} else if (syncMessage.request.type == OWSSignalServiceProtosSyncMessageRequestTypeGroups) {
OWSSyncGroupsMessage *syncGroupsMessage = [[OWSSyncGroupsMessage alloc] init];
@ -683,6 +711,9 @@ NS_ASSUME_NONNULL_BEGIN
[[OWSReadReceiptsProcessor alloc] initWithReadReceiptProtos:syncMessage.read
storageManager:self.storageManager];
[readReceiptsProcessor process];
} else if (syncMessage.hasVerified) {
DDLogInfo(@"%@ Received verification state for %@", self.tag, syncMessage.verified.destination);
[self.identityManager processIncomingSyncMessage:syncMessage.verified];
} else {
DDLogWarn(@"%@ Ignoring unsupported sync message.", self.tag);
}
@ -758,7 +789,7 @@ NS_ASSUME_NONNULL_BEGIN
if (gThread.groupModel.groupImage) {
[self.messageSender sendAttachmentData:UIImagePNGRepresentation(gThread.groupModel.groupImage)
contentType:OWSMimeTypeImagePng
filename:nil
sourceFilename:nil
inMessage:message
success:^{
DDLogDebug(@"%@ Successfully sent group update with avatar", self.tag);
@ -889,7 +920,7 @@ NS_ASSUME_NONNULL_BEGIN
messageBody:body
attachmentIds:attachmentIds
expiresInSeconds:dataMessage.expireTimer];
DDLogDebug(@"%@ incoming group text message: %@", self.tag, incomingMessage.debugDescription);
[incomingMessage saveWithTransaction:transaction];
break;
}
@ -911,19 +942,22 @@ NS_ASSUME_NONNULL_BEGIN
messageBody:body
attachmentIds:attachmentIds
expiresInSeconds:dataMessage.expireTimer];
DDLogDebug(@"%@ incoming 1:1 text message: %@", self.tag, incomingMessage.debugDescription);
[incomingMessage saveWithTransaction:transaction];
thread = cThread;
}
if (thread && incomingMessage) {
[incomingMessage saveWithTransaction:transaction];
// Any messages sent from the current user - from this device or another - should be
// automatically marked as read.
BOOL shouldMarkMessageAsRead = [envelope.source isEqualToString:localNumber];
if (shouldMarkMessageAsRead) {
[incomingMessage markAsReadLocallyWithTransaction:transaction];
// Don't send a read receipt for messages sent by ourselves.
[incomingMessage markAsReadWithTransaction:transaction sendReadReceipt:NO updateExpiration:YES];
}
DDLogDebug(@"%@ shouldMarkMessageAsRead: %d (%@)", self.tag, shouldMarkMessageAsRead, envelope.source);
// Other clients allow attachments to be sent along with body, we want the text displayed as a separate
// message
if ([attachmentIds count] > 0 && body != nil && ![body isEqualToString:@""]) {
@ -936,6 +970,7 @@ NS_ASSUME_NONNULL_BEGIN
messageBody:body
attachmentIds:@[]
expiresInSeconds:dataMessage.expireTimer];
DDLogDebug(@"%@ incoming extra text message: %@", self.tag, incomingMessage.debugDescription);
[textMessage saveWithTransaction:transaction];
}
}
@ -954,11 +989,7 @@ NS_ASSUME_NONNULL_BEGIN
// Update thread preview in inbox
[thread touch];
// TODO Delay notification by 100ms?
// It's pretty annoying when you're phone keeps buzzing while you're having a conversation on Desktop.
NSString *name = [thread name];
[[TextSecureKitEnv sharedEnv].notificationsManager notifyUserForIncomingMessage:incomingMessage
from:name
inThread:thread
contactsManager:self.contactsManager];
}
@ -974,9 +1005,9 @@ NS_ASSUME_NONNULL_BEGIN
exception.description,
exception.name,
exception.reason);
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
TSErrorMessage *errorMessage;
__block TSErrorMessage *errorMessage;
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
if ([exception.name isEqualToString:NoSessionException]) {
errorMessage = [TSErrorMessage missingSessionWithEnvelope:envelope withTransaction:transaction];
} else if ([exception.name isEqualToString:InvalidKeyException]) {
@ -989,14 +1020,28 @@ NS_ASSUME_NONNULL_BEGIN
} else if ([exception.name isEqualToString:InvalidVersionException]) {
errorMessage = [TSErrorMessage invalidVersionWithEnvelope:envelope withTransaction:transaction];
} else if ([exception.name isEqualToString:UntrustedIdentityKeyException]) {
errorMessage =
[TSInvalidIdentityKeyReceivingErrorMessage untrustedKeyWithEnvelope:envelope withTransaction:transaction];
// Should no longer get here, since we now record the new identity for incoming messages.
OWSFail(@"%@ Failed to trust identity on incoming message from: %@.%d",
self.tag,
envelope.source,
envelope.sourceDevice);
return;
} else {
errorMessage = [TSErrorMessage corruptedMessageWithEnvelope:envelope withTransaction:transaction];
}
[errorMessage saveWithTransaction:transaction];
}];
if (errorMessage != nil) {
[self notififyForErrorMessage:errorMessage withEnvelope:envelope];
}
}
- (void)notififyForErrorMessage:(TSErrorMessage *)errorMessage withEnvelope:(OWSSignalServiceProtosEnvelope *)envelope
{
TSThread *contactThread = [TSContactThread getOrCreateThreadWithContactId:envelope.source];
[[TextSecureKitEnv sharedEnv].notificationsManager notifyUserForErrorMessage:errorMessage inThread:contactThread];
}
#pragma mark - helpers
@ -1038,6 +1083,12 @@ NS_ASSUME_NONNULL_BEGIN
return numberOfItems;
}
- (void)updateApplicationBadgeCount
{
NSUInteger numberOfItems = [self unreadMessagesCount];
[[UIApplication sharedApplication] setApplicationIconBadgeNumber:numberOfItems];
}
- (NSUInteger)unreadMessagesInThread:(TSThread *)thread {
__block NSUInteger numberOfItems;
[self.dbConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {

View File

@ -0,0 +1,15 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "TSRequest.h"
NS_ASSUME_NONNULL_BEGIN
@interface OWSGetProfileRequest : TSRequest
- (instancetype)initWithRecipientId:(NSString *)recipientId;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,30 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "OWSGetProfileRequest.h"
#import "TSConstants.h"
NS_ASSUME_NONNULL_BEGIN
@implementation OWSGetProfileRequest
- (instancetype)initWithRecipientId:(NSString *)recipientId
{
OWSAssert(recipientId.length > 0);
NSString *path = [NSString stringWithFormat:textSecureProfileAPIFormat, recipientId];
self = [super initWithURL:[NSURL URLWithString:path]];
if (!self) {
return self;
}
self.HTTPMethod = @"GET";
self.parameters = nil;
return self;
}
@end
NS_ASSUME_NONNULL_END

View File

@ -30,7 +30,7 @@ extern NSString *const TSNetworkManagerDomain;
- (instancetype)init NS_UNAVAILABLE;
+ (id)sharedManager;
+ (instancetype)sharedManager;
- (void)makeRequest:(TSRequest *)request
success:(void (^)(NSURLSessionDataTask *task, id responseObject))success

View File

@ -14,7 +14,6 @@ NSString *const TSNetworkManagerDomain = @"org.whispersystems.signal.networkMana
@interface TSNetworkManager ()
@property (nonatomic, readonly, strong) OWSSignalService *signalService;
typedef void (^failureBlock)(NSURLSessionDataTask *task, NSError *error);
@end
@ -23,26 +22,24 @@ typedef void (^failureBlock)(NSURLSessionDataTask *task, NSError *error);
#pragma mark Singleton implementation
+ (id)sharedManager {
+ (instancetype)sharedManager
{
static TSNetworkManager *sharedMyManager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
OWSSignalService *signalService = [[OWSSignalService alloc] init];
sharedMyManager = [[self alloc] initWithSignalService:signalService];
sharedMyManager = [[self alloc] initDefault];
});
return sharedMyManager;
}
- (instancetype)initWithSignalService:(OWSSignalService *)signalService
- (instancetype)initDefault
{
self = [super init];
if (!self) {
return self;
}
_signalService = signalService;
OWSSingletonAssert();
return self;
@ -59,7 +56,7 @@ typedef void (^failureBlock)(NSURLSessionDataTask *task, NSError *error);
void (^failure)(NSURLSessionDataTask *task, NSError *error) =
[TSNetworkManager errorPrettifyingForFailureBlock:failureBlock];
AFHTTPSessionManager *sessionManager = self.signalService.HTTPSessionManager;
AFHTTPSessionManager *sessionManager = [OWSSignalService sharedInstance].HTTPSessionManager;
if ([request isKindOfClass:[TSVerifyCodeRequest class]]) {
// We plant the Authorization parameter ourselves, no need to double add.

View File

@ -1,5 +1,6 @@
// Created by Michael Kirk on 12/20/16.
// Copyright © 2016 Open Whisper Systems. All rights reserved.
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
NS_ASSUME_NONNULL_BEGIN
@ -7,9 +8,9 @@ NS_ASSUME_NONNULL_BEGIN
@interface OWSCensorshipConfiguration : NSObject
- (NSString *)frontingHost:(NSString *)e164PhonNumber;
- (NSString *)frontingHost:(NSString *)e164PhoneNumber;
- (NSString *)reflectorHost;
- (BOOL)isCensoredPhoneNumber:(NSString *)e164PhonNumber;
- (BOOL)isCensoredPhoneNumber:(NSString *)e164PhoneNumber;
@end

View File

@ -11,20 +11,20 @@ NSString *const OWSCensorshipConfigurationReflectorHost = @"signal-reflector-mee
@implementation OWSCensorshipConfiguration
- (NSString *)frontingHost:(NSString *)e164PhonNumber
- (NSString *)frontingHost:(NSString *)e164PhoneNumber
{
OWSAssert(e164PhonNumber.length > 0);
OWSAssert(e164PhoneNumber.length > 0);
NSString *domain = nil;
for (NSString *countryCode in self.censoredCountryCodes) {
if ([e164PhonNumber hasPrefix:countryCode]) {
if ([e164PhoneNumber hasPrefix:countryCode]) {
domain = self.censoredCountryCodes[countryCode];
}
}
// Fronting should only be used for countries specified in censoredCountryCodes,
// all of which have a domain specified.
OWSAssert(domain);
// Fronting should only be auto-activated for countries specified in censoredCountryCodes,
// all of which have a domain specified. However users can also manually enable
// censorship circumvention.
if (!domain) {
domain = @"google.com";
}
@ -60,10 +60,10 @@ NSString *const OWSCensorshipConfigurationReflectorHost = @"signal-reflector-mee
};
}
- (BOOL)isCensoredPhoneNumber:(NSString *)e164PhonNumber
- (BOOL)isCensoredPhoneNumber:(NSString *)e164PhoneNumber
{
for (NSString *countryCode in self.censoredCountryCodes) {
if ([e164PhonNumber hasPrefix:countryCode]) {
if ([e164PhoneNumber hasPrefix:countryCode]) {
return YES;
}
}

Some files were not shown because too many files have changed in this diff Show More