Retire the (unused!) binary message format

This commit is contained in:
Jon Chambers
2022-07-27 11:22:20 -04:00
committed by Jon Chambers
parent aa36dc95ef
commit e9119da040
9 changed files with 144 additions and 829 deletions

View File

@@ -1,90 +0,0 @@
/*
* Copyright 2021 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.providers;
import com.google.common.annotations.VisibleForTesting;
import java.io.IOException;
import java.io.InputStream;
import java.util.UUID;
import javax.ws.rs.BadRequestException;
import javax.ws.rs.WebApplicationException;
public abstract class BinaryProviderBase {
/**
* Reads a UUID in network byte order and converts to a UUID object.
*/
UUID readUuid(InputStream stream) throws IOException {
byte[] buffer = new byte[8];
int read = stream.readNBytes(buffer, 0, 8);
if (read != 8) {
throw new IOException("Insufficient bytes for UUID");
}
long msb = convertNetworkByteOrderToLong(buffer);
read = stream.readNBytes(buffer, 0, 8);
if (read != 8) {
throw new IOException("Insufficient bytes for UUID");
}
long lsb = convertNetworkByteOrderToLong(buffer);
return new UUID(msb, lsb);
}
private long convertNetworkByteOrderToLong(byte[] buffer) {
long result = 0;
for (int i = 0; i < 8; i++) {
result = (result << 8) | (buffer[i] & 0xFFL);
}
return result;
}
/**
* Reads a varint. A varint larger than 64 bits is rejected with a {@code WebApplicationException}. An
* {@code IOException} is thrown if the stream ends before we finish reading the varint.
*
* @return the varint value
*/
static long readVarint(InputStream stream) throws IOException, WebApplicationException {
boolean hasMore = true;
int currentOffset = 0;
long result = 0;
while (hasMore) {
if (currentOffset >= 64) {
throw new BadRequestException("varint is too large");
}
int b = stream.read();
if (b == -1) {
throw new IOException("Missing byte " + (currentOffset / 7) + " of varint");
}
if (currentOffset == 63 && (b & 0xFE) != 0) {
throw new BadRequestException("varint is too large");
}
hasMore = (b & 0x80) != 0;
result |= ((long)(b & 0x7F)) << currentOffset;
currentOffset += 7;
}
return result;
}
/**
* Reads two bytes with most significant byte first. Treats the value as unsigned so the range returned is
* {@code [0, 65535]}.
*/
@VisibleForTesting
static int readU16(InputStream stream) throws IOException {
int b1 = stream.read();
if (b1 == -1) {
throw new IOException("Missing byte 1 of U16");
}
int b2 = stream.read();
if (b2 == -1) {
throw new IOException("Missing byte 2 of U16");
}
return (b1 << 8) | b2;
}
}

View File

@@ -1,81 +0,0 @@
/*
* Copyright 2021 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.providers;
import io.dropwizard.util.DataSizeUnit;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import javax.ws.rs.BadRequestException;
import javax.ws.rs.Consumes;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.NoContentException;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.ext.MessageBodyReader;
import javax.ws.rs.ext.Provider;
import org.whispersystems.textsecuregcm.entities.IncomingDeviceMessage;
@Provider
@Consumes(MultiDeviceMessageListProvider.MEDIA_TYPE)
public class MultiDeviceMessageListProvider extends BinaryProviderBase implements MessageBodyReader<IncomingDeviceMessage[]> {
public static final String MEDIA_TYPE = "application/vnd.signal-messenger.mdml";
public static final int MAX_MESSAGE_COUNT = 50;
public static final int MAX_MESSAGE_SIZE = Math.toIntExact(DataSizeUnit.KIBIBYTES.toBytes(256));
public static final byte VERSION = 0x01;
@Override
public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
return MEDIA_TYPE.equals(mediaType.toString()) && IncomingDeviceMessage[].class.isAssignableFrom(type);
}
@Override
public IncomingDeviceMessage[]
readFrom(Class<IncomingDeviceMessage[]> resultType, Type genericType,
Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, String> httpHeaders,
InputStream entityStream)
throws IOException, WebApplicationException {
int versionByte = entityStream.read();
if (versionByte == -1) {
throw new NoContentException("Empty body not allowed");
}
if (versionByte != VERSION) {
throw new BadRequestException("Unsupported version");
}
int count = entityStream.read();
if (count == -1) {
throw new IOException("Missing count");
}
if (count > MAX_MESSAGE_COUNT) {
throw new BadRequestException("Maximum recipient count exceeded");
}
IncomingDeviceMessage[] messages = new IncomingDeviceMessage[count];
for (int i = 0; i < count; i++) {
long deviceId = readVarint(entityStream);
int registrationId = readU16(entityStream);
int type = entityStream.read();
if (type == -1) {
throw new IOException("Unexpected end of stream reading message type");
}
long messageLength = readVarint(entityStream);
if (messageLength > MAX_MESSAGE_SIZE) {
throw new WebApplicationException("Message body too large", Status.REQUEST_ENTITY_TOO_LARGE);
}
byte[] contents = entityStream.readNBytes(Math.toIntExact(messageLength));
if (contents.length != messageLength) {
throw new IOException("Unexpected end of stream in the middle of message contents");
}
messages[i] = new IncomingDeviceMessage(type, deviceId, registrationId, contents);
}
return messages;
}
}

View File

@@ -5,6 +5,7 @@
package org.whispersystems.textsecuregcm.providers;
import com.google.common.annotations.VisibleForTesting;
import io.dropwizard.util.DataSizeUnit;
import java.io.IOException;
import java.io.InputStream;
@@ -23,7 +24,7 @@ import org.whispersystems.textsecuregcm.entities.MultiRecipientMessage;
@Provider
@Consumes(MultiRecipientMessageProvider.MEDIA_TYPE)
public class MultiRecipientMessageProvider extends BinaryProviderBase implements MessageBodyReader<MultiRecipientMessage> {
public class MultiRecipientMessageProvider implements MessageBodyReader<MultiRecipientMessage> {
public static final String MEDIA_TYPE = "application/vnd.signal-messenger.mrm";
public static final int MAX_RECIPIENT_COUNT = 5000;
@@ -70,4 +71,78 @@ public class MultiRecipientMessageProvider extends BinaryProviderBase implements
}
return new MultiRecipientMessage(recipients, commonPayload);
}
/**
* Reads a UUID in network byte order and converts to a UUID object.
*/
private UUID readUuid(InputStream stream) throws IOException {
byte[] buffer = new byte[8];
int read = stream.readNBytes(buffer, 0, 8);
if (read != 8) {
throw new IOException("Insufficient bytes for UUID");
}
long msb = convertNetworkByteOrderToLong(buffer);
read = stream.readNBytes(buffer, 0, 8);
if (read != 8) {
throw new IOException("Insufficient bytes for UUID");
}
long lsb = convertNetworkByteOrderToLong(buffer);
return new UUID(msb, lsb);
}
private long convertNetworkByteOrderToLong(byte[] buffer) {
long result = 0;
for (int i = 0; i < 8; i++) {
result = (result << 8) | (buffer[i] & 0xFFL);
}
return result;
}
/**
* Reads a varint. A varint larger than 64 bits is rejected with a {@code WebApplicationException}. An
* {@code IOException} is thrown if the stream ends before we finish reading the varint.
*
* @return the varint value
*/
private long readVarint(InputStream stream) throws IOException, WebApplicationException {
boolean hasMore = true;
int currentOffset = 0;
int result = 0;
while (hasMore) {
if (currentOffset >= 64) {
throw new BadRequestException("varint is too large");
}
int b = stream.read();
if (b == -1) {
throw new IOException("Missing byte " + (currentOffset / 7) + " of varint");
}
if (currentOffset == 63 && (b & 0xFE) != 0) {
throw new BadRequestException("varint is too large");
}
hasMore = (b & 0x80) != 0;
result |= (b & 0x7F) << currentOffset;
currentOffset += 7;
}
return result;
}
/**
* Reads two bytes with most significant byte first. Treats the value as unsigned so the range returned is
* {@code [0, 65535]}.
*/
@VisibleForTesting
static int readU16(InputStream stream) throws IOException {
int b1 = stream.read();
if (b1 == -1) {
throw new IOException("Missing byte 1 of U16");
}
int b2 = stream.read();
if (b2 == -1) {
throw new IOException("Missing byte 2 of U16");
}
return (b1 << 8) | b2;
}
}