That fuck shit the fascists are using
1package org.tm.archive.database
2
3import android.content.Context
4import androidx.core.content.contentValuesOf
5import org.signal.core.util.Base64
6import org.signal.core.util.SqlUtil
7import org.signal.core.util.delete
8import org.signal.core.util.deleteAll
9import org.signal.core.util.logging.Log
10import org.signal.core.util.requireNonNullString
11import org.signal.core.util.update
12import org.signal.libsignal.protocol.InvalidKeyException
13import org.signal.libsignal.protocol.ecc.Curve
14import org.signal.libsignal.protocol.ecc.ECKeyPair
15import org.signal.libsignal.protocol.state.PreKeyRecord
16import org.whispersystems.signalservice.api.push.ServiceId
17import java.io.IOException
18
19class OneTimePreKeyTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTable(context, databaseHelper) {
20 companion object {
21 private val TAG = Log.tag(OneTimePreKeyTable::class.java)
22
23 const val TABLE_NAME = "one_time_prekeys"
24 const val ID = "_id"
25 const val ACCOUNT_ID = "account_id"
26 const val KEY_ID = "key_id"
27 const val PUBLIC_KEY = "public_key"
28 const val PRIVATE_KEY = "private_key"
29 const val STALE_TIMESTAMP = "stale_timestamp"
30
31 const val CREATE_TABLE = """
32 CREATE TABLE $TABLE_NAME (
33 $ID INTEGER PRIMARY KEY,
34 $ACCOUNT_ID TEXT NOT NULL,
35 $KEY_ID INTEGER NOT NULL,
36 $PUBLIC_KEY TEXT NOT NULL,
37 $PRIVATE_KEY TEXT NOT NULL,
38 $STALE_TIMESTAMP INTEGER NOT NULL DEFAULT 0,
39 UNIQUE($ACCOUNT_ID, $KEY_ID)
40 )
41 """
42
43 const val PNI_ACCOUNT_ID = "PNI"
44 }
45
46 fun get(serviceId: ServiceId, keyId: Int): PreKeyRecord? {
47 readableDatabase.query(TABLE_NAME, null, "$ACCOUNT_ID = ? AND $KEY_ID = ?", SqlUtil.buildArgs(serviceId.toAccountId(), keyId), null, null, null).use { cursor ->
48 if (cursor.moveToFirst()) {
49 try {
50 val publicKey = Curve.decodePoint(Base64.decode(cursor.requireNonNullString(PUBLIC_KEY)), 0)
51 val privateKey = Curve.decodePrivatePoint(Base64.decode(cursor.requireNonNullString(PRIVATE_KEY)))
52 return PreKeyRecord(keyId, ECKeyPair(publicKey, privateKey))
53 } catch (e: InvalidKeyException) {
54 Log.w(TAG, e)
55 } catch (e: IOException) {
56 Log.w(TAG, e)
57 }
58 }
59 }
60
61 return null
62 }
63
64 fun insert(serviceId: ServiceId, keyId: Int, record: PreKeyRecord) {
65 val contentValues = contentValuesOf(
66 ACCOUNT_ID to serviceId.toAccountId(),
67 KEY_ID to keyId,
68 PUBLIC_KEY to Base64.encodeWithPadding(record.keyPair.publicKey.serialize()),
69 PRIVATE_KEY to Base64.encodeWithPadding(record.keyPair.privateKey.serialize())
70 )
71
72 writableDatabase.replace(TABLE_NAME, null, contentValues)
73 }
74
75 fun delete(serviceId: ServiceId, keyId: Int) {
76 val database = databaseHelper.signalWritableDatabase
77 database.delete(TABLE_NAME, "$ACCOUNT_ID = ? AND $KEY_ID = ?", SqlUtil.buildArgs(serviceId.toAccountId(), keyId))
78 }
79
80 fun markAllStaleIfNecessary(serviceId: ServiceId, staleTime: Long) {
81 writableDatabase
82 .update(TABLE_NAME)
83 .values(STALE_TIMESTAMP to staleTime)
84 .where("$ACCOUNT_ID = ? AND $STALE_TIMESTAMP = 0", serviceId.toAccountId())
85 .run()
86 }
87
88 /**
89 * Deletes all keys that have been stale since before the specified threshold.
90 * We will always keep at least [minCount] items, preferring more recent ones.
91 */
92 fun deleteAllStaleBefore(serviceId: ServiceId, threshold: Long, minCount: Int) {
93 val count = writableDatabase
94 .delete(TABLE_NAME)
95 .where(
96 """
97 $ACCOUNT_ID = ?
98 AND $STALE_TIMESTAMP > 0
99 AND $STALE_TIMESTAMP < $threshold
100 AND $ID NOT IN (
101 SELECT $ID
102 FROM $TABLE_NAME
103 WHERE $ACCOUNT_ID = ?
104 ORDER BY
105 CASE $STALE_TIMESTAMP WHEN 0 THEN 1 ELSE 0 END DESC,
106 $STALE_TIMESTAMP DESC,
107 $ID DESC
108 LIMIT $minCount
109 )
110 """,
111 serviceId.toAccountId(),
112 serviceId.toAccountId()
113 )
114 .run()
115
116 Log.i(TAG, "Deleted $count stale one-time EC prekeys.")
117 }
118
119 fun debugDeleteAll() {
120 writableDatabase.deleteAll(TABLE_NAME)
121 }
122
123 private fun ServiceId.toAccountId(): String {
124 return when (this) {
125 is ServiceId.ACI -> this.toString()
126 is ServiceId.PNI -> PNI_ACCOUNT_ID
127 }
128 }
129}