Skip to content

Commit faef614

Browse files
committedMar 24, 2025
Parse message content as a byte array in request entities
1 parent db2cd20 commit faef614

File tree

7 files changed

+45
-59
lines changed

7 files changed

+45
-59
lines changed
 

‎integration-tests/src/test/java/org/signal/integration/MessagingTest.java

+3-5
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
99

1010
import java.nio.charset.StandardCharsets;
11-
import java.util.Base64;
1211
import java.util.List;
1312
import org.apache.commons.lang3.tuple.Pair;
1413
import org.junit.jupiter.api.Test;
@@ -27,11 +26,10 @@ public void testSendMessageUnsealed() {
2726

2827
try {
2928
final byte[] expectedContent = "Hello, World!".getBytes(StandardCharsets.UTF_8);
30-
final String contentBase64 = Base64.getEncoder().encodeToString(expectedContent);
31-
final IncomingMessage message = new IncomingMessage(1, Device.PRIMARY_ID, userB.registrationId(), contentBase64);
29+
final IncomingMessage message = new IncomingMessage(1, Device.PRIMARY_ID, userB.registrationId(), expectedContent);
3230
final IncomingMessageList messages = new IncomingMessageList(List.of(message), false, true, System.currentTimeMillis());
3331

34-
final Pair<Integer, SendMessageResponse> sendMessage = Operations
32+
Operations
3533
.apiPut("/v1/messages/%s".formatted(userB.aciUuid().toString()), messages)
3634
.authorized(userA)
3735
.execute(SendMessageResponse.class);
@@ -40,7 +38,7 @@ public void testSendMessageUnsealed() {
4038
.authorized(userB)
4139
.execute(OutgoingMessageEntityList.class);
4240

43-
final byte[] actualContent = receiveMessages.getRight().messages().get(0).content();
41+
final byte[] actualContent = receiveMessages.getRight().messages().getFirst().content();
4442
assertArrayEquals(expectedContent, actualContent);
4543
} finally {
4644
Operations.deleteUser(userA);

‎service/src/main/java/org/whispersystems/textsecuregcm/controllers/MessageController.java

+1-17
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,6 @@
6262
import java.util.stream.Collectors;
6363
import java.util.stream.Stream;
6464
import javax.annotation.Nullable;
65-
import org.apache.commons.lang3.StringUtils;
6665
import org.glassfish.jersey.server.ManagedAsync;
6766
import org.signal.libsignal.protocol.SealedSenderMultiRecipientMessage;
6867
import org.signal.libsignal.protocol.ServiceId;
@@ -324,7 +323,7 @@ public Response sendMessage(@ReadOnly @Auth final Optional<AuthenticatedDevice>
324323
int totalContentLength = 0;
325324

326325
for (final IncomingMessage message : messages.messages()) {
327-
final int contentLength = decodedSize(message.content());
326+
final int contentLength = message.content() != null ? message.content().length : 0;
328327

329328
validateContentLength(contentLength, false, isSyncMessage, isStory, userAgent);
330329

@@ -955,19 +954,4 @@ private void validateContentLength(final int contentLength,
955954
.increment();
956955
}
957956
}
958-
959-
@VisibleForTesting
960-
static int decodedSize(final String base64) {
961-
final int padding;
962-
963-
if (StringUtils.endsWith(base64, "==")) {
964-
padding = 2;
965-
} else if (StringUtils.endsWith(base64, "=")) {
966-
padding = 1;
967-
} else {
968-
padding = 0;
969-
}
970-
971-
return ((StringUtils.length(base64) - padding) * 3) / 4;
972-
}
973957
}

‎service/src/main/java/org/whispersystems/textsecuregcm/entities/IncomingMessage.java

+25-5
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,25 @@
44
*/
55
package org.whispersystems.textsecuregcm.entities;
66

7+
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
78
import com.google.protobuf.ByteString;
9+
import com.webauthn4j.converter.jackson.deserializer.json.ByteArrayBase64Deserializer;
810
import io.micrometer.core.instrument.Metrics;
911
import jakarta.validation.constraints.AssertTrue;
10-
import java.util.Base64;
1112
import javax.annotation.Nullable;
12-
import org.apache.commons.lang3.StringUtils;
1313
import org.whispersystems.textsecuregcm.identity.AciServiceIdentifier;
1414
import org.whispersystems.textsecuregcm.identity.ServiceIdentifier;
1515
import org.whispersystems.textsecuregcm.metrics.MetricsUtil;
1616
import org.whispersystems.textsecuregcm.storage.Account;
17+
import java.util.Arrays;
18+
import java.util.Objects;
1719

18-
public record IncomingMessage(int type, byte destinationDeviceId, int destinationRegistrationId, String content) {
20+
public record IncomingMessage(int type,
21+
byte destinationDeviceId,
22+
int destinationRegistrationId,
23+
24+
@JsonDeserialize(using = ByteArrayBase64Deserializer.class)
25+
byte[] content) {
1926

2027
private static final String REJECT_INVALID_ENVELOPE_TYPE_COUNTER_NAME =
2128
MetricsUtil.name(IncomingMessage.class, "rejectInvalidEnvelopeType");
@@ -50,8 +57,8 @@ public MessageProtos.Envelope toEnvelope(final ServiceIdentifier destinationIden
5057
envelopeBuilder.setReportSpamToken(ByteString.copyFrom(reportSpamToken));
5158
}
5259

53-
if (StringUtils.isNotEmpty(content())) {
54-
envelopeBuilder.setContent(ByteString.copyFrom(Base64.getDecoder().decode(content())));
60+
if (content() != null && content().length > 0) {
61+
envelopeBuilder.setContent(ByteString.copyFrom(content()));
5562
}
5663

5764
return envelopeBuilder.build();
@@ -69,4 +76,17 @@ public boolean isValidEnvelopeType() {
6976

7077
return true;
7178
}
79+
80+
@Override
81+
public boolean equals(final Object o) {
82+
if (!(o instanceof IncomingMessage(int otherType, byte otherDeviceId, int otherRegistrationId, byte[] otherContent)))
83+
return false;
84+
return type == otherType && destinationDeviceId == otherDeviceId
85+
&& destinationRegistrationId == otherRegistrationId && Objects.deepEquals(content, otherContent);
86+
}
87+
88+
@Override
89+
public int hashCode() {
90+
return Objects.hash(type, destinationDeviceId, destinationRegistrationId, Arrays.hashCode(content));
91+
}
7292
}

‎service/src/main/java/org/whispersystems/textsecuregcm/storage/ChangeNumberManager.java

+2-8
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
import java.util.stream.Collectors;
1414
import javax.annotation.Nullable;
1515
import org.apache.commons.lang3.ObjectUtils;
16-
import org.apache.commons.lang3.StringUtils;
1716
import org.signal.libsignal.protocol.IdentityKey;
1817
import org.slf4j.Logger;
1918
import org.slf4j.LoggerFactory;
@@ -138,16 +137,11 @@ private void sendDeviceMessages(final Account account, final List<IncomingMessag
138137
}
139138

140139
private static Optional<byte[]> getMessageContent(final IncomingMessage message) {
141-
if (StringUtils.isEmpty(message.content())) {
140+
if (message.content() == null || message.content().length == 0) {
142141
logger.warn("Message has no content");
143142
return Optional.empty();
144143
}
145144

146-
try {
147-
return Optional.of(Base64.getDecoder().decode(message.content()));
148-
} catch (final IllegalArgumentException e) {
149-
logger.warn("Failed to parse message content", e);
150-
return Optional.empty();
151-
}
145+
return Optional.of(message.content());
152146
}
153147
}

‎service/src/test/java/org/whispersystems/textsecuregcm/controllers/MessageControllerTest.java

+1-11
Original file line numberDiff line numberDiff line change
@@ -1096,7 +1096,7 @@ void testValidateContentLength() {
10961096
.request()
10971097
.header(HeaderUtils.UNIDENTIFIED_ACCESS_KEY, Base64.getEncoder().encodeToString(UNIDENTIFIED_ACCESS_BYTES))
10981098
.put(Entity.entity(new IncomingMessageList(
1099-
List.of(new IncomingMessage(1, (byte) 1, 1, Base64.getEncoder().encodeToString(contentBytes))), false, true,
1099+
List.of(new IncomingMessage(1, (byte) 1, 1, contentBytes)), false, true,
11001100
System.currentTimeMillis()),
11011101
MediaType.APPLICATION_JSON_TYPE))) {
11021102

@@ -1642,14 +1642,4 @@ private static Envelope generateEnvelope(UUID guid, int type, long timestamp, UU
16421642

16431643
return builder.build();
16441644
}
1645-
1646-
@Test
1647-
void decodedSize() {
1648-
for (int size = MessageController.MAX_MESSAGE_SIZE - 3; size <= MessageController.MAX_MESSAGE_SIZE + 3; size++) {
1649-
final byte[] bytes = TestRandomUtil.nextBytes(size);
1650-
final String base64Encoded = Base64.getEncoder().encodeToString(bytes);
1651-
1652-
assertEquals(bytes.length, MessageController.decodedSize(base64Encoded));
1653-
}
1654-
}
16551645
}

‎service/src/test/java/org/whispersystems/textsecuregcm/entities/OutgoingMessageEntityTest.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ void entityPreservesEnvelope() {
6969
final Account account = new Account();
7070
account.setUuid(UUID.randomUUID());
7171

72-
IncomingMessage message = new IncomingMessage(1, (byte) 44, 55, "AAAAAA");
72+
IncomingMessage message = new IncomingMessage(1, (byte) 44, 55, TestRandomUtil.nextBytes(4));
7373

7474
MessageProtos.Envelope baseEnvelope = message.toEnvelope(
7575
new AciServiceIdentifier(UUID.randomUUID()),

‎service/src/test/java/org/whispersystems/textsecuregcm/storage/ChangeNumberManagerTest.java

+12-12
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@
1515
import static org.mockito.Mockito.verify;
1616
import static org.mockito.Mockito.when;
1717

18+
import java.nio.charset.StandardCharsets;
1819
import java.util.ArrayList;
19-
import java.util.Base64;
2020
import java.util.Collections;
2121
import java.util.HashMap;
2222
import java.util.List;
@@ -150,7 +150,7 @@ void changeNumberSetPrimaryDevicePrekeyAndSendMessages() throws Exception {
150150

151151
final IncomingMessage msg = mock(IncomingMessage.class);
152152
when(msg.destinationDeviceId()).thenReturn(deviceId2);
153-
when(msg.content()).thenReturn(Base64.getEncoder().encodeToString(new byte[]{1}));
153+
when(msg.content()).thenReturn(new byte[]{1});
154154

155155
changeNumberManager.changeNumber(account, changedE164, pniIdentityKey, prekeys, null, List.of(msg), registrationIds);
156156

@@ -203,7 +203,7 @@ void changeNumberSetPrimaryDevicePrekeyPqAndSendMessages() throws Exception {
203203

204204
final IncomingMessage msg = mock(IncomingMessage.class);
205205
when(msg.destinationDeviceId()).thenReturn(deviceId2);
206-
when(msg.content()).thenReturn(Base64.getEncoder().encodeToString(new byte[]{1}));
206+
when(msg.content()).thenReturn(new byte[]{1});
207207

208208
changeNumberManager.changeNumber(account, changedE164, pniIdentityKey, prekeys, pqPrekeys, List.of(msg), registrationIds);
209209

@@ -254,7 +254,7 @@ void changeNumberSameNumberSetPrimaryDevicePrekeyAndSendMessages() throws Except
254254

255255
final IncomingMessage msg = mock(IncomingMessage.class);
256256
when(msg.destinationDeviceId()).thenReturn(deviceId2);
257-
when(msg.content()).thenReturn(Base64.getEncoder().encodeToString(new byte[]{1}));
257+
when(msg.content()).thenReturn(new byte[]{1});
258258

259259
changeNumberManager.changeNumber(account, originalE164, pniIdentityKey, prekeys, pqPrekeys, List.of(msg), registrationIds);
260260

@@ -301,7 +301,7 @@ void updatePniKeysSetPrimaryDevicePrekeyAndSendMessages() throws Exception {
301301

302302
final IncomingMessage msg = mock(IncomingMessage.class);
303303
when(msg.destinationDeviceId()).thenReturn(deviceId2);
304-
when(msg.content()).thenReturn(Base64.getEncoder().encodeToString(new byte[]{1}));
304+
when(msg.content()).thenReturn(new byte[]{1});
305305

306306
changeNumberManager.updatePniKeys(account, pniIdentityKey, prekeys, null, List.of(msg), registrationIds);
307307

@@ -350,7 +350,7 @@ void updatePniKeysSetPrimaryDevicePrekeyPqAndSendMessages() throws Exception {
350350

351351
final IncomingMessage msg = mock(IncomingMessage.class);
352352
when(msg.destinationDeviceId()).thenReturn(deviceId2);
353-
when(msg.content()).thenReturn(Base64.getEncoder().encodeToString(new byte[]{1}));
353+
when(msg.content()).thenReturn(new byte[]{1});
354354

355355
changeNumberManager.updatePniKeys(account, pniIdentityKey, prekeys, pqPrekeys, List.of(msg), registrationIds);
356356

@@ -393,8 +393,8 @@ void changeNumberMismatchedRegistrationId() {
393393
final byte destinationDeviceId2 = 2;
394394
final byte destinationDeviceId3 = 3;
395395
final List<IncomingMessage> messages = List.of(
396-
new IncomingMessage(1, destinationDeviceId2, 1, "foo"),
397-
new IncomingMessage(1, destinationDeviceId3, 1, "foo"));
396+
new IncomingMessage(1, destinationDeviceId2, 1, "foo".getBytes(StandardCharsets.UTF_8)),
397+
new IncomingMessage(1, destinationDeviceId3, 1, "foo".getBytes(StandardCharsets.UTF_8)));
398398

399399
final ECKeyPair pniIdentityKeyPair = Curve.generateKeyPair();
400400
final ECPublicKey pniIdentityKey = pniIdentityKeyPair.getPublicKey();
@@ -431,8 +431,8 @@ void updatePniKeysMismatchedRegistrationId() {
431431
final byte destinationDeviceId2 = 2;
432432
final byte destinationDeviceId3 = 3;
433433
final List<IncomingMessage> messages = List.of(
434-
new IncomingMessage(1, destinationDeviceId2, 1, "foo"),
435-
new IncomingMessage(1, destinationDeviceId3, 1, "foo"));
434+
new IncomingMessage(1, destinationDeviceId2, 1, "foo".getBytes(StandardCharsets.UTF_8)),
435+
new IncomingMessage(1, destinationDeviceId3, 1, "foo".getBytes(StandardCharsets.UTF_8)));
436436

437437
final ECKeyPair pniIdentityKeyPair = Curve.generateKeyPair();
438438
final ECPublicKey pniIdentityKey = pniIdentityKeyPair.getPublicKey();
@@ -469,8 +469,8 @@ void changeNumberMissingData() {
469469
final byte destinationDeviceId2 = 2;
470470
final byte destinationDeviceId3 = 3;
471471
final List<IncomingMessage> messages = List.of(
472-
new IncomingMessage(1, destinationDeviceId2, 2, "foo"),
473-
new IncomingMessage(1, destinationDeviceId3, 3, "foo"));
472+
new IncomingMessage(1, destinationDeviceId2, 2, "foo".getBytes(StandardCharsets.UTF_8)),
473+
new IncomingMessage(1, destinationDeviceId3, 3, "foo".getBytes(StandardCharsets.UTF_8)));
474474

475475
final Map<Byte, Integer> registrationIds = Map.of((byte) 1, 17, destinationDeviceId2, 47, destinationDeviceId3, 89);
476476

0 commit comments

Comments
 (0)
Please sign in to comment.