That fuck shit the fascists are using
at master 397 lines 17 kB view raw
1package org.tm.archive.database 2 3import android.content.ContentValues 4import android.content.Context 5import android.database.sqlite.SQLiteConstraintException 6import org.signal.core.util.CursorUtil 7import org.signal.core.util.SqlUtil 8import org.signal.core.util.delete 9import org.signal.core.util.logging.Log 10import org.signal.core.util.readToList 11import org.signal.core.util.requireBoolean 12import org.signal.core.util.requireLong 13import org.signal.core.util.toInt 14import org.tm.archive.database.model.MessageId 15import org.tm.archive.database.model.MessageLogEntry 16import org.tm.archive.recipients.Recipient 17import org.tm.archive.recipients.RecipientId 18import org.tm.archive.util.FeatureFlags 19import org.tm.archive.util.RecipientAccessList 20import org.whispersystems.signalservice.api.crypto.ContentHint 21import org.whispersystems.signalservice.api.messages.SendMessageResult 22import org.whispersystems.signalservice.internal.push.Content 23 24/** 25 * Stores a rolling buffer of all outgoing messages. Used for the retry logic required for sender key. 26 * 27 * General note: This class is actually three tables: 28 * - one to store the entry 29 * - one to store all the devices that were sent it, and 30 * - one to store the set of related messages. 31 * 32 * The general lifecycle of entries in the store goes something like this: 33 * - Upon sending a message, put an entry in the 'payload table', an entry for each recipient you sent it to in the 'recipient table', and an entry for each 34 * related message in the 'message table' 35 * - Whenever you get a delivery receipt, delete the entries in the 'recipient table' 36 * - Whenever there's no more records in the 'recipient table' for a given message, delete the entry in the 'message table' 37 * - Whenever you delete a message, delete the relevant entries from the 'payload table' 38 * - Whenever you read an entry from the table, first trim off all the entries that are too old 39 * 40 * Because of all of this, you can be sure that if an entry is in this store, it's safe to resend to someone upon request 41 * 42 * Worth noting that we use triggers + foreign keys to make sure entries in this table are properly cleaned up. Triggers for when you delete a message, and 43 * cascading delete foreign keys between these three tables. 44 * 45 * Performance considerations: 46 * - The most common operations by far are: 47 * - Inserting into the table 48 * - Deleting a recipient (in response to a delivery receipt) 49 * - We should also optimize for when we delete messages from the sms/mms tables, since you can delete a bunch at once 50 * - We *don't* really need to optimize for retrieval, since that happens very infrequently. In particular, we don't want to slow down inserts in order to 51 * improve retrieval time. That means we shouldn't be adding indexes that optimize for retrieval. 52 */ 53class MessageSendLogTables constructor(context: Context?, databaseHelper: SignalDatabase?) : DatabaseTable(context, databaseHelper), RecipientIdDatabaseReference { 54 55 companion object { 56 private val TAG = Log.tag(MessageSendLogTables::class.java) 57 58 @JvmField 59 val CREATE_TABLE: Array<String> = arrayOf(MslPayloadTable.CREATE_TABLE, MslRecipientTable.CREATE_TABLE, MslMessageTable.CREATE_TABLE) 60 61 @JvmField 62 val CREATE_INDEXES: Array<String> = MslPayloadTable.CREATE_INDEXES + MslRecipientTable.CREATE_INDEXES + MslMessageTable.CREATE_INDEXES 63 64 @JvmField 65 val CREATE_TRIGGERS: Array<String> = MslPayloadTable.CREATE_TRIGGERS 66 } 67 68 private object MslPayloadTable { 69 const val TABLE_NAME = "msl_payload" 70 71 const val ID = "_id" 72 const val DATE_SENT = "date_sent" 73 const val CONTENT = "content" 74 const val CONTENT_HINT = "content_hint" 75 const val URGENT = "urgent" 76 77 const val CREATE_TABLE = """ 78 CREATE TABLE $TABLE_NAME ( 79 $ID INTEGER PRIMARY KEY, 80 $DATE_SENT INTEGER NOT NULL, 81 $CONTENT BLOB NOT NULL, 82 $CONTENT_HINT INTEGER NOT NULL, 83 $URGENT INTEGER NOT NULL DEFAULT 1 84 ) 85 """ 86 87 /** Created for [deleteEntriesForRecipient] */ 88 val CREATE_INDEXES = arrayOf( 89 "CREATE INDEX msl_payload_date_sent_index ON $TABLE_NAME ($DATE_SENT)" 90 ) 91 92 val CREATE_TRIGGERS = arrayOf( 93 """ 94 CREATE TRIGGER msl_message_delete AFTER DELETE ON ${MessageTable.TABLE_NAME} 95 BEGIN 96 DELETE FROM $TABLE_NAME WHERE $ID IN (SELECT ${MslMessageTable.PAYLOAD_ID} FROM ${MslMessageTable.TABLE_NAME} WHERE ${MslMessageTable.MESSAGE_ID} = old.${MessageTable.ID}); 97 END 98 """, 99 """ 100 CREATE TRIGGER msl_attachment_delete AFTER DELETE ON ${AttachmentTable.TABLE_NAME} 101 BEGIN 102 DELETE FROM $TABLE_NAME WHERE $ID IN (SELECT ${MslMessageTable.PAYLOAD_ID} FROM ${MslMessageTable.TABLE_NAME} WHERE ${MslMessageTable.TABLE_NAME}.${MslMessageTable.MESSAGE_ID} = old.${AttachmentTable.MESSAGE_ID}); 103 END 104 """ 105 ) 106 } 107 108 private object MslRecipientTable { 109 const val TABLE_NAME = "msl_recipient" 110 111 const val ID = "_id" 112 const val PAYLOAD_ID = "payload_id" 113 const val RECIPIENT_ID = "recipient_id" 114 const val DEVICE = "device" 115 116 const val CREATE_TABLE = """ 117 CREATE TABLE $TABLE_NAME ( 118 $ID INTEGER PRIMARY KEY, 119 $PAYLOAD_ID INTEGER NOT NULL REFERENCES ${MslPayloadTable.TABLE_NAME} (${MslPayloadTable.ID}) ON DELETE CASCADE, 120 $RECIPIENT_ID INTEGER NOT NULL, 121 $DEVICE INTEGER NOT NULL 122 ) 123 """ 124 125 /** Created for [deleteEntriesForRecipient] */ 126 val CREATE_INDEXES = arrayOf( 127 "CREATE INDEX msl_recipient_recipient_index ON $TABLE_NAME ($RECIPIENT_ID, $DEVICE, $PAYLOAD_ID)", 128 "CREATE INDEX msl_recipient_payload_index ON $TABLE_NAME ($PAYLOAD_ID)" 129 ) 130 } 131 132 private object MslMessageTable { 133 const val TABLE_NAME = "msl_message" 134 135 const val ID = "_id" 136 const val PAYLOAD_ID = "payload_id" 137 const val MESSAGE_ID = "message_id" 138 139 const val CREATE_TABLE = """ 140 CREATE TABLE $TABLE_NAME ( 141 $ID INTEGER PRIMARY KEY, 142 $PAYLOAD_ID INTEGER NOT NULL REFERENCES ${MslPayloadTable.TABLE_NAME} (${MslPayloadTable.ID}) ON DELETE CASCADE, 143 $MESSAGE_ID INTEGER NOT NULL 144 ) 145 """ 146 147 /** Created for [MslPayloadTable.CREATE_TRIGGERS] and [deleteAllRelatedToMessage] */ 148 val CREATE_INDEXES = arrayOf( 149 "CREATE INDEX msl_message_message_index ON $TABLE_NAME ($MESSAGE_ID, $PAYLOAD_ID)", 150 "CREATE INDEX msl_message_payload_index ON $TABLE_NAME ($PAYLOAD_ID)" 151 ) 152 } 153 154 /** @return The ID of the inserted entry, or -1 if none was inserted. Can be used with [addRecipientToExistingEntryIfPossible] */ 155 fun insertIfPossible(recipientId: RecipientId, sentTimestamp: Long, sendMessageResult: SendMessageResult, contentHint: ContentHint, messageId: MessageId, urgent: Boolean): Long { 156 if (!FeatureFlags.retryReceipts()) return -1 157 158 if (sendMessageResult.isSuccess && sendMessageResult.success.content.isPresent) { 159 val recipientDevice = listOf(RecipientDevice(recipientId, sendMessageResult.success.devices)) 160 return insert(recipientDevice, sentTimestamp, sendMessageResult.success.content.get(), contentHint, listOf(messageId), urgent) 161 } 162 163 return -1 164 } 165 166 /** @return The ID of the inserted entry, or -1 if none was inserted. Can be used with [addRecipientToExistingEntryIfPossible] */ 167 fun insertIfPossible(recipientId: RecipientId, sentTimestamp: Long, sendMessageResult: SendMessageResult, contentHint: ContentHint, messageIds: List<MessageId>, urgent: Boolean): Long { 168 if (!FeatureFlags.retryReceipts()) return -1 169 170 if (sendMessageResult.isSuccess && sendMessageResult.success.content.isPresent) { 171 val recipientDevice = listOf(RecipientDevice(recipientId, sendMessageResult.success.devices)) 172 return insert(recipientDevice, sentTimestamp, sendMessageResult.success.content.get(), contentHint, messageIds, urgent) 173 } 174 175 return -1 176 } 177 178 /** @return The ID of the inserted entry, or -1 if none was inserted. Can be used with [addRecipientToExistingEntryIfPossible] */ 179 fun insertIfPossible(sentTimestamp: Long, possibleRecipients: List<Recipient>, results: List<SendMessageResult>, contentHint: ContentHint, messageId: MessageId, urgent: Boolean): Long { 180 if (!FeatureFlags.retryReceipts()) return -1 181 182 val accessList = RecipientAccessList(possibleRecipients) 183 184 val recipientDevices: List<RecipientDevice> = results 185 .filter { it.isSuccess && it.success.content.isPresent } 186 .map { result -> 187 val recipient: Recipient = accessList.requireByAddress(result.address) 188 RecipientDevice(recipient.id, result.success.devices) 189 } 190 191 if (recipientDevices.isEmpty()) { 192 return -1 193 } 194 195 val content: Content = results.first { it.isSuccess && it.success.content.isPresent }.success.content.get() 196 197 return insert(recipientDevices, sentTimestamp, content, contentHint, listOf(messageId), urgent) 198 } 199 200 fun addRecipientToExistingEntryIfPossible(payloadId: Long, recipientId: RecipientId, sentTimestamp: Long, sendMessageResult: SendMessageResult, contentHint: ContentHint, messageId: MessageId, urgent: Boolean): Long { 201 if (!FeatureFlags.retryReceipts()) return payloadId 202 203 if (sendMessageResult.isSuccess && sendMessageResult.success.content.isPresent) { 204 val db = databaseHelper.signalWritableDatabase 205 206 db.beginTransaction() 207 try { 208 sendMessageResult.success.devices.forEach { device -> 209 val recipientValues = ContentValues().apply { 210 put(MslRecipientTable.PAYLOAD_ID, payloadId) 211 put(MslRecipientTable.RECIPIENT_ID, recipientId.serialize()) 212 put(MslRecipientTable.DEVICE, device) 213 } 214 215 db.insert(MslRecipientTable.TABLE_NAME, null, recipientValues) 216 } 217 218 db.setTransactionSuccessful() 219 } catch (e: SQLiteConstraintException) { 220 Log.w(TAG, "Failed to append to existing entry. Creating a new one.") 221 val newPayloadId = insertIfPossible(recipientId, sentTimestamp, sendMessageResult, contentHint, messageId, urgent) 222 db.setTransactionSuccessful() 223 return newPayloadId 224 } finally { 225 db.endTransaction() 226 } 227 } 228 229 return payloadId 230 } 231 232 private fun insert(recipients: List<RecipientDevice>, dateSent: Long, content: Content, contentHint: ContentHint, messageIds: List<MessageId>, urgent: Boolean): Long { 233 val db = databaseHelper.signalWritableDatabase 234 235 db.beginTransaction() 236 try { 237 val payloadValues = ContentValues().apply { 238 put(MslPayloadTable.DATE_SENT, dateSent) 239 put(MslPayloadTable.CONTENT, content.encode()) 240 put(MslPayloadTable.CONTENT_HINT, contentHint.type) 241 put(MslPayloadTable.URGENT, urgent.toInt()) 242 } 243 244 val payloadId: Long = db.insert(MslPayloadTable.TABLE_NAME, null, payloadValues) 245 246 val recipientValues: MutableList<ContentValues> = mutableListOf() 247 recipients.forEach { recipientDevice -> 248 recipientDevice.devices.forEach { device -> 249 recipientValues += ContentValues().apply { 250 put(MslRecipientTable.PAYLOAD_ID, payloadId) 251 put(MslRecipientTable.RECIPIENT_ID, recipientDevice.recipientId.serialize()) 252 put(MslRecipientTable.DEVICE, device) 253 } 254 } 255 } 256 SqlUtil.buildBulkInsert(MslRecipientTable.TABLE_NAME, arrayOf(MslRecipientTable.PAYLOAD_ID, MslRecipientTable.RECIPIENT_ID, MslRecipientTable.DEVICE), recipientValues) 257 .forEach { query -> db.execSQL(query.where, query.whereArgs) } 258 259 val messageValues: MutableList<ContentValues> = mutableListOf() 260 messageIds.forEach { messageId -> 261 messageValues += ContentValues().apply { 262 put(MslMessageTable.PAYLOAD_ID, payloadId) 263 put(MslMessageTable.MESSAGE_ID, messageId.id) 264 } 265 } 266 SqlUtil.buildBulkInsert(MslMessageTable.TABLE_NAME, arrayOf(MslMessageTable.PAYLOAD_ID, MslMessageTable.MESSAGE_ID), messageValues) 267 .forEach { query -> db.execSQL(query.where, query.whereArgs) } 268 269 db.setTransactionSuccessful() 270 return payloadId 271 } finally { 272 db.endTransaction() 273 } 274 } 275 276 fun getLogEntry(recipientId: RecipientId, device: Int, dateSent: Long): MessageLogEntry? { 277 if (!FeatureFlags.retryReceipts()) return null 278 279 trimOldMessages(System.currentTimeMillis(), FeatureFlags.retryRespondMaxAge()) 280 281 val db = databaseHelper.signalReadableDatabase 282 val table = "${MslPayloadTable.TABLE_NAME} LEFT JOIN ${MslRecipientTable.TABLE_NAME} ON ${MslPayloadTable.TABLE_NAME}.${MslPayloadTable.ID} = ${MslRecipientTable.TABLE_NAME}.${MslRecipientTable.PAYLOAD_ID}" 283 val query = "${MslPayloadTable.DATE_SENT} = ? AND ${MslRecipientTable.RECIPIENT_ID} = ? AND ${MslRecipientTable.DEVICE} = ?" 284 val args = SqlUtil.buildArgs(dateSent, recipientId, device) 285 286 db.query(table, null, query, args, null, null, null).use { entryCursor -> 287 if (entryCursor.moveToFirst()) { 288 val payloadId = CursorUtil.requireLong(entryCursor, MslRecipientTable.PAYLOAD_ID) 289 290 db.query(MslMessageTable.TABLE_NAME, null, "${MslMessageTable.PAYLOAD_ID} = ?", SqlUtil.buildArgs(payloadId), null, null, null).use { messageCursor -> 291 val messageIds: MutableList<MessageId> = mutableListOf() 292 293 while (messageCursor.moveToNext()) { 294 messageIds.add( 295 MessageId( 296 id = CursorUtil.requireLong(messageCursor, MslMessageTable.MESSAGE_ID) 297 ) 298 ) 299 } 300 301 return MessageLogEntry( 302 recipientId = RecipientId.from(CursorUtil.requireLong(entryCursor, MslRecipientTable.RECIPIENT_ID)), 303 dateSent = CursorUtil.requireLong(entryCursor, MslPayloadTable.DATE_SENT), 304 content = Content.ADAPTER.decode(CursorUtil.requireBlob(entryCursor, MslPayloadTable.CONTENT)), 305 contentHint = ContentHint.fromType(CursorUtil.requireInt(entryCursor, MslPayloadTable.CONTENT_HINT)), 306 urgent = entryCursor.requireBoolean(MslPayloadTable.URGENT), 307 relatedMessages = messageIds 308 ) 309 } 310 } 311 } 312 313 return null 314 } 315 316 fun deleteAllRelatedToMessage(messageId: Long) { 317 val db = databaseHelper.signalWritableDatabase 318 val query = "${MslPayloadTable.ID} IN (SELECT ${MslMessageTable.PAYLOAD_ID} FROM ${MslMessageTable.TABLE_NAME} WHERE ${MslMessageTable.MESSAGE_ID} = ?)" 319 val args = SqlUtil.buildArgs(messageId) 320 321 db.delete(MslPayloadTable.TABLE_NAME, query, args) 322 } 323 324 fun deleteEntryForRecipient(dateSent: Long, recipientId: RecipientId, device: Int) { 325 deleteEntriesForRecipient(listOf(dateSent), recipientId, device) 326 } 327 328 fun deleteEntriesForRecipient(dateSent: List<Long>, recipientId: RecipientId, device: Int) { 329 val db = databaseHelper.signalWritableDatabase 330 db.beginTransaction() 331 try { 332 val query = """ 333 DELETE FROM ${MslRecipientTable.TABLE_NAME} WHERE 334 ${MslRecipientTable.RECIPIENT_ID} = ? AND 335 ${MslRecipientTable.DEVICE} = ? AND 336 ${MslRecipientTable.PAYLOAD_ID} IN ( 337 SELECT ${MslPayloadTable.ID} 338 FROM ${MslPayloadTable.TABLE_NAME} 339 WHERE ${MslPayloadTable.DATE_SENT} IN (${dateSent.joinToString(",")}) 340 ) 341 RETURNING ${MslRecipientTable.PAYLOAD_ID}""" 342 val args = SqlUtil.buildArgs(recipientId, device) 343 344 val payloadIds = db.rawQuery(query, args).readToList { 345 it.requireLong(MslRecipientTable.PAYLOAD_ID) 346 } 347 348 val queries = SqlUtil.buildCollectionQuery(MslPayloadTable.ID, payloadIds) 349 queries.forEach { 350 db.delete(MslPayloadTable.TABLE_NAME, "${it.where} AND ${MslPayloadTable.ID} NOT IN (SELECT ${MslRecipientTable.PAYLOAD_ID} FROM ${MslRecipientTable.TABLE_NAME})", it.whereArgs) 351 } 352 db.setTransactionSuccessful() 353 } finally { 354 db.endTransaction() 355 } 356 } 357 358 fun deleteAllForRecipient(recipientId: RecipientId) { 359 if (!FeatureFlags.retryReceipts()) return 360 361 writableDatabase 362 .delete(MslRecipientTable.TABLE_NAME) 363 .where("${MslRecipientTable.RECIPIENT_ID} = ?", recipientId) 364 .run() 365 366 writableDatabase 367 .delete(MslPayloadTable.TABLE_NAME) 368 .where("${MslPayloadTable.ID} NOT IN (SELECT ${MslRecipientTable.PAYLOAD_ID} FROM ${MslRecipientTable.TABLE_NAME})") 369 .run() 370 } 371 372 fun deleteAll() { 373 databaseHelper.signalWritableDatabase.delete(MslPayloadTable.TABLE_NAME, null, null) 374 } 375 376 fun trimOldMessages(currentTime: Long, maxAge: Long) { 377 val db = databaseHelper.signalWritableDatabase 378 val query = "${MslPayloadTable.DATE_SENT} < ?" 379 val args = SqlUtil.buildArgs(currentTime - maxAge) 380 381 db.delete(MslPayloadTable.TABLE_NAME, query, args) 382 } 383 384 override fun remapRecipient(oldRecipientId: RecipientId, newRecipientId: RecipientId) { 385 val values = ContentValues().apply { 386 put(MslRecipientTable.RECIPIENT_ID, newRecipientId.serialize()) 387 } 388 389 val db = databaseHelper.signalWritableDatabase 390 val query = "${MslRecipientTable.RECIPIENT_ID} = ?" 391 val args = SqlUtil.buildArgs(oldRecipientId.serialize()) 392 393 db.update(MslRecipientTable.TABLE_NAME, values, query, args) 394 } 395 396 private data class RecipientDevice(val recipientId: RecipientId, val devices: List<Int>) 397}