That fuck shit the fascists are using
at master 190 lines 6.2 kB view raw
1package org.tm.archive.database 2 3import android.content.Context 4import org.signal.core.util.delete 5import org.signal.core.util.deleteAll 6import org.signal.core.util.exists 7import org.signal.core.util.insertInto 8import org.signal.core.util.logging.Log 9import org.signal.core.util.readToList 10import org.signal.core.util.readToSingleObject 11import org.signal.core.util.requireBoolean 12import org.signal.core.util.requireNonNullBlob 13import org.signal.core.util.select 14import org.signal.core.util.toInt 15import org.signal.core.util.update 16import org.signal.libsignal.protocol.state.KyberPreKeyRecord 17import org.whispersystems.signalservice.api.push.ServiceId 18 19/** 20 * A table for storing data related to [org.tm.archive.crypto.storage.SignalKyberPreKeyStore]. 21 */ 22class KyberPreKeyTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTable(context, databaseHelper) { 23 companion object { 24 private val TAG = Log.tag(KyberPreKeyTable::class.java) 25 26 const val TABLE_NAME = "kyber_prekey" 27 const val ID = "_id" 28 const val ACCOUNT_ID = "account_id" 29 const val KEY_ID = "key_id" 30 const val TIMESTAMP = "timestamp" 31 const val LAST_RESORT = "last_resort" 32 const val SERIALIZED = "serialized" 33 const val STALE_TIMESTAMP = "stale_timestamp" 34 35 const val CREATE_TABLE = """ 36 CREATE TABLE $TABLE_NAME ( 37 $ID INTEGER PRIMARY KEY, 38 $ACCOUNT_ID TEXT NOT NULL, 39 $KEY_ID INTEGER NOT NULL, 40 $TIMESTAMP INTEGER NOT NULL, 41 $LAST_RESORT INTEGER NOT NULL, 42 $SERIALIZED BLOB NOT NULL, 43 $STALE_TIMESTAMP INTEGER NOT NULL DEFAULT 0, 44 UNIQUE($ACCOUNT_ID, $KEY_ID) 45 ) 46 """ 47 48 private const val INDEX_ACCOUNT_KEY = "kyber_account_id_key_id" 49 50 val CREATE_INDEXES = arrayOf( 51 "CREATE INDEX IF NOT EXISTS $INDEX_ACCOUNT_KEY ON $TABLE_NAME ($ACCOUNT_ID, $KEY_ID, $LAST_RESORT, $SERIALIZED)" 52 ) 53 54 const val PNI_ACCOUNT_ID = "PNI" 55 } 56 57 fun get(serviceId: ServiceId, keyId: Int): KyberPreKey? { 58 return readableDatabase 59 .select(LAST_RESORT, SERIALIZED) 60 .from("$TABLE_NAME INDEXED BY $INDEX_ACCOUNT_KEY") 61 .where("$ACCOUNT_ID = ? AND $KEY_ID = ?", serviceId.toAccountId(), keyId) 62 .run() 63 .readToSingleObject { cursor -> 64 KyberPreKey( 65 record = KyberPreKeyRecord(cursor.requireNonNullBlob(SERIALIZED)), 66 lastResort = cursor.requireBoolean(LAST_RESORT) 67 ) 68 } 69 } 70 71 fun getAll(serviceId: ServiceId): List<KyberPreKey> { 72 return readableDatabase 73 .select(LAST_RESORT, SERIALIZED) 74 .from("$TABLE_NAME INDEXED BY $INDEX_ACCOUNT_KEY") 75 .where("$ACCOUNT_ID = ?", serviceId.toAccountId()) 76 .run() 77 .readToList { cursor -> 78 KyberPreKey( 79 record = KyberPreKeyRecord(cursor.requireNonNullBlob(SERIALIZED)), 80 lastResort = cursor.requireBoolean(LAST_RESORT) 81 ) 82 } 83 } 84 85 fun getAllLastResort(serviceId: ServiceId): List<KyberPreKey> { 86 return readableDatabase 87 .select(LAST_RESORT, SERIALIZED) 88 .from("$TABLE_NAME INDEXED BY $INDEX_ACCOUNT_KEY") 89 .where("$ACCOUNT_ID = ? AND $LAST_RESORT = ?", serviceId.toAccountId(), 1) 90 .run() 91 .readToList { cursor -> 92 KyberPreKey( 93 record = KyberPreKeyRecord(cursor.requireNonNullBlob(SERIALIZED)), 94 lastResort = cursor.requireBoolean(LAST_RESORT) 95 ) 96 } 97 } 98 99 fun contains(serviceId: ServiceId, keyId: Int): Boolean { 100 return readableDatabase 101 .exists("$TABLE_NAME INDEXED BY $INDEX_ACCOUNT_KEY") 102 .where("$ACCOUNT_ID = ? AND $KEY_ID = ?", serviceId.toAccountId(), keyId) 103 .run() 104 } 105 106 fun insert(serviceId: ServiceId, keyId: Int, record: KyberPreKeyRecord, lastResort: Boolean) { 107 writableDatabase 108 .insertInto(TABLE_NAME) 109 .values( 110 ACCOUNT_ID to serviceId.toAccountId(), 111 KEY_ID to keyId, 112 TIMESTAMP to record.timestamp, 113 SERIALIZED to record.serialize(), 114 LAST_RESORT to lastResort.toInt() 115 ) 116 .run(SQLiteDatabase.CONFLICT_REPLACE) 117 } 118 119 fun deleteIfNotLastResort(serviceId: ServiceId, keyId: Int) { 120 writableDatabase 121 .delete("$TABLE_NAME INDEXED BY $INDEX_ACCOUNT_KEY") 122 .where("$ACCOUNT_ID = ? AND $KEY_ID = ? AND $LAST_RESORT = ?", serviceId.toAccountId(), keyId, 0) 123 .run() 124 } 125 126 fun delete(serviceId: ServiceId, keyId: Int) { 127 writableDatabase 128 .delete("$TABLE_NAME INDEXED BY $INDEX_ACCOUNT_KEY") 129 .where("$ACCOUNT_ID = ? AND $KEY_ID = ?", serviceId.toAccountId(), keyId) 130 .run() 131 } 132 133 fun markAllStaleIfNecessary(serviceId: ServiceId, staleTime: Long) { 134 writableDatabase 135 .update(TABLE_NAME) 136 .values(STALE_TIMESTAMP to staleTime) 137 .where("$ACCOUNT_ID = ? AND $STALE_TIMESTAMP = 0 AND $LAST_RESORT = 0", serviceId.toAccountId()) 138 .run() 139 } 140 141 /** 142 * Deletes all keys that have been stale since before the specified threshold. 143 * We will always keep at least [minCount] items, preferring more recent ones. 144 */ 145 fun deleteAllStaleBefore(serviceId: ServiceId, threshold: Long, minCount: Int) { 146 val count = writableDatabase 147 .delete(TABLE_NAME) 148 .where( 149 """ 150 $ACCOUNT_ID = ? 151 AND $LAST_RESORT = 0 152 AND $STALE_TIMESTAMP > 0 153 AND $STALE_TIMESTAMP < $threshold 154 AND $ID NOT IN ( 155 SELECT $ID 156 FROM $TABLE_NAME 157 WHERE 158 $ACCOUNT_ID = ? 159 AND $LAST_RESORT = 0 160 ORDER BY 161 CASE $STALE_TIMESTAMP WHEN 0 THEN 1 ELSE 0 END DESC, 162 $STALE_TIMESTAMP DESC, 163 $ID DESC 164 LIMIT $minCount 165 ) 166 """, 167 serviceId.toAccountId(), 168 serviceId.toAccountId() 169 ) 170 .run() 171 172 Log.i(TAG, "Deleted $count stale one-time EC prekeys.") 173 } 174 175 fun debugDeleteAll() { 176 writableDatabase.deleteAll(OneTimePreKeyTable.TABLE_NAME) 177 } 178 179 data class KyberPreKey( 180 val record: KyberPreKeyRecord, 181 val lastResort: Boolean 182 ) 183 184 private fun ServiceId.toAccountId(): String { 185 return when (this) { 186 is ServiceId.ACI -> this.toString() 187 is ServiceId.PNI -> PNI_ACCOUNT_ID 188 } 189 } 190}