That fuck shit the fascists are using
1package org.tm.archive.database
2
3import app.cash.exhaustive.Exhaustive
4import org.tm.archive.database.model.RecipientRecord
5import org.tm.archive.recipients.RecipientId
6import org.whispersystems.signalservice.api.push.ServiceId
7import org.whispersystems.signalservice.api.push.ServiceId.ACI
8import org.whispersystems.signalservice.api.push.ServiceId.PNI
9
10/**
11 * Encapsulates data around processing a tuple of user data into a user entry in [RecipientTable].
12 * Also lets you apply a list of [PnpOperation]s to get what the resulting dataset would be.
13 */
14data class PnpDataSet(
15 val e164: String?,
16 val pni: PNI?,
17 val aci: ACI?,
18 val byE164: RecipientId?,
19 val byPni: RecipientId?,
20 val byAci: RecipientId?,
21 val e164Record: RecipientRecord? = null,
22 val pniRecord: RecipientRecord? = null,
23 val aciRecord: RecipientRecord? = null
24) {
25
26 /**
27 * @return The common id if all non-null ids are equal, or null if all are null or at least one non-null pair doesn't match.
28 */
29 val commonId: RecipientId? = findCommonId(listOf(byE164, byPni, byAci))
30
31 /** The ID that would be used to contact this user. */
32 val serviceId: ServiceId? = aci ?: pni
33
34 fun MutableSet<RecipientRecord>.replace(recipientId: RecipientId, update: (RecipientRecord) -> RecipientRecord) {
35 val toUpdate = this.first { it.id == recipientId }
36 this.removeIf { it.id == toUpdate.id }
37 this += update(toUpdate)
38 }
39
40 /**
41 * Applies the set of operations and returns the resulting dataset.
42 * Important: This only occurs _in memory_. You must still apply the operations to disk to persist them.
43 */
44 fun perform(operations: LinkedHashSet<PnpOperation>): PnpDataSet {
45 if (operations.isEmpty()) {
46 return this
47 }
48
49 val records: MutableSet<RecipientRecord> = listOfNotNull(e164Record, pniRecord, aciRecord).toMutableSet()
50
51 for (operation in operations) {
52 @Exhaustive
53 when (operation) {
54 is PnpOperation.RemoveE164 -> {
55 records.replace(operation.recipientId) { it.copy(e164 = null) }
56 }
57 is PnpOperation.RemovePni -> {
58 records.replace(operation.recipientId) { record ->
59 record.copy(
60 pni = null,
61 aci = record.aci
62 )
63 }
64 }
65 is PnpOperation.SetAci -> {
66 records.replace(operation.recipientId) { it.copy(aci = operation.aci) }
67 }
68 is PnpOperation.SetE164 -> {
69 records.replace(operation.recipientId) { it.copy(e164 = operation.e164) }
70 }
71 is PnpOperation.SetPni -> {
72 records.replace(operation.recipientId) { record ->
73 record.copy(
74 pni = operation.pni
75 )
76 }
77 }
78 is PnpOperation.Merge -> {
79 val primary: RecipientRecord = records.first { it.id == operation.primaryId }
80 val secondary: RecipientRecord = records.first { it.id == operation.secondaryId }
81
82 records.replace(primary.id) { _ ->
83 primary.copy(
84 e164 = primary.e164 ?: secondary.e164,
85 pni = primary.pni ?: secondary.pni,
86 aci = primary.aci ?: secondary.aci
87 )
88 }
89
90 records.removeIf { it.id == secondary.id }
91 }
92 is PnpOperation.SessionSwitchoverInsert -> Unit
93 is PnpOperation.ChangeNumberInsert -> Unit
94 }
95 }
96
97 val newE164Record = if (e164 != null) records.firstOrNull { it.e164 == e164 } else null
98 val newPniRecord = if (pni != null) records.firstOrNull { it.pni == pni } else null
99 val newAciRecord = if (aci != null) records.firstOrNull { it.aci == aci } else null
100
101 return this.copy(
102 byE164 = newE164Record?.id,
103 byPni = newPniRecord?.id,
104 byAci = newAciRecord?.id,
105 e164Record = newE164Record,
106 pniRecord = newPniRecord,
107 aciRecord = newAciRecord
108 )
109 }
110
111 companion object {
112 private fun findCommonId(ids: List<RecipientId?>): RecipientId? {
113 val nonNull = ids.filterNotNull()
114
115 return when {
116 nonNull.isEmpty() -> null
117 nonNull.all { it == nonNull[0] } -> nonNull[0]
118 else -> null
119 }
120 }
121 }
122}
123
124/**
125 * Represents a set of actions that need to be applied to incorporate a tuple of user data
126 * into [RecipientTable].
127 */
128data class PnpChangeSet(
129 val id: PnpIdResolver,
130 val operations: LinkedHashSet<PnpOperation> = linkedSetOf(),
131 val breadCrumbs: List<String> = emptyList()
132) {
133 // We want to exclude breadcrumbs from equality for testing purposes
134 override fun equals(other: Any?): Boolean {
135 if (this === other) return true
136 if (javaClass != other?.javaClass) return false
137
138 other as PnpChangeSet
139
140 if (id != other.id) return false
141 if (operations != other.operations) return false
142
143 return true
144 }
145
146 override fun hashCode(): Int {
147 var result = id.hashCode()
148 result = 31 * result + operations.hashCode()
149 return result
150 }
151}
152
153sealed class PnpIdResolver {
154 data class PnpNoopId(
155 val recipientId: RecipientId
156 ) : PnpIdResolver()
157
158 data class PnpInsert(
159 val e164: String?,
160 val pni: PNI?,
161 val aci: ACI?
162 ) : PnpIdResolver()
163}
164
165/**
166 * An operation that needs to be performed on the [RecipientTable] as part of merging in new user data.
167 * Lets us describe various situations as a series of operations, making code clearer and tests easier.
168 */
169sealed class PnpOperation {
170 abstract val recipientId: RecipientId
171
172 data class RemoveE164(
173 override val recipientId: RecipientId
174 ) : PnpOperation()
175
176 data class RemovePni(
177 override val recipientId: RecipientId
178 ) : PnpOperation()
179
180 data class SetE164(
181 override val recipientId: RecipientId,
182 val e164: String
183 ) : PnpOperation()
184
185 data class SetPni(
186 override val recipientId: RecipientId,
187 val pni: PNI
188 ) : PnpOperation()
189
190 data class SetAci(
191 override val recipientId: RecipientId,
192 val aci: ACI
193 ) : PnpOperation()
194
195 /**
196 * Merge two rows into one. Prefer data in the primary row when there's conflicts. Delete the secondary row afterwards.
197 */
198 data class Merge(
199 val primaryId: RecipientId,
200 val secondaryId: RecipientId
201 ) : PnpOperation() {
202 override val recipientId: RecipientId
203 get() = throw UnsupportedOperationException()
204 }
205
206 data class SessionSwitchoverInsert(
207 override val recipientId: RecipientId,
208 val e164: String?
209 ) : PnpOperation()
210
211 data class ChangeNumberInsert(
212 override val recipientId: RecipientId,
213 val oldE164: String,
214 val newE164: String
215 ) : PnpOperation()
216}