Improve validator testing.

This commit is contained in:
Greyson Parrelli
2026-04-08 16:39:21 -04:00
parent 256ee9b1aa
commit 88b21b6113
3 changed files with 136 additions and 5 deletions
@@ -1252,7 +1252,7 @@ object DataMessageProcessor {
SignalDatabase.polls.insertVotes(
pollId = pollId,
pollOptionIds = pollVote.optionIndexes.map { index -> allOptionIds[index] },
pollOptionIds = pollVote.optionIndexes.distinct().map { index -> allOptionIds[index] },
voterId = senderRecipient.id.toLong(),
voteCount = pollVote.voteCount?.toLong() ?: 0,
messageId = messageId
@@ -1586,15 +1586,16 @@ object DataMessageProcessor {
}
warn(timestamp, "Didn't find matching message record...")
val cappedQuoteRanges = quote.bodyRanges.take(BODY_RANGE_PROCESSING_LIMIT)
return QuoteModel(
id = quote.id!!,
author = authorId,
text = quote.text ?: "",
isOriginalMissing = true,
attachment = quote.attachments.firstNotNullOfOrNull { PointerAttachment.forPointer(it).orNull() },
mentions = getMentions(quote.bodyRanges),
mentions = getMentions(cappedQuoteRanges),
type = QuoteModel.Type.fromProto(quote.type),
bodyRanges = quote.bodyRanges.filter { Util.allAreNull(it.mentionAci, it.mentionAciBinary) }.toBodyRangeList()
bodyRanges = cappedQuoteRanges.filter { Util.allAreNull(it.mentionAci, it.mentionAciBinary) }.toBodyRangeList()
)
}
@@ -33,6 +33,7 @@ object EnvelopeContentValidator {
private const val MAX_POLL_QUESTION_CHARACTER_LENGTH = 200
private const val MAX_POLL_CHARACTER_LENGTH = 100
private const val MIN_POLL_OPTIONS = 2
private const val MAX_POLL_OPTIONS = 10
fun validate(envelope: Envelope, content: Content, localAci: ACI, ciphertextMessageType: Int): Result {
if (envelope.type == Envelope.Type.PLAINTEXT_CONTENT || ciphertextMessageType == CiphertextMessage.PLAINTEXT_CONTENT_TYPE) {
@@ -91,6 +92,10 @@ object EnvelopeContentValidator {
return Result.Invalid("[DataMessage] Invalid ACI on quote!")
}
if (dataMessage.quote != null && dataMessage.quote.bodyRanges.any { Util.anyNotNull(it.mentionAci, it.mentionAciBinary) && ACI.parseOrNull(it.mentionAci, it.mentionAciBinary).isNullOrInvalidServiceId() }) {
return Result.Invalid("[DataMessage] Invalid ACI on quote body range!")
}
if (dataMessage.contact.any { it.avatar != null && it.avatar.avatar.isPresentAndInvalid() }) {
return Result.Invalid("[DataMessage] Invalid AttachmentPointer on DataMessage.contactList.avatar!")
}
@@ -153,7 +158,7 @@ object EnvelopeContentValidator {
return Result.Invalid("[DataMessage] Invalid poll terminate!")
}
if (dataMessage.pollVote != null && (dataMessage.pollVote.targetAuthorAciBinary.isNullOrInvalidAci() || dataMessage.pollVote.targetSentTimestamp == null || dataMessage.pollVote.voteCount == null)) {
if (dataMessage.pollVote != null && (dataMessage.pollVote.targetAuthorAciBinary.isNullOrInvalidAci() || dataMessage.pollVote.targetSentTimestamp == null || dataMessage.pollVote.voteCount == null || dataMessage.pollVote.optionIndexes.size > MAX_POLL_OPTIONS)) {
return Result.Invalid("[DataMessage] Invalid poll vote!")
}
@@ -250,7 +255,7 @@ object EnvelopeContentValidator {
return Result.Invalid("[SyncMessage] Missing packId in stickerPackOperationList!")
}
if (syncMessage.blocked != null && syncMessage.blocked.acis.any { it.isNullOrInvalidAci() } && syncMessage.blocked.acisBinary.any { it.isNullOrInvalidAci() }) {
if (syncMessage.blocked != null && (syncMessage.blocked.acis.any { it.isNullOrInvalidAci() } || syncMessage.blocked.acisBinary.any { it.isNullOrInvalidAci() })) {
return Result.Invalid("[SyncMessage] Invalid ACI in SyncMessage.blocked!")
}
@@ -10,14 +10,17 @@ import org.junit.Test
import org.signal.core.models.ServiceId
import org.signal.libsignal.protocol.message.CiphertextMessage
import org.signal.libsignal.protocol.message.DecryptionErrorMessage
import org.whispersystems.signalservice.internal.push.BodyRange
import org.whispersystems.signalservice.internal.push.Content
import org.whispersystems.signalservice.internal.push.DataMessage
import org.whispersystems.signalservice.internal.push.Envelope
import org.whispersystems.signalservice.internal.push.SyncMessage
class EnvelopeContentValidatorTest {
companion object {
private val SELF_ACI = ServiceId.ACI.parseOrThrow("0a5ebe7e-9de7-41a5-a25f-6ace4f8e11d1")
private val OTHER_ACI = ServiceId.ACI.parseOrThrow("11111111-9de7-41a5-a25f-6ace4f8e11d1")
}
@Test
@@ -261,4 +264,126 @@ class EnvelopeContentValidatorTest {
)
return decryptionErrorMessage.serialize().toByteString()
}
@Test
fun `validate - ensure poll votes with too many option indexes are marked invalid`() {
val content = Content(
dataMessage = DataMessage(
timestamp = 1234,
pollVote = DataMessage.PollVote(
targetAuthorAciBinary = OTHER_ACI.toByteString(),
targetSentTimestamp = 1000,
voteCount = 1,
optionIndexes = List(11) { 0 }
)
)
)
val result = EnvelopeContentValidator.validate(Envelope(clientTimestamp = 1234), content, SELF_ACI, CiphertextMessage.WHISPER_TYPE)
assert(result is EnvelopeContentValidator.Result.Invalid)
}
@Test
fun `validate - ensure poll votes with max option indexes are marked valid`() {
val content = Content(
dataMessage = DataMessage(
timestamp = 1234,
pollVote = DataMessage.PollVote(
targetAuthorAciBinary = OTHER_ACI.toByteString(),
targetSentTimestamp = 1000,
voteCount = 1,
optionIndexes = List(10) { it }
)
)
)
val result = EnvelopeContentValidator.validate(Envelope(clientTimestamp = 1234), content, SELF_ACI, CiphertextMessage.WHISPER_TYPE)
assert(result is EnvelopeContentValidator.Result.Valid)
}
@Test
fun `validate - ensure quote body range mentions with invalid aci are marked invalid`() {
val content = Content(
dataMessage = DataMessage(
timestamp = 1234,
quote = DataMessage.Quote(
id = 1000,
authorAci = OTHER_ACI.toString(),
bodyRanges = listOf(
BodyRange(start = 0, length = 1, mentionAci = "not-a-uuid")
)
)
)
)
val result = EnvelopeContentValidator.validate(Envelope(clientTimestamp = 1234), content, SELF_ACI, CiphertextMessage.WHISPER_TYPE)
assert(result is EnvelopeContentValidator.Result.Invalid)
}
@Test
fun `validate - ensure quote body range mentions with valid aci are marked valid`() {
val content = Content(
dataMessage = DataMessage(
timestamp = 1234,
quote = DataMessage.Quote(
id = 1000,
authorAci = OTHER_ACI.toString(),
bodyRanges = listOf(
BodyRange(start = 0, length = 1, mentionAci = OTHER_ACI.toString())
)
)
)
)
val result = EnvelopeContentValidator.validate(Envelope(clientTimestamp = 1234), content, SELF_ACI, CiphertextMessage.WHISPER_TYPE)
assert(result is EnvelopeContentValidator.Result.Valid)
}
@Test
fun `validate - ensure sync blocked with invalid string aci and empty binary list is marked invalid`() {
val envelope = Envelope(sourceServiceId = SELF_ACI.toString())
val content = Content(
syncMessage = SyncMessage(
blocked = SyncMessage.Blocked(
acis = listOf("not-a-uuid"),
acisBinary = emptyList()
)
)
)
val result = EnvelopeContentValidator.validate(envelope, content, SELF_ACI, CiphertextMessage.WHISPER_TYPE)
assert(result is EnvelopeContentValidator.Result.Invalid)
}
@Test
fun `validate - ensure sync blocked with invalid binary aci and empty string list is marked invalid`() {
val envelope = Envelope(sourceServiceId = SELF_ACI.toString())
val content = Content(
syncMessage = SyncMessage(
blocked = SyncMessage.Blocked(
acis = emptyList(),
acisBinary = listOf("bad".toByteArray().toByteString())
)
)
)
val result = EnvelopeContentValidator.validate(envelope, content, SELF_ACI, CiphertextMessage.WHISPER_TYPE)
assert(result is EnvelopeContentValidator.Result.Invalid)
}
@Test
fun `validate - ensure sync blocked with valid acis is marked valid`() {
val envelope = Envelope(sourceServiceId = SELF_ACI.toString())
val content = Content(
syncMessage = SyncMessage(
blocked = SyncMessage.Blocked(
acis = listOf(OTHER_ACI.toString()),
acisBinary = emptyList()
)
)
)
val result = EnvelopeContentValidator.validate(envelope, content, SELF_ACI, CiphertextMessage.WHISPER_TYPE)
assert(result is EnvelopeContentValidator.Result.Valid)
}
}