Compare commits
185 Commits
mkirk/bloc
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5c3b30104a | ||
|
|
1499840de5 | ||
|
|
d98c07ecff | ||
|
|
493aaca242 | ||
|
|
e20d63240b | ||
|
|
faeb7100b9 | ||
|
|
fe0f01daec | ||
|
|
c255504959 | ||
|
|
640ec13b2e | ||
|
|
6fa3fac4ae | ||
|
|
7a50d6b996 | ||
|
|
762f915179 | ||
|
|
96da091e9b | ||
|
|
9115a1f973 | ||
|
|
57b90e1462 | ||
|
|
e0f805f80f | ||
|
|
ab6c1fb3b8 | ||
|
|
9d3175b5cf | ||
|
|
957979585c | ||
|
|
8361ffb818 | ||
|
|
ea681a61e2 | ||
|
|
c1e1247eff | ||
|
|
92276157dc | ||
|
|
2216c2d413 | ||
|
|
a23b4871e0 | ||
|
|
72e893d5f7 | ||
|
|
e24f18320d | ||
|
|
58fb86b8e0 | ||
|
|
065017cafd | ||
|
|
daae31d30e | ||
|
|
8714a8f37c | ||
|
|
deff1fa4e7 | ||
|
|
1fc5f77286 | ||
|
|
fd625dff50 | ||
|
|
89f86c4fd2 | ||
|
|
04ef06ce95 | ||
|
|
f59779c118 | ||
|
|
85fe68d3c4 | ||
|
|
d6c5497f64 | ||
|
|
0b33ef6161 | ||
|
|
acf31db4b3 | ||
|
|
a8ea2428c6 | ||
|
|
605db6b788 | ||
|
|
1d71ca5e50 | ||
|
|
8f9af85cca | ||
|
|
1b9aae2ea6 | ||
|
|
e652dff4b4 | ||
|
|
edf5852e82 | ||
|
|
1fb9fa79df | ||
|
|
13c5bdb8c5 | ||
|
|
2addb9e81d | ||
|
|
c711b4a66d | ||
|
|
b2f9abbc7c | ||
|
|
14c64239a3 | ||
|
|
e9219743ff | ||
|
|
793a7449b0 | ||
|
|
5da7dc1fd3 | ||
|
|
07e0291377 | ||
|
|
e8e7b6bcba | ||
|
|
8fda18a8e3 | ||
|
|
5a2169fa78 | ||
|
|
4c22f371a9 | ||
|
|
6dea4c9fef | ||
|
|
a5660f4db4 | ||
|
|
d927cba5ce | ||
|
|
badaa54327 | ||
|
|
12bfae10ed | ||
|
|
35ee92f38f | ||
|
|
99cd8fc27d | ||
|
|
f653bc36a8 | ||
|
|
48b3f498a9 | ||
|
|
f526a372c9 | ||
|
|
6566ea694c | ||
|
|
8b04e2a880 | ||
|
|
ed369436fb | ||
|
|
b946badd97 | ||
|
|
4609c508ec | ||
|
|
30961cf2a5 | ||
|
|
4eacfe768f | ||
|
|
d8b34f6302 | ||
|
|
2315ab79de | ||
|
|
e86e175ceb | ||
|
|
beb4ed71e7 | ||
|
|
cf65cc3be5 | ||
|
|
ed249840c6 | ||
|
|
27e5c836b7 | ||
|
|
7af758bc67 | ||
|
|
07bec72f61 | ||
|
|
5110c5892d | ||
|
|
e746c6b7ec | ||
|
|
2059bb4966 | ||
|
|
742e0d3c97 | ||
|
|
c0cb153f29 | ||
|
|
5d7c012b5c | ||
|
|
d80e42e0a0 | ||
|
|
18e6a1b1cb | ||
|
|
a9bac8bce7 | ||
|
|
498c0ef681 | ||
|
|
44e1f4a14a | ||
|
|
26e6aab071 | ||
|
|
8ef118f5da | ||
|
|
b9f9b6a0c3 | ||
|
|
32e4eb2a42 | ||
|
|
7379e6a679 | ||
|
|
470cee0e11 | ||
|
|
09513fc1c3 | ||
|
|
22109d719b | ||
|
|
0f96341059 | ||
|
|
bbc7c44c93 | ||
|
|
96dc0e4fdb | ||
|
|
d53db17447 | ||
|
|
f999c4abb5 | ||
|
|
0c503c379a | ||
|
|
d8199a444f | ||
|
|
bf07a8401e | ||
|
|
1f1410ffae | ||
|
|
3598cc18fa | ||
|
|
e1439a54dc | ||
|
|
857fb535d9 | ||
|
|
26836a5725 | ||
|
|
7052b97c72 | ||
|
|
42cef65de4 | ||
|
|
9168512058 | ||
|
|
dfab38b941 | ||
|
|
5d1a33b5fc | ||
|
|
4a028d32b1 | ||
|
|
dcbb72d851 | ||
|
|
5e50711412 | ||
|
|
dc9a2253d5 | ||
|
|
c5c4643782 | ||
|
|
49f1180431 | ||
|
|
c29549c213 | ||
|
|
13a119b4b6 | ||
|
|
f324327880 | ||
|
|
1052915c1b | ||
|
|
12aed7a4a1 | ||
|
|
fbc1bad881 | ||
|
|
8bd0281254 | ||
|
|
0df5ea3ee5 | ||
|
|
cfd9b84e65 | ||
|
|
1db9c8b344 | ||
|
|
f2f654af19 | ||
|
|
ca04d912db | ||
|
|
841271dc6c | ||
|
|
fdd172bda3 | ||
|
|
eed6377910 | ||
|
|
fba94754a6 | ||
|
|
7fc73e2ba1 | ||
|
|
94fb7d50e8 | ||
|
|
dfd0f8073d | ||
|
|
edc6578b9b | ||
|
|
0e11566824 | ||
|
|
e27e55ca99 | ||
|
|
5acb209427 | ||
|
|
3c2835d318 | ||
|
|
07ac17fd39 | ||
|
|
90d671924f | ||
|
|
89b1da7666 | ||
|
|
b2425ddf9a | ||
|
|
33df1fb6c0 | ||
|
|
90087c34fa | ||
|
|
435f13f2ff | ||
|
|
0c2a1ff89f | ||
|
|
d475c58348 | ||
|
|
81d36a4652 | ||
|
|
4a73ab2851 | ||
|
|
1603e8bfbf | ||
|
|
e408250ef2 | ||
|
|
167961a45f | ||
|
|
12b5c2c26a | ||
|
|
8a26883876 | ||
|
|
fcc17ca869 | ||
|
|
ecf7ef61f4 | ||
|
|
d9acaced2a | ||
|
|
f1d85c2a95 | ||
|
|
559c389f9f | ||
|
|
702f7677c6 | ||
|
|
0fcd0afd17 | ||
|
|
9154cc46f0 | ||
|
|
43d1aa49dc | ||
|
|
69e40cdf1c | ||
|
|
72fb925af5 | ||
|
|
f63c85f5d5 | ||
|
|
0ad794dfd9 | ||
|
|
ab378f79b4 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -17,6 +17,7 @@ DerivedData
|
||||
*.hmap
|
||||
*.ipa
|
||||
*.xcuserstate
|
||||
xcshareddata
|
||||
|
||||
Pods/
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 */
|
||||
|
||||
@ -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
5
Gemfile
Normal file
@ -0,0 +1,5 @@
|
||||
source 'https://rubygems.org'
|
||||
|
||||
gem 'fastlane'
|
||||
gem 'cocoapods'
|
||||
|
||||
189
Gemfile.lock
Normal file
189
Gemfile.lock
Normal 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
|
||||
7
Makefile
7
Makefile
@ -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) \
|
||||
|
||||
24
README.md
24
README.md
@ -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
|
||||
|
||||
@ -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
1
fastlane/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
test_output
|
||||
8
fastlane/Scanfile
Normal file
8
fastlane/Scanfile
Normal 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"]
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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];
|
||||
|
||||
@ -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];
|
||||
}
|
||||
|
||||
@ -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]);
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -72,7 +72,6 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
- (BOOL)hasSafetyNumbers;
|
||||
|
||||
- (void)markAllAsRead;
|
||||
- (void)markAllAsReadWithTransaction:(YapDatabaseReadWriteTransaction *)transaction;
|
||||
|
||||
/**
|
||||
|
||||
@ -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];
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)]];
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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 =
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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];
|
||||
|
||||
24
src/Devices/OWSVerificationStateSyncMessage.h
Normal file
24
src/Devices/OWSVerificationStateSyncMessage.h
Normal 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
|
||||
101
src/Devices/OWSVerificationStateSyncMessage.m
Normal file
101
src/Devices/OWSVerificationStateSyncMessage.m
Normal 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
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
}];
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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];
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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];
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
53
src/Messages/OWSIdentityManager.h
Normal file
53
src/Messages/OWSIdentityManager.h
Normal 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
|
||||
779
src/Messages/OWSIdentityManager.m
Normal file
779
src/Messages/OWSIdentityManager.m
Normal 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
|
||||
@ -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];
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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;
|
||||
|
||||
/**
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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.
|
||||
|
||||
19
src/Messages/OWSOutgoingNullMessage.h
Normal file
19
src/Messages/OWSOutgoingNullMessage.h
Normal 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
|
||||
76
src/Messages/OWSOutgoingNullMessage.m
Normal file
76
src/Messages/OWSOutgoingNullMessage.m
Normal 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
|
||||
@ -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
|
||||
|
||||
@ -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
@ -16,6 +16,8 @@ typedef enum {
|
||||
// These call types are used until the call connects.
|
||||
RPRecentCallTypeOutgoingIncomplete,
|
||||
RPRecentCallTypeIncomingIncomplete,
|
||||
RPRecentCallTypeMissedBecauseOfChangedIdentity,
|
||||
RPRecentCallTypeIncomingDeclined
|
||||
} RPRecentCallType;
|
||||
|
||||
@interface TSCall : TSInteraction <OWSReadTracking>
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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, ^{
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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]);
|
||||
|
||||
@ -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
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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];
|
||||
|
||||
@ -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
|
||||
@ -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
|
||||
@ -7,6 +7,8 @@
|
||||
|
||||
@interface TSStorageManager (SessionStore) <SessionStore>
|
||||
|
||||
- (void)archiveAllSessionsForContact:(NSString *)contactIdentifier;
|
||||
|
||||
#pragma mark - debug
|
||||
|
||||
- (void)printAllSessions;
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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];
|
||||
|
||||
}];
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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];
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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
Loading…
Reference in New Issue
Block a user