• OWSFormat.localizedDecimalString(from:) would convert Int into
a localized string using `decimal` style (preferred style for displaying
in the UI).
• OWSFormat.localizedDurationString(from:) would convert a time interval
into localized string like "HH:mm:ss", using current locale.
We already have this sort of check on the Refresh/Rotate jobs, but for
the Create job you should always have an identity key at the time
creation is requested. That's not true when mixing a new linked device
with an old primary, though. Add some defensive checks so the linked
device won't generate an identity key and try to upload it; it should
skip the job instead and ask the primary for its identity key later.
Our envelopes go through a lot of transformations during processing:
0. If using REST, JSON delivered by the server is converted to
SSKProtoEnvelope (MessageFetcherJob.buildEnvelope(messageDict:))
1a. If using REST, envelopes are serialized and enqueued for individual
jobs (MessageFetcherJob.fetchMessagesViaRest())
1b. If using the web socket, the message is deserialized from protobuf
(MessageProcessor.processEncryptedEnvelopeData(...))
2. If the envelope uses sealed sender, the sender is written into
a new proto and reserialized
(OWSMessageDecryptor.decryptUnidentifiedSenderEnvelope(...))
3. The serialized envelope is deserialized *again* for preprocessing,
GV2 discard mode checking, and (potentially) actual processing.
(MessageProcessor.processEnvelope(_:transaction:))
4. If the envelope is delayed, it'll be deserialized when it's finally
processed
(IncomingGroupsV2MessageJob.envelope)
This commit improves the situation by
- skipping the serialization in step #1a
- skipping the serialization in step #2
- reusing the SSKProtoEnvelope from decryption in message processing,
avoiding the deserialization in step #3
- potentially serializing in step #4 if there's no up-to-date
serialized data (either because the envelope was modified, or
because it came from REST and we never had it in the first place)
Note that IncomingGroupsV2MessageJob is persisted across transactions
in the database, so it needs the envelope to be serialized.
We already have the deserialized envelope at the point where we're
first checking, but also we don't even use any information from it
besides the source address.
This commit also stops checking whether a group context has a revision
once we've created a job; I think that's reasonable.
They aren't needed; the code only checked that the envelope had some
content, any content.
No functionality change in practice: all call sites already had
plaintext data alongside the envelope, where that data was assumed to
come from the envelope, proving it had content.
No one is using the unreadCount* APIs anymore *except*
updateApplicationBadgeCount, so we can inline them and then move that
function to an existing class.
This maintains much of the spirit of the original implementation while
being almost a total rewrite of its implementation. Before,
BlockingManager would load it's entire state into memory and only touch
the database if there's a compelling reason.
This new implementation maintains this behavior. Block state is always
kept in memory (there's certainly a more efficient way to do this but
that's out-of-scope for this change). The difference is now every time
the cached state is accessed, BlockingManager consults a change tag
stored in the database.
By requiring a transaction to access block state, we can make sure that
our block state remains up-to-date in cases where the database is
changed by another process..
Pre-keys are refreshed at regular (throttled) intervals, but the
"refresh" operation will only actually upload new pre-keys if the
server says we're running low. In addition, after the "refresh",
there's a "rotation" operation for the signed pre-key if the current
one is more than two days old. This only runs if the refresh was
skipped, since uploading new one-time pre-keys is already an
opportunity to rotate the signed pre-key (and it's worth doing so,
since pre-keys running low can indicate high traffic).
None of this has changed. However, we can now do this for both ACI and
PNI pre-keys. This reworked +[TSPreKeyManager
checkPreKeysWithShouldThrottle:] quite a bit to declare the proper
operation dependencies, which isn't *strictly* necessary because the
queue they're executed on is already limited to a
maxConcurrentOperationCount of 1. We could remove that at this point
with proper use of "barrier" operations, but since all the operations
are hitting the server and account updates have to be serialized on
*that* end, there doesn't seem to be much of a benefit.
After a certain number of failures over a certain number of days, the
app will refuse to send messages until a new signed pre-key can be
uploaded. This should apply to both ACI and PNI pre-key upload
failures. (If we wanted to distinguish them, we could, but this is
mostly a fallback mechanism. Normal pre-key refreshing will come
soon and will not tie the two together.)
...from TSPreKeyManager. This will make it easier to track failures
for ACI and PNI pre-keys separately, and removes some indirection and
possible weird states.
At launch time, if a primary device doesn't have a PNI identity key,
it should generate one immediately, as well as pre-keys. If a linked
device doesn't have a PNI identity key, it messages the primary to ask
for one; when it gets a response it generates its own pre-keys.
...so it can use an existing one when there is one, which in practice
there always is because we never refresh one-time pre-keys without
rotating our signed pre-key too.