Compare commits

..

185 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
124 changed files with 4421 additions and 1630 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']

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

@ -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

@ -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;
}

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

@ -72,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"
@ -130,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) {
@ -171,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];
}];
@ -192,14 +193,13 @@ NS_ASSUME_NONNULL_BEGIN
- (NSArray<id<OWSReadTracking>> *)unseenMessagesWithTransaction:(YapDatabaseReadTransaction *)transaction
{
NSMutableArray<id<OWSReadTracking>> *messages = [NSMutableArray new];
[[transaction ext:TSUnseenDatabaseViewExtensionName]
[[TSDatabaseView unseenDatabaseViewExtension:transaction]
enumerateRowsInGroup:self.uniqueId
usingBlock:^(
NSString *collection, NSString *key, id object, id metadata, NSUInteger index, BOOL *stop) {
if (![object conformsToProtocol:@protocol(OWSReadTracking)]) {
DDLogError(@"%@ Unexpected object in unseen messages: %@", self.tag, object);
OWSFail(@"Unexpected object in unseen messages.");
OWSFail(@"%@ Unexpected object in unseen messages: %@", self.tag, object);
return;
}
[messages addObject:(id<OWSReadTracking>)object];
@ -228,26 +228,42 @@ NS_ASSUME_NONNULL_BEGIN
- (void)markAllAsReadWithTransaction:(YapDatabaseReadWriteTransaction *)transaction
{
for (id<OWSReadTracking> message in [self unseenMessagesWithTransaction:transaction]) {
[message markAsReadLocallyWithTransaction:transaction];
[message markAsReadWithTransaction:transaction sendReadReceipt:YES updateExpiration:YES];
}
// Just to be defensive, we'll also check for unread messages.
OWSAssert([self unseenMessagesWithTransaction:transaction].count < 1);
}
- (void)markAllAsRead
{
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) {
[self markAllAsReadWithTransaction:transaction];
}];
}
- (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 {
@ -259,29 +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 dateForSorting];
// Returns YES IFF the interaction should show up in the inbox as the last message.
+ (BOOL)shouldInteractionAppearInInbox:(TSInteraction *)interaction
{
OWSAssert(interaction);
if ([lastMessage isKindOfClass:[TSErrorMessage class]]) {
TSErrorMessage *errorMessage = (TSErrorMessage *)lastMessage;
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.
DDLogDebug(@"%@ not updating lastMessage for thread:%@ nonblocking identity change: %@",
self.tag,
self,
errorMessage.debugDescription);
return;
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;
@ -361,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];
}];
@ -91,7 +98,7 @@ NS_ASSUME_NONNULL_BEGIN
- (BOOL)hasSafetyNumbers
{
return !![self.storageManager identityKeyForRecipientId:self.contactIdentifier];
return !![[OWSIdentityManager sharedManager] identityKeyForRecipientId:self.contactIdentifier];
}
- (NSString *)name

View File

@ -18,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) {
@ -31,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];
@ -43,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];
@ -58,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];
@ -70,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;
@ -79,11 +99,15 @@ 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)]];
}

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

@ -27,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
@ -44,7 +46,6 @@ NS_ASSUME_NONNULL_BEGIN
+ (void)deleteAttachments;
+ (NSString *)attachmentsFolder;
+ (NSUInteger)numberOfItemsInAttachmentsFolder;
- (CGSize)imageSizeWithTransaction:(YapDatabaseReadWriteTransaction *)transaction;
- (CGSize)imageSizeWithoutTransaction;

View File

@ -41,9 +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];
// This instance hasn't been persisted yet.
[self ensureFilePathAndPersist:NO];
[self ensureFilePath];
return self;
}
@ -63,9 +63,9 @@ NS_ASSUME_NONNULL_BEGIN
// attachments which don't need to be uploaded.
_isUploaded = YES;
self.attachmentType = pointer.attachmentType;
_creationTimestamp = [NSDate new];
// This instance hasn't been persisted yet.
[self ensureFilePathAndPersist:NO];
[self ensureFilePath];
return self;
}
@ -77,9 +77,13 @@ NS_ASSUME_NONNULL_BEGIN
return self;
}
// This instance has been persisted, we need to
// update it in the database.
[self ensureFilePathAndPersist:YES];
// 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;
}
@ -97,7 +101,7 @@ NS_ASSUME_NONNULL_BEGIN
}
}
- (void)ensureFilePathAndPersist:(BOOL)shouldPersist
- (void)ensureFilePath
{
if (self.localRelativeFilePath) {
return;
@ -127,18 +131,6 @@ NS_ASSUME_NONNULL_BEGIN
self.localRelativeFilePath = localRelativeFilePath;
OWSAssert(self.filePath);
if (shouldPersist) {
// It's not ideal to do this asynchronously, but we can create a new transaction
// within initWithCoder: which will be called from within a transaction.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
OWSAssert(transaction);
[self saveWithTransaction:transaction];
}];
});
}
}
#pragma mark - File Management
@ -170,33 +162,31 @@ NS_ASSUME_NONNULL_BEGIN
+ (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
@ -291,10 +281,28 @@ 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
@ -366,7 +374,7 @@ NS_ASSUME_NONNULL_BEGIN
if (transaction) {
updateDataStore(transaction);
} else {
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[self.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
updateDataStore(transaction);
}];
}
@ -437,7 +445,7 @@ NS_ASSUME_NONNULL_BEGIN
if (transaction) {
updateDataStore(transaction);
} else {
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[self.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
updateDataStore(transaction);
}];
}

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

@ -12,14 +12,16 @@ NS_ASSUME_NONNULL_BEGIN
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;
@ -57,8 +59,6 @@ typedef NS_ENUM(int32_t, TSErrorMessageType) {
@property (nonatomic, readonly) TSErrorMessageType errorType;
@property (nullable, nonatomic, readonly) NSString *recipientId;
- (void)markAsReadLocallyWithTransaction:(YapDatabaseReadWriteTransaction *)transaction;
@end
NS_ASSUME_NONNULL_END

View File

@ -115,6 +115,9 @@ NSUInteger TSErrorMessageSchemaVersion = 1;
case TSErrorMessageUnknownContactBlockOffer:
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;
@ -167,19 +170,22 @@ NSUInteger TSErrorMessageSchemaVersion = 1;
return NO;
}
- (void)markAsReadLocally
{
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[self markAsReadLocallyWithTransaction:transaction];
}];
}
- (void)markAsReadLocallyWithTransaction:(YapDatabaseReadWriteTransaction *)transaction
- (void)markAsReadWithTransaction:(YapDatabaseReadWriteTransaction *)transaction
sendReadReceipt:(BOOL)sendReadReceipt
updateExpiration:(BOOL)updateExpiration
{
OWSAssert(transaction);
DDLogInfo(@"%@ marking as read uniqueId: %@ which has timestamp: %llu", self.tag, self.uniqueId, self.timestamp);
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

View File

@ -108,20 +108,6 @@ extern NSString *const TSIncomingMessageWasReadOnThisDeviceNotification;
// This will be 0 for messages created before we were tracking sourceDeviceId
@property (nonatomic, readonly) UInt32 sourceDeviceId;
/*
* 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
NS_ASSUME_NONNULL_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"
@ -76,7 +78,7 @@ NSString *const TSIncomingMessageWasReadOnThisDeviceNotification = @"TSIncomingM
+ (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
@ -115,37 +117,33 @@ NSString *const TSIncomingMessageWasReadOnThisDeviceNotification = @"TSIncomingM
return YES;
}
- (void)markAsReadFromReadReceipt
- (void)markAsReadWithTransaction:(YapDatabaseReadWriteTransaction *)transaction
sendReadReceipt:(BOOL)sendReadReceipt
updateExpiration:(BOOL)updateExpiration
{
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[self markAsReadWithoutNotificationWithTransaction:transaction];
}];
}
OWSAssert(transaction);
- (void)markAsReadLocallyWithTransaction:(YapDatabaseReadWriteTransaction *)transaction
{
[self markAsReadWithoutNotificationWithTransaction:transaction];
[[NSNotificationCenter defaultCenter] postNotificationName:TSIncomingMessageWasReadOnThisDeviceNotification
object:self];
}
if (_read) {
return;
}
- (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];
}
- (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

@ -12,11 +12,13 @@ NS_ASSUME_NONNULL_BEGIN
typedef NS_ENUM(NSInteger, TSInfoMessageType) {
TSInfoMessageTypeSessionDidEnd,
TSInfoMessageUserNotRegistered,
// TSInfoMessageTypeUnsupportedMessage appears to be obsolete.
TSInfoMessageTypeUnsupportedMessage,
TSInfoMessageTypeGroupUpdate,
TSInfoMessageTypeGroupQuit,
TSInfoMessageTypeDisappearingMessagesUpdate,
TSInfoMessageAddToContactsOffer,
TSInfoMessageVerificationStateChange,
};
+ (instancetype)userNotRegisteredMessageInThread:(TSThread *)thread;
@ -42,8 +44,6 @@ typedef NS_ENUM(NSInteger, TSInfoMessageType) {
expiresInSeconds:(uint32_t)expiresInSeconds
expireStartedAt:(uint64_t)expireStartedAt NS_UNAVAILABLE;
- (void)markAsReadLocallyWithTransaction:(YapDatabaseReadWriteTransaction *)transaction;
@end
NS_ASSUME_NONNULL_END

View File

@ -92,6 +92,9 @@ NSUInteger TSInfoMessageSchemaVersion = 1;
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;
}
@ -106,19 +109,22 @@ NSUInteger TSInfoMessageSchemaVersion = 1;
return NO;
}
- (void)markAsReadLocally
{
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[self markAsReadLocallyWithTransaction:transaction];
}];
}
- (void)markAsReadLocallyWithTransaction:(YapDatabaseReadWriteTransaction *)transaction
- (void)markAsReadWithTransaction:(YapDatabaseReadWriteTransaction *)transaction
sendReadReceipt:(BOOL)sendReadReceipt
updateExpiration:(BOOL)updateExpiration
{
OWSAssert(transaction);
DDLogInfo(@"%@ marking as read uniqueId: %@ which has timestamp: %llu", self.tag, self.uniqueId, self.timestamp);
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

View File

@ -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;
/**
@ -164,6 +157,8 @@ typedef NS_ENUM(NSInteger, TSGroupMetaMessage) {
// This isn't a perfect arrangement, but in practice this will prevent
// data loss and will resolve all known issues.
- (void)updateWithMessageState:(TSOutgoingMessageState)messageState;
- (void)updateWithMessageState:(TSOutgoingMessageState)messageState
transaction:(YapDatabaseReadWriteTransaction *)transaction;
- (void)updateWithSendingError:(NSError *)error;
- (void)updateWithHasSyncedTranscript:(BOOL)hasSyncedTranscript;
- (void)updateWithCustomMessage:(NSString *)customMessage transaction:(YapDatabaseReadWriteTransaction *)transaction;

View File

@ -245,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,17 +256,25 @@ NSString *const kTSOutgoingMessageSentRecipientAll = @"kTSOutgoingMessageSentRec
- (void)updateWithMessageState:(TSOutgoingMessageState)messageState
{
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[self applyChangeToSelfAndLatestOutgoingMessage:transaction
changeBlock:^(TSOutgoingMessage *message) {
[message setMessageState:messageState];
}];
[self.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[self updateWithMessageState:messageState transaction:transaction];
}];
}
- (void)updateWithMessageState:(TSOutgoingMessageState)messageState
transaction:(YapDatabaseReadWriteTransaction *)transaction
{
OWSAssert(transaction);
[self applyChangeToSelfAndLatestOutgoingMessage:transaction
changeBlock:^(TSOutgoingMessage *message) {
[message setMessageState:messageState];
}];
}
- (void)updateWithHasSyncedTranscript:(BOOL)hasSyncedTranscript
{
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[self.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[self applyChangeToSelfAndLatestOutgoingMessage:transaction
changeBlock:^(TSOutgoingMessage *message) {
[message setHasSyncedTranscript:hasSyncedTranscript];
@ -287,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];
}];
}
@ -304,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];
@ -389,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];
}];
}
@ -447,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

@ -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,10 +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
approvedForBlockingUse:YES
approvedForNonBlockingUse:YES];
[[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

@ -15,11 +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 *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
@ -43,26 +43,15 @@ NSString *TSInvalidRecipientKey = @"TSInvalidRecipientKey";
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
approvedForBlockingUse:YES
approvedForNonBlockingUse:YES];
[[OWSIdentityManager sharedManager] saveRemoteIdentity:self.newIdentityKey recipientId:self.recipientId];
});
}

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

@ -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();
}
@ -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]) {
@ -1137,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 {
@ -1150,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;
}
@ -1184,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);
@ -1225,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 {
@ -1248,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]];
@ -1258,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];
@ -1313,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

@ -20,10 +20,10 @@
- (BOOL)shouldAffectUnreadCounts;
/**
* 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.
* Used for *responding* to a remote read receipt or in response to user activity.
*/
- (void)markAsReadLocally;
- (void)markAsReadLocallyWithTransaction:(YapDatabaseReadWriteTransaction *)transaction;
- (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

@ -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

@ -36,7 +36,7 @@ NSUInteger TSCallCurrentSchemaVersion = 1;
_callSchemaVersion = TSCallCurrentSchemaVersion;
_callType = callType;
if (_callType == RPRecentCallTypeMissed) {
if (_callType == RPRecentCallTypeMissed || _callType == RPRecentCallTypeMissedBecauseOfChangedIdentity) {
_read = NO;
} else {
_read = YES;
@ -74,6 +74,11 @@ 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");
}
}
@ -84,22 +89,22 @@ NSUInteger TSCallCurrentSchemaVersion = 1;
return YES;
}
- (void)markAsReadLocallyWithTransaction:(YapDatabaseReadWriteTransaction *)transaction
- (void)markAsReadWithTransaction:(YapDatabaseReadWriteTransaction *)transaction
sendReadReceipt:(BOOL)sendReadReceipt
updateExpiration:(BOOL)updateExpiration
{
DDLogInfo(@"%@ marking as read uniqueId: %@ which has timestamp: %llu", self.tag, self.uniqueId, self.timestamp);
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];
// redraw any thread-related unread count UI.
[self touchThreadWithTransaction:transaction];
}
- (void)markAsReadLocally
{
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) {
[self markAsReadLocallyWithTransaction:transaction];
}];
// Ignore sendReadReceipt and updateExpiration; they don't apply to calls.
}
#pragma mark - Methods
@ -114,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

@ -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,18 +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];
[self notififyForErrorMessage:errorMessage withEnvelope:messageEnvelope];
}];
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;
@ -364,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];
@ -416,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];
@ -450,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;
}
@ -463,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);
}
@ -500,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];
}];
@ -609,6 +634,8 @@ NS_ASSUME_NONNULL_BEGIN
return;
}
DDLogDebug(@"%@ incoming attachment message: %@", self.tag, createdMessage.debugDescription);
[attachmentsProcessor fetchAttachmentsForMessage:createdMessage
success:^(TSAttachmentStream *attachmentStream) {
DDLogDebug(
@ -647,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
@ -658,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];
@ -684,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);
}
@ -890,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;
}
@ -912,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:@""]) {
@ -937,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];
}
}
@ -986,8 +1020,12 @@ 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];
}
@ -1045,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

@ -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

@ -22,7 +22,8 @@ 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, ^{

View File

@ -160,7 +160,6 @@ NSString *const kNSNotificationName_IsCensorshipCircumventionActiveDidChange =
DDLogInfo(@"%@ using reflector HTTPSessionManager", self.tag);
return self.reflectorHTTPSessionManager;
} else {
DDLogDebug(@"%@ using default HTTPSessionManager", self.tag);
return self.defaultHTTPSessionManager;
}
}

View File

@ -156,7 +156,6 @@ NSString *const kNSNotification_SocketManagerStateDidChange = @"kNSNotification_
if (self.websocket) {
switch ([self.websocket readyState]) {
case SR_OPEN:
DDLogVerbose(@"WebSocket already open on connection request");
self.state = SocketManagerStateOpen;
return;
case SR_CONNECTING:
@ -234,7 +233,7 @@ NSString *const kNSNotification_SocketManagerStateDidChange = @"kNSNotification_
return;
}
DDLogWarn(@"%@ Socket state change: %@ -> %@",
DDLogWarn(@"%@ Socket state: %@ -> %@",
self.tag,
[self stringFromSocketManagerState:_state],
[self stringFromSocketManagerState:state]);

View File

@ -1,13 +0,0 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
NS_ASSUME_NONNULL_BEGIN
@protocol TSPreferences <NSObject>
- (BOOL)isSendingIdentityApprovalRequired;
@end
NS_ASSUME_NONNULL_END

View File

@ -5,7 +5,7 @@
#import "OWSFingerprintBuilder.h"
#import "ContactsManagerProtocol.h"
#import "OWSFingerprint.h"
#import "TSStorageManager+IdentityKeyStore.h"
#import "OWSIdentityManager.h"
#import "TSStorageManager+keyingMaterial.h"
#import <25519/Curve25519.h>
@ -36,7 +36,7 @@ NS_ASSUME_NONNULL_BEGIN
- (nullable OWSFingerprint *)fingerprintWithTheirSignalId:(NSString *)theirSignalId
{
NSData *_Nullable theirIdentityKey = [self.storageManager identityKeyForRecipientId:theirSignalId];
NSData *_Nullable theirIdentityKey = [[OWSIdentityManager sharedManager] identityKeyForRecipientId:theirSignalId];
if (theirIdentityKey == nil) {
OWSAssert(NO);
@ -51,7 +51,7 @@ NS_ASSUME_NONNULL_BEGIN
NSString *theirName = [self.contactsManager displayNameForPhoneIdentifier:theirSignalId];
NSString *mySignalId = [self.storageManager localNumber];
NSData *myIdentityKey = [self.storageManager identityKeyPair].publicKey;
NSData *myIdentityKey = [[OWSIdentityManager sharedManager] identityKeyPair].publicKey;
return [OWSFingerprint fingerprintWithMyStableId:mySignalId
myIdentityKey:myIdentityKey

View File

@ -2,10 +2,20 @@
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import <SignalServiceKit/TSYapDatabaseObject.h>
#import "OWSSignalServiceProtos.pb.h"
#import "TSYapDatabaseObject.h"
NS_ASSUME_NONNULL_BEGIN
typedef NS_ENUM(NSUInteger, OWSVerificationState) {
OWSVerificationStateDefault,
OWSVerificationStateVerified,
OWSVerificationStateNoLongerVerified,
};
NSString *OWSVerificationStateToString(OWSVerificationState verificationState);
OWSSignalServiceProtosVerifiedState OWSVerificationStateToProtoState(OWSVerificationState verificationState);
@interface OWSRecipientIdentity : TSYapDatabaseObject
@property (nonatomic, readonly) NSString *recipientId;
@ -13,28 +23,23 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, readonly) NSDate *createdAt;
@property (nonatomic, readonly) BOOL isFirstKnownKey;
#pragma mark - get/set Seen
#pragma mark - Verification State
@property (atomic, readonly) BOOL wasSeen;
- (void)updateAsSeen;
@property (atomic, readonly) OWSVerificationState verificationState;
#pragma mark - get/set Approval
@property (atomic, readonly) BOOL approvedForBlockingUse;
@property (atomic, readonly) BOOL approvedForNonBlockingUse;
- (void)updateWithApprovedForBlockingUse:(BOOL)approvedForBlockingUse
approvedForNonBlockingUse:(BOOL)approvedForNonBlockingUse;
- (void)updateWithVerificationState:(OWSVerificationState)verificationState;
#pragma mark - Initializers
- (instancetype)initWithUniqueId:(NSString *)uniqueId NS_UNAVAILABLE;
- (instancetype)initWithCoder:(NSCoder *)coder NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithRecipientId:(NSString *)recipientId
identityKey:(NSData *)identityKey
isFirstKnownKey:(BOOL)isFirstKnownKey
createdAt:(NSDate *)createdAt
approvedForBlockingUse:(BOOL)approvedForBlockingUse
approvedForNonBlockingUse:(BOOL)approvedForNonBlockingUse NS_DESIGNATED_INITIALIZER;
verificationState:(OWSVerificationState)verificationState NS_DESIGNATED_INITIALIZER;
#pragma mark - debug

View File

@ -8,11 +8,33 @@
NS_ASSUME_NONNULL_BEGIN
NSString *OWSVerificationStateToString(OWSVerificationState verificationState)
{
switch (verificationState) {
case OWSVerificationStateDefault:
return @"OWSVerificationStateDefault";
case OWSVerificationStateVerified:
return @"OWSVerificationStateVerified";
case OWSVerificationStateNoLongerVerified:
return @"OWSVerificationStateNoLongerVerified";
}
}
OWSSignalServiceProtosVerifiedState OWSVerificationStateToProtoState(OWSVerificationState verificationState)
{
switch (verificationState) {
case OWSVerificationStateDefault:
return OWSSignalServiceProtosVerifiedStateDefault;
case OWSVerificationStateVerified:
return OWSSignalServiceProtosVerifiedStateVerified;
case OWSVerificationStateNoLongerVerified:
return OWSSignalServiceProtosVerifiedStateUnverified;
}
}
@interface OWSRecipientIdentity ()
@property (atomic) BOOL wasSeen;
@property (atomic) BOOL approvedForBlockingUse;
@property (atomic) BOOL approvedForNonBlockingUse;
@property (atomic) OWSVerificationState verificationState;
@end
@ -24,12 +46,24 @@ NS_ASSUME_NONNULL_BEGIN
*/
@implementation OWSRecipientIdentity
- (instancetype)initWithCoder:(NSCoder *)coder
{
self = [super initWithCoder:coder];
if (self) {
if (![coder decodeObjectForKey:@"verificationState"]) {
_verificationState = OWSVerificationStateDefault;
}
}
return self;
}
- (instancetype)initWithRecipientId:(NSString *)recipientId
identityKey:(NSData *)identityKey
isFirstKnownKey:(BOOL)isFirstKnownKey
createdAt:(NSDate *)createdAt
approvedForBlockingUse:(BOOL)approvedForBlockingUse
approvedForNonBlockingUse:(BOOL)approvedForNonBlockingUse
verificationState:(OWSVerificationState)verificationState
{
self = [super initWithUniqueId:recipientId];
if (!self) {
@ -40,27 +74,16 @@ NS_ASSUME_NONNULL_BEGIN
_identityKey = identityKey;
_isFirstKnownKey = isFirstKnownKey;
_createdAt = createdAt;
_approvedForBlockingUse = approvedForBlockingUse;
_approvedForNonBlockingUse = approvedForNonBlockingUse;
_wasSeen = NO;
_verificationState = verificationState;
return self;
}
- (void)updateAsSeen
{
[self updateWithChangeBlock:^(OWSRecipientIdentity *_Nonnull obj) {
obj.wasSeen = YES;
}];
}
- (void)updateWithApprovedForBlockingUse:(BOOL)approvedForBlockingUse
approvedForNonBlockingUse:(BOOL)approvedForNonBlockingUse
- (void)updateWithVerificationState:(OWSVerificationState)verificationState
{
// Ensure changes are persisted without clobbering any work done on another thread or instance.
[self updateWithChangeBlock:^(OWSRecipientIdentity *_Nonnull obj) {
obj.approvedForBlockingUse = approvedForBlockingUse;
obj.approvedForNonBlockingUse = approvedForNonBlockingUse;
obj.verificationState = verificationState;
}];
}
@ -68,7 +91,7 @@ NS_ASSUME_NONNULL_BEGIN
{
changeBlock(self);
[[self class].dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) {
[[self class].dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) {
OWSRecipientIdentity *latest = [[self class] fetchObjectWithUniqueID:self.uniqueId transaction:transaction];
if (latest == nil) {
[self saveWithTransaction:transaction];

View File

@ -1,58 +0,0 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import <AxolotlKit/IdentityKeyStore.h>
#import "TSStorageManager.h"
NS_ASSUME_NONNULL_BEGIN
@class OWSRecipientIdentity;
extern NSString *const TSStorageManagerTrustedKeysCollection;
@interface TSStorageManager (IdentityKeyStore) <IdentityKeyStore>
/**
* Explicitly mark an identity as approved for blocking/nonblocking use
* e.g. in response to a user confirmation action.
*
* @param identityKey key data used to identify the recipient
* @param recipientId unique stable identifier for the recipient, e.g. e164 phone number
* @param approvedForBlockingUse if the user wants explicit confirmation before sending to changed numbers, whether
* that confirmation has occurred.
* @param approvedForNonBlockingUse YES to override the duration during which we consider an SN "too soon" to send.
*
* @returns YES if we are replacing an existing known identity key for recipientId.
* NO if there was no previously stored identity key for the recipient.
*/
- (BOOL)saveRemoteIdentity:(NSData *)identityKey
recipientId:(NSString *)recipientId
approvedForBlockingUse:(BOOL)approvedForBlockingUse
approvedForNonBlockingUse:(BOOL)approvedForNonBlockingUse;
/**
* Check if a recipient identity corresponds to an untrusted identity
*
* @param recipientId unique stable identifier for the recipient, e.g. e164 phone number
* @returns nil if the identity doesn't exist or if it's trusted
* else returns the untrusted identity
*/
- (nullable OWSRecipientIdentity *)unconfirmedIdentityThatShouldBlockSendingForRecipientId:(NSString *)recipientId;
/**
* @param recipientId unique stable identifier for the recipient, e.g. e164 phone number
* @returns nil if the recipient's current id has been seen, or if it's the users first key
* else returns the unseen identity
*
*/
- (nullable OWSRecipientIdentity *)unseenIdentityChangeForRecipientId:(NSString *)recipientId;
- (NSArray<OWSRecipientIdentity *> *)unseenIdentityChangesForRecipientIds:(NSArray<NSString *> *)recipientIds;
- (void)generateNewIdentityKey;
- (nullable NSData *)identityKeyForRecipientId:(NSString *)recipientId;
- (void)removeIdentityKeyForRecipient:(NSString *)receipientId;
@end
NS_ASSUME_NONNULL_END

View File

@ -1,325 +0,0 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "NSDate+millisecondTimeStamp.h"
#import "NotificationsProtocol.h"
#import "OWSRecipientIdentity.h"
#import "TSAccountManager.h"
#import "TSContactThread.h"
#import "TSErrorMessage.h"
#import "TSGroupThread.h"
#import "TSPreferences.h"
#import "TSStorageManager+IdentityKeyStore.h"
#import "TSStorageManager+SessionStore.h"
#import "TextSecureKitEnv.h"
#import <25519/Curve25519.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";
// Don't trust an identity for sending to unless they've been around for at least this long
const NSTimeInterval kIdentityKeyStoreNonBlockingSecondsThreshold = 5.0;
@implementation TSStorageManager (IdentityKeyStore)
+ (id)sharedIdentityKeyLock
{
static id identityKeyLock;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
identityKeyLock = [NSObject new];
});
return identityKeyLock;
}
- (void)generateNewIdentityKey {
[self setObject:[Curve25519 generateKeyPair]
forKey:TSStorageManagerIdentityKeyStoreIdentityKey
inCollection:TSStorageManagerIdentityKeyStoreCollection];
}
- (nullable NSData *)identityKeyForRecipientId:(NSString *)recipientId
{
@synchronized([[self class] sharedIdentityKeyLock])
{
return [OWSRecipientIdentity fetchObjectWithUniqueID:recipientId].identityKey;
}
}
- (nullable ECKeyPair *)identityKeyPair
{
return [self keyPairForKey:TSStorageManagerIdentityKeyStoreIdentityKey
inCollection:TSStorageManagerIdentityKeyStoreCollection];
}
- (int)localRegistrationId {
return (int)[TSAccountManager getOrGenerateRegistrationId];
}
- (BOOL)saveRemoteIdentity:(NSData *)identityKey recipientId:(NSString *)recipientId
{
OWSAssert(identityKey != nil);
OWSAssert(recipientId != nil);
@synchronized([[self class] sharedIdentityKeyLock])
{
// 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 setObject:identityKey forKey:recipientId inCollection:TSStorageManagerTrustedKeysCollection];
// If send-blocking is disabled at the time the identity was saved, we want to consider the identity as
// approved for blocking. Otherwise the user will see inexplicable failures when trying to send to this
// identity, if they later enabled send-blocking.
BOOL approvedForBlockingUse = ![TextSecureKitEnv sharedEnv].preferences.isSendingIdentityApprovalRequired;
return [self saveRemoteIdentity:identityKey
recipientId:recipientId
approvedForBlockingUse:approvedForBlockingUse
approvedForNonBlockingUse:NO];
}
}
- (BOOL)saveRemoteIdentity:(NSData *)identityKey
recipientId:(NSString *)recipientId
approvedForBlockingUse:(BOOL)approvedForBlockingUse
approvedForNonBlockingUse:(BOOL)approvedForNonBlockingUse
{
OWSAssert(identityKey != nil);
OWSAssert(recipientId != nil);
NSString const *logTag = @"[IdentityKeyStore]";
@synchronized ([[self class] sharedIdentityKeyLock]) {
OWSRecipientIdentity *existingIdentity = [OWSRecipientIdentity fetchObjectWithUniqueID:recipientId];
if (existingIdentity == nil) {
DDLogInfo(@"%@ saving first use identity for recipient: %@", logTag, recipientId);
[[[OWSRecipientIdentity alloc] initWithRecipientId:recipientId
identityKey:identityKey
isFirstKnownKey:YES
createdAt:[NSDate new]
approvedForBlockingUse:approvedForBlockingUse
approvedForNonBlockingUse:approvedForNonBlockingUse] save];
return NO;
}
if (![existingIdentity.identityKey isEqual:identityKey]) {
DDLogInfo(@"%@ replacing identity for existing recipient: %@", logTag, recipientId);
[self createIdentityChangeInfoMessageForRecipientId:recipientId];
[[[OWSRecipientIdentity alloc] initWithRecipientId:recipientId
identityKey:identityKey
isFirstKnownKey:NO
createdAt:[NSDate new]
approvedForBlockingUse:approvedForBlockingUse
approvedForNonBlockingUse:approvedForNonBlockingUse] save];
return YES;
}
if ([self isBlockingApprovalRequiredForIdentity:existingIdentity] || [self isNonBlockingApprovalRequiredForIdentity:existingIdentity]) {
[existingIdentity updateWithApprovedForBlockingUse:approvedForBlockingUse
approvedForNonBlockingUse:approvedForNonBlockingUse];
return NO;
}
DDLogDebug(@"%@ no changes for identity saved for recipient: %@", logTag, recipientId);
return NO;
}
}
- (BOOL)isTrustedIdentityKey:(NSData *)identityKey
recipientId:(NSString *)recipientId
direction:(TSMessageDirection)direction
{
OWSAssert(identityKey != nil);
OWSAssert(recipientId != nil);
OWSAssert(direction != TSMessageDirectionUnknown);
@synchronized([[self class] sharedIdentityKeyLock])
{
if ([[[self class] localNumber] isEqualToString:recipientId]) {
if ([[self identityKeyPair].publicKey isEqualToData:identityKey]) {
return YES;
} else {
DDLogError(@"%s Wrong identity: %@ for local key: %@",
__PRETTY_FUNCTION__,
identityKey,
[self identityKeyPair].publicKey);
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(@"%s unexpected message direction: %ld", __PRETTY_FUNCTION__, (long)direction);
OWSAssert(NO);
return NO;
}
}
}
}
- (nullable OWSRecipientIdentity *)unconfirmedIdentityThatShouldBlockSendingForRecipientId:(NSString *)recipientId;
{
OWSAssert(recipientId != nil);
@synchronized([[self class] sharedIdentityKeyLock])
{
OWSRecipientIdentity *currentIdentity = [OWSRecipientIdentity fetchObjectWithUniqueID:recipientId];
if (currentIdentity == nil) {
// No preexisting key, Trust On First Use
return nil;
}
if ([self isTrustedIdentityKey:currentIdentity.identityKey
recipientId:currentIdentity.recipientId
direction:TSMessageDirectionOutgoing]) {
return nil;
}
// identity not yet trusted for sending
return currentIdentity;
}
}
- (NSArray<OWSRecipientIdentity *> *)unseenIdentityChangesForRecipientIds:(NSArray<NSString *> *)recipientIds
{
NSMutableArray<OWSRecipientIdentity *> *unseenRecipientIdentities = [NSMutableArray new];
@synchronized([[self class] sharedIdentityKeyLock])
{
for (NSString *recipientId in recipientIds) {
OWSRecipientIdentity *identity = [self unseenIdentityChangeForRecipientId:recipientId];
if (identity) {
[unseenRecipientIdentities addObject:identity];
}
}
}
return [unseenRecipientIdentities copy];
}
- (nullable OWSRecipientIdentity *)unseenIdentityChangeForRecipientId:(NSString *)recipientId
{
OWSAssert(recipientId != nil);
@synchronized([[self class] sharedIdentityKeyLock])
{
OWSRecipientIdentity *currentIdentity = [OWSRecipientIdentity fetchObjectWithUniqueID:recipientId];
if (currentIdentity == nil) {
// No preexisting key, Trust On First Use
return nil;
}
if (currentIdentity.isFirstKnownKey) {
return nil;
}
if (currentIdentity.wasSeen) {
return nil;
}
// identity not yet seen
return currentIdentity;
}
}
- (BOOL)isTrustedKey:(NSData *)identityKey forSendingToIdentity:(nullable OWSRecipientIdentity *)recipientIdentity
{
OWSAssert(identityKey != nil);
@synchronized([[self class] sharedIdentityKeyLock])
{
if (recipientIdentity == nil) {
DDLogDebug(@"%s Trusting on first use for recipient: %@", __PRETTY_FUNCTION__, recipientIdentity.recipientId);
return YES;
}
OWSAssert(recipientIdentity.identityKey != nil);
if (![recipientIdentity.identityKey isEqualToData:identityKey]) {
DDLogWarn(@"%s key mismatch for recipient: %@", __PRETTY_FUNCTION__, recipientIdentity.recipientId);
return NO;
}
if ([self isBlockingApprovalRequiredForIdentity:recipientIdentity]) {
DDLogWarn(@"%s not trusting until blocking approval is granted. recipient: %@",
__PRETTY_FUNCTION__,
recipientIdentity.recipientId);
return NO;
}
if ([self isNonBlockingApprovalRequiredForIdentity:recipientIdentity]) {
DDLogWarn(@"%s not trusting until non-blocking approval is granted. recipient: %@",
__PRETTY_FUNCTION__,
recipientIdentity.recipientId);
return NO;
}
return YES;
}
}
- (BOOL)isBlockingApprovalRequiredForIdentity:(OWSRecipientIdentity *)recipientIdentity
{
OWSAssert(recipientIdentity != nil);
OWSAssert([TextSecureKitEnv sharedEnv].preferences != nil);
return !recipientIdentity.isFirstKnownKey &&
[TextSecureKitEnv sharedEnv].preferences.isSendingIdentityApprovalRequired &&
!recipientIdentity.approvedForBlockingUse;
}
- (BOOL)isNonBlockingApprovalRequiredForIdentity:(OWSRecipientIdentity *)recipientIdentity
{
OWSAssert(recipientIdentity != nil);
return !recipientIdentity.isFirstKnownKey &&
[[NSDate new] timeIntervalSinceDate:recipientIdentity.createdAt] < kIdentityKeyStoreNonBlockingSecondsThreshold &&
!recipientIdentity.approvedForNonBlockingUse;
}
- (void)removeIdentityKeyForRecipient:(NSString *)recipientId
{
OWSAssert(recipientId != nil);
[[OWSRecipientIdentity fetchObjectWithUniqueID:recipientId] remove];
}
- (void)createIdentityChangeInfoMessageForRecipientId:(NSString *)recipientId
{
OWSAssert(recipientId != nil);
TSContactThread *contactThread = [TSContactThread getOrCreateThreadWithContactId:recipientId];
OWSAssert(contactThread != nil);
TSErrorMessage *errorMessage =
[TSErrorMessage nonblockingIdentityChangeInThread:contactThread recipientId:recipientId];
[errorMessage save];
[[TextSecureKitEnv sharedEnv].notificationsManager notifyUserForErrorMessage:errorMessage inThread:contactThread];
for (TSGroupThread *groupThread in [TSGroupThread groupThreadsWithRecipientId:recipientId]) {
[[TSErrorMessage nonblockingIdentityChangeInThread:groupThread recipientId:recipientId] save];
}
}
@end
NS_ASSUME_NONNULL_END

View File

@ -7,6 +7,8 @@
@interface TSStorageManager (SessionStore) <SessionStore>
- (void)archiveAllSessionsForContact:(NSString *)contactIdentifier;
#pragma mark - debug
- (void)printAllSessions;

View File

@ -3,6 +3,7 @@
//
#import "TSStorageManager+SessionStore.h"
#import <AxolotlKit/SessionRecord.h>
NSString *const TSStorageManagerSessionStoreCollection = @"TSStorageManagerSessionStoreCollection";
NSString *const kSessionStoreDBConnectionKey = @"kSessionStoreDBConnectionKey";
@ -158,6 +159,34 @@ void AssertIsOnSessionStoreQueue()
}];
}
- (void)archiveAllSessionsForContact:(NSString *)contactIdentifier
{
AssertIsOnSessionStoreQueue();
DDLogInfo(@"[TSStorageManager (SessionStore)] archiving all sessions for contact: %@", contactIdentifier);
__block NSDictionary<NSNumber *, SessionRecord *> *sessionRecords;
[self.sessionDBConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
sessionRecords =
[transaction objectForKey:contactIdentifier inCollection:TSStorageManagerSessionStoreCollection];
for (id deviceId in sessionRecords) {
id object = sessionRecords[deviceId];
if (![object isKindOfClass:[SessionRecord class]]) {
OWSFail(@"Unexpected object in session dict: %@", object);
continue;
}
SessionRecord *sessionRecord = (SessionRecord *)object;
[sessionRecord archiveCurrentState];
}
[transaction setObject:sessionRecords
forKey:contactIdentifier
inCollection:TSStorageManagerSessionStoreCollection];
}];
}
#pragma mark - debug
- (void)printAllSessions

View File

@ -2,7 +2,7 @@
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "TSStorageManager+IdentityKeyStore.h"
#import "OWSIdentityManager.h"
#import "TSStorageManager+PreKeyStore.h"
#import "TSStorageManager+SignedPreKeyStore.h"
#import "TSStorageManager+keyFromIntLong.h"
@ -26,10 +26,11 @@ NSString *const TSStorageManagerKeyPrekeyCurrentSignedPrekeyId = @"currentSigned
// Signed prekey ids must be > 0.
int preKeyId = 1 + arc4random_uniform(INT32_MAX - 1);
ECKeyPair *_Nullable identityKeyPair = [[OWSIdentityManager sharedManager] identityKeyPair];
return [[SignedPreKeyRecord alloc]
initWithId:preKeyId
keyPair:keyPair
signature:[Ed25519 sign:keyPair.publicKey.prependKeyType withKeyPair:[self identityKeyPair]]
signature:[Ed25519 sign:keyPair.publicKey.prependKeyType withKeyPair:identityKeyPair]
generatedAt:[NSDate date]];
}
@ -147,7 +148,7 @@ NSString *const TSStorageManagerKeyPrekeyCurrentSignedPrekeyId = @"currentSigned
NSDate *firstPrekeyUpdateFailureDate = [self firstPrekeyUpdateFailureDate];
NSUInteger prekeyUpdateFailureCount = [self prekeyUpdateFailureCount];
[self.dbConnection readWithBlock:^(YapDatabaseReadTransaction *_Nonnull transaction) {
[self.dbReadConnection readWithBlock:^(YapDatabaseReadTransaction *_Nonnull transaction) {
__block int i = 0;
DDLogInfo(@"%@ SignedPreKeys Report:", tag);

View File

@ -1,10 +1,30 @@
// Copyright (c) 2016 Open Whisper Systems. All rights reserved.
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
NS_ASSUME_NONNULL_BEGIN
// Notes:
//
// * On disk, we only bother cleaning up files, not directories.
// * For code simplicity, we don't guarantee that everything is
// cleaned up in a single pass. If an interaction is cleaned up,
// it's attachments might not be cleaned up until the next pass.
// If an attachment is cleaned up, it's file on disk might not
// be cleaned up until the next pass.
@interface OWSOrphanedDataCleaner : NSObject
/**
* Remove any inaccessible data left behind due to application bugs.
*/
- (void)removeOrphanedData;
- (instancetype)init NS_UNAVAILABLE;
+ (void)auditAsync;
// completion, if present, will be invoked on the main thread.
+ (void)auditAndCleanupAsync:(void (^_Nullable)())completion;
+ (NSSet<NSString *> *)filePathsInAttachmentsFolder;
+ (long long)fileSizeOfFilePaths:(NSArray<NSString *> *)filePaths;
@end
NS_ASSUME_NONNULL_END

View File

@ -1,4 +1,6 @@
// Copyright (c) 2016 Open Whisper Systems. All rights reserved.
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "OWSOrphanedDataCleaner.h"
#import "TSAttachmentStream.h"
@ -7,84 +9,256 @@
#import "TSStorageManager.h"
#import "TSThread.h"
NS_ASSUME_NONNULL_BEGIN
#ifdef SSK_BUILDING_FOR_TESTS
#define CleanupLogDebug NSLog
#define CleanupLogInfo NSLog
#else
#define CleanupLogDebug DDLogDebug
#define CleanupLogInfo DDLogInfo
#endif
@implementation OWSOrphanedDataCleaner
- (void)removeOrphanedData
+ (void)auditAsync
{
// Remove interactions whose threads have been deleted
for (NSString *interactionId in [self orphanedInteractionIds]) {
DDLogWarn(@"Removing orphaned interaction with id: %@", interactionId);
TSInteraction *interaction = [TSInteraction fetchObjectWithUniqueID:interactionId];
[interaction remove];
}
// Remove any lingering attachments
for (NSString *path in [self orphanedFilePaths]) {
DDLogWarn(@"Removing orphaned file attachment at path: %@", path);
NSError *error;
[[NSFileManager defaultManager] removeItemAtPath:path error:&error];
if (error) {
DDLogError(@"Unable to remove orphaned file attachment at path:%@", path);
}
}
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[OWSOrphanedDataCleaner auditAndCleanup:NO completion:nil];
});
}
- (NSArray<NSString *> *)orphanedInteractionIds
+ (void)auditAndCleanupAsync:(void (^_Nullable)())completion
{
NSMutableArray *interactionIds = [NSMutableArray new];
[[TSInteraction dbConnection] readWithBlock:^(YapDatabaseReadTransaction *_Nonnull transaction) {
[TSInteraction enumerateCollectionObjectsWithTransaction:transaction
usingBlock:^(TSInteraction *interaction, BOOL *stop) {
TSThread *thread = [TSThread
fetchObjectWithUniqueID:interaction.uniqueThreadId
transaction:transaction];
if (!thread) {
[interactionIds addObject:interaction.uniqueId];
}
}];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[OWSOrphanedDataCleaner auditAndCleanup:YES completion:completion];
});
}
// This method finds and optionally cleans up:
//
// * Orphan messages (with no thread).
// * Orphan attachments (with no message).
// * Orphan attachment files (with no attachment).
// * Missing attachment files (cannot be cleaned up).
// These are attachments which have no file on disk. They should be extremely rare -
// the only cases I have seen are probably due to debugging.
// They can't be cleaned up - we don't want to delete the TSAttachmentStream or
// its corresponding message. Better that the broken message shows up in the
// conversation view.
+ (void)auditAndCleanup:(BOOL)shouldCleanup completion:(void (^_Nullable)())completion
{
NSSet<NSString *> *diskFilePaths = [self filePathsInAttachmentsFolder];
long long totalFileSize = [self fileSizeOfFilePaths:diskFilePaths.allObjects];
NSUInteger fileCount = diskFilePaths.count;
TSStorageManager *storageManager = [TSStorageManager sharedManager];
YapDatabaseConnection *databaseConnection = storageManager.newDatabaseConnection;
__block int attachmentStreamCount = 0;
NSMutableSet<NSString *> *attachmentFilePaths = [NSMutableSet new];
NSMutableSet<NSString *> *attachmentIds = [NSMutableSet new];
[databaseConnection readWithBlock:^(YapDatabaseReadTransaction *_Nonnull transaction) {
[transaction enumerateKeysAndObjectsInCollection:TSAttachmentStream.collection
usingBlock:^(NSString *key, TSAttachment *attachment, BOOL *stop) {
[attachmentIds addObject:attachment.uniqueId];
if (![attachment isKindOfClass:[TSAttachmentStream class]]) {
return;
}
TSAttachmentStream *attachmentStream
= (TSAttachmentStream *)attachment;
attachmentStreamCount++;
NSString *_Nullable filePath = [attachmentStream filePath];
OWSAssert(filePath);
[attachmentFilePaths addObject:filePath];
}];
}];
CleanupLogDebug(@"fileCount: %zd", fileCount);
CleanupLogDebug(@"totalFileSize: %lld", totalFileSize);
CleanupLogDebug(@"attachmentStreams: %d", attachmentStreamCount);
CleanupLogDebug(@"attachmentStreams with file paths: %zd", attachmentFilePaths.count);
return [interactionIds copy];
NSMutableSet<NSString *> *orphanDiskFilePaths = [diskFilePaths mutableCopy];
[orphanDiskFilePaths minusSet:attachmentFilePaths];
NSMutableSet<NSString *> *missingAttachmentFilePaths = [attachmentFilePaths mutableCopy];
[missingAttachmentFilePaths minusSet:diskFilePaths];
CleanupLogDebug(@"orphan disk file paths: %zd", orphanDiskFilePaths.count);
CleanupLogDebug(@"missing attachment file paths: %zd", missingAttachmentFilePaths.count);
[self printPaths:orphanDiskFilePaths.allObjects label:@"orphan disk file paths"];
[self printPaths:missingAttachmentFilePaths.allObjects label:@"missing attachment file paths"];
NSMutableSet *threadIds = [NSMutableSet new];
[databaseConnection readWithBlock:^(YapDatabaseReadTransaction *_Nonnull transaction) {
[transaction enumerateKeysInCollection:TSThread.collection
usingBlock:^(NSString *_Nonnull key, BOOL *_Nonnull stop) {
[threadIds addObject:key];
}];
}];
NSMutableSet<NSString *> *orphanInteractionIds = [NSMutableSet new];
NSMutableSet<NSString *> *messageAttachmentIds = [NSMutableSet new];
[databaseConnection readWithBlock:^(YapDatabaseReadTransaction *_Nonnull transaction) {
[transaction enumerateKeysAndObjectsInCollection:TSMessage.collection
usingBlock:^(NSString *key, TSInteraction *interaction, BOOL *stop) {
if (![threadIds containsObject:interaction.uniqueThreadId]) {
[orphanInteractionIds addObject:interaction.uniqueId];
}
if (![interaction isKindOfClass:[TSMessage class]]) {
return;
}
TSMessage *message = (TSMessage *)interaction;
if (message.attachmentIds.count > 0) {
[messageAttachmentIds addObjectsFromArray:message.attachmentIds];
}
}];
}];
CleanupLogDebug(@"attachmentIds: %zd", attachmentIds.count);
CleanupLogDebug(@"messageAttachmentIds: %zd", messageAttachmentIds.count);
NSMutableSet<NSString *> *orphanAttachmentIds = [attachmentIds mutableCopy];
[orphanAttachmentIds minusSet:messageAttachmentIds];
NSMutableSet<NSString *> *missingAttachmentIds = [messageAttachmentIds mutableCopy];
[missingAttachmentIds minusSet:attachmentIds];
CleanupLogDebug(@"orphan attachmentIds: %zd", orphanAttachmentIds.count);
CleanupLogDebug(@"missing attachmentIds: %zd", missingAttachmentIds.count);
CleanupLogDebug(@"orphan interactions: %zd", orphanInteractionIds.count);
// We need to avoid cleaning up new attachments and files that are still in the process of
// being created/written, so we don't clean up anything recent.
#ifdef SSK_BUILDING_FOR_TESTS
const NSTimeInterval kMinimumOrphanAge = 0.f;
#else
const NSTimeInterval kMinimumOrphanAge = 15 * 60.f;
#endif
if (!shouldCleanup) {
return;
}
[databaseConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) {
for (NSString *interactionId in orphanInteractionIds) {
TSInteraction *interaction = [TSInteraction fetchObjectWithUniqueID:interactionId transaction:transaction];
if (!interaction) {
// This could just be a race condition, but it should be very unlikely.
OWSFail(@"Could not load interaction: %@", interactionId);
continue;
}
CleanupLogInfo(@"Removing orphan message: %@", interaction.uniqueId);
[interaction removeWithTransaction:transaction];
}
for (NSString *attachmentId in orphanAttachmentIds) {
TSAttachment *attachment = [TSAttachment fetchObjectWithUniqueID:attachmentId transaction:transaction];
if (!attachment) {
// This could just be a race condition, but it should be very unlikely.
OWSFail(@"Could not load attachment: %@", attachmentId);
continue;
}
if (![attachment isKindOfClass:[TSAttachmentStream class]]) {
continue;
}
TSAttachmentStream *attachmentStream = (TSAttachmentStream *)attachment;
// Don't delete attachments which were created in the last N minutes.
if (fabs([attachmentStream.creationTimestamp timeIntervalSinceNow]) < kMinimumOrphanAge) {
CleanupLogInfo(@"Skipping orphan attachment due to age: %f",
fabs([attachmentStream.creationTimestamp timeIntervalSinceNow]));
continue;
}
CleanupLogInfo(@"Removing orphan attachment: %@", attachmentStream.uniqueId);
[attachmentStream removeWithTransaction:transaction];
}
}];
for (NSString *filePath in orphanDiskFilePaths) {
NSError *error;
NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:&error];
if (!attributes || error) {
OWSFail(@"Could not get attributes of file at: %@", filePath);
continue;
}
// Don't delete files which were created in the last N minutes.
if (fabs([attributes.fileModificationDate timeIntervalSinceNow]) < kMinimumOrphanAge) {
CleanupLogInfo(@"Skipping orphan attachment file due to age: %f",
fabs([attributes.fileModificationDate timeIntervalSinceNow]));
continue;
}
CleanupLogInfo(@"Removing orphan attachment file: %@", filePath);
[[NSFileManager defaultManager] removeItemAtPath:filePath error:&error];
if (error) {
OWSFail(@"Could not remove orphan file at: %@", filePath);
}
}
if (completion) {
dispatch_async(dispatch_get_main_queue(), ^{
completion();
});
}
}
- (NSArray<NSString *> *)orphanedFilePaths
+ (void)printPaths:(NSArray<NSString *> *)paths label:(NSString *)label
{
for (NSString *path in [paths sortedArrayUsingSelector:@selector(compare:)]) {
CleanupLogDebug(@"%@: %@", label, path);
}
}
+ (NSSet<NSString *> *)filePathsInAttachmentsFolder
{
NSString *attachmentsFolder = [TSAttachmentStream attachmentsFolder];
CleanupLogDebug(@"attachmentsFolder: %@", attachmentsFolder);
return [self filePathsInDirectory:attachmentsFolder];
}
+ (NSSet<NSString *> *)filePathsInDirectory:(NSString *)dirPath
{
NSMutableSet *filePaths = [NSMutableSet new];
NSError *error;
NSArray<NSString *> *fileNames = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:dirPath error:&error];
if (error) {
OWSFail(@"contentsOfDirectoryAtPath error: %@", error);
return [NSSet new];
}
for (NSString *fileName in fileNames) {
NSString *filePath = [dirPath stringByAppendingPathComponent:fileName];
BOOL isDirectory;
[[NSFileManager defaultManager] fileExistsAtPath:filePath isDirectory:&isDirectory];
if (isDirectory) {
[filePaths addObjectsFromArray:[self filePathsInDirectory:filePath].allObjects];
} else {
[filePaths addObject:filePath];
}
}
return filePaths;
}
+ (long long)fileSizeOfFilePath:(NSString *)filePath
{
NSError *error;
NSMutableArray<NSString *> *filenames =
[[[NSFileManager defaultManager] contentsOfDirectoryAtPath:[TSAttachmentStream attachmentsFolder] error:&error]
mutableCopy];
NSNumber *fileSize = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:&error][NSFileSize];
if (error) {
DDLogError(@"error getting orphanedFilePaths:%@", error);
return @[];
OWSFail(@"attributesOfItemAtPath: %@ error: %@", filePath, error);
return 0;
}
return fileSize.longLongValue;
}
NSMutableDictionary<NSString *, NSString *> *attachmentIdFilenames = [NSMutableDictionary new];
for (NSString *filename in filenames) {
// Remove extension from (e.g.) 1234.png to get the attachmentId "1234"
NSString *attachmentId = [filename stringByDeletingPathExtension];
attachmentIdFilenames[attachmentId] = filename;
+ (long long)fileSizeOfFilePaths:(NSArray<NSString *> *)filePaths
{
long long result = 0;
for (NSString *filePath in filePaths) {
result += [self fileSizeOfFilePath:filePath];
}
[TSInteraction enumerateCollectionObjectsUsingBlock:^(TSInteraction *interaction, BOOL *stop) {
if ([interaction isKindOfClass:[TSMessage class]]) {
TSMessage *message = (TSMessage *)interaction;
if ([message hasAttachments]) {
for (NSString *attachmentId in message.attachmentIds) {
[attachmentIdFilenames removeObjectForKey:attachmentId];
}
}
}
}];
NSArray<NSString *> *filenamesToDelete = [attachmentIdFilenames allValues];
NSMutableArray<NSString *> *absolutePathsToDelete = [NSMutableArray arrayWithCapacity:[filenamesToDelete count]];
for (NSString *filename in filenamesToDelete) {
NSString *absolutePath = [[TSAttachmentStream attachmentsFolder] stringByAppendingFormat:@"/%@", filename];
[absolutePathsToDelete addObject:absolutePath];
}
return [absolutePathsToDelete copy];
return result;
}
@end
NS_ASSUME_NONNULL_END

View File

@ -1,9 +1,5 @@
//
// TSDatabaseSecondaryIndexes.m
// Signal
//
// Created by Frederic Jacobs on 26/01/15.
// Copyright (c) 2015 Open Whisper Systems. All rights reserved.
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "TSDatabaseSecondaryIndexes.h"
@ -43,4 +39,5 @@
YapDatabaseQuery *query = [YapDatabaseQuery queryWithFormat:formattedString];
[[transaction ext:@"idx"] enumerateKeysMatchingQuery:query usingBlock:block];
}
@end

View File

@ -5,40 +5,56 @@
#import <Foundation/Foundation.h>
#import <YapDatabase/YapDatabaseViewTransaction.h>
extern NSString *const kNSNotificationName_DatabaseViewRegistrationComplete;
extern NSString *const TSInboxGroup;
extern NSString *const TSArchiveGroup;
extern NSString *const TSUnreadIncomingMessagesGroup;
extern NSString *const TSSecondaryDevicesGroup;
extern NSString *const TSThreadDatabaseViewExtensionName;
extern NSString *const TSMessageDatabaseViewExtensionName;
extern NSString *const TSUnreadDatabaseViewExtensionName;
extern NSString *const TSSecondaryDevicesDatabaseViewExtensionName;
@interface TSDatabaseView : NSObject
extern NSString *TSInboxGroup;
extern NSString *TSArchiveGroup;
extern NSString *TSUnreadIncomingMessagesGroup;
extern NSString *TSSecondaryDevicesGroup;
- (instancetype)init NS_UNAVAILABLE;
extern NSString *TSThreadDatabaseViewExtensionName;
extern NSString *TSMessageDatabaseViewExtensionName;
extern NSString *TSThreadIncomingMessageDatabaseViewExtensionName;
extern NSString *TSThreadOutgoingMessageDatabaseViewExtensionName;
extern NSString *TSUnreadDatabaseViewExtensionName;
extern NSString *TSUnseenDatabaseViewExtensionName;
extern NSString *TSDynamicMessagesDatabaseViewExtensionName;
extern NSString *TSSafetyNumberChangeDatabaseViewExtensionName;
extern NSString *TSSecondaryDevicesDatabaseViewExtensionName;
+ (BOOL)hasPendingViewRegistrations;
+ (BOOL)registerThreadDatabaseView;
+ (BOOL)registerThreadInteractionsDatabaseView;
+ (BOOL)registerThreadIncomingMessagesDatabaseView;
+ (BOOL)registerThreadOutgoingMessagesDatabaseView;
+ (void)registerThreadDatabaseView;
+ (void)registerThreadInteractionsDatabaseView;
+ (void)asyncRegisterThreadOutgoingMessagesDatabaseView;
// Instances of OWSReadTracking for wasRead is NO and shouldAffectUnreadCounts is YES.
//
// Should be used for "unread message counts".
+ (BOOL)registerUnreadDatabaseView;
+ (void)registerUnreadDatabaseView;
// Should be used for "unread indicator".
//
// Instances of OWSReadTracking for wasRead is NO.
+ (BOOL)registerUnseenDatabaseView;
+ (void)asyncRegisterUnseenDatabaseView;
+ (void)asyncRegisterThreadSpecialMessagesDatabaseView;
+ (BOOL)registerDynamicMessagesDatabaseView;
+ (BOOL)registerSafetyNumberChangeDatabaseView;
+ (void)asyncRegisterSecondaryDevicesDatabaseView;
// Returns the "unseen" database view if it is ready;
// otherwise it returns the "unread" database view.
+ (id)unseenDatabaseViewExtension:(YapDatabaseReadTransaction *)transaction;
// NOTE: It is not safe to call this method while hasPendingViewRegistrations is YES.
+ (id)threadOutgoingMessageDatabaseView:(YapDatabaseReadTransaction *)transaction;
// NOTE: It is not safe to call this method while hasPendingViewRegistrations is YES.
+ (id)threadSpecialMessagesDatabaseView:(YapDatabaseReadTransaction *)transaction;
// This method should be called _after_ all async database registrations have been started.
+ (void)asyncRegistrationCompletion;
@end

View File

@ -12,34 +12,77 @@
#import "TSThread.h"
#import <YapDatabase/YapDatabaseView.h>
NSString *TSInboxGroup = @"TSInboxGroup";
NSString *TSArchiveGroup = @"TSArchiveGroup";
NSString *const kNSNotificationName_DatabaseViewRegistrationComplete =
@"kNSNotificationName_DatabaseViewRegistrationComplete";
NSString *TSUnreadIncomingMessagesGroup = @"TSUnreadIncomingMessagesGroup";
NSString *TSSecondaryDevicesGroup = @"TSSecondaryDevicesGroup";
NSString *const TSInboxGroup = @"TSInboxGroup";
NSString *const TSArchiveGroup = @"TSArchiveGroup";
NSString *TSThreadDatabaseViewExtensionName = @"TSThreadDatabaseViewExtensionName";
NSString *TSMessageDatabaseViewExtensionName = @"TSMessageDatabaseViewExtensionName";
NSString *TSThreadIncomingMessageDatabaseViewExtensionName = @"TSThreadOutgoingMessageDatabaseViewExtensionName";
NSString *TSThreadOutgoingMessageDatabaseViewExtensionName = @"TSThreadOutgoingMessageDatabaseViewExtensionName";
NSString *TSUnreadDatabaseViewExtensionName = @"TSUnreadDatabaseViewExtensionName";
NSString *TSUnseenDatabaseViewExtensionName = @"TSUnseenDatabaseViewExtensionName";
NSString *TSDynamicMessagesDatabaseViewExtensionName = @"TSDynamicMessagesDatabaseViewExtensionName";
NSString *TSSafetyNumberChangeDatabaseViewExtensionName = @"TSSafetyNumberChangeDatabaseViewExtensionName";
NSString *TSSecondaryDevicesDatabaseViewExtensionName = @"TSSecondaryDevicesDatabaseViewExtensionName";
NSString *const TSUnreadIncomingMessagesGroup = @"TSUnreadIncomingMessagesGroup";
NSString *const TSSecondaryDevicesGroup = @"TSSecondaryDevicesGroup";
// YAPDB BUG: when changing from non-persistent to persistent view, we had to rename TSThreadDatabaseViewExtensionName
// -> TSThreadDatabaseViewExtensionName2 to work around https://github.com/yapstudios/YapDatabase/issues/324
NSString *const TSThreadDatabaseViewExtensionName = @"TSThreadDatabaseViewExtensionName2";
NSString *const TSMessageDatabaseViewExtensionName = @"TSMessageDatabaseViewExtensionName";
NSString *const TSThreadOutgoingMessageDatabaseViewExtensionName = @"TSThreadOutgoingMessageDatabaseViewExtensionName";
NSString *const TSUnreadDatabaseViewExtensionName = @"TSUnreadDatabaseViewExtensionName";
NSString *const TSUnseenDatabaseViewExtensionName = @"TSUnseenDatabaseViewExtensionName";
NSString *const TSThreadSpecialMessagesDatabaseViewExtensionName = @"TSThreadSpecialMessagesDatabaseViewExtensionName";
NSString *const TSSecondaryDevicesDatabaseViewExtensionName = @"TSSecondaryDevicesDatabaseViewExtensionName";
@interface TSDatabaseView ()
@property (nonatomic) BOOL areAllAsyncRegistrationsComplete;
@end
#pragma mark -
@implementation TSDatabaseView
+ (BOOL)registerMessageDatabaseViewWithName:(NSString *)viewName
+ (instancetype)sharedInstance
{
static TSDatabaseView *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] initDefault];
});
return sharedInstance;
}
- (instancetype)initDefault
{
self = [super init];
if (!self) {
return self;
}
OWSSingletonAssert();
return self;
}
+ (BOOL)hasPendingViewRegistrations
{
OWSAssert([NSThread isMainThread]);
return ![TSDatabaseView sharedInstance].areAllAsyncRegistrationsComplete;
}
+ (void)registerMessageDatabaseViewWithName:(NSString *)viewName
viewGrouping:(YapDatabaseViewGrouping *)viewGrouping
version:(NSString *)version
async:(BOOL)async
{
OWSAssert([NSThread isMainThread]);
OWSAssert(viewName.length > 0);
OWSAssert((viewGrouping));
YapDatabaseView *existingView = [[TSStorageManager sharedManager].database registeredExtension:viewName];
if (existingView) {
return YES;
return;
}
YapDatabaseViewSorting *viewSorting = [self messagesSorting];
@ -52,10 +95,21 @@ NSString *TSSecondaryDevicesDatabaseViewExtensionName = @"TSSecondaryDevicesData
YapDatabaseView *view =
[[YapDatabaseView alloc] initWithGrouping:viewGrouping sorting:viewSorting versionTag:version options:options];
return [[TSStorageManager sharedManager].database registerExtension:view withName:viewName];
if (async) {
[[TSStorageManager sharedManager].database
asyncRegisterExtension:view
withName:viewName
completionBlock:^(BOOL ready) {
OWSCAssert(ready);
DDLogInfo(@"%@ asyncRegisterExtension: %@ -> %d", self.tag, viewName, ready);
}];
} else {
[[TSStorageManager sharedManager].database registerExtension:view withName:viewName];
}
}
+ (BOOL)registerUnreadDatabaseView
+ (void)registerUnreadDatabaseView
{
YapDatabaseViewGrouping *viewGrouping = [YapDatabaseViewGrouping withObjectBlock:^NSString *(
YapDatabaseReadTransaction *transaction, NSString *collection, NSString *key, id object) {
@ -68,12 +122,13 @@ NSString *TSSecondaryDevicesDatabaseViewExtensionName = @"TSSecondaryDevicesData
return nil;
}];
return [self registerMessageDatabaseViewWithName:TSUnreadDatabaseViewExtensionName
viewGrouping:viewGrouping
version:@"1"];
[self registerMessageDatabaseViewWithName:TSUnreadDatabaseViewExtensionName
viewGrouping:viewGrouping
version:@"1"
async:NO];
}
+ (BOOL)registerUnseenDatabaseView
+ (void)asyncRegisterUnseenDatabaseView
{
YapDatabaseViewGrouping *viewGrouping = [YapDatabaseViewGrouping withObjectBlock:^NSString *(
YapDatabaseReadTransaction *transaction, NSString *collection, NSString *key, id object) {
@ -86,36 +141,22 @@ NSString *TSSecondaryDevicesDatabaseViewExtensionName = @"TSSecondaryDevicesData
return nil;
}];
return [self registerMessageDatabaseViewWithName:TSUnseenDatabaseViewExtensionName
viewGrouping:viewGrouping
version:@"1"];
[self registerMessageDatabaseViewWithName:TSUnseenDatabaseViewExtensionName
viewGrouping:viewGrouping
version:@"1"
async:YES];
}
+ (BOOL)registerDynamicMessagesDatabaseView
+ (void)asyncRegisterThreadSpecialMessagesDatabaseView
{
YapDatabaseViewGrouping *viewGrouping = [YapDatabaseViewGrouping withObjectBlock:^NSString *(
YapDatabaseReadTransaction *transaction, NSString *collection, NSString *key, id object) {
if ([object isKindOfClass:[TSInteraction class]]) {
TSInteraction *interaction = (TSInteraction *)object;
if ([interaction isDynamicInteraction]) {
return interaction.uniqueThreadId;
}
} else {
OWSAssert(0);
}
return nil;
}];
OWSAssert([object isKindOfClass:[TSInteraction class]]);
return [self registerMessageDatabaseViewWithName:TSDynamicMessagesDatabaseViewExtensionName
viewGrouping:viewGrouping
version:@"3"];
}
+ (BOOL)registerSafetyNumberChangeDatabaseView
{
YapDatabaseViewGrouping *viewGrouping = [YapDatabaseViewGrouping withObjectBlock:^NSString *(
YapDatabaseReadTransaction *transaction, NSString *collection, NSString *key, id object) {
if ([object isKindOfClass:[TSInvalidIdentityKeyErrorMessage class]]) {
TSInteraction *interaction = (TSInteraction *)object;
if ([interaction isDynamicInteraction]) {
return interaction.uniqueThreadId;
} else if ([object isKindOfClass:[TSInvalidIdentityKeyErrorMessage class]]) {
TSInteraction *interaction = (TSInteraction *)object;
return interaction.uniqueThreadId;
} else if ([object isKindOfClass:[TSErrorMessage class]]) {
@ -127,42 +168,29 @@ NSString *TSSecondaryDevicesDatabaseViewExtensionName = @"TSSecondaryDevicesData
return nil;
}];
return [self registerMessageDatabaseViewWithName:TSSafetyNumberChangeDatabaseViewExtensionName
viewGrouping:viewGrouping
version:@"2"];
[self registerMessageDatabaseViewWithName:TSThreadSpecialMessagesDatabaseViewExtensionName
viewGrouping:viewGrouping
version:@"1"
async:YES];
}
+ (BOOL)registerThreadInteractionsDatabaseView
+ (void)registerThreadInteractionsDatabaseView
{
YapDatabaseViewGrouping *viewGrouping = [YapDatabaseViewGrouping withObjectBlock:^NSString *(
YapDatabaseReadTransaction *transaction, NSString *collection, NSString *key, id object) {
if ([object isKindOfClass:[TSInteraction class]]) {
return ((TSInteraction *)object).uniqueThreadId;
}
return nil;
OWSAssert([object isKindOfClass:[TSInteraction class]]);
TSInteraction *interaction = (TSInteraction *)object;
return interaction.uniqueThreadId;
}];
return [self registerMessageDatabaseViewWithName:TSMessageDatabaseViewExtensionName
viewGrouping:viewGrouping
version:@"1"];
[self registerMessageDatabaseViewWithName:TSMessageDatabaseViewExtensionName
viewGrouping:viewGrouping
version:@"1"
async:NO];
}
+ (BOOL)registerThreadIncomingMessagesDatabaseView
{
YapDatabaseViewGrouping *viewGrouping = [YapDatabaseViewGrouping withObjectBlock:^NSString *(
YapDatabaseReadTransaction *transaction, NSString *collection, NSString *key, id object) {
if ([object isKindOfClass:[TSIncomingMessage class]]) {
return ((TSIncomingMessage *)object).uniqueThreadId;
}
return nil;
}];
return [self registerMessageDatabaseViewWithName:TSThreadIncomingMessageDatabaseViewExtensionName
viewGrouping:viewGrouping
version:@"1"];
}
+ (BOOL)registerThreadOutgoingMessagesDatabaseView
+ (void)asyncRegisterThreadOutgoingMessagesDatabaseView
{
YapDatabaseViewGrouping *viewGrouping = [YapDatabaseViewGrouping withObjectBlock:^NSString *(
YapDatabaseReadTransaction *transaction, NSString *collection, NSString *key, id object) {
@ -172,16 +200,18 @@ NSString *TSSecondaryDevicesDatabaseViewExtensionName = @"TSSecondaryDevicesData
return nil;
}];
return [self registerMessageDatabaseViewWithName:TSThreadOutgoingMessageDatabaseViewExtensionName
viewGrouping:viewGrouping
version:@"1"];
[self registerMessageDatabaseViewWithName:TSThreadOutgoingMessageDatabaseViewExtensionName
viewGrouping:viewGrouping
version:@"2"
async:YES];
}
+ (BOOL)registerThreadDatabaseView {
+ (void)registerThreadDatabaseView
{
YapDatabaseView *threadView =
[[TSStorageManager sharedManager].database registeredExtension:TSThreadDatabaseViewExtensionName];
if (threadView) {
return YES;
return;
}
YapDatabaseViewGrouping *viewGrouping = [YapDatabaseViewGrouping
@ -203,16 +233,15 @@ NSString *TSSecondaryDevicesDatabaseViewExtensionName = @"TSSecondaryDevicesData
YapDatabaseViewSorting *viewSorting = [self threadSorting];
YapDatabaseViewOptions *options = [[YapDatabaseViewOptions alloc] init];
options.isPersistent = NO;
options.isPersistent = YES;
options.allowedCollections =
[[YapWhitelistBlacklist alloc] initWithWhitelist:[NSSet setWithObject:[TSThread collection]]];
YapDatabaseView *databaseView =
[[YapDatabaseView alloc] initWithGrouping:viewGrouping sorting:viewSorting versionTag:@"1" options:options];
return [[TSStorageManager sharedManager]
.database registerExtension:databaseView
withName:TSThreadDatabaseViewExtensionName];
[[TSStorageManager sharedManager].database registerExtension:databaseView
withName:TSThreadDatabaseViewExtensionName];
}
/**
@ -337,6 +366,57 @@ NSString *TSSecondaryDevicesDatabaseViewExtensionName = @"TSSecondaryDevicesData
}];
}
+ (id)unseenDatabaseViewExtension:(YapDatabaseReadTransaction *)transaction
{
OWSAssert(transaction);
id result = [transaction ext:TSUnseenDatabaseViewExtensionName];
if (!result) {
result = [transaction ext:TSUnreadDatabaseViewExtensionName];
OWSAssert(result);
}
return result;
}
+ (id)threadOutgoingMessageDatabaseView:(YapDatabaseReadTransaction *)transaction
{
OWSAssert(transaction);
id result = [transaction ext:TSThreadOutgoingMessageDatabaseViewExtensionName];
OWSAssert(result);
return result;
}
+ (id)threadSpecialMessagesDatabaseView:(YapDatabaseReadTransaction *)transaction
{
OWSAssert(transaction);
id result = [transaction ext:TSThreadSpecialMessagesDatabaseViewExtensionName];
OWSAssert(result);
return result;
}
+ (void)asyncRegistrationCompletion
{
OWSAssert([NSThread isMainThread]);
// All async registrations are complete when writes are unblocked.
[[TSStorageManager sharedManager].newDatabaseConnection
asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) {
dispatch_async(dispatch_get_main_queue(), ^{
TSDatabaseView.sharedInstance.areAllAsyncRegistrationsComplete = YES;
[[NSNotificationCenter defaultCenter]
postNotificationName:kNSNotificationName_DatabaseViewRegistrationComplete
object:nil
userInfo:nil];
});
}];
}
#pragma mark - Logging
+ (NSString *)tag

View File

@ -1,9 +1,5 @@
//
// TSStorageHeaders.h
// Signal
//
// Created by Frederic Jacobs on 22/08/15.
// Copyright (c) 2015 Open Whisper Systems. All rights reserved.
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#ifndef Signal_TSStorageHeaders_h
@ -11,7 +7,7 @@
#import "TSStorageManager.h"
#import "TSStorageManager+IdentityKeyStore.h"
#import "OWSIdentityManager.h"
#import "TSStorageManager+PreKeyStore.h"
#import "TSStorageManager+SessionStore.h"
#import "TSStorageManager+SignedPreKeyStore.h"

View File

@ -53,7 +53,7 @@
- (void)storePhoneNumber:(NSString *)phoneNumber
{
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[self.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[transaction setObject:phoneNumber
forKey:TSStorageRegisteredNumberKey
inCollection:TSStorageUserAccountCollection];
@ -61,13 +61,12 @@
}
+ (void)storeServerToken:(NSString *)authToken signalingKey:(NSString *)signalingKey {
YapDatabaseConnection *dbConn = [[self sharedManager] dbConnection];
[dbConn readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[transaction setObject:authToken forKey:TSStorageServerAuthToken inCollection:TSStorageUserAccountCollection];
[transaction setObject:signalingKey
forKey:TSStorageServerSignalingKey
inCollection:TSStorageUserAccountCollection];
TSStorageManager *sharedManager = self.sharedManager;
[sharedManager.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[transaction setObject:authToken forKey:TSStorageServerAuthToken inCollection:TSStorageUserAccountCollection];
[transaction setObject:signalingKey
forKey:TSStorageServerSignalingKey
inCollection:TSStorageUserAccountCollection];
}];
}

View File

@ -27,7 +27,16 @@ NS_ASSUME_NONNULL_BEGIN
*/
+ (BOOL)isDatabasePasswordAccessible;
- (void)setupDatabase;
/**
* The safeBlockingMigrationsBlock block will
* run any outstanding version migrations that are a) blocking and b) safe
* to be run before the environment and storage is completely configured.
*
* Specifically, these migration should not depend on or affect the data
* of any database view.
*/
- (void)setupDatabaseWithSafeBlockingMigrations:(void (^_Nonnull)())safeBlockingMigrationsBlock;
- (void)deleteThreadsAndMessages;
- (void)resetSignalStorage;
@ -52,7 +61,8 @@ NS_ASSUME_NONNULL_BEGIN
- (nullable SignedPreKeyRecord *)signedPreKeyRecordForKey:(NSString *)key inCollection:(NSString *)collection;
- (void)purgeCollection:(NSString *)collection;
@property (nullable, nonatomic, readonly) YapDatabaseConnection *dbConnection;
@property (nullable, nonatomic, readonly) YapDatabaseConnection *dbReadConnection;
@property (nullable, nonatomic, readonly) YapDatabaseConnection *dbReadWriteConnection;
@end

View File

@ -156,7 +156,8 @@ static NSString *keychainDBPassAccount = @"TSDatabasePass";
if (!_database) {
return NO;
}
_dbConnection = self.newDatabaseConnection;
_dbReadConnection = self.newDatabaseConnection;
_dbReadWriteConnection = self.newDatabaseConnection;
return YES;
}
@ -191,30 +192,45 @@ static NSString *keychainDBPassAccount = @"TSDatabasePass";
};
}
- (void)setupDatabase
- (void)setupDatabaseWithSafeBlockingMigrations:(void (^_Nonnull)())safeBlockingMigrationsBlock
{
// Register extensions which are essential for rendering threads synchronously
// Synchronously register extensions which are essential for views.
[TSDatabaseView registerThreadDatabaseView];
[TSDatabaseView registerThreadInteractionsDatabaseView];
[TSDatabaseView registerThreadIncomingMessagesDatabaseView];
[TSDatabaseView registerThreadOutgoingMessagesDatabaseView];
[TSDatabaseView registerUnreadDatabaseView];
[TSDatabaseView registerUnseenDatabaseView];
[TSDatabaseView registerDynamicMessagesDatabaseView];
[TSDatabaseView registerSafetyNumberChangeDatabaseView];
[self.database registerExtension:[TSDatabaseSecondaryIndexes registerTimeStampIndex] withName:@"idx"];
// Run the blocking migrations.
//
// These need to run _before_ the async registered database views or
// they will block on them, which (in the upgrade case) can block
// return of appDidFinishLaunching... which in term can cause the
// app to crash on launch.
safeBlockingMigrationsBlock();
// Asynchronously register other extensions.
//
// All sync registrations must be done before all async registrations,
// or the sync registrations will block on the async registrations.
[TSDatabaseView asyncRegisterUnseenDatabaseView];
[TSDatabaseView asyncRegisterThreadOutgoingMessagesDatabaseView];
[TSDatabaseView asyncRegisterThreadSpecialMessagesDatabaseView];
// Register extensions which aren't essential for rendering threads async
[[OWSIncomingMessageFinder new] asyncRegisterExtension];
[TSDatabaseView asyncRegisterSecondaryDevicesDatabaseView];
[OWSReadReceipt asyncRegisterIndexOnSenderIdAndTimestampWithDatabase:self.database];
OWSDisappearingMessagesFinder *finder = [[OWSDisappearingMessagesFinder alloc] initWithStorageManager:self];
[finder asyncRegisterDatabaseExtensions];
[OWSDisappearingMessagesFinder asyncRegisterDatabaseExtensions:self];
OWSFailedMessagesJob *failedMessagesJob = [[OWSFailedMessagesJob alloc] initWithStorageManager:self];
[failedMessagesJob asyncRegisterDatabaseExtensions];
OWSFailedAttachmentDownloadsJob *failedAttachmentDownloadsMessagesJob =
[[OWSFailedAttachmentDownloadsJob alloc] initWithStorageManager:self];
[failedAttachmentDownloadsMessagesJob asyncRegisterDatabaseExtensions];
// NOTE: [TSDatabaseView asyncRegistrationCompletion] ensures that
// kNSNotificationName_DatabaseViewRegistrationComplete is not fired until all
// of the async registrations are complete.
[TSDatabaseView asyncRegistrationCompletion];
}
- (void)protectSignalFiles {
@ -373,19 +389,19 @@ static NSString *keychainDBPassAccount = @"TSDatabasePass";
#pragma mark - convenience methods
- (void)purgeCollection:(NSString *)collection {
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[transaction removeAllObjectsInCollection:collection];
[self.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[transaction removeAllObjectsInCollection:collection];
}];
}
- (void)setObject:(id)object forKey:(NSString *)key inCollection:(NSString *)collection {
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[transaction setObject:object forKey:key inCollection:collection];
[self.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[transaction setObject:object forKey:key inCollection:collection];
}];
}
- (void)removeObjectForKey:(NSString *)string inCollection:(NSString *)collection {
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[self.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[transaction removeObjectForKey:string inCollection:collection];
}];
}
@ -393,8 +409,8 @@ static NSString *keychainDBPassAccount = @"TSDatabasePass";
- (id)objectForKey:(NSString *)key inCollection:(NSString *)collection {
__block NSString *object;
[self.dbConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
object = [transaction objectForKey:key inCollection:collection];
[self.dbReadConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
object = [transaction objectForKey:key inCollection:collection];
}];
return object;
@ -404,8 +420,8 @@ static NSString *keychainDBPassAccount = @"TSDatabasePass";
{
__block NSDictionary *object;
[self.dbConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
object = [transaction objectForKey:key inCollection:collection];
[self.dbReadConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
object = [transaction objectForKey:key inCollection:collection];
}];
return object;
@ -464,7 +480,7 @@ static NSString *keychainDBPassAccount = @"TSDatabasePass";
- (int)incrementIntForKey:(NSString *)key inCollection:(NSString *)collection
{
__block int value = 0;
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[self.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
value = [[transaction objectForKey:key inCollection:collection] intValue];
value++;
[transaction setObject:@(value) forKey:key inCollection:collection];
@ -488,11 +504,11 @@ static NSString *keychainDBPassAccount = @"TSDatabasePass";
}
- (void)deleteThreadsAndMessages {
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[transaction removeAllObjectsInCollection:[TSThread collection]];
[transaction removeAllObjectsInCollection:[SignalRecipient collection]];
[transaction removeAllObjectsInCollection:[TSInteraction collection]];
[transaction removeAllObjectsInCollection:[TSAttachment collection]];
[self.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[transaction removeAllObjectsInCollection:[TSThread collection]];
[transaction removeAllObjectsInCollection:[SignalRecipient collection]];
[transaction removeAllObjectsInCollection:[TSInteraction collection]];
[transaction removeAllObjectsInCollection:[TSAttachment collection]];
}];
[TSAttachmentStream deleteAttachments];
}
@ -514,7 +530,8 @@ static NSString *keychainDBPassAccount = @"TSDatabasePass";
- (void)resetSignalStorage
{
self.database = nil;
_dbConnection = nil;
_dbReadConnection = nil;
_dbReadWriteConnection = nil;
[self deletePasswordFromKeychain];

View File

@ -58,10 +58,12 @@
usingBlock:(void (^)(id object, BOOL *stop))block;
/**
* @return A shared database connection.
* @return Shared database connections for reading and writing.
*/
- (YapDatabaseConnection *)dbConnection;
+ (YapDatabaseConnection *)dbConnection;
- (YapDatabaseConnection *)dbReadConnection;
+ (YapDatabaseConnection *)dbReadConnection;
- (YapDatabaseConnection *)dbReadWriteConnection;
+ (YapDatabaseConnection *)dbReadWriteConnection;
- (TSStorageManager *)storageManager;
+ (TSStorageManager *)storageManager;

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 "TSYapDatabaseObject.h"
#import "TSStorageManager.h"
@ -31,7 +32,7 @@
- (void)save
{
[[self dbConnection] readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[[self dbReadWriteConnection] readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[self saveWithTransaction:transaction];
}];
}
@ -43,7 +44,7 @@
- (void)touch
{
[[self dbConnection] readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[[self dbReadWriteConnection] readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[self touchWithTransaction:transaction];
}];
}
@ -55,14 +56,19 @@
- (void)remove
{
[[self dbConnection] readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[[self dbReadWriteConnection] readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[self removeWithTransaction:transaction];
}];
}
- (YapDatabaseConnection *)dbConnection
- (YapDatabaseConnection *)dbReadConnection
{
return [[self class] dbConnection];
return [[self class] dbReadConnection];
}
- (YapDatabaseConnection *)dbReadWriteConnection
{
return [[self class] dbReadWriteConnection];
}
- (TSStorageManager *)storageManager
@ -72,9 +78,26 @@
#pragma mark Class Methods
+ (YapDatabaseConnection *)dbConnection
+ (YapDatabaseConnection *)dbReadConnection
{
return [self storageManager].dbConnection;
// We use TSYapDatabaseObject's dbReadWriteConnection (not TSStorageManager's
// dbReadConnection) for consistency, since we tend to [TSYapDatabaseObject
// save] and want to write to the same connection we read from. To get true
// consistency, we'd want to update entities by reading & writing from within
// the same transaction, but that'll be a big refactor.
return self.dbReadWriteConnection;
}
+ (YapDatabaseConnection *)dbReadWriteConnection
{
// Use a dedicated connection for model reads & writes.
static YapDatabaseConnection *dbReadWriteConnection = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
dbReadWriteConnection = [self storageManager].newDatabaseConnection;
});
return dbReadWriteConnection;
}
+ (TSStorageManager *)storageManager
@ -90,7 +113,7 @@
+ (NSUInteger)numberOfKeysInCollection
{
__block NSUInteger count;
[[self dbConnection] readWithBlock:^(YapDatabaseReadTransaction *transaction) {
[[self dbReadConnection] readWithBlock:^(YapDatabaseReadTransaction *transaction) {
count = [self numberOfKeysInCollectionWithTransaction:transaction];
}];
return count;
@ -112,7 +135,7 @@
+ (void)enumerateCollectionObjectsUsingBlock:(void (^)(id object, BOOL *stop))block
{
[[self dbConnection] readWithBlock:^(YapDatabaseReadTransaction *transaction) {
[[self dbReadConnection] readWithBlock:^(YapDatabaseReadTransaction *transaction) {
[self enumerateCollectionObjectsWithTransaction:transaction usingBlock:block];
}];
}
@ -131,7 +154,7 @@
+ (void)removeAllObjectsInCollection
{
[[self dbConnection] readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[[self dbReadWriteConnection] readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[transaction removeAllObjectsInCollection:[self collection]];
}];
}
@ -144,7 +167,7 @@
+ (instancetype)fetchObjectWithUniqueID:(NSString *)uniqueID
{
__block id object;
[[self dbConnection] readWithBlock:^(YapDatabaseReadTransaction *transaction) {
[[self dbReadConnection] readWithBlock:^(YapDatabaseReadTransaction *transaction) {
object = [transaction objectForKey:uniqueID inCollection:[self collection]];
}];
return object;

View File

@ -8,15 +8,13 @@ NS_ASSUME_NONNULL_BEGIN
@class OWSMessageSender;
@protocol NotificationsProtocol;
@protocol OWSCallMessageHandler;
@protocol TSPreferences;
@interface TextSecureKitEnv : NSObject
- (instancetype)initWithCallMessageHandler:(id<OWSCallMessageHandler>)callMessageHandler
contactsManager:(id<ContactsManagerProtocol>)contactsManager
messageSender:(OWSMessageSender *)messageSender
notificationsManager:(id<NotificationsProtocol>)notificationsManager
preferences:(id<TSPreferences>)preferences NS_DESIGNATED_INITIALIZER;
notificationsManager:(id<NotificationsProtocol>)notificationsManager NS_DESIGNATED_INITIALIZER;
- (instancetype)init NS_UNAVAILABLE;
@ -27,7 +25,6 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, readonly) id<ContactsManagerProtocol> contactsManager;
@property (nonatomic, readonly) OWSMessageSender *messageSender;
@property (nonatomic, readonly) id<NotificationsProtocol> notificationsManager;
@property (nonatomic, readonly) id<TSPreferences> preferences;
@end

View File

@ -10,17 +10,13 @@ static TextSecureKitEnv *TextSecureKitEnvSharedInstance;
@implementation TextSecureKitEnv
@synthesize callMessageHandler = _callMessageHandler,
contactsManager = _contactsManager,
messageSender = _messageSender,
notificationsManager = _notificationsManager,
preferences = _preferences;
@synthesize callMessageHandler = _callMessageHandler, contactsManager = _contactsManager,
messageSender = _messageSender, notificationsManager = _notificationsManager;
- (instancetype)initWithCallMessageHandler:(id<OWSCallMessageHandler>)callMessageHandler
contactsManager:(id<ContactsManagerProtocol>)contactsManager
messageSender:(OWSMessageSender *)messageSender
notificationsManager:(id<NotificationsProtocol>)notificationsManager
preferences:(nonnull id<TSPreferences>)preferences
{
self = [super init];
if (!self) {
@ -31,7 +27,6 @@ static TextSecureKitEnv *TextSecureKitEnvSharedInstance;
_contactsManager = contactsManager;
_messageSender = messageSender;
_notificationsManager = notificationsManager;
_preferences = preferences;
return self;
}
@ -76,12 +71,6 @@ static TextSecureKitEnv *TextSecureKitEnvSharedInstance;
return _notificationsManager;
}
- (id<TSPreferences>)preferences
{
NSAssert(_preferences, @"Trying to access preferences before it's set.");
return _preferences;
}
@end
NS_ASSUME_NONNULL_END

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