mirror of
https://github.com/oxen-io/session-android.git
synced 2025-04-30 06:30:52 +00:00
Support for group sync messages and requests.
// FREEBIE
This commit is contained in:
parent
d044a11bc0
commit
a20818f018
@ -1,5 +1,5 @@
|
|||||||
subprojects {
|
subprojects {
|
||||||
ext.version_number = "1.6.0-RC19"
|
ext.version_number = "1.6.0-RC21"
|
||||||
ext.group_info = "org.whispersystems"
|
ext.group_info = "org.whispersystems"
|
||||||
ext.axolotl_version = "1.3.1"
|
ext.axolotl_version = "1.3.1"
|
||||||
|
|
||||||
|
@ -32,6 +32,7 @@ import org.whispersystems.textsecure.api.messages.TextSecureAttachment;
|
|||||||
import org.whispersystems.textsecure.api.messages.TextSecureAttachmentStream;
|
import org.whispersystems.textsecure.api.messages.TextSecureAttachmentStream;
|
||||||
import org.whispersystems.textsecure.api.messages.TextSecureDataMessage;
|
import org.whispersystems.textsecure.api.messages.TextSecureDataMessage;
|
||||||
import org.whispersystems.textsecure.api.messages.TextSecureGroup;
|
import org.whispersystems.textsecure.api.messages.TextSecureGroup;
|
||||||
|
import org.whispersystems.textsecure.api.messages.multidevice.TextSecureSyncMessage;
|
||||||
import org.whispersystems.textsecure.api.push.TextSecureAddress;
|
import org.whispersystems.textsecure.api.push.TextSecureAddress;
|
||||||
import org.whispersystems.textsecure.api.push.TrustStore;
|
import org.whispersystems.textsecure.api.push.TrustStore;
|
||||||
import org.whispersystems.textsecure.api.push.exceptions.EncapsulatedExceptions;
|
import org.whispersystems.textsecure.api.push.exceptions.EncapsulatedExceptions;
|
||||||
@ -122,7 +123,7 @@ public class TextSecureMessageSender {
|
|||||||
SendMessageResponse response = sendMessage(recipient, timestamp, content, true);
|
SendMessageResponse response = sendMessage(recipient, timestamp, content, true);
|
||||||
|
|
||||||
if (response != null && response.getNeedsSync()) {
|
if (response != null && response.getNeedsSync()) {
|
||||||
byte[] syncMessage = createSentTranscriptMessage(content, Optional.of(recipient), timestamp);
|
byte[] syncMessage = createMultiDeviceSentTranscriptContent(content, Optional.of(recipient), timestamp);
|
||||||
sendMessage(localAddress, timestamp, syncMessage, false);
|
sendMessage(localAddress, timestamp, syncMessage, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -152,7 +153,7 @@ public class TextSecureMessageSender {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
if (response != null && response.getNeedsSync()) {
|
if (response != null && response.getNeedsSync()) {
|
||||||
byte[] syncMessage = createSentTranscriptMessage(content, Optional.<TextSecureAddress>absent(), timestamp);
|
byte[] syncMessage = createMultiDeviceSentTranscriptContent(content, Optional.<TextSecureAddress>absent(), timestamp);
|
||||||
sendMessage(localAddress, timestamp, syncMessage, false);
|
sendMessage(localAddress, timestamp, syncMessage, false);
|
||||||
}
|
}
|
||||||
} catch (UntrustedIdentityException e) {
|
} catch (UntrustedIdentityException e) {
|
||||||
@ -160,10 +161,19 @@ public class TextSecureMessageSender {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void sendMultiDeviceContactsUpdate(TextSecureAttachmentStream contacts)
|
public void sendMessage(TextSecureSyncMessage message)
|
||||||
throws IOException, UntrustedIdentityException
|
throws IOException, UntrustedIdentityException
|
||||||
{
|
{
|
||||||
byte[] content = createMultiDeviceContactsContent(contacts);
|
byte[] content;
|
||||||
|
|
||||||
|
if (message.getContacts().isPresent()) {
|
||||||
|
content = createMultiDeviceContactsContent(message.getContacts().get().asStream());
|
||||||
|
} else if (message.getGroups().isPresent()) {
|
||||||
|
content = createMultiDeviceGroupsContent(message.getGroups().get().asStream());
|
||||||
|
} else {
|
||||||
|
throw new IOException("Unsupported sync message!");
|
||||||
|
}
|
||||||
|
|
||||||
sendMessage(localAddress, System.currentTimeMillis(), content, false);
|
sendMessage(localAddress, System.currentTimeMillis(), content, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -199,7 +209,16 @@ public class TextSecureMessageSender {
|
|||||||
return container.setSyncMessage(builder).build().toByteArray();
|
return container.setSyncMessage(builder).build().toByteArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] createSentTranscriptMessage(byte[] content, Optional<TextSecureAddress> recipient, long timestamp) {
|
private byte[] createMultiDeviceGroupsContent(TextSecureAttachmentStream groups) throws IOException {
|
||||||
|
Content.Builder container = Content.newBuilder();
|
||||||
|
SyncMessage.Builder builder = SyncMessage.newBuilder();
|
||||||
|
builder.setGroups(SyncMessage.Groups.newBuilder()
|
||||||
|
.setBlob(createAttachmentPointer(groups)));
|
||||||
|
|
||||||
|
return container.setSyncMessage(builder).build().toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] createMultiDeviceSentTranscriptContent(byte[] content, Optional<TextSecureAddress> recipient, long timestamp) {
|
||||||
try {
|
try {
|
||||||
Content.Builder container = Content.newBuilder();
|
Content.Builder container = Content.newBuilder();
|
||||||
SyncMessage.Builder syncMessage = SyncMessage.newBuilder();
|
SyncMessage.Builder syncMessage = SyncMessage.newBuilder();
|
||||||
|
@ -28,7 +28,6 @@ import org.whispersystems.libaxolotl.LegacyMessageException;
|
|||||||
import org.whispersystems.libaxolotl.NoSessionException;
|
import org.whispersystems.libaxolotl.NoSessionException;
|
||||||
import org.whispersystems.libaxolotl.SessionCipher;
|
import org.whispersystems.libaxolotl.SessionCipher;
|
||||||
import org.whispersystems.libaxolotl.UntrustedIdentityException;
|
import org.whispersystems.libaxolotl.UntrustedIdentityException;
|
||||||
import org.whispersystems.libaxolotl.logging.Log;
|
|
||||||
import org.whispersystems.libaxolotl.protocol.CiphertextMessage;
|
import org.whispersystems.libaxolotl.protocol.CiphertextMessage;
|
||||||
import org.whispersystems.libaxolotl.protocol.PreKeyWhisperMessage;
|
import org.whispersystems.libaxolotl.protocol.PreKeyWhisperMessage;
|
||||||
import org.whispersystems.libaxolotl.protocol.WhisperMessage;
|
import org.whispersystems.libaxolotl.protocol.WhisperMessage;
|
||||||
@ -36,9 +35,9 @@ import org.whispersystems.libaxolotl.state.AxolotlStore;
|
|||||||
import org.whispersystems.textsecure.api.messages.TextSecureAttachment;
|
import org.whispersystems.textsecure.api.messages.TextSecureAttachment;
|
||||||
import org.whispersystems.textsecure.api.messages.TextSecureAttachmentPointer;
|
import org.whispersystems.textsecure.api.messages.TextSecureAttachmentPointer;
|
||||||
import org.whispersystems.textsecure.api.messages.TextSecureContent;
|
import org.whispersystems.textsecure.api.messages.TextSecureContent;
|
||||||
|
import org.whispersystems.textsecure.api.messages.TextSecureDataMessage;
|
||||||
import org.whispersystems.textsecure.api.messages.TextSecureEnvelope;
|
import org.whispersystems.textsecure.api.messages.TextSecureEnvelope;
|
||||||
import org.whispersystems.textsecure.api.messages.TextSecureGroup;
|
import org.whispersystems.textsecure.api.messages.TextSecureGroup;
|
||||||
import org.whispersystems.textsecure.api.messages.TextSecureDataMessage;
|
|
||||||
import org.whispersystems.textsecure.api.messages.multidevice.RequestMessage;
|
import org.whispersystems.textsecure.api.messages.multidevice.RequestMessage;
|
||||||
import org.whispersystems.textsecure.api.messages.multidevice.SentTranscriptMessage;
|
import org.whispersystems.textsecure.api.messages.multidevice.SentTranscriptMessage;
|
||||||
import org.whispersystems.textsecure.api.messages.multidevice.TextSecureSyncMessage;
|
import org.whispersystems.textsecure.api.messages.multidevice.TextSecureSyncMessage;
|
||||||
@ -175,16 +174,16 @@ public class TextSecureCipher {
|
|||||||
private TextSecureSyncMessage createSynchronizeMessage(TextSecureEnvelope envelope, SyncMessage content) {
|
private TextSecureSyncMessage createSynchronizeMessage(TextSecureEnvelope envelope, SyncMessage content) {
|
||||||
if (content.hasSent()) {
|
if (content.hasSent()) {
|
||||||
SyncMessage.Sent sentContent = content.getSent();
|
SyncMessage.Sent sentContent = content.getSent();
|
||||||
return new TextSecureSyncMessage(new SentTranscriptMessage(sentContent.getDestination(),
|
return TextSecureSyncMessage.forSentTranscript(new SentTranscriptMessage(sentContent.getDestination(),
|
||||||
sentContent.getTimestamp(),
|
sentContent.getTimestamp(),
|
||||||
createTextSecureMessage(envelope, sentContent.getMessage())));
|
createTextSecureMessage(envelope, sentContent.getMessage())));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (content.hasRequest()) {
|
if (content.hasRequest()) {
|
||||||
return new TextSecureSyncMessage(new RequestMessage(content.getRequest()));
|
return TextSecureSyncMessage.forRequest(new RequestMessage(content.getRequest()));
|
||||||
}
|
}
|
||||||
|
|
||||||
return new TextSecureSyncMessage();
|
return TextSecureSyncMessage.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
private TextSecureGroup createGroupInfo(TextSecureEnvelope envelope, DataMessage content) {
|
private TextSecureGroup createGroupInfo(TextSecureEnvelope envelope, DataMessage content) {
|
||||||
|
@ -0,0 +1,116 @@
|
|||||||
|
package org.whispersystems.textsecure.api.messages.multidevice;
|
||||||
|
|
||||||
|
import java.io.FilterInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
public class ChunkedInputStream {
|
||||||
|
|
||||||
|
protected final InputStream in;
|
||||||
|
|
||||||
|
public ChunkedInputStream(InputStream in) {
|
||||||
|
this.in = in;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected int readRawVarint32() throws IOException {
|
||||||
|
byte tmp = (byte)in.read();
|
||||||
|
if (tmp >= 0) {
|
||||||
|
return tmp;
|
||||||
|
}
|
||||||
|
int result = tmp & 0x7f;
|
||||||
|
if ((tmp = (byte)in.read()) >= 0) {
|
||||||
|
result |= tmp << 7;
|
||||||
|
} else {
|
||||||
|
result |= (tmp & 0x7f) << 7;
|
||||||
|
if ((tmp = (byte)in.read()) >= 0) {
|
||||||
|
result |= tmp << 14;
|
||||||
|
} else {
|
||||||
|
result |= (tmp & 0x7f) << 14;
|
||||||
|
if ((tmp = (byte)in.read()) >= 0) {
|
||||||
|
result |= tmp << 21;
|
||||||
|
} else {
|
||||||
|
result |= (tmp & 0x7f) << 21;
|
||||||
|
result |= (tmp = (byte)in.read()) << 28;
|
||||||
|
if (tmp < 0) {
|
||||||
|
// Discard upper 32 bits.
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
if ((byte)in.read() >= 0) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new IOException("Malformed varint!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static final class LimitedInputStream extends FilterInputStream {
|
||||||
|
|
||||||
|
private long left;
|
||||||
|
private long mark = -1;
|
||||||
|
|
||||||
|
LimitedInputStream(InputStream in, long limit) {
|
||||||
|
super(in);
|
||||||
|
left = limit;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public int available() throws IOException {
|
||||||
|
return (int) Math.min(in.available(), left);
|
||||||
|
}
|
||||||
|
|
||||||
|
// it's okay to mark even if mark isn't supported, as reset won't work
|
||||||
|
@Override public synchronized void mark(int readLimit) {
|
||||||
|
in.mark(readLimit);
|
||||||
|
mark = left;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public int read() throws IOException {
|
||||||
|
if (left == 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int result = in.read();
|
||||||
|
if (result != -1) {
|
||||||
|
--left;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public int read(byte[] b, int off, int len) throws IOException {
|
||||||
|
if (left == 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
len = (int) Math.min(len, left);
|
||||||
|
int result = in.read(b, off, len);
|
||||||
|
if (result != -1) {
|
||||||
|
left -= result;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public synchronized void reset() throws IOException {
|
||||||
|
if (!in.markSupported()) {
|
||||||
|
throw new IOException("Mark not supported");
|
||||||
|
}
|
||||||
|
if (mark == -1) {
|
||||||
|
throw new IOException("Mark not set");
|
||||||
|
}
|
||||||
|
|
||||||
|
in.reset();
|
||||||
|
left = mark;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public long skip(long n) throws IOException {
|
||||||
|
n = Math.min(n, left);
|
||||||
|
long skipped = in.skip(n);
|
||||||
|
left -= skipped;
|
||||||
|
return skipped;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
package org.whispersystems.textsecure.api.messages.multidevice;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
|
||||||
|
public class ChunkedOutputStream {
|
||||||
|
|
||||||
|
protected final OutputStream out;
|
||||||
|
|
||||||
|
public ChunkedOutputStream(OutputStream out) {
|
||||||
|
this.out = out;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void writeVarint32(int value) throws IOException {
|
||||||
|
while (true) {
|
||||||
|
if ((value & ~0x7F) == 0) {
|
||||||
|
out.write(value);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
out.write((value & 0x7F) | 0x80);
|
||||||
|
value >>>= 7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void writeStream(InputStream in) throws IOException {
|
||||||
|
byte[] buffer = new byte[4096];
|
||||||
|
int read;
|
||||||
|
|
||||||
|
while ((read = in.read(buffer)) != -1) {
|
||||||
|
out.write(buffer, 0, read);
|
||||||
|
}
|
||||||
|
|
||||||
|
in.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -5,16 +5,13 @@ import org.whispersystems.textsecure.api.messages.TextSecureAttachmentStream;
|
|||||||
import org.whispersystems.textsecure.internal.push.TextSecureProtos;
|
import org.whispersystems.textsecure.internal.push.TextSecureProtos;
|
||||||
import org.whispersystems.textsecure.internal.util.Util;
|
import org.whispersystems.textsecure.internal.util.Util;
|
||||||
|
|
||||||
import java.io.FilterInputStream;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|
||||||
public class DeviceContactsInputStream {
|
public class DeviceContactsInputStream extends ChunkedInputStream {
|
||||||
|
|
||||||
private final InputStream in;
|
|
||||||
|
|
||||||
public DeviceContactsInputStream(InputStream in) {
|
public DeviceContactsInputStream(InputStream in) {
|
||||||
this.in = in;
|
super(in);
|
||||||
}
|
}
|
||||||
|
|
||||||
public DeviceContact read() throws IOException {
|
public DeviceContact read() throws IOException {
|
||||||
@ -38,105 +35,4 @@ public class DeviceContactsInputStream {
|
|||||||
return new DeviceContact(number, name, avatar);
|
return new DeviceContact(number, name, avatar);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int readRawVarint32() throws IOException {
|
|
||||||
byte tmp = (byte)in.read();
|
|
||||||
if (tmp >= 0) {
|
|
||||||
return tmp;
|
|
||||||
}
|
|
||||||
int result = tmp & 0x7f;
|
|
||||||
if ((tmp = (byte)in.read()) >= 0) {
|
|
||||||
result |= tmp << 7;
|
|
||||||
} else {
|
|
||||||
result |= (tmp & 0x7f) << 7;
|
|
||||||
if ((tmp = (byte)in.read()) >= 0) {
|
|
||||||
result |= tmp << 14;
|
|
||||||
} else {
|
|
||||||
result |= (tmp & 0x7f) << 14;
|
|
||||||
if ((tmp = (byte)in.read()) >= 0) {
|
|
||||||
result |= tmp << 21;
|
|
||||||
} else {
|
|
||||||
result |= (tmp & 0x7f) << 21;
|
|
||||||
result |= (tmp = (byte)in.read()) << 28;
|
|
||||||
if (tmp < 0) {
|
|
||||||
// Discard upper 32 bits.
|
|
||||||
for (int i = 0; i < 5; i++) {
|
|
||||||
if ((byte)in.read() >= 0) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new IOException("Malformed varint!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final class LimitedInputStream extends FilterInputStream {
|
|
||||||
|
|
||||||
private long left;
|
|
||||||
private long mark = -1;
|
|
||||||
|
|
||||||
LimitedInputStream(InputStream in, long limit) {
|
|
||||||
super(in);
|
|
||||||
left = limit;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override public int available() throws IOException {
|
|
||||||
return (int) Math.min(in.available(), left);
|
|
||||||
}
|
|
||||||
|
|
||||||
// it's okay to mark even if mark isn't supported, as reset won't work
|
|
||||||
@Override public synchronized void mark(int readLimit) {
|
|
||||||
in.mark(readLimit);
|
|
||||||
mark = left;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override public int read() throws IOException {
|
|
||||||
if (left == 0) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int result = in.read();
|
|
||||||
if (result != -1) {
|
|
||||||
--left;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override public int read(byte[] b, int off, int len) throws IOException {
|
|
||||||
if (left == 0) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
len = (int) Math.min(len, left);
|
|
||||||
int result = in.read(b, off, len);
|
|
||||||
if (result != -1) {
|
|
||||||
left -= result;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override public synchronized void reset() throws IOException {
|
|
||||||
if (!in.markSupported()) {
|
|
||||||
throw new IOException("Mark not supported");
|
|
||||||
}
|
|
||||||
if (mark == -1) {
|
|
||||||
throw new IOException("Mark not set");
|
|
||||||
}
|
|
||||||
|
|
||||||
in.reset();
|
|
||||||
left = mark;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override public long skip(long n) throws IOException {
|
|
||||||
n = Math.min(n, left);
|
|
||||||
long skipped = in.skip(n);
|
|
||||||
left -= skipped;
|
|
||||||
return skipped;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,14 @@
|
|||||||
package org.whispersystems.textsecure.api.messages.multidevice;
|
package org.whispersystems.textsecure.api.messages.multidevice;
|
||||||
|
|
||||||
import org.whispersystems.textsecure.internal.push.TextSecureProtos;
|
import org.whispersystems.textsecure.internal.push.TextSecureProtos;
|
||||||
import org.whispersystems.textsecure.internal.util.Util;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
|
||||||
public class DeviceContactsOutputStream {
|
public class DeviceContactsOutputStream extends ChunkedOutputStream {
|
||||||
|
|
||||||
private final OutputStream out;
|
|
||||||
|
|
||||||
public DeviceContactsOutputStream(OutputStream out) {
|
public DeviceContactsOutputStream(OutputStream out) {
|
||||||
this.out = out;
|
super(out);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void write(DeviceContact contact) throws IOException {
|
public void write(DeviceContact contact) throws IOException {
|
||||||
@ -26,16 +22,7 @@ public class DeviceContactsOutputStream {
|
|||||||
|
|
||||||
private void writeAvatarImage(DeviceContact contact) throws IOException {
|
private void writeAvatarImage(DeviceContact contact) throws IOException {
|
||||||
if (contact.getAvatar().isPresent()) {
|
if (contact.getAvatar().isPresent()) {
|
||||||
InputStream in = contact.getAvatar().get().getInputStream();
|
writeStream(contact.getAvatar().get().getInputStream());
|
||||||
byte[] buffer = new byte[4096];
|
|
||||||
|
|
||||||
int read;
|
|
||||||
|
|
||||||
while ((read = in.read(buffer)) != -1) {
|
|
||||||
out.write(buffer, 0, read);
|
|
||||||
}
|
|
||||||
|
|
||||||
in.close();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,7 +37,7 @@ public class DeviceContactsOutputStream {
|
|||||||
if (contact.getAvatar().isPresent()) {
|
if (contact.getAvatar().isPresent()) {
|
||||||
TextSecureProtos.ContactDetails.Avatar.Builder avatarBuilder = TextSecureProtos.ContactDetails.Avatar.newBuilder();
|
TextSecureProtos.ContactDetails.Avatar.Builder avatarBuilder = TextSecureProtos.ContactDetails.Avatar.newBuilder();
|
||||||
avatarBuilder.setContentType(contact.getAvatar().get().getContentType());
|
avatarBuilder.setContentType(contact.getAvatar().get().getContentType());
|
||||||
avatarBuilder.setLength(contact.getAvatar().get().getLength());
|
avatarBuilder.setLength((int)contact.getAvatar().get().getLength());
|
||||||
contactDetails.setAvatar(avatarBuilder);
|
contactDetails.setAvatar(avatarBuilder);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,16 +47,4 @@ public class DeviceContactsOutputStream {
|
|||||||
out.write(serializedContactDetails);
|
out.write(serializedContactDetails);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void writeVarint32(int value) throws IOException {
|
|
||||||
while (true) {
|
|
||||||
if ((value & ~0x7F) == 0) {
|
|
||||||
out.write(value);
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
out.write((value & 0x7F) | 0x80);
|
|
||||||
value >>>= 7;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,38 @@
|
|||||||
|
package org.whispersystems.textsecure.api.messages.multidevice;
|
||||||
|
|
||||||
|
import org.whispersystems.libaxolotl.util.guava.Optional;
|
||||||
|
import org.whispersystems.textsecure.api.messages.TextSecureAttachmentStream;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class DeviceGroup {
|
||||||
|
|
||||||
|
private final byte[] id;
|
||||||
|
private final Optional<String> name;
|
||||||
|
private final List<String> members;
|
||||||
|
private final Optional<TextSecureAttachmentStream> avatar;
|
||||||
|
|
||||||
|
public DeviceGroup(byte[] id, Optional<String> name, List<String> members, Optional<TextSecureAttachmentStream> avatar) {
|
||||||
|
this.id = id;
|
||||||
|
this.name = name;
|
||||||
|
this.members = members;
|
||||||
|
this.avatar = avatar;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<TextSecureAttachmentStream> getAvatar() {
|
||||||
|
return avatar;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<String> getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getMembers() {
|
||||||
|
return members;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,45 @@
|
|||||||
|
package org.whispersystems.textsecure.api.messages.multidevice;
|
||||||
|
|
||||||
|
import org.whispersystems.libaxolotl.util.guava.Optional;
|
||||||
|
import org.whispersystems.textsecure.api.messages.TextSecureAttachmentStream;
|
||||||
|
import org.whispersystems.textsecure.internal.push.TextSecureProtos;
|
||||||
|
import org.whispersystems.textsecure.internal.util.Util;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class DeviceGroupsInputStream extends ChunkedInputStream{
|
||||||
|
|
||||||
|
public DeviceGroupsInputStream(InputStream in) {
|
||||||
|
super(in);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DeviceGroup read() throws IOException {
|
||||||
|
long detailsLength = readRawVarint32();
|
||||||
|
byte[] detailsSerialized = new byte[(int)detailsLength];
|
||||||
|
Util.readFully(in, detailsSerialized);
|
||||||
|
|
||||||
|
TextSecureProtos.GroupDetails details = TextSecureProtos.GroupDetails.parseFrom(detailsSerialized);
|
||||||
|
|
||||||
|
if (!details.hasId()) {
|
||||||
|
throw new IOException("ID missing on group record!");
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] id = details.getId().toByteArray();
|
||||||
|
Optional<String> name = Optional.fromNullable(details.getName());
|
||||||
|
List<String> members = details.getMembersList();
|
||||||
|
Optional<TextSecureAttachmentStream> avatar = Optional.absent();
|
||||||
|
|
||||||
|
if (details.hasAvatar()) {
|
||||||
|
long avatarLength = details.getAvatar().getLength();
|
||||||
|
InputStream avatarStream = new ChunkedInputStream.LimitedInputStream(in, avatarLength);
|
||||||
|
String avatarContentType = details.getAvatar().getContentType();
|
||||||
|
|
||||||
|
avatar = Optional.of(new TextSecureAttachmentStream(avatarStream, avatarContentType, avatarLength));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new DeviceGroup(id, name, members, avatar);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,55 @@
|
|||||||
|
package org.whispersystems.textsecure.api.messages.multidevice;
|
||||||
|
|
||||||
|
import com.google.protobuf.ByteString;
|
||||||
|
|
||||||
|
import org.whispersystems.textsecure.internal.push.TextSecureProtos;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
|
||||||
|
public class DeviceGroupsOutputStream extends ChunkedOutputStream {
|
||||||
|
|
||||||
|
public DeviceGroupsOutputStream(OutputStream out) {
|
||||||
|
super(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void write(DeviceGroup group) throws IOException {
|
||||||
|
writeGroupDetails(group);
|
||||||
|
writeAvatarImage(group);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void close() throws IOException {
|
||||||
|
out.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeAvatarImage(DeviceGroup contact) throws IOException {
|
||||||
|
if (contact.getAvatar().isPresent()) {
|
||||||
|
writeStream(contact.getAvatar().get().getInputStream());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeGroupDetails(DeviceGroup group) throws IOException {
|
||||||
|
TextSecureProtos.GroupDetails.Builder groupDetails = TextSecureProtos.GroupDetails.newBuilder();
|
||||||
|
groupDetails.setId(ByteString.copyFrom(group.getId()));
|
||||||
|
|
||||||
|
if (group.getName().isPresent()) {
|
||||||
|
groupDetails.setName(group.getName().get());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (group.getAvatar().isPresent()) {
|
||||||
|
TextSecureProtos.GroupDetails.Avatar.Builder avatarBuilder = TextSecureProtos.GroupDetails.Avatar.newBuilder();
|
||||||
|
avatarBuilder.setContentType(group.getAvatar().get().getContentType());
|
||||||
|
avatarBuilder.setLength((int)group.getAvatar().get().getLength());
|
||||||
|
groupDetails.setAvatar(avatarBuilder);
|
||||||
|
}
|
||||||
|
|
||||||
|
groupDetails.addAllMembers(group.getMembers());
|
||||||
|
|
||||||
|
byte[] serializedContactDetails = groupDetails.build().toByteArray();
|
||||||
|
|
||||||
|
writeVarint32(serializedContactDetails.length);
|
||||||
|
out.write(serializedContactDetails);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -13,4 +13,8 @@ public class RequestMessage {
|
|||||||
public boolean isContactsRequest() {
|
public boolean isContactsRequest() {
|
||||||
return request.getType() == Request.Type.CONTACTS;
|
return request.getType() == Request.Type.CONTACTS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isGroupsRequest() {
|
||||||
|
return request.getType() == Request.Type.GROUPS;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,50 +8,61 @@ public class TextSecureSyncMessage {
|
|||||||
|
|
||||||
private final Optional<SentTranscriptMessage> sent;
|
private final Optional<SentTranscriptMessage> sent;
|
||||||
private final Optional<TextSecureAttachment> contacts;
|
private final Optional<TextSecureAttachment> contacts;
|
||||||
private final Optional<TextSecureGroup> group;
|
private final Optional<TextSecureAttachment> groups;
|
||||||
private final Optional<RequestMessage> request;
|
private final Optional<RequestMessage> request;
|
||||||
|
|
||||||
public TextSecureSyncMessage() {
|
private TextSecureSyncMessage(Optional<SentTranscriptMessage> sent,
|
||||||
this.sent = Optional.absent();
|
Optional<TextSecureAttachment> contacts,
|
||||||
this.contacts = Optional.absent();
|
Optional<TextSecureAttachment> groups,
|
||||||
this.group = Optional.absent();
|
Optional<RequestMessage> request)
|
||||||
this.request = Optional.absent();
|
{
|
||||||
|
this.sent = sent;
|
||||||
|
this.contacts = contacts;
|
||||||
|
this.groups = groups;
|
||||||
|
this.request = request;
|
||||||
}
|
}
|
||||||
|
|
||||||
public TextSecureSyncMessage(SentTranscriptMessage sent) {
|
public static TextSecureSyncMessage forSentTranscript(SentTranscriptMessage sent) {
|
||||||
this.sent = Optional.of(sent);
|
return new TextSecureSyncMessage(Optional.of(sent),
|
||||||
this.contacts = Optional.absent();
|
Optional.<TextSecureAttachment>absent(),
|
||||||
this.group = Optional.absent();
|
Optional.<TextSecureAttachment>absent(),
|
||||||
this.request = Optional.absent();
|
Optional.<RequestMessage>absent());
|
||||||
}
|
}
|
||||||
|
|
||||||
public TextSecureSyncMessage(TextSecureAttachment contacts) {
|
public static TextSecureSyncMessage forContacts(TextSecureAttachment contacts) {
|
||||||
this.contacts = Optional.of(contacts);
|
return new TextSecureSyncMessage(Optional.<SentTranscriptMessage>absent(),
|
||||||
this.sent = Optional.absent();
|
Optional.of(contacts),
|
||||||
this.group = Optional.absent();
|
Optional.<TextSecureAttachment>absent(),
|
||||||
this.request = Optional.absent();
|
Optional.<RequestMessage>absent());
|
||||||
}
|
}
|
||||||
|
|
||||||
public TextSecureSyncMessage(TextSecureGroup group) {
|
public static TextSecureSyncMessage forGroups(TextSecureAttachment groups) {
|
||||||
this.group = Optional.of(group);
|
return new TextSecureSyncMessage(Optional.<SentTranscriptMessage>absent(),
|
||||||
this.sent = Optional.absent();
|
Optional.<TextSecureAttachment>absent(),
|
||||||
this.contacts = Optional.absent();
|
Optional.of(groups),
|
||||||
this.request = Optional.absent();
|
Optional.<RequestMessage>absent());
|
||||||
}
|
}
|
||||||
|
|
||||||
public TextSecureSyncMessage(RequestMessage contactsRequest) {
|
public static TextSecureSyncMessage forRequest(RequestMessage request) {
|
||||||
this.request = Optional.of(contactsRequest);
|
return new TextSecureSyncMessage(Optional.<SentTranscriptMessage>absent(),
|
||||||
this.sent = Optional.absent();
|
Optional.<TextSecureAttachment>absent(),
|
||||||
this.contacts = Optional.absent();
|
Optional.<TextSecureAttachment>absent(),
|
||||||
this.group = Optional.absent();
|
Optional.of(request));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TextSecureSyncMessage empty() {
|
||||||
|
return new TextSecureSyncMessage(Optional.<SentTranscriptMessage>absent(),
|
||||||
|
Optional.<TextSecureAttachment>absent(),
|
||||||
|
Optional.<TextSecureAttachment>absent(),
|
||||||
|
Optional.<RequestMessage>absent());
|
||||||
}
|
}
|
||||||
|
|
||||||
public Optional<SentTranscriptMessage> getSent() {
|
public Optional<SentTranscriptMessage> getSent() {
|
||||||
return sent;
|
return sent;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Optional<TextSecureGroup> getGroup() {
|
public Optional<TextSecureAttachment> getGroups() {
|
||||||
return group;
|
return groups;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Optional<TextSecureAttachment> getContacts() {
|
public Optional<TextSecureAttachment> getContacts() {
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -48,14 +48,15 @@ message SyncMessage {
|
|||||||
optional AttachmentPointer blob = 1;
|
optional AttachmentPointer blob = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
message Group {
|
message Groups {
|
||||||
optional GroupContext group = 1;
|
optional AttachmentPointer blob = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
message Request {
|
message Request {
|
||||||
enum Type {
|
enum Type {
|
||||||
UNKNOWN = 0;
|
UNKNOWN = 0;
|
||||||
CONTACTS = 1;
|
CONTACTS = 1;
|
||||||
|
GROUPS = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
optional Type type = 1;
|
optional Type type = 1;
|
||||||
@ -63,7 +64,7 @@ message SyncMessage {
|
|||||||
|
|
||||||
optional Sent sent = 1;
|
optional Sent sent = 1;
|
||||||
optional Contacts contacts = 2;
|
optional Contacts contacts = 2;
|
||||||
optional Group group = 3;
|
optional Groups groups = 3;
|
||||||
optional Request request = 4;
|
optional Request request = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,10 +91,22 @@ message GroupContext {
|
|||||||
message ContactDetails {
|
message ContactDetails {
|
||||||
message Avatar {
|
message Avatar {
|
||||||
optional string contentType = 1;
|
optional string contentType = 1;
|
||||||
optional uint64 length = 2;
|
optional uint32 length = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
optional string number = 1;
|
optional string number = 1;
|
||||||
optional string name = 2;
|
optional string name = 2;
|
||||||
optional Avatar avatar = 3;
|
optional Avatar avatar = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message GroupDetails {
|
||||||
|
message Avatar {
|
||||||
|
optional string contentType = 1;
|
||||||
|
optional uint32 length = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
optional bytes id = 1;
|
||||||
|
optional string name = 2;
|
||||||
|
repeated string members = 3;
|
||||||
|
optional Avatar avatar = 4;
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user