That fuck shit the fascists are using
1package org.tm.archive.testing
2
3import okio.ByteString
4import okio.ByteString.Companion.toByteString
5import org.tm.archive.groups.GroupId
6import org.tm.archive.messages.SignalServiceProtoUtil.buildWith
7import org.tm.archive.messages.TestMessage
8import org.tm.archive.recipients.Recipient
9import org.tm.archive.recipients.RecipientId
10import org.whispersystems.signalservice.api.crypto.EnvelopeMetadata
11import org.whispersystems.signalservice.internal.push.AttachmentPointer
12import org.whispersystems.signalservice.internal.push.BodyRange
13import org.whispersystems.signalservice.internal.push.Content
14import org.whispersystems.signalservice.internal.push.DataMessage
15import org.whispersystems.signalservice.internal.push.EditMessage
16import org.whispersystems.signalservice.internal.push.Envelope
17import org.whispersystems.signalservice.internal.push.GroupContextV2
18import org.whispersystems.signalservice.internal.push.SyncMessage
19import java.util.UUID
20import kotlin.random.Random
21import kotlin.random.nextInt
22import kotlin.time.Duration.Companion.days
23
24/**
25 * Random but deterministic fuzzer for create various message content protos.
26 */
27object MessageContentFuzzer {
28
29 private val mediaTypes = listOf("image/png", "image/jpeg", "image/heic", "image/heif", "image/avif", "image/webp", "image/gif", "audio/aac", "audio/*", "video/mp4", "video/*", "text/x-vcard", "text/x-signal-plain", "application/x-signal-view-once", "*/*", "application/octet-stream")
30 private val emojis = listOf("😂", "❤️", "🔥", "😍", "👀", "🤔", "🙏", "👍", "🤷", "🥺")
31
32 private val random = Random(1)
33
34 /**
35 * Create an [Envelope].
36 */
37 fun envelope(timestamp: Long, serverGuid: UUID = UUID.randomUUID()): Envelope {
38 return Envelope.Builder()
39 .timestamp(timestamp)
40 .serverTimestamp(timestamp + 5)
41 .serverGuid(serverGuid.toString())
42 .build()
43 }
44
45 /**
46 * Create metadata to match an [Envelope].
47 */
48 fun envelopeMetadata(source: RecipientId, destination: RecipientId, sourceDeviceId: Int = 1, groupId: GroupId.V2? = null): EnvelopeMetadata {
49 return EnvelopeMetadata(
50 sourceServiceId = Recipient.resolved(source).requireServiceId(),
51 sourceE164 = null,
52 sourceDeviceId = sourceDeviceId,
53 sealedSender = true,
54 groupId = groupId?.decodedId,
55 destinationServiceId = Recipient.resolved(destination).requireServiceId()
56 )
57 }
58
59 /**
60 * Create a random text message that will contain a body but may also contain
61 * - An expire timer value
62 * - Bold style body ranges
63 */
64 fun fuzzTextMessage(sentTimestamp: Long? = null, groupContextV2: GroupContextV2? = null): Content {
65 return Content.Builder()
66 .dataMessage(
67 DataMessage.Builder().buildWith {
68 timestamp = sentTimestamp
69 body = string()
70 if (random.nextBoolean()) {
71 expireTimer = random.nextInt(0..28.days.inWholeSeconds.toInt())
72 }
73 if (random.nextBoolean()) {
74 bodyRanges(
75 listOf(
76 BodyRange.Builder().buildWith {
77 start = 0
78 length = 1
79 style = BodyRange.Style.BOLD
80 }
81 )
82 )
83 }
84 if (groupContextV2 != null) {
85 groupV2 = groupContextV2
86 }
87 }
88 )
89 .build()
90 }
91
92 /**
93 * Create an edit message.
94 */
95 fun editTextMessage(targetTimestamp: Long, editedDataMessage: DataMessage): Content {
96 return Content.Builder()
97 .editMessage(
98 EditMessage.Builder().buildWith {
99 targetSentTimestamp = targetTimestamp
100 dataMessage = editedDataMessage
101 }
102 )
103 .build()
104 }
105
106 /**
107 * Create a sync sent text message for the given [DataMessage].
108 */
109 fun syncSentTextMessage(
110 textMessage: DataMessage,
111 deliveredTo: List<RecipientId>,
112 recipientUpdate: Boolean = false
113 ): Content {
114 return Content
115 .Builder()
116 .syncMessage(
117 SyncMessage.Builder().buildWith {
118 sent = SyncMessage.Sent.Builder().buildWith {
119 timestamp = textMessage.timestamp
120 message = textMessage
121 isRecipientUpdate = recipientUpdate
122 unidentifiedStatus(
123 deliveredTo.map {
124 SyncMessage.Sent.UnidentifiedDeliveryStatus.Builder().buildWith {
125 destinationServiceId = Recipient.resolved(it).requireServiceId().toString()
126 unidentified = true
127 }
128 }
129 )
130 }
131 }
132 ).build()
133 }
134
135 /**
136 * Create a sync reads message for the given [RecipientId] and message timestamp pairings.
137 */
138 fun syncReadsMessage(timestamps: List<Pair<RecipientId, Long>>): Content {
139 return Content
140 .Builder()
141 .syncMessage(
142 SyncMessage.Builder().buildWith {
143 read = timestamps.map { (senderId, timestamp) ->
144 SyncMessage.Read.Builder().buildWith {
145 this.senderAci = Recipient.resolved(senderId).requireAci().toString()
146 this.timestamp = timestamp
147 }
148 }
149 }
150 ).build()
151 }
152
153 /**
154 * Create a random media message that may be:
155 * - A text body
156 * - A text body with a quote that references an existing message
157 * - A text body with a quote that references a non existing message
158 * - A message with 0-2 attachment pointers and may contain a text body
159 */
160 fun fuzzMediaMessageWithBody(quoteAble: List<TestMessage> = emptyList()): Content {
161 return Content.Builder()
162 .dataMessage(
163 DataMessage.Builder().buildWith {
164 if (random.nextBoolean()) {
165 body = string()
166 }
167
168 if (random.nextBoolean() && quoteAble.isNotEmpty()) {
169 body = string()
170 val quoted = quoteAble.random(random)
171 quote = DataMessage.Quote.Builder().buildWith {
172 id = quoted.envelope.timestamp
173 authorAci = quoted.metadata.sourceServiceId.toString()
174 text = quoted.content.dataMessage?.body
175 attachments(quoted.content.dataMessage?.attachments ?: emptyList())
176 bodyRanges(quoted.content.dataMessage?.bodyRanges ?: emptyList())
177 type = DataMessage.Quote.Type.NORMAL
178 }
179 }
180
181 if (random.nextFloat() < 0.1 && quoteAble.isNotEmpty()) {
182 val quoted = quoteAble.random(random)
183 quote = DataMessage.Quote.Builder().buildWith {
184 id = random.nextLong(quoted.envelope.timestamp!! - 1000000, quoted.envelope.timestamp!!)
185 authorAci = quoted.metadata.sourceServiceId.toString()
186 text = quoted.content.dataMessage?.body
187 }
188 }
189
190 if (random.nextFloat() < 0.25) {
191 val total = random.nextInt(1, 2)
192 attachments((0..total).map { attachmentPointer() })
193 }
194 }
195 )
196 .build()
197 }
198
199 /**
200 * Creates a random media message that contains no traditional media content. It may be:
201 * - A reaction to a prior message
202 */
203 fun fuzzMediaMessageNoContent(previousMessages: List<TestMessage> = emptyList()): Content {
204 return Content.Builder()
205 .dataMessage(
206 DataMessage.Builder().buildWith {
207 if (random.nextFloat() < 0.25) {
208 val reactTo = previousMessages.random(random)
209 reaction = DataMessage.Reaction.Builder().buildWith {
210 emoji = emojis.random(random)
211 remove = false
212 targetAuthorAci = reactTo.metadata.sourceServiceId.toString()
213 targetSentTimestamp = reactTo.envelope.timestamp
214 }
215 }
216 }
217 ).build()
218 }
219
220 /**
221 * Create a random media message that contains a sticker.
222 */
223 fun fuzzStickerMediaMessage(sentTimestamp: Long? = null, groupContextV2: GroupContextV2? = null): Content {
224 return Content.Builder()
225 .dataMessage(
226 DataMessage.Builder().buildWith {
227 timestamp = sentTimestamp
228 sticker = DataMessage.Sticker.Builder().buildWith {
229 packId = byteString(length = 24)
230 packKey = byteString(length = 128)
231 stickerId = random.nextInt()
232 data_ = attachmentPointer()
233 emoji = emojis.random(random)
234 }
235 groupV2 = groupContextV2
236 }
237 ).build()
238 }
239
240 /**
241 * Generate a random [String].
242 */
243 fun string(length: Int = 10, allowNullString: Boolean = false): String {
244 var string = ""
245
246 if (allowNullString && random.nextBoolean()) {
247 return string
248 }
249
250 for (i in 0 until length) {
251 string += random.nextInt(65..90).toChar()
252 }
253 return string
254 }
255
256 /**
257 * Generate a random [ByteString].
258 */
259 fun byteString(length: Int = 512): ByteString {
260 return random.nextBytes(length).toByteString()
261 }
262
263 /**
264 * Generate a random [AttachmentPointer].
265 */
266 fun attachmentPointer(): AttachmentPointer {
267 return AttachmentPointer.Builder().run {
268 cdnKey = string()
269 contentType = mediaTypes.random(random)
270 key = byteString()
271 size = random.nextInt(1024 * 1024 * 50)
272 thumbnail = byteString()
273 digest = byteString()
274 fileName = string()
275 flags = 0
276 width = random.nextInt(until = 1024)
277 height = random.nextInt(until = 1024)
278 caption = string(allowNullString = true)
279 blurHash = string()
280 uploadTimestamp = random.nextLong()
281 cdnNumber = 1
282
283 build()
284 }
285 }
286
287 /**
288 * Creates a server delivered timestamp that is always later than the envelope and server "received" timestamp.
289 */
290 fun fuzzServerDeliveredTimestamp(envelopeTimestamp: Long): Long {
291 return envelopeTimestamp + 10
292 }
293}