diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 867ef97233..cfe8d094bc 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -133,6 +133,13 @@ # Preserve class names for Kryo -keepnames class org.session.libsession.messaging.messages.Destination$** +# Protobuf messages are persisted as their own wire format and rebuilt through parseFrom, +# which is reached reflectively -- invisible to the shrinker, which otherwise removes it and +# leaves every queued message carrying a protobuf unreadable after a restart. +-keepclassmembers class * extends com.google.protobuf.GeneratedMessage { + public static *** parseFrom(byte[]); +} + ########## OPEN GROUP API (MESSAGES) ########## -keep class org.session.libsession.messaging.open_groups.OpenGroupApi$Message { *; } -keepclassmembers class org.session.libsession.messaging.open_groups.OpenGroupApi$Message { (); } diff --git a/app/src/test/java/org/session/libsession/messaging/jobs/JobKryoTest.kt b/app/src/test/java/org/session/libsession/messaging/jobs/JobKryoTest.kt index 44bd6d3fbe..3ce8e83877 100644 --- a/app/src/test/java/org/session/libsession/messaging/jobs/JobKryoTest.kt +++ b/app/src/test/java/org/session/libsession/messaging/jobs/JobKryoTest.kt @@ -2,10 +2,17 @@ package org.session.libsession.messaging.jobs import com.esotericsoftware.kryo.io.Input import com.esotericsoftware.kryo.io.Output +import io.mockk.every +import io.mockk.mockk +import io.mockk.slot import org.junit.Assert.assertEquals +import org.junit.Assert.assertNotNull import org.junit.Assert.assertTrue import org.junit.Test +import org.session.libsession.messaging.messages.Destination +import org.session.libsession.messaging.messages.Message import org.session.libsession.messaging.messages.control.GroupUpdated +import org.session.libsession.messaging.utilities.Data import org.session.protos.SessionProtos class JobKryoTest { @@ -26,6 +33,40 @@ class JobKryoTest { assertEquals(message.sentTimestamp, restored.sentTimestamp) } + /** + * The round trip above proves the serializer; this proves the job is wired to it, in both + * directions — a `serialize()` and a `create()` that disagree lose the message silently. + */ + @Test + fun `a GroupUpdated send job survives serialize and deserialize`() { + val message = GroupUpdated( + SessionProtos.GroupUpdateMessage.newBuilder() + .setMemberLeftMessage(SessionProtos.GroupUpdateMemberLeftMessage.getDefaultInstance()) + .build() + ) + + val restored = slot() + val factory = mockk { + every { create(any()) } answers { callOriginal() } + every { create(capture(restored), any(), any()) } returns mockk(relaxed = true) + } + + assertNotNull(factory.create(sendJob(message).serialize())) + assertEquals(message.inner, (restored.captured as GroupUpdated).inner) + } + + private fun sendJob(message: Message) = MessageSendJob( + message = message, + destination = Destination.ClosedGroup("03${"11".repeat(32)}"), + statusCallback = null, + attachmentUploadJobFactory = mockk(relaxed = true), + messageDataProvider = mockk(relaxed = true), + storage = mockk(relaxed = true), + configFactory = mockk(relaxed = true), + messageSender = mockk(relaxed = true), + jobQueue = mockk(relaxed = true), + ) + private fun roundTrip(value: Any): Any { val output = Output(ByteArray(4096), Job.MAX_BUFFER_SIZE_BYTES) jobKryo().writeClassAndObject(output, value)