Add initial support for send/receive on CDN2.

This commit is contained in:
Ehren Kret 2020-04-05 17:32:06 -07:00 committed by Greyson Parrelli
parent 1290d0ead9
commit 37a35e8f70
32 changed files with 510 additions and 144 deletions

View File

@ -115,6 +115,7 @@ android {
buildConfigField "String", "SIGNAL_URL", "\"https://textsecure-service.whispersystems.org\"" buildConfigField "String", "SIGNAL_URL", "\"https://textsecure-service.whispersystems.org\""
buildConfigField "String", "STORAGE_URL", "\"https://storage.signal.org\"" buildConfigField "String", "STORAGE_URL", "\"https://storage.signal.org\""
buildConfigField "String", "SIGNAL_CDN_URL", "\"https://cdn.signal.org\"" buildConfigField "String", "SIGNAL_CDN_URL", "\"https://cdn.signal.org\""
buildConfigField "String", "SIGNAL_CDN2_URL", "\"https://cdn2.signal.org\""
buildConfigField "String", "SIGNAL_CONTACT_DISCOVERY_URL", "\"https://api.directory.signal.org\"" buildConfigField "String", "SIGNAL_CONTACT_DISCOVERY_URL", "\"https://api.directory.signal.org\""
buildConfigField "String", "SIGNAL_SERVICE_STATUS_URL", "\"uptime.signal.org\"" buildConfigField "String", "SIGNAL_SERVICE_STATUS_URL", "\"uptime.signal.org\""
buildConfigField "String", "SIGNAL_KEY_BACKUP_URL", "\"https://api.backup.signal.org\"" buildConfigField "String", "SIGNAL_KEY_BACKUP_URL", "\"https://api.backup.signal.org\""
@ -194,6 +195,7 @@ android {
buildConfigField "String", "SIGNAL_URL", "\"https://textsecure-service-staging.whispersystems.org\"" buildConfigField "String", "SIGNAL_URL", "\"https://textsecure-service-staging.whispersystems.org\""
buildConfigField "String", "STORAGE_URL", "\"https://storage-staging.signal.org\"" buildConfigField "String", "STORAGE_URL", "\"https://storage-staging.signal.org\""
buildConfigField "String", "SIGNAL_CDN_URL", "\"https://cdn-staging.signal.org\"" buildConfigField "String", "SIGNAL_CDN_URL", "\"https://cdn-staging.signal.org\""
buildConfigField "String", "SIGNAL_CDN2_URL", "\"https://cdn2-staging.signal.org\""
buildConfigField "String", "SIGNAL_CONTACT_DISCOVERY_URL", "\"https://api-staging.directory.signal.org\"" buildConfigField "String", "SIGNAL_CONTACT_DISCOVERY_URL", "\"https://api-staging.directory.signal.org\""
buildConfigField "String", "SIGNAL_KEY_BACKUP_URL", "\"https://api-staging.backup.signal.org\"" buildConfigField "String", "SIGNAL_KEY_BACKUP_URL", "\"https://api-staging.backup.signal.org\""
buildConfigField "String", "MRENCLAVE", "\"ba4ebb438bc07713819ee6c98d94037747006d7df63fc9e44d2d6f1fec962a79\"" buildConfigField "String", "MRENCLAVE", "\"ba4ebb438bc07713819ee6c98d94037747006d7df63fc9e44d2d6f1fec962a79\""

View File

@ -19,6 +19,8 @@ public abstract class Attachment {
@Nullable @Nullable
private final String fileName; private final String fileName;
private final int cdnNumber;
@Nullable @Nullable
private final String location; private final String location;
@ -53,7 +55,7 @@ public abstract class Attachment {
private final TransformProperties transformProperties; private final TransformProperties transformProperties;
public Attachment(@NonNull String contentType, int transferState, long size, @Nullable String fileName, public Attachment(@NonNull String contentType, int transferState, long size, @Nullable String fileName,
@Nullable String location, @Nullable String key, @Nullable String relay, int cdnNumber, @Nullable String location, @Nullable String key, @Nullable String relay,
@Nullable byte[] digest, @Nullable String fastPreflightId, boolean voiceNote, @Nullable byte[] digest, @Nullable String fastPreflightId, boolean voiceNote,
int width, int height, boolean quote, long uploadTimestamp, @Nullable String caption, int width, int height, boolean quote, long uploadTimestamp, @Nullable String caption,
@Nullable StickerLocator stickerLocator, @Nullable BlurHash blurHash, @Nullable StickerLocator stickerLocator, @Nullable BlurHash blurHash,
@ -63,6 +65,7 @@ public abstract class Attachment {
this.transferState = transferState; this.transferState = transferState;
this.size = size; this.size = size;
this.fileName = fileName; this.fileName = fileName;
this.cdnNumber = cdnNumber;
this.location = location; this.location = location;
this.key = key; this.key = key;
this.relay = relay; this.relay = relay;
@ -108,6 +111,10 @@ public abstract class Attachment {
return contentType; return contentType;
} }
public int getCdnNumber() {
return cdnNumber;
}
@Nullable @Nullable
public String getLocation() { public String getLocation() {
return location; return location;

View File

@ -2,11 +2,9 @@ package org.thoughtcrime.securesms.attachments;
import android.net.Uri; import android.net.Uri;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import org.thoughtcrime.securesms.blurhash.BlurHash; import org.thoughtcrime.securesms.blurhash.BlurHash;
import org.thoughtcrime.securesms.database.AttachmentDatabase;
import org.thoughtcrime.securesms.database.AttachmentDatabase.TransformProperties; import org.thoughtcrime.securesms.database.AttachmentDatabase.TransformProperties;
import org.thoughtcrime.securesms.mms.PartAuthority; import org.thoughtcrime.securesms.mms.PartAuthority;
import org.thoughtcrime.securesms.stickers.StickerLocator; import org.thoughtcrime.securesms.stickers.StickerLocator;
@ -24,14 +22,14 @@ public class DatabaseAttachment extends Attachment {
public DatabaseAttachment(AttachmentId attachmentId, long mmsId, public DatabaseAttachment(AttachmentId attachmentId, long mmsId,
boolean hasData, boolean hasThumbnail, boolean hasData, boolean hasThumbnail,
String contentType, int transferProgress, long size, String contentType, int transferProgress, long size,
String fileName, String location, String key, String relay, String fileName, int cdnNumber, String location, String key, String relay,
byte[] digest, String fastPreflightId, boolean voiceNote, byte[] digest, String fastPreflightId, boolean voiceNote,
int width, int height, boolean quote, @Nullable String caption, int width, int height, boolean quote, @Nullable String caption,
@Nullable StickerLocator stickerLocator, @Nullable BlurHash blurHash, @Nullable StickerLocator stickerLocator, @Nullable BlurHash blurHash,
@Nullable TransformProperties transformProperties, int displayOrder, @Nullable TransformProperties transformProperties, int displayOrder,
long uploadTimestamp) long uploadTimestamp)
{ {
super(contentType, transferProgress, size, fileName, location, key, relay, digest, fastPreflightId, voiceNote, width, height, quote, uploadTimestamp, caption, stickerLocator, blurHash, transformProperties); super(contentType, transferProgress, size, fileName, cdnNumber, location, key, relay, digest, fastPreflightId, voiceNote, width, height, quote, uploadTimestamp, caption, stickerLocator, blurHash, transformProperties);
this.attachmentId = attachmentId; this.attachmentId = attachmentId;
this.hasData = hasData; this.hasData = hasData;
this.hasThumbnail = hasThumbnail; this.hasThumbnail = hasThumbnail;

View File

@ -10,7 +10,7 @@ import org.thoughtcrime.securesms.database.MmsDatabase;
public class MmsNotificationAttachment extends Attachment { public class MmsNotificationAttachment extends Attachment {
public MmsNotificationAttachment(int status, long size) { public MmsNotificationAttachment(int status, long size) {
super("application/mms", getTransferStateFromStatus(status), size, null, null, null, null, null, null, false, 0, 0, false, 0, null, null, null, null); super("application/mms", getTransferStateFromStatus(status), size, null, 0, null, null, null, null, null, false, 0, 0, false, 0, null, null, null, null);
} }
@Nullable @Nullable

View File

@ -18,13 +18,13 @@ import java.util.List;
public class PointerAttachment extends Attachment { public class PointerAttachment extends Attachment {
private PointerAttachment(@NonNull String contentType, int transferState, long size, private PointerAttachment(@NonNull String contentType, int transferState, long size,
@Nullable String fileName, @NonNull String location, @Nullable String fileName, int cdnNumber, @NonNull String location,
@Nullable String key, @Nullable String relay, @Nullable String key, @Nullable String relay,
@Nullable byte[] digest, @Nullable String fastPreflightId, boolean voiceNote, @Nullable byte[] digest, @Nullable String fastPreflightId, boolean voiceNote,
int width, int height, long uploadTimestamp, @Nullable String caption, @Nullable StickerLocator stickerLocator, int width, int height, long uploadTimestamp, @Nullable String caption, @Nullable StickerLocator stickerLocator,
@Nullable BlurHash blurHash) @Nullable BlurHash blurHash)
{ {
super(contentType, transferState, size, fileName, location, key, relay, digest, fastPreflightId, voiceNote, width, height, false, uploadTimestamp, caption, stickerLocator, blurHash, null); super(contentType, transferState, size, fileName, cdnNumber, location, key, relay, digest, fastPreflightId, voiceNote, width, height, false, uploadTimestamp, caption, stickerLocator, blurHash, null);
} }
@Nullable @Nullable
@ -93,7 +93,8 @@ public class PointerAttachment extends Attachment {
AttachmentDatabase.TRANSFER_PROGRESS_PENDING, AttachmentDatabase.TRANSFER_PROGRESS_PENDING,
pointer.get().asPointer().getSize().or(0), pointer.get().asPointer().getSize().or(0),
pointer.get().asPointer().getFileName().orNull(), pointer.get().asPointer().getFileName().orNull(),
String.valueOf(pointer.get().asPointer().getId()), pointer.get().asPointer().getCdnNumber(),
pointer.get().asPointer().getRemoteId().toString(),
encodedKey, null, encodedKey, null,
pointer.get().asPointer().getDigest().orNull(), pointer.get().asPointer().getDigest().orNull(),
fastPreflightId, fastPreflightId,
@ -114,7 +115,8 @@ public class PointerAttachment extends Attachment {
AttachmentDatabase.TRANSFER_PROGRESS_PENDING, AttachmentDatabase.TRANSFER_PROGRESS_PENDING,
thumbnail != null ? thumbnail.asPointer().getSize().or(0) : 0, thumbnail != null ? thumbnail.asPointer().getSize().or(0) : 0,
pointer.getFileName(), pointer.getFileName(),
String.valueOf(thumbnail != null ? thumbnail.asPointer().getId() : 0), thumbnail != null ? thumbnail.asPointer().getCdnNumber() : 0,
thumbnail != null ? thumbnail.asPointer().getRemoteId().toString() : "0",
thumbnail != null && thumbnail.asPointer().getKey() != null ? Base64.encodeBytes(thumbnail.asPointer().getKey()) : null, thumbnail != null && thumbnail.asPointer().getKey() != null ? Base64.encodeBytes(thumbnail.asPointer().getKey()) : null,
null, null,
thumbnail != null ? thumbnail.asPointer().getDigest().orNull() : null, thumbnail != null ? thumbnail.asPointer().getDigest().orNull() : null,

View File

@ -16,7 +16,7 @@ import org.thoughtcrime.securesms.database.AttachmentDatabase;
public class TombstoneAttachment extends Attachment { public class TombstoneAttachment extends Attachment {
public TombstoneAttachment(@NonNull String contentType, boolean quote) { public TombstoneAttachment(@NonNull String contentType, boolean quote) {
super(contentType, AttachmentDatabase.TRANSFER_PROGRESS_DONE, 0, null, null, null, null, null, null, false, 0, 0, quote, 0, null, null, null, null); super(contentType, AttachmentDatabase.TRANSFER_PROGRESS_DONE, 0, null, 0, null, null, null, null, null, false, 0, 0, quote, 0, null, null, null, null);
} }
@Override @Override

View File

@ -1,11 +1,11 @@
package org.thoughtcrime.securesms.attachments; package org.thoughtcrime.securesms.attachments;
import android.net.Uri; import android.net.Uri;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import org.thoughtcrime.securesms.blurhash.BlurHash; import org.thoughtcrime.securesms.blurhash.BlurHash;
import org.thoughtcrime.securesms.database.AttachmentDatabase;
import org.thoughtcrime.securesms.database.AttachmentDatabase.TransformProperties; import org.thoughtcrime.securesms.database.AttachmentDatabase.TransformProperties;
import org.thoughtcrime.securesms.stickers.StickerLocator; import org.thoughtcrime.securesms.stickers.StickerLocator;
@ -27,7 +27,7 @@ public class UriAttachment extends Attachment {
boolean voiceNote, boolean quote, @Nullable String caption, @Nullable StickerLocator stickerLocator, boolean voiceNote, boolean quote, @Nullable String caption, @Nullable StickerLocator stickerLocator,
@Nullable BlurHash blurHash, @Nullable TransformProperties transformProperties) @Nullable BlurHash blurHash, @Nullable TransformProperties transformProperties)
{ {
super(contentType, transferState, size, fileName, null, null, null, null, fastPreflightId, voiceNote, width, height, quote, 0, caption, stickerLocator, blurHash, transformProperties); super(contentType, transferState, size, fileName, 0, null, null, null, null, fastPreflightId, voiceNote, width, height, quote, 0, caption, stickerLocator, blurHash, transformProperties);
this.dataUri = dataUri; this.dataUri = dataUri;
this.thumbnailUri = thumbnailUri; this.thumbnailUri = thumbnailUri;
} }

View File

@ -124,6 +124,7 @@ public class AttachmentDatabase extends Database {
static final String TRANSFORM_PROPERTIES = "transform_properties"; static final String TRANSFORM_PROPERTIES = "transform_properties";
static final String DISPLAY_ORDER = "display_order"; static final String DISPLAY_ORDER = "display_order";
static final String UPLOAD_TIMESTAMP = "upload_timestamp"; static final String UPLOAD_TIMESTAMP = "upload_timestamp";
static final String CDN_NUMBER = "cdn_number";
public static final String DIRECTORY = "parts"; public static final String DIRECTORY = "parts";
@ -139,13 +140,14 @@ public class AttachmentDatabase extends Database {
private static final String[] PROJECTION = new String[] {ROW_ID, private static final String[] PROJECTION = new String[] {ROW_ID,
MMS_ID, CONTENT_TYPE, NAME, CONTENT_DISPOSITION, MMS_ID, CONTENT_TYPE, NAME, CONTENT_DISPOSITION,
CONTENT_LOCATION, DATA, THUMBNAIL, TRANSFER_STATE, CDN_NUMBER, CONTENT_LOCATION, DATA, THUMBNAIL,
SIZE, FILE_NAME, THUMBNAIL, THUMBNAIL_ASPECT_RATIO, TRANSFER_STATE, SIZE, FILE_NAME, THUMBNAIL,
UNIQUE_ID, DIGEST, FAST_PREFLIGHT_ID, VOICE_NOTE, THUMBNAIL_ASPECT_RATIO, UNIQUE_ID, DIGEST,
QUOTE, DATA_RANDOM, THUMBNAIL_RANDOM, WIDTH, HEIGHT, FAST_PREFLIGHT_ID, VOICE_NOTE, QUOTE, DATA_RANDOM,
CAPTION, STICKER_PACK_ID, STICKER_PACK_KEY, STICKER_ID, THUMBNAIL_RANDOM, WIDTH, HEIGHT, CAPTION, STICKER_PACK_ID,
DATA_HASH, BLUR_HASH, TRANSFORM_PROPERTIES, TRANSFER_FILE, STICKER_PACK_KEY, STICKER_ID, DATA_HASH, BLUR_HASH,
DISPLAY_ORDER, UPLOAD_TIMESTAMP }; TRANSFORM_PROPERTIES, TRANSFER_FILE, DISPLAY_ORDER,
UPLOAD_TIMESTAMP };
public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + ROW_ID + " INTEGER PRIMARY KEY, " + public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + ROW_ID + " INTEGER PRIMARY KEY, " +
MMS_ID + " INTEGER, " + MMS_ID + " INTEGER, " +
@ -184,7 +186,8 @@ public class AttachmentDatabase extends Database {
TRANSFORM_PROPERTIES + " TEXT DEFAULT NULL, " + TRANSFORM_PROPERTIES + " TEXT DEFAULT NULL, " +
TRANSFER_FILE + " TEXT DEFAULT NULL, " + TRANSFER_FILE + " TEXT DEFAULT NULL, " +
DISPLAY_ORDER + " INTEGER DEFAULT 0, " + DISPLAY_ORDER + " INTEGER DEFAULT 0, " +
UPLOAD_TIMESTAMP + " INTEGER DEFAULT 0);"; UPLOAD_TIMESTAMP + " INTEGER DEFAULT 0, " +
CDN_NUMBER + " INTEGER DEFAULT 0);";
public static final String[] CREATE_INDEXS = { public static final String[] CREATE_INDEXS = {
"CREATE INDEX IF NOT EXISTS part_mms_id_index ON " + TABLE_NAME + " (" + MMS_ID + ");", "CREATE INDEX IF NOT EXISTS part_mms_id_index ON " + TABLE_NAME + " (" + MMS_ID + ");",
@ -532,6 +535,7 @@ public class AttachmentDatabase extends Database {
} }
values.put(TRANSFER_STATE, TRANSFER_PROGRESS_DONE); values.put(TRANSFER_STATE, TRANSFER_PROGRESS_DONE);
values.put(CDN_NUMBER, 0);
values.put(CONTENT_LOCATION, (String)null); values.put(CONTENT_LOCATION, (String)null);
values.put(CONTENT_DISPOSITION, (String)null); values.put(CONTENT_DISPOSITION, (String)null);
values.put(DIGEST, (byte[])null); values.put(DIGEST, (byte[])null);
@ -584,6 +588,7 @@ public class AttachmentDatabase extends Database {
contentValues.put(DATA_RANDOM, sourceDataInfo.random); contentValues.put(DATA_RANDOM, sourceDataInfo.random);
contentValues.put(TRANSFER_STATE, sourceAttachment.getTransferState()); contentValues.put(TRANSFER_STATE, sourceAttachment.getTransferState());
contentValues.put(CDN_NUMBER, sourceAttachment.getCdnNumber());
contentValues.put(CONTENT_LOCATION, sourceAttachment.getLocation()); contentValues.put(CONTENT_LOCATION, sourceAttachment.getLocation());
contentValues.put(DIGEST, sourceAttachment.getDigest()); contentValues.put(DIGEST, sourceAttachment.getDigest());
contentValues.put(CONTENT_DISPOSITION, sourceAttachment.getKey()); contentValues.put(CONTENT_DISPOSITION, sourceAttachment.getKey());
@ -630,6 +635,7 @@ public class AttachmentDatabase extends Database {
ContentValues values = new ContentValues(); ContentValues values = new ContentValues();
values.put(TRANSFER_STATE, TRANSFER_PROGRESS_DONE); values.put(TRANSFER_STATE, TRANSFER_PROGRESS_DONE);
values.put(CDN_NUMBER, attachment.getCdnNumber());
values.put(CONTENT_LOCATION, attachment.getLocation()); values.put(CONTENT_LOCATION, attachment.getLocation());
values.put(DIGEST, attachment.getDigest()); values.put(DIGEST, attachment.getDigest());
values.put(CONTENT_DISPOSITION, attachment.getKey()); values.put(CONTENT_DISPOSITION, attachment.getKey());
@ -1105,6 +1111,7 @@ public class AttachmentDatabase extends Database {
object.getInt(TRANSFER_STATE), object.getInt(TRANSFER_STATE),
object.getLong(SIZE), object.getLong(SIZE),
object.getString(FILE_NAME), object.getString(FILE_NAME),
object.getInt(CDN_NUMBER),
object.getString(CONTENT_LOCATION), object.getString(CONTENT_LOCATION),
object.getString(CONTENT_DISPOSITION), object.getString(CONTENT_DISPOSITION),
object.getString(NAME), object.getString(NAME),
@ -1138,6 +1145,7 @@ public class AttachmentDatabase extends Database {
cursor.getInt(cursor.getColumnIndexOrThrow(TRANSFER_STATE)), cursor.getInt(cursor.getColumnIndexOrThrow(TRANSFER_STATE)),
cursor.getLong(cursor.getColumnIndexOrThrow(SIZE)), cursor.getLong(cursor.getColumnIndexOrThrow(SIZE)),
cursor.getString(cursor.getColumnIndexOrThrow(FILE_NAME)), cursor.getString(cursor.getColumnIndexOrThrow(FILE_NAME)),
cursor.getInt(cursor.getColumnIndexOrThrow(CDN_NUMBER)),
cursor.getString(cursor.getColumnIndexOrThrow(CONTENT_LOCATION)), cursor.getString(cursor.getColumnIndexOrThrow(CONTENT_LOCATION)),
cursor.getString(cursor.getColumnIndexOrThrow(CONTENT_DISPOSITION)), cursor.getString(cursor.getColumnIndexOrThrow(CONTENT_DISPOSITION)),
cursor.getString(cursor.getColumnIndexOrThrow(NAME)), cursor.getString(cursor.getColumnIndexOrThrow(NAME)),
@ -1197,6 +1205,7 @@ public class AttachmentDatabase extends Database {
contentValues.put(CONTENT_TYPE, template.getContentType()); contentValues.put(CONTENT_TYPE, template.getContentType());
contentValues.put(TRANSFER_STATE, attachment.getTransferState()); contentValues.put(TRANSFER_STATE, attachment.getTransferState());
contentValues.put(UNIQUE_ID, uniqueId); contentValues.put(UNIQUE_ID, uniqueId);
contentValues.put(CDN_NUMBER, useTemplateUpload ? template.getCdnNumber() : attachment.getCdnNumber());
contentValues.put(CONTENT_LOCATION, useTemplateUpload ? template.getLocation() : attachment.getLocation()); contentValues.put(CONTENT_LOCATION, useTemplateUpload ? template.getLocation() : attachment.getLocation());
contentValues.put(DIGEST, useTemplateUpload ? template.getDigest() : attachment.getDigest()); contentValues.put(DIGEST, useTemplateUpload ? template.getDigest() : attachment.getDigest());
contentValues.put(CONTENT_DISPOSITION, useTemplateUpload ? template.getKey() : attachment.getKey()); contentValues.put(CONTENT_DISPOSITION, useTemplateUpload ? template.getKey() : attachment.getKey());

View File

@ -282,7 +282,7 @@ public final class GroupDatabase extends Database {
contentValues.put(MEMBERS, RecipientId.toSerializedList(members)); contentValues.put(MEMBERS, RecipientId.toSerializedList(members));
if (avatar != null) { if (avatar != null) {
contentValues.put(AVATAR_ID, avatar.getId()); contentValues.put(AVATAR_ID, avatar.getRemoteId().getV2().get());
contentValues.put(AVATAR_KEY, avatar.getKey()); contentValues.put(AVATAR_KEY, avatar.getKey());
contentValues.put(AVATAR_CONTENT_TYPE, avatar.getContentType()); contentValues.put(AVATAR_CONTENT_TYPE, avatar.getContentType());
contentValues.put(AVATAR_DIGEST, avatar.getDigest().orNull()); contentValues.put(AVATAR_DIGEST, avatar.getDigest().orNull());
@ -326,7 +326,7 @@ public final class GroupDatabase extends Database {
if (title != null) contentValues.put(TITLE, title); if (title != null) contentValues.put(TITLE, title);
if (avatar != null) { if (avatar != null) {
contentValues.put(AVATAR_ID, avatar.getId()); contentValues.put(AVATAR_ID, avatar.getRemoteId().getV2().get());
contentValues.put(AVATAR_CONTENT_TYPE, avatar.getContentType()); contentValues.put(AVATAR_CONTENT_TYPE, avatar.getContentType());
contentValues.put(AVATAR_KEY, avatar.getKey()); contentValues.put(AVATAR_KEY, avatar.getKey());
contentValues.put(AVATAR_DIGEST, avatar.getDigest().orNull()); contentValues.put(AVATAR_DIGEST, avatar.getDigest().orNull());

View File

@ -31,6 +31,7 @@ public class MediaDatabase extends Database {
+ AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.FILE_NAME + ", " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.FILE_NAME + ", "
+ AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.DATA + ", " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.DATA + ", "
+ AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.THUMBNAIL + ", " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.THUMBNAIL + ", "
+ AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.CDN_NUMBER + ", "
+ AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.CONTENT_LOCATION + ", " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.CONTENT_LOCATION + ", "
+ AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.CONTENT_DISPOSITION + ", " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.CONTENT_DISPOSITION + ", "
+ AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.DIGEST + ", " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.DIGEST + ", "

View File

@ -198,6 +198,7 @@ public class MmsDatabase extends MessagingDatabase {
"'" + AttachmentDatabase.DATA + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.DATA + ", " + "'" + AttachmentDatabase.DATA + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.DATA + ", " +
"'" + AttachmentDatabase.THUMBNAIL + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.THUMBNAIL + ", " + "'" + AttachmentDatabase.THUMBNAIL + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.THUMBNAIL + ", " +
"'" + AttachmentDatabase.CONTENT_TYPE + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.CONTENT_TYPE + ", " + "'" + AttachmentDatabase.CONTENT_TYPE + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.CONTENT_TYPE + ", " +
"'" + AttachmentDatabase.CDN_NUMBER + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.CDN_NUMBER + ", " +
"'" + AttachmentDatabase.CONTENT_LOCATION + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.CONTENT_LOCATION + ", " + "'" + AttachmentDatabase.CONTENT_LOCATION + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.CONTENT_LOCATION + ", " +
"'" + AttachmentDatabase.FAST_PREFLIGHT_ID + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.FAST_PREFLIGHT_ID + "," + "'" + AttachmentDatabase.FAST_PREFLIGHT_ID + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.FAST_PREFLIGHT_ID + "," +
"'" + AttachmentDatabase.VOICE_NOTE + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.VOICE_NOTE + "," + "'" + AttachmentDatabase.VOICE_NOTE + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.VOICE_NOTE + "," +

View File

@ -346,6 +346,7 @@ public class MmsSmsDatabase extends Database {
"'" + AttachmentDatabase.DATA + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.DATA + ", " + "'" + AttachmentDatabase.DATA + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.DATA + ", " +
"'" + AttachmentDatabase.THUMBNAIL + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.THUMBNAIL + ", " + "'" + AttachmentDatabase.THUMBNAIL + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.THUMBNAIL + ", " +
"'" + AttachmentDatabase.CONTENT_TYPE + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.CONTENT_TYPE + ", " + "'" + AttachmentDatabase.CONTENT_TYPE + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.CONTENT_TYPE + ", " +
"'" + AttachmentDatabase.CDN_NUMBER + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.CDN_NUMBER + ", " +
"'" + AttachmentDatabase.CONTENT_LOCATION + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.CONTENT_LOCATION + ", " + "'" + AttachmentDatabase.CONTENT_LOCATION + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.CONTENT_LOCATION + ", " +
"'" + AttachmentDatabase.FAST_PREFLIGHT_ID + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.FAST_PREFLIGHT_ID + ", " + "'" + AttachmentDatabase.FAST_PREFLIGHT_ID + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.FAST_PREFLIGHT_ID + ", " +
"'" + AttachmentDatabase.VOICE_NOTE + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.VOICE_NOTE + ", " + "'" + AttachmentDatabase.VOICE_NOTE + "', " + AttachmentDatabase.TABLE_NAME + "." + AttachmentDatabase.VOICE_NOTE + ", " +
@ -455,30 +456,6 @@ public class MmsSmsDatabase extends Database {
mmsColumnsPresent.add(MmsDatabase.STATUS); mmsColumnsPresent.add(MmsDatabase.STATUS);
mmsColumnsPresent.add(MmsDatabase.UNIDENTIFIED); mmsColumnsPresent.add(MmsDatabase.UNIDENTIFIED);
mmsColumnsPresent.add(MmsDatabase.NETWORK_FAILURE); mmsColumnsPresent.add(MmsDatabase.NETWORK_FAILURE);
mmsColumnsPresent.add(AttachmentDatabase.ROW_ID);
mmsColumnsPresent.add(AttachmentDatabase.UNIQUE_ID);
mmsColumnsPresent.add(AttachmentDatabase.MMS_ID);
mmsColumnsPresent.add(AttachmentDatabase.SIZE);
mmsColumnsPresent.add(AttachmentDatabase.FILE_NAME);
mmsColumnsPresent.add(AttachmentDatabase.DATA);
mmsColumnsPresent.add(AttachmentDatabase.THUMBNAIL);
mmsColumnsPresent.add(AttachmentDatabase.CONTENT_TYPE);
mmsColumnsPresent.add(AttachmentDatabase.CONTENT_LOCATION);
mmsColumnsPresent.add(AttachmentDatabase.DIGEST);
mmsColumnsPresent.add(AttachmentDatabase.FAST_PREFLIGHT_ID);
mmsColumnsPresent.add(AttachmentDatabase.VOICE_NOTE);
mmsColumnsPresent.add(AttachmentDatabase.WIDTH);
mmsColumnsPresent.add(AttachmentDatabase.HEIGHT);
mmsColumnsPresent.add(AttachmentDatabase.QUOTE);
mmsColumnsPresent.add(AttachmentDatabase.STICKER_PACK_ID);
mmsColumnsPresent.add(AttachmentDatabase.STICKER_PACK_KEY);
mmsColumnsPresent.add(AttachmentDatabase.STICKER_ID);
mmsColumnsPresent.add(AttachmentDatabase.CAPTION);
mmsColumnsPresent.add(AttachmentDatabase.CONTENT_DISPOSITION);
mmsColumnsPresent.add(AttachmentDatabase.NAME);
mmsColumnsPresent.add(AttachmentDatabase.TRANSFER_STATE);
mmsColumnsPresent.add(AttachmentDatabase.ATTACHMENT_JSON_ALIAS);
mmsColumnsPresent.add(MmsDatabase.QUOTE_ID); mmsColumnsPresent.add(MmsDatabase.QUOTE_ID);
mmsColumnsPresent.add(MmsDatabase.QUOTE_AUTHOR); mmsColumnsPresent.add(MmsDatabase.QUOTE_AUTHOR);
mmsColumnsPresent.add(MmsDatabase.QUOTE_BODY); mmsColumnsPresent.add(MmsDatabase.QUOTE_BODY);

View File

@ -126,8 +126,9 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
private static final int AVATAR_LOCATION_MIGRATION = 54; private static final int AVATAR_LOCATION_MIGRATION = 54;
private static final int GROUPS_V2 = 55; private static final int GROUPS_V2 = 55;
private static final int ATTACHMENT_UPLOAD_TIMESTAMP = 56; private static final int ATTACHMENT_UPLOAD_TIMESTAMP = 56;
private static final int ATTACHMENT_CDN_NUMBER = 57;
private static final int DATABASE_VERSION = 56; private static final int DATABASE_VERSION = ATTACHMENT_CDN_NUMBER;
private static final String DATABASE_NAME = "signal.db"; private static final String DATABASE_NAME = "signal.db";
private final Context context; private final Context context;
@ -863,6 +864,10 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
db.execSQL("ALTER TABLE part ADD COLUMN upload_timestamp DEFAULT 0"); db.execSQL("ALTER TABLE part ADD COLUMN upload_timestamp DEFAULT 0");
} }
if (oldVersion < ATTACHMENT_CDN_NUMBER) {
db.execSQL("ALTER TABLE part ADD COLUMN cdn_number INTEGER DEFAULT 0");
}
db.setTransactionSuccessful(); db.setTransactionSuccessful();
} finally { } finally {
db.endTransaction(); db.endTransaction();

View File

@ -13,6 +13,7 @@ import org.thoughtcrime.securesms.megaphone.MegaphoneRepository;
import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess; import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess;
import org.thoughtcrime.securesms.recipients.LiveRecipientCache; import org.thoughtcrime.securesms.recipients.LiveRecipientCache;
import org.thoughtcrime.securesms.service.IncomingMessageObserver; import org.thoughtcrime.securesms.service.IncomingMessageObserver;
import org.thoughtcrime.securesms.util.FeatureFlags;
import org.thoughtcrime.securesms.util.FrameRateTracker; import org.thoughtcrime.securesms.util.FrameRateTracker;
import org.thoughtcrime.securesms.util.IasKeyStore; import org.thoughtcrime.securesms.util.IasKeyStore;
import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.TextSecurePreferences;
@ -83,8 +84,11 @@ public class ApplicationDependencies {
if (messageSender == null) { if (messageSender == null) {
messageSender = provider.provideSignalServiceMessageSender(); messageSender = provider.provideSignalServiceMessageSender();
} else { } else {
messageSender.setMessagePipe(IncomingMessageObserver.getPipe(), IncomingMessageObserver.getUnidentifiedPipe()); messageSender.update(
messageSender.setIsMultiDevice(TextSecurePreferences.isMultiDevice(application)); IncomingMessageObserver.getPipe(),
IncomingMessageObserver.getUnidentifiedPipe(),
TextSecurePreferences.isMultiDevice(application),
FeatureFlags.attachmentsV3());
} }
return messageSender; return messageSender;

View File

@ -25,6 +25,7 @@ import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess;
import org.thoughtcrime.securesms.recipients.LiveRecipientCache; import org.thoughtcrime.securesms.recipients.LiveRecipientCache;
import org.thoughtcrime.securesms.service.IncomingMessageObserver; import org.thoughtcrime.securesms.service.IncomingMessageObserver;
import org.thoughtcrime.securesms.util.AlarmSleepTimer; import org.thoughtcrime.securesms.util.AlarmSleepTimer;
import org.thoughtcrime.securesms.util.FeatureFlags;
import org.thoughtcrime.securesms.util.FrameRateTracker; import org.thoughtcrime.securesms.util.FrameRateTracker;
import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.libsignal.util.guava.Optional;
@ -79,6 +80,7 @@ public class ApplicationDependencyProvider implements ApplicationDependencies.Pr
new SignalProtocolStoreImpl(context), new SignalProtocolStoreImpl(context),
BuildConfig.SIGNAL_AGENT, BuildConfig.SIGNAL_AGENT,
TextSecurePreferences.isMultiDevice(context), TextSecurePreferences.isMultiDevice(context),
FeatureFlags.attachmentsV3(),
Optional.fromNullable(IncomingMessageObserver.getPipe()), Optional.fromNullable(IncomingMessageObserver.getPipe()),
Optional.fromNullable(IncomingMessageObserver.getUnidentifiedPipe()), Optional.fromNullable(IncomingMessageObserver.getUnidentifiedPipe()),
Optional.of(new SecurityEventListener(context)), Optional.of(new SecurityEventListener(context)),

View File

@ -271,7 +271,7 @@ public final class GroupV1MessageProcessor {
if (group.getAvatar().isPresent() && group.getAvatar().get().isPointer()) { if (group.getAvatar().isPresent() && group.getAvatar().get().isPointer()) {
builder.setAvatar(AttachmentPointer.newBuilder() builder.setAvatar(AttachmentPointer.newBuilder()
.setId(group.getAvatar().get().asPointer().getId()) .setCdnId(group.getAvatar().get().asPointer().getRemoteId().getV2().get())
.setKey(ByteString.copyFrom(group.getAvatar().get().asPointer().getKey())) .setKey(ByteString.copyFrom(group.getAvatar().get().asPointer().getKey()))
.setContentType(group.getAvatar().get().getContentType())); .setContentType(group.getAvatar().get().getContentType()));
} }

View File

@ -1,8 +1,9 @@
package org.thoughtcrime.securesms.jobs; package org.thoughtcrime.securesms.jobs;
import android.text.TextUtils;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
import android.text.TextUtils;
import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.EventBus;
import org.thoughtcrime.securesms.attachments.Attachment; import org.thoughtcrime.securesms.attachments.Attachment;
@ -27,6 +28,7 @@ import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.libsignal.InvalidMessageException; import org.whispersystems.libsignal.InvalidMessageException;
import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.SignalServiceMessageReceiver; import org.whispersystems.signalservice.api.SignalServiceMessageReceiver;
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentRemoteId;
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer; import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer;
import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException; import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException;
import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException; import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException;
@ -182,13 +184,8 @@ public class AttachmentDownloadJob extends BaseJob {
} }
try { try {
long id = Long.parseLong(attachment.getLocation()); final SignalServiceAttachmentRemoteId remoteId = SignalServiceAttachmentRemoteId.from(attachment.getLocation());
byte[] key = Base64.decode(attachment.getKey()); final byte[] key = Base64.decode(attachment.getKey());
String relay = null;
if (TextUtils.isEmpty(attachment.getRelay())) {
relay = attachment.getRelay();
}
if (attachment.getDigest() != null) { if (attachment.getDigest() != null) {
Log.i(TAG, "Downloading attachment with digest: " + Hex.toString(attachment.getDigest())); Log.i(TAG, "Downloading attachment with digest: " + Hex.toString(attachment.getDigest()));
@ -196,7 +193,7 @@ public class AttachmentDownloadJob extends BaseJob {
Log.i(TAG, "Downloading attachment with no digest..."); Log.i(TAG, "Downloading attachment with no digest...");
} }
return new SignalServiceAttachmentPointer(id, null, key, return new SignalServiceAttachmentPointer(attachment.getCdnNumber(), remoteId, null, key,
Optional.of(Util.toIntExact(attachment.getSize())), Optional.of(Util.toIntExact(attachment.getSize())),
Optional.absent(), Optional.absent(),
0, 0, 0, 0,

View File

@ -1,7 +1,5 @@
package org.thoughtcrime.securesms.jobs; package org.thoughtcrime.securesms.jobs;
import android.graphics.Bitmap;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DatabaseFactory;
@ -13,14 +11,12 @@ import org.thoughtcrime.securesms.jobmanager.Data;
import org.thoughtcrime.securesms.jobmanager.Job; import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint; import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.mms.AttachmentStreamUriLoader.AttachmentModel;
import org.thoughtcrime.securesms.profiles.AvatarHelper; import org.thoughtcrime.securesms.profiles.AvatarHelper;
import org.thoughtcrime.securesms.util.BitmapDecodingException;
import org.thoughtcrime.securesms.util.BitmapUtil;
import org.thoughtcrime.securesms.util.Hex; import org.thoughtcrime.securesms.util.Hex;
import org.whispersystems.libsignal.InvalidMessageException; import org.whispersystems.libsignal.InvalidMessageException;
import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.SignalServiceMessageReceiver; import org.whispersystems.signalservice.api.SignalServiceMessageReceiver;
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentRemoteId;
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer; import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer;
import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException; import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException;
@ -90,7 +86,7 @@ public class AvatarDownloadJob extends BaseJob {
attachment.deleteOnExit(); attachment.deleteOnExit();
SignalServiceMessageReceiver receiver = ApplicationDependencies.getSignalServiceMessageReceiver(); SignalServiceMessageReceiver receiver = ApplicationDependencies.getSignalServiceMessageReceiver();
SignalServiceAttachmentPointer pointer = new SignalServiceAttachmentPointer(avatarId, contentType, key, Optional.of(0), Optional.absent(), 0, 0, digest, fileName, false, Optional.absent(), Optional.absent(), System.currentTimeMillis()); SignalServiceAttachmentPointer pointer = new SignalServiceAttachmentPointer(0, new SignalServiceAttachmentRemoteId(avatarId), contentType, key, Optional.of(0), Optional.absent(), 0, 0, digest, fileName, false, Optional.absent(), Optional.absent(), System.currentTimeMillis());
InputStream inputStream = receiver.retrieveAttachment(pointer, attachment, AvatarHelper.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE); InputStream inputStream = receiver.retrieveAttachment(pointer, attachment, AvatarHelper.AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE);
AvatarHelper.setAvatar(context, record.get().getRecipientId(), inputStream); AvatarHelper.setAvatar(context, record.get().getRecipientId(), inputStream);

View File

@ -2,9 +2,10 @@ package org.thoughtcrime.securesms.jobs;
import android.content.Context; import android.content.Context;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.text.TextUtils;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import android.text.TextUtils;
import com.annimon.stream.Stream; import com.annimon.stream.Stream;
@ -43,6 +44,7 @@ import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair; import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair;
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment; import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentRemoteId;
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer; import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer;
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage; import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage.Preview; import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage.Preview;
@ -184,10 +186,11 @@ public abstract class PushSendJob extends SendJob {
} }
try { try {
long id = Long.parseLong(attachment.getLocation()); final SignalServiceAttachmentRemoteId remoteId = SignalServiceAttachmentRemoteId.from(attachment.getLocation());
byte[] key = Base64.decode(attachment.getKey()); final byte[] key = Base64.decode(attachment.getKey());
return new SignalServiceAttachmentPointer(id, return new SignalServiceAttachmentPointer(attachment.getCdnNumber(),
remoteId,
attachment.getContentType(), attachment.getContentType(),
key, key,
Optional.of(Util.toIntExact(attachment.getSize())), Optional.of(Util.toIntExact(attachment.getSize())),

View File

@ -121,6 +121,16 @@ public class SignalServiceNetworkAccess {
final SignalCdnUrl omanGoogleCdn = new SignalCdnUrl("https://www.google.com.om/cdn", SERVICE_REFLECTOR_HOST, trustStore, GMAIL_CONNECTION_SPEC); final SignalCdnUrl omanGoogleCdn = new SignalCdnUrl("https://www.google.com.om/cdn", SERVICE_REFLECTOR_HOST, trustStore, GMAIL_CONNECTION_SPEC);
final SignalCdnUrl qatarGoogleCdn = new SignalCdnUrl("https://www.google.com.qa/cdn", SERVICE_REFLECTOR_HOST, trustStore, GMAIL_CONNECTION_SPEC); final SignalCdnUrl qatarGoogleCdn = new SignalCdnUrl("https://www.google.com.qa/cdn", SERVICE_REFLECTOR_HOST, trustStore, GMAIL_CONNECTION_SPEC);
final SignalCdnUrl baseGoogleCdn2 = new SignalCdnUrl("https://www.google.com/cdn2", SERVICE_REFLECTOR_HOST, trustStore, GMAIL_CONNECTION_SPEC);
final SignalCdnUrl baseAndroidCdn2 = new SignalCdnUrl("https://android.clients.google.com/cdn2", SERVICE_REFLECTOR_HOST, trustStore, PLAY_CONNECTION_SPEC);
final SignalCdnUrl mapsOneAndroidCdn2 = new SignalCdnUrl("https://clients3.google.com/cdn2", SERVICE_REFLECTOR_HOST, trustStore, GMAPS_CONNECTION_SPEC);
final SignalCdnUrl mapsTwoAndroidCdn2 = new SignalCdnUrl("https://clients4.google.com/cdn2", SERVICE_REFLECTOR_HOST, trustStore, GMAPS_CONNECTION_SPEC);
final SignalCdnUrl mailAndroidCdn2 = new SignalCdnUrl("https://inbox.google.com/cdn2", SERVICE_REFLECTOR_HOST, trustStore, GMAIL_CONNECTION_SPEC);
final SignalCdnUrl egyptGoogleCdn2 = new SignalCdnUrl("https://www.google.com.eg/cdn2", SERVICE_REFLECTOR_HOST, trustStore, GMAIL_CONNECTION_SPEC);
final SignalCdnUrl uaeGoogleCdn2 = new SignalCdnUrl("https://www.google.ae/cdn2", SERVICE_REFLECTOR_HOST, trustStore, GMAIL_CONNECTION_SPEC);
final SignalCdnUrl omanGoogleCdn2 = new SignalCdnUrl("https://www.google.com.om/cdn2", SERVICE_REFLECTOR_HOST, trustStore, GMAIL_CONNECTION_SPEC);
final SignalCdnUrl qatarGoogleCdn2 = new SignalCdnUrl("https://www.google.com.qa/cdn2", SERVICE_REFLECTOR_HOST, trustStore, GMAIL_CONNECTION_SPEC);
final SignalContactDiscoveryUrl baseGoogleDiscovery = new SignalContactDiscoveryUrl("https://www.google.com/directory", SERVICE_REFLECTOR_HOST, trustStore, GMAIL_CONNECTION_SPEC); final SignalContactDiscoveryUrl baseGoogleDiscovery = new SignalContactDiscoveryUrl("https://www.google.com/directory", SERVICE_REFLECTOR_HOST, trustStore, GMAIL_CONNECTION_SPEC);
final SignalContactDiscoveryUrl baseAndroidDiscovery = new SignalContactDiscoveryUrl("https://android.clients.google.com/directory", SERVICE_REFLECTOR_HOST, trustStore, PLAY_CONNECTION_SPEC); final SignalContactDiscoveryUrl baseAndroidDiscovery = new SignalContactDiscoveryUrl("https://android.clients.google.com/directory", SERVICE_REFLECTOR_HOST, trustStore, PLAY_CONNECTION_SPEC);
final SignalContactDiscoveryUrl mapsOneAndroidDiscovery = new SignalContactDiscoveryUrl("https://clients3.google.com/directory", SERVICE_REFLECTOR_HOST, trustStore, GMAPS_CONNECTION_SPEC); final SignalContactDiscoveryUrl mapsOneAndroidDiscovery = new SignalContactDiscoveryUrl("https://clients3.google.com/directory", SERVICE_REFLECTOR_HOST, trustStore, GMAPS_CONNECTION_SPEC);
@ -165,6 +175,7 @@ public class SignalServiceNetworkAccess {
this.censorshipConfiguration = new HashMap<String, SignalServiceConfiguration>() {{ this.censorshipConfiguration = new HashMap<String, SignalServiceConfiguration>() {{
put(COUNTRY_CODE_EGYPT, new SignalServiceConfiguration(new SignalServiceUrl[] {egyptGoogleService, baseGoogleService, baseAndroidService, mapsOneAndroidService, mapsTwoAndroidService, mailAndroidService}, put(COUNTRY_CODE_EGYPT, new SignalServiceConfiguration(new SignalServiceUrl[] {egyptGoogleService, baseGoogleService, baseAndroidService, mapsOneAndroidService, mapsTwoAndroidService, mailAndroidService},
new SignalCdnUrl[] {egyptGoogleCdn, baseAndroidCdn, baseGoogleCdn, mapsOneAndroidCdn, mapsTwoAndroidCdn, mailAndroidCdn, mailAndroidCdn}, new SignalCdnUrl[] {egyptGoogleCdn, baseAndroidCdn, baseGoogleCdn, mapsOneAndroidCdn, mapsTwoAndroidCdn, mailAndroidCdn, mailAndroidCdn},
new SignalCdnUrl[] {egyptGoogleCdn2, baseAndroidCdn2, baseGoogleCdn2, mapsOneAndroidCdn2, mapsTwoAndroidCdn2, mailAndroidCdn2, mailAndroidCdn2},
new SignalContactDiscoveryUrl[] {egyptGoogleDiscovery, baseGoogleDiscovery, baseAndroidDiscovery, mapsOneAndroidDiscovery, mapsTwoAndroidDiscovery, mailAndroidDiscovery}, new SignalContactDiscoveryUrl[] {egyptGoogleDiscovery, baseGoogleDiscovery, baseAndroidDiscovery, mapsOneAndroidDiscovery, mapsTwoAndroidDiscovery, mailAndroidDiscovery},
new SignalKeyBackupServiceUrl[] {egyptGoogleKbs, baseGoogleKbs, baseAndroidKbs, mapsOneAndroidKbs, mapsTwoAndroidKbs, mailAndroidKbs}, new SignalKeyBackupServiceUrl[] {egyptGoogleKbs, baseGoogleKbs, baseAndroidKbs, mapsOneAndroidKbs, mapsTwoAndroidKbs, mailAndroidKbs},
new SignalStorageUrl[] {egyptGoogleStorage, baseGoogleStorage, baseAndroidStorage, mapsOneAndroidStorage, mapsTwoAndroidStorage, mailAndroidStorage}, new SignalStorageUrl[] {egyptGoogleStorage, baseGoogleStorage, baseAndroidStorage, mapsOneAndroidStorage, mapsTwoAndroidStorage, mailAndroidStorage},
@ -174,6 +185,7 @@ public class SignalServiceNetworkAccess {
put(COUNTRY_CODE_UAE, new SignalServiceConfiguration(new SignalServiceUrl[] {uaeGoogleService, baseAndroidService, baseGoogleService, mapsOneAndroidService, mapsTwoAndroidService, mailAndroidService}, put(COUNTRY_CODE_UAE, new SignalServiceConfiguration(new SignalServiceUrl[] {uaeGoogleService, baseAndroidService, baseGoogleService, mapsOneAndroidService, mapsTwoAndroidService, mailAndroidService},
new SignalCdnUrl[] {uaeGoogleCdn, baseAndroidCdn, baseGoogleCdn, mapsOneAndroidCdn, mapsTwoAndroidCdn, mailAndroidCdn}, new SignalCdnUrl[] {uaeGoogleCdn, baseAndroidCdn, baseGoogleCdn, mapsOneAndroidCdn, mapsTwoAndroidCdn, mailAndroidCdn},
new SignalCdnUrl[] {uaeGoogleCdn2, baseAndroidCdn2, baseGoogleCdn2, mapsOneAndroidCdn2, mapsTwoAndroidCdn2, mailAndroidCdn2},
new SignalContactDiscoveryUrl[] {uaeGoogleDiscovery, baseGoogleDiscovery, baseAndroidDiscovery, mapsOneAndroidDiscovery, mapsTwoAndroidDiscovery, mailAndroidDiscovery}, new SignalContactDiscoveryUrl[] {uaeGoogleDiscovery, baseGoogleDiscovery, baseAndroidDiscovery, mapsOneAndroidDiscovery, mapsTwoAndroidDiscovery, mailAndroidDiscovery},
new SignalKeyBackupServiceUrl[] {uaeGoogleKbs, baseGoogleKbs, baseAndroidKbs, mapsOneAndroidKbs, mapsTwoAndroidKbs, mailAndroidKbs}, new SignalKeyBackupServiceUrl[] {uaeGoogleKbs, baseGoogleKbs, baseAndroidKbs, mapsOneAndroidKbs, mapsTwoAndroidKbs, mailAndroidKbs},
new SignalStorageUrl[] {uaeGoogleStorage, baseGoogleStorage, baseAndroidStorage, mapsOneAndroidStorage, mapsTwoAndroidStorage, mailAndroidStorage}, new SignalStorageUrl[] {uaeGoogleStorage, baseGoogleStorage, baseAndroidStorage, mapsOneAndroidStorage, mapsTwoAndroidStorage, mailAndroidStorage},
@ -183,6 +195,7 @@ public class SignalServiceNetworkAccess {
put(COUNTRY_CODE_OMAN, new SignalServiceConfiguration(new SignalServiceUrl[] {omanGoogleService, baseAndroidService, baseGoogleService, mapsOneAndroidService, mapsTwoAndroidService, mailAndroidService}, put(COUNTRY_CODE_OMAN, new SignalServiceConfiguration(new SignalServiceUrl[] {omanGoogleService, baseAndroidService, baseGoogleService, mapsOneAndroidService, mapsTwoAndroidService, mailAndroidService},
new SignalCdnUrl[] {omanGoogleCdn, baseAndroidCdn, baseGoogleCdn, mapsOneAndroidCdn, mapsTwoAndroidCdn, mailAndroidCdn}, new SignalCdnUrl[] {omanGoogleCdn, baseAndroidCdn, baseGoogleCdn, mapsOneAndroidCdn, mapsTwoAndroidCdn, mailAndroidCdn},
new SignalCdnUrl[] {omanGoogleCdn2, baseAndroidCdn2, baseGoogleCdn2, mapsOneAndroidCdn2, mapsTwoAndroidCdn2, mailAndroidCdn2},
new SignalContactDiscoveryUrl[] {omanGoogleDiscovery, baseGoogleDiscovery, baseAndroidDiscovery, mapsOneAndroidDiscovery, mapsTwoAndroidDiscovery, mailAndroidDiscovery}, new SignalContactDiscoveryUrl[] {omanGoogleDiscovery, baseGoogleDiscovery, baseAndroidDiscovery, mapsOneAndroidDiscovery, mapsTwoAndroidDiscovery, mailAndroidDiscovery},
new SignalKeyBackupServiceUrl[] {omanGoogleKbs, baseGoogleKbs, baseAndroidKbs, mapsOneAndroidKbs, mapsTwoAndroidKbs, mailAndroidKbs}, new SignalKeyBackupServiceUrl[] {omanGoogleKbs, baseGoogleKbs, baseAndroidKbs, mapsOneAndroidKbs, mapsTwoAndroidKbs, mailAndroidKbs},
new SignalStorageUrl[] {omanGoogleStorage, baseGoogleStorage, baseAndroidStorage, mapsOneAndroidStorage, mapsTwoAndroidStorage, mailAndroidStorage}, new SignalStorageUrl[] {omanGoogleStorage, baseGoogleStorage, baseAndroidStorage, mapsOneAndroidStorage, mapsTwoAndroidStorage, mailAndroidStorage},
@ -193,6 +206,7 @@ public class SignalServiceNetworkAccess {
put(COUNTRY_CODE_QATAR, new SignalServiceConfiguration(new SignalServiceUrl[] {qatarGoogleService, baseAndroidService, baseGoogleService, mapsOneAndroidService, mapsTwoAndroidService, mailAndroidService}, put(COUNTRY_CODE_QATAR, new SignalServiceConfiguration(new SignalServiceUrl[] {qatarGoogleService, baseAndroidService, baseGoogleService, mapsOneAndroidService, mapsTwoAndroidService, mailAndroidService},
new SignalCdnUrl[] {qatarGoogleCdn, baseAndroidCdn, baseGoogleCdn, mapsOneAndroidCdn, mapsTwoAndroidCdn, mailAndroidCdn}, new SignalCdnUrl[] {qatarGoogleCdn, baseAndroidCdn, baseGoogleCdn, mapsOneAndroidCdn, mapsTwoAndroidCdn, mailAndroidCdn},
new SignalCdnUrl[] {qatarGoogleCdn2, baseAndroidCdn2, baseGoogleCdn2, mapsOneAndroidCdn2, mapsTwoAndroidCdn2, mailAndroidCdn2},
new SignalContactDiscoveryUrl[] {qatarGoogleDiscovery, baseGoogleDiscovery, baseAndroidDiscovery, mapsOneAndroidDiscovery, mapsTwoAndroidDiscovery, mailAndroidDiscovery}, new SignalContactDiscoveryUrl[] {qatarGoogleDiscovery, baseGoogleDiscovery, baseAndroidDiscovery, mapsOneAndroidDiscovery, mapsTwoAndroidDiscovery, mailAndroidDiscovery},
new SignalKeyBackupServiceUrl[] {qatarGoogleKbs, baseGoogleKbs, baseAndroidKbs, mapsOneAndroidKbs, mapsTwoAndroidKbs, mailAndroidKbs}, new SignalKeyBackupServiceUrl[] {qatarGoogleKbs, baseGoogleKbs, baseAndroidKbs, mapsOneAndroidKbs, mapsTwoAndroidKbs, mailAndroidKbs},
new SignalStorageUrl[] {qatarGoogleStorage, baseGoogleStorage, baseAndroidStorage, mapsOneAndroidStorage, mapsTwoAndroidStorage, mailAndroidStorage}, new SignalStorageUrl[] {qatarGoogleStorage, baseGoogleStorage, baseAndroidStorage, mapsOneAndroidStorage, mapsTwoAndroidStorage, mailAndroidStorage},
@ -203,6 +217,7 @@ public class SignalServiceNetworkAccess {
this.uncensoredConfiguration = new SignalServiceConfiguration(new SignalServiceUrl[] {new SignalServiceUrl(BuildConfig.SIGNAL_URL, new SignalServiceTrustStore(context))}, this.uncensoredConfiguration = new SignalServiceConfiguration(new SignalServiceUrl[] {new SignalServiceUrl(BuildConfig.SIGNAL_URL, new SignalServiceTrustStore(context))},
new SignalCdnUrl[] {new SignalCdnUrl(BuildConfig.SIGNAL_CDN_URL, new SignalServiceTrustStore(context))}, new SignalCdnUrl[] {new SignalCdnUrl(BuildConfig.SIGNAL_CDN_URL, new SignalServiceTrustStore(context))},
new SignalCdnUrl[] {new SignalCdnUrl(BuildConfig.SIGNAL_CDN2_URL, new SignalServiceTrustStore(context))},
new SignalContactDiscoveryUrl[] {new SignalContactDiscoveryUrl(BuildConfig.SIGNAL_CONTACT_DISCOVERY_URL, new SignalServiceTrustStore(context))}, new SignalContactDiscoveryUrl[] {new SignalContactDiscoveryUrl(BuildConfig.SIGNAL_CONTACT_DISCOVERY_URL, new SignalServiceTrustStore(context))},
new SignalKeyBackupServiceUrl[] { new SignalKeyBackupServiceUrl(BuildConfig.SIGNAL_KEY_BACKUP_URL, new SignalServiceTrustStore(context)) }, new SignalKeyBackupServiceUrl[] { new SignalKeyBackupServiceUrl(BuildConfig.SIGNAL_KEY_BACKUP_URL, new SignalServiceTrustStore(context)) },
new SignalStorageUrl[] {new SignalStorageUrl(BuildConfig.STORAGE_URL, new SignalServiceTrustStore(context))}, new SignalStorageUrl[] {new SignalStorageUrl(BuildConfig.STORAGE_URL, new SignalServiceTrustStore(context))},

View File

@ -54,6 +54,7 @@ public final class FeatureFlags {
private static final String PINS_MEGAPHONE_KILL_SWITCH = "android.pinsMegaphoneKillSwitch"; private static final String PINS_MEGAPHONE_KILL_SWITCH = "android.pinsMegaphoneKillSwitch";
private static final String PROFILE_NAMES_MEGAPHONE = "android.profileNamesMegaphone"; private static final String PROFILE_NAMES_MEGAPHONE = "android.profileNamesMegaphone";
private static final String STORAGE_SERVICE = "android.storageService.2"; private static final String STORAGE_SERVICE = "android.storageService.2";
private static final String ATTACHMENTS_V3 = "android.attachmentsV3";
/** /**
* We will only store remote values for flags in this set. If you want a flag to be controllable * We will only store remote values for flags in this set. If you want a flag to be controllable
@ -66,7 +67,8 @@ public final class FeatureFlags {
PINS_MEGAPHONE_KILL_SWITCH, PINS_MEGAPHONE_KILL_SWITCH,
PROFILE_NAMES_MEGAPHONE, PROFILE_NAMES_MEGAPHONE,
MESSAGE_REQUESTS, MESSAGE_REQUESTS,
STORAGE_SERVICE STORAGE_SERVICE,
ATTACHMENTS_V3
); );
/** /**
@ -88,7 +90,8 @@ public final class FeatureFlags {
*/ */
private static final Set<String> HOT_SWAPPABLE = Sets.newHashSet( private static final Set<String> HOT_SWAPPABLE = Sets.newHashSet(
PINS_MEGAPHONE_KILL_SWITCH, PINS_MEGAPHONE_KILL_SWITCH,
STORAGE_SERVICE STORAGE_SERVICE,
ATTACHMENTS_V3
); );
/** /**
@ -213,6 +216,11 @@ public final class FeatureFlags {
return getValue(STORAGE_SERVICE, false); return getValue(STORAGE_SERVICE, false);
} }
/** Whether or not we use the attachments v3 form. */
public static boolean attachmentsV3() {
return getValue(ATTACHMENTS_V3, false);
}
/** Only for rendering debug info. */ /** Only for rendering debug info. */
public static synchronized @NonNull Map<String, Boolean> getMemoryValues() { public static synchronized @NonNull Map<String, Boolean> getMemoryValues() {
return new TreeMap<>(REMOTE_VALUES); return new TreeMap<>(REMOTE_VALUES);

View File

@ -26,7 +26,8 @@ import org.whispersystems.signalservice.api.profiles.ProfileAndCredential;
import org.whispersystems.signalservice.api.profiles.SignalServiceProfile; import org.whispersystems.signalservice.api.profiles.SignalServiceProfile;
import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.api.util.CredentialsProvider; import org.whispersystems.signalservice.api.util.CredentialsProvider;
import org.whispersystems.signalservice.internal.push.AttachmentUploadAttributes; import org.whispersystems.signalservice.internal.push.AttachmentV2UploadAttributes;
import org.whispersystems.signalservice.internal.push.AttachmentV3UploadAttributes;
import org.whispersystems.signalservice.internal.push.OutgoingPushMessageList; import org.whispersystems.signalservice.internal.push.OutgoingPushMessageList;
import org.whispersystems.signalservice.internal.push.SendMessageResponse; import org.whispersystems.signalservice.internal.push.SendMessageResponse;
import org.whispersystems.signalservice.internal.util.JsonUtil; import org.whispersystems.signalservice.internal.util.JsonUtil;
@ -219,7 +220,7 @@ public class SignalServiceMessagePipe {
} }
} }
public AttachmentUploadAttributes getAttachmentUploadAttributes() throws IOException { public AttachmentV2UploadAttributes getAttachmentV2UploadAttributes() throws IOException {
try { try {
WebSocketRequestMessage requestMessage = WebSocketRequestMessage.newBuilder() WebSocketRequestMessage requestMessage = WebSocketRequestMessage.newBuilder()
.setId(new SecureRandom().nextLong()) .setId(new SecureRandom().nextLong())
@ -233,7 +234,27 @@ public class SignalServiceMessagePipe {
throw new IOException("Non-successful response: " + response.first()); throw new IOException("Non-successful response: " + response.first());
} }
return JsonUtil.fromJson(response.second(), AttachmentUploadAttributes.class); return JsonUtil.fromJson(response.second(), AttachmentV2UploadAttributes.class);
} catch (InterruptedException | ExecutionException | TimeoutException e) {
throw new IOException(e);
}
}
public AttachmentV3UploadAttributes getAttachmentV3UploadAttributes() throws IOException {
try {
WebSocketRequestMessage requestMessage = WebSocketRequestMessage.newBuilder()
.setId(new SecureRandom().nextLong())
.setVerb("GET")
.setPath("/v3/attachments/form/upload")
.build();
Pair<Integer, String> response = websocket.sendRequest(requestMessage).get(10, TimeUnit.SECONDS);
if (response.first() < 200 || response.first() >= 300) {
throw new IOException("Non-successful response: " + response.first());
}
return JsonUtil.fromJson(response.second(), AttachmentV3UploadAttributes.class);
} catch (InterruptedException | ExecutionException | TimeoutException e) { } catch (InterruptedException | ExecutionException | TimeoutException e) {
throw new IOException(e); throw new IOException(e);
} }

View File

@ -178,7 +178,7 @@ public class SignalServiceMessageReceiver {
{ {
if (!pointer.getDigest().isPresent()) throw new InvalidMessageException("No attachment digest!"); if (!pointer.getDigest().isPresent()) throw new InvalidMessageException("No attachment digest!");
socket.retrieveAttachment(pointer.getId(), destination, maxSizeBytes, listener); socket.retrieveAttachment(pointer.getCdnNumber(), pointer.getRemoteId(), destination, maxSizeBytes, listener);
return AttachmentCipherInputStream.createForAttachment(destination, pointer.getSize().or(0), pointer.getKey(), pointer.getDigest().get()); return AttachmentCipherInputStream.createForAttachment(destination, pointer.getSize().or(0), pointer.getKey(), pointer.getDigest().get());
} }

View File

@ -25,6 +25,7 @@ import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
import org.whispersystems.signalservice.api.groupsv2.ClientZkOperations; import org.whispersystems.signalservice.api.groupsv2.ClientZkOperations;
import org.whispersystems.signalservice.api.messages.SendMessageResult; import org.whispersystems.signalservice.api.messages.SendMessageResult;
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment; import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentRemoteId;
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer; import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer;
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentStream; import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentStream;
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage; import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
@ -50,12 +51,14 @@ import org.whispersystems.signalservice.api.messages.multidevice.ViewOnceOpenMes
import org.whispersystems.signalservice.api.messages.shared.SharedContact; import org.whispersystems.signalservice.api.messages.shared.SharedContact;
import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.api.push.exceptions.AuthorizationFailedException; import org.whispersystems.signalservice.api.push.exceptions.AuthorizationFailedException;
import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException;
import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException; import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException;
import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException; import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException;
import org.whispersystems.signalservice.api.util.CredentialsProvider; import org.whispersystems.signalservice.api.util.CredentialsProvider;
import org.whispersystems.signalservice.internal.configuration.SignalServiceConfiguration; import org.whispersystems.signalservice.internal.configuration.SignalServiceConfiguration;
import org.whispersystems.signalservice.internal.crypto.PaddingInputStream; import org.whispersystems.signalservice.internal.crypto.PaddingInputStream;
import org.whispersystems.signalservice.internal.push.AttachmentUploadAttributes; import org.whispersystems.signalservice.internal.push.AttachmentV2UploadAttributes;
import org.whispersystems.signalservice.internal.push.AttachmentV3UploadAttributes;
import org.whispersystems.signalservice.internal.push.MismatchedDevices; import org.whispersystems.signalservice.internal.push.MismatchedDevices;
import org.whispersystems.signalservice.internal.push.OutgoingPushMessage; import org.whispersystems.signalservice.internal.push.OutgoingPushMessage;
import org.whispersystems.signalservice.internal.push.OutgoingPushMessageList; import org.whispersystems.signalservice.internal.push.OutgoingPushMessageList;
@ -110,6 +113,7 @@ public class SignalServiceMessageSender {
private final AtomicReference<Optional<SignalServiceMessagePipe>> pipe; private final AtomicReference<Optional<SignalServiceMessagePipe>> pipe;
private final AtomicReference<Optional<SignalServiceMessagePipe>> unidentifiedPipe; private final AtomicReference<Optional<SignalServiceMessagePipe>> unidentifiedPipe;
private final AtomicBoolean isMultiDevice; private final AtomicBoolean isMultiDevice;
private final AtomicBoolean attachmentsV3;
/** /**
* Construct a SignalServiceMessageSender. * Construct a SignalServiceMessageSender.
@ -127,12 +131,13 @@ public class SignalServiceMessageSender {
SignalProtocolStore store, SignalProtocolStore store,
String signalAgent, String signalAgent,
boolean isMultiDevice, boolean isMultiDevice,
boolean attachmentsV3,
Optional<SignalServiceMessagePipe> pipe, Optional<SignalServiceMessagePipe> pipe,
Optional<SignalServiceMessagePipe> unidentifiedPipe, Optional<SignalServiceMessagePipe> unidentifiedPipe,
Optional<EventListener> eventListener, Optional<EventListener> eventListener,
ClientZkProfileOperations clientZkProfileOperations) ClientZkProfileOperations clientZkProfileOperations)
{ {
this(urls, new StaticCredentialsProvider(uuid, e164, password, null), store, signalAgent, isMultiDevice, pipe, unidentifiedPipe, eventListener, clientZkProfileOperations); this(urls, new StaticCredentialsProvider(uuid, e164, password, null), store, signalAgent, isMultiDevice, attachmentsV3, pipe, unidentifiedPipe, eventListener, clientZkProfileOperations);
} }
public SignalServiceMessageSender(SignalServiceConfiguration urls, public SignalServiceMessageSender(SignalServiceConfiguration urls,
@ -140,6 +145,7 @@ public class SignalServiceMessageSender {
SignalProtocolStore store, SignalProtocolStore store,
String signalAgent, String signalAgent,
boolean isMultiDevice, boolean isMultiDevice,
boolean attachmentsV3,
Optional<SignalServiceMessagePipe> pipe, Optional<SignalServiceMessagePipe> pipe,
Optional<SignalServiceMessagePipe> unidentifiedPipe, Optional<SignalServiceMessagePipe> unidentifiedPipe,
Optional<EventListener> eventListener, Optional<EventListener> eventListener,
@ -151,6 +157,7 @@ public class SignalServiceMessageSender {
this.pipe = new AtomicReference<>(pipe); this.pipe = new AtomicReference<>(pipe);
this.unidentifiedPipe = new AtomicReference<>(unidentifiedPipe); this.unidentifiedPipe = new AtomicReference<>(unidentifiedPipe);
this.isMultiDevice = new AtomicBoolean(isMultiDevice); this.isMultiDevice = new AtomicBoolean(isMultiDevice);
this.attachmentsV3 = new AtomicBoolean(attachmentsV3);
this.eventListener = eventListener; this.eventListener = eventListener;
} }
@ -336,13 +343,11 @@ public class SignalServiceMessageSender {
socket.cancelInFlightRequests(); socket.cancelInFlightRequests();
} }
public void setMessagePipe(SignalServiceMessagePipe pipe, SignalServiceMessagePipe unidentifiedPipe) { public void update(SignalServiceMessagePipe pipe, SignalServiceMessagePipe unidentifiedPipe, boolean isMultiDevice, boolean attachmentsV3) {
this.pipe.set(Optional.fromNullable(pipe)); this.pipe.set(Optional.fromNullable(pipe));
this.unidentifiedPipe.set(Optional.fromNullable(unidentifiedPipe)); this.unidentifiedPipe.set(Optional.fromNullable(unidentifiedPipe));
}
public void setIsMultiDevice(boolean isMultiDevice) {
this.isMultiDevice.set(isMultiDevice); this.isMultiDevice.set(isMultiDevice);
this.attachmentsV3.set(attachmentsV3);
} }
public SignalServiceAttachmentPointer uploadAttachment(SignalServiceAttachmentStream attachment) throws IOException { public SignalServiceAttachmentPointer uploadAttachment(SignalServiceAttachmentStream attachment) throws IOException {
@ -357,26 +362,35 @@ public class SignalServiceMessageSender {
attachment.getListener(), attachment.getListener(),
attachment.getCancelationSignal()); attachment.getCancelationSignal());
AttachmentUploadAttributes uploadAttributes = null; if (attachmentsV3.get()) {
return uploadAttachmentV3(attachment, attachmentKey, attachmentData);
} else {
return uploadAttachmentV2(attachment, attachmentKey, attachmentData);
}
}
private SignalServiceAttachmentPointer uploadAttachmentV2(SignalServiceAttachmentStream attachment, byte[] attachmentKey, PushAttachmentData attachmentData) throws NonSuccessfulResponseCodeException, PushNetworkException {
AttachmentV2UploadAttributes v2UploadAttributes = null;
Optional<SignalServiceMessagePipe> localPipe = pipe.get(); Optional<SignalServiceMessagePipe> localPipe = pipe.get();
if (localPipe.isPresent()) { if (localPipe.isPresent()) {
Log.d(TAG, "Using pipe to retrieve attachment upload attributes..."); Log.d(TAG, "Using pipe to retrieve attachment upload attributes...");
try { try {
uploadAttributes = localPipe.get().getAttachmentUploadAttributes(); v2UploadAttributes = localPipe.get().getAttachmentV2UploadAttributes();
} catch (IOException e) { } catch (IOException e) {
Log.w(TAG, "Failed to retrieve attachment upload attributes using pipe. Falling back..."); Log.w(TAG, "Failed to retrieve attachment upload attributes using pipe. Falling back...");
} }
} }
if (uploadAttributes == null) { if (v2UploadAttributes == null) {
Log.d(TAG, "Not using pipe to retrieve attachment upload attributes..."); Log.d(TAG, "Not using pipe to retrieve attachment upload attributes...");
uploadAttributes = socket.getAttachmentUploadAttributes(); v2UploadAttributes = socket.getAttachmentV2UploadAttributes();
} }
Pair<Long, byte[]> attachmentIdAndDigest = socket.uploadAttachment(attachmentData, uploadAttributes); Pair<Long, byte[]> attachmentIdAndDigest = socket.uploadAttachment(attachmentData, v2UploadAttributes);
return new SignalServiceAttachmentPointer(attachmentIdAndDigest.first(), return new SignalServiceAttachmentPointer(0,
new SignalServiceAttachmentRemoteId(attachmentIdAndDigest.first()),
attachment.getContentType(), attachment.getContentType(),
attachmentKey, attachmentKey,
Optional.of(Util.toIntExact(attachment.getLength())), Optional.of(Util.toIntExact(attachment.getLength())),
@ -390,6 +404,41 @@ public class SignalServiceMessageSender {
attachment.getUploadTimestamp()); attachment.getUploadTimestamp());
} }
private SignalServiceAttachmentPointer uploadAttachmentV3(SignalServiceAttachmentStream attachment, byte[] attachmentKey, PushAttachmentData attachmentData) throws IOException {
AttachmentV3UploadAttributes v3UploadAttributes = null;
Optional<SignalServiceMessagePipe> localPipe = pipe.get();
if (localPipe.isPresent()) {
Log.d(TAG, "Using pipe to retrieve attachment upload attributes...");
try {
v3UploadAttributes = localPipe.get().getAttachmentV3UploadAttributes();
} catch (IOException e) {
Log.w(TAG, "Failed to retrieve attachment upload attributes using pipe. Falling back...");
}
}
if (v3UploadAttributes == null) {
Log.d(TAG, "Not using pipe to retrieve attachment upload attributes...");
v3UploadAttributes = socket.getAttachmentV3UploadAttributes();
}
byte[] digest = socket.uploadAttachment(attachmentData, v3UploadAttributes);
return new SignalServiceAttachmentPointer(v3UploadAttributes.getCdn(),
new SignalServiceAttachmentRemoteId(v3UploadAttributes.getKey()),
attachment.getContentType(),
attachmentKey,
Optional.of(Util.toIntExact(attachment.getLength())),
attachment.getPreview(),
attachment.getWidth(),
attachment.getHeight(),
Optional.of(digest),
attachment.getFileName(),
attachment.getVoiceNote(),
attachment.getCaption(),
attachment.getBlurHash(),
attachment.getUploadTimestamp());
}
private void sendMessage(VerifiedMessage message, Optional<UnidentifiedAccessPair> unidentifiedAccess) private void sendMessage(VerifiedMessage message, Optional<UnidentifiedAccessPair> unidentifiedAccess)
throws IOException, UntrustedIdentityException throws IOException, UntrustedIdentityException
@ -1205,12 +1254,20 @@ public class SignalServiceMessageSender {
private AttachmentPointer createAttachmentPointer(SignalServiceAttachmentPointer attachment) { private AttachmentPointer createAttachmentPointer(SignalServiceAttachmentPointer attachment) {
AttachmentPointer.Builder builder = AttachmentPointer.newBuilder() AttachmentPointer.Builder builder = AttachmentPointer.newBuilder()
.setCdnNumber(attachment.getCdnNumber())
.setContentType(attachment.getContentType()) .setContentType(attachment.getContentType())
.setId(attachment.getId())
.setKey(ByteString.copyFrom(attachment.getKey())) .setKey(ByteString.copyFrom(attachment.getKey()))
.setDigest(ByteString.copyFrom(attachment.getDigest().get())) .setDigest(ByteString.copyFrom(attachment.getDigest().get()))
.setSize(attachment.getSize().get()); .setSize(attachment.getSize().get());
if (attachment.getRemoteId().getV2().isPresent()) {
builder.setCdnId(attachment.getRemoteId().getV2().get());
}
if (attachment.getRemoteId().getV3().isPresent()) {
builder.setCdnKey(attachment.getRemoteId().getV3().get());
}
if (attachment.getFileName().isPresent()) { if (attachment.getFileName().isPresent()) {
builder.setFileName(attachment.getFileName().get()); builder.setFileName(attachment.getFileName().get());
} }
@ -1245,8 +1302,7 @@ public class SignalServiceMessageSender {
private AttachmentPointer createAttachmentPointer(SignalServiceAttachmentStream attachment) private AttachmentPointer createAttachmentPointer(SignalServiceAttachmentStream attachment)
throws IOException throws IOException
{ {
SignalServiceAttachmentPointer pointer = uploadAttachment(attachment); return createAttachmentPointer(uploadAttachment(attachment));
return createAttachmentPointer(pointer);
} }

View File

@ -18,7 +18,8 @@ import org.whispersystems.signalservice.api.SignalServiceMessageReceiver;
*/ */
public class SignalServiceAttachmentPointer extends SignalServiceAttachment { public class SignalServiceAttachmentPointer extends SignalServiceAttachment {
private final long id; private final int cdnNumber;
private final SignalServiceAttachmentRemoteId remoteId;
private final byte[] key; private final byte[] key;
private final Optional<Integer> size; private final Optional<Integer> size;
private final Optional<byte[]> preview; private final Optional<byte[]> preview;
@ -31,15 +32,17 @@ public class SignalServiceAttachmentPointer extends SignalServiceAttachment {
private final Optional<String> blurHash; private final Optional<String> blurHash;
private final long uploadTimestamp; private final long uploadTimestamp;
public SignalServiceAttachmentPointer(long id, String contentType, byte[] key, public SignalServiceAttachmentPointer(int cdnNumber, SignalServiceAttachmentRemoteId remoteId,
Optional<Integer> size, Optional<byte[]> preview, String contentType, byte[] key,
int width, int height, Optional<Integer> size, Optional<byte[]> preview, int width,
Optional<byte[]> digest, Optional<String> fileName, int height, Optional<byte[]> digest,
boolean voiceNote, Optional<String> caption, Optional<String> fileName, boolean voiceNote,
Optional<String> blurHash, long uploadTimestamp) Optional<String> caption, Optional<String> blurHash,
long uploadTimestamp)
{ {
super(contentType); super(contentType);
this.id = id; this.cdnNumber = cdnNumber;
this.remoteId = remoteId;
this.key = key; this.key = key;
this.size = size; this.size = size;
this.preview = preview; this.preview = preview;
@ -53,8 +56,12 @@ public class SignalServiceAttachmentPointer extends SignalServiceAttachment {
this.uploadTimestamp = uploadTimestamp; this.uploadTimestamp = uploadTimestamp;
} }
public long getId() { public int getCdnNumber() {
return id; return cdnNumber;
}
public SignalServiceAttachmentRemoteId getRemoteId() {
return remoteId;
} }
public byte[] getKey() { public byte[] getKey() {

View File

@ -0,0 +1,69 @@
package org.whispersystems.signalservice.api.messages;
import org.signal.libsignal.metadata.ProtocolInvalidMessageException;
import org.whispersystems.libsignal.InvalidMessageException;
import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.AttachmentPointer;
/**
* Represents a signal service attachment identifier. This can be either a CDN key or a long, but
* not both at once. Attachments V2 used a long as an attachment identifier. This lacks sufficient
* entropy to reduce the likelihood of any two uploads going to the same location within a 30-day
* window. Attachments V3 uses an opaque string as an attachment identifier which provides more
* flexibility in the amount of entropy present.
*/
public final class SignalServiceAttachmentRemoteId {
private final Optional<Long> v2;
private final Optional<String> v3;
public SignalServiceAttachmentRemoteId(long v2) {
this.v2 = Optional.of(v2);
this.v3 = Optional.absent();
}
public SignalServiceAttachmentRemoteId(String v3) {
this.v2 = Optional.absent();
this.v3 = Optional.of(v3);
}
public Optional<Long> getV2() {
return v2;
}
public Optional<String> getV3() {
return v3;
}
@Override
public String toString() {
if (v2.isPresent()) {
return v2.get().toString();
} else {
return v3.get();
}
}
public static SignalServiceAttachmentRemoteId from(AttachmentPointer attachmentPointer) throws ProtocolInvalidMessageException {
switch (attachmentPointer.getAttachmentIdentifierCase()) {
case CDNID:
return new SignalServiceAttachmentRemoteId(attachmentPointer.getCdnId());
case CDNKEY:
return new SignalServiceAttachmentRemoteId(attachmentPointer.getCdnKey());
case ATTACHMENTIDENTIFIER_NOT_SET:
throw new ProtocolInvalidMessageException(new InvalidMessageException("AttachmentPointer CDN location not set"), null, 0);
}
return null;
}
/**
* Guesses that strings which contain values parseable to {@code long} should use an id-based
* CDN path. Otherwise, use key-based CDN path.
*/
public static SignalServiceAttachmentRemoteId from(String string) {
try {
return new SignalServiceAttachmentRemoteId(Long.parseLong(string));
} catch (NumberFormatException e) {
return new SignalServiceAttachmentRemoteId(string);
}
}
}

View File

@ -562,7 +562,7 @@ public final class SignalServiceContent {
Optional.<byte[]>absent()); Optional.<byte[]>absent());
} }
private static SignalServiceDataMessage.Quote createQuote(SignalServiceProtos.DataMessage content) { private static SignalServiceDataMessage.Quote createQuote(SignalServiceProtos.DataMessage content) throws ProtocolInvalidMessageException {
if (!content.hasQuote()) return null; if (!content.hasQuote()) return null;
List<SignalServiceDataMessage.Quote.QuotedAttachment> attachments = new LinkedList<>(); List<SignalServiceDataMessage.Quote.QuotedAttachment> attachments = new LinkedList<>();
@ -586,7 +586,7 @@ public final class SignalServiceContent {
} }
} }
private static List<SignalServiceDataMessage.Preview> createPreviews(SignalServiceProtos.DataMessage content) { private static List<SignalServiceDataMessage.Preview> createPreviews(SignalServiceProtos.DataMessage content) throws ProtocolInvalidMessageException {
if (content.getPreviewCount() <= 0) return null; if (content.getPreviewCount() <= 0) return null;
List<SignalServiceDataMessage.Preview> results = new LinkedList<>(); List<SignalServiceDataMessage.Preview> results = new LinkedList<>();
@ -606,7 +606,7 @@ public final class SignalServiceContent {
return results; return results;
} }
private static SignalServiceDataMessage.Sticker createSticker(SignalServiceProtos.DataMessage content) { private static SignalServiceDataMessage.Sticker createSticker(SignalServiceProtos.DataMessage content) throws ProtocolInvalidMessageException {
if (!content.hasSticker() || if (!content.hasSticker() ||
!content.getSticker().hasPackId() || !content.getSticker().hasPackId() ||
!content.getSticker().hasPackKey() || !content.getSticker().hasPackKey() ||
@ -641,7 +641,7 @@ public final class SignalServiceContent {
reaction.getTargetSentTimestamp()); reaction.getTargetSentTimestamp());
} }
private static List<SharedContact> createSharedContacts(SignalServiceProtos.DataMessage content) { private static List<SharedContact> createSharedContacts(SignalServiceProtos.DataMessage content) throws ProtocolInvalidMessageException {
if (content.getContactCount() <= 0) return null; if (content.getContactCount() <= 0) return null;
List<SharedContact> results = new LinkedList<>(); List<SharedContact> results = new LinkedList<>();
@ -736,8 +736,9 @@ public final class SignalServiceContent {
return results; return results;
} }
private static SignalServiceAttachmentPointer createAttachmentPointer(SignalServiceProtos.AttachmentPointer pointer) { private static SignalServiceAttachmentPointer createAttachmentPointer(SignalServiceProtos.AttachmentPointer pointer) throws ProtocolInvalidMessageException {
return new SignalServiceAttachmentPointer(pointer.getId(), return new SignalServiceAttachmentPointer(pointer.getCdnNumber(),
SignalServiceAttachmentRemoteId.from(pointer),
pointer.getContentType(), pointer.getContentType(),
pointer.getKey().toByteArray(), pointer.getKey().toByteArray(),
pointer.hasSize() ? Optional.of(pointer.getSize()) : Optional.<Integer>absent(), pointer.hasSize() ? Optional.of(pointer.getSize()) : Optional.<Integer>absent(),
@ -795,7 +796,8 @@ public final class SignalServiceContent {
if (content.getGroup().hasAvatar()) { if (content.getGroup().hasAvatar()) {
SignalServiceProtos.AttachmentPointer pointer = content.getGroup().getAvatar(); SignalServiceProtos.AttachmentPointer pointer = content.getGroup().getAvatar();
avatar = new SignalServiceAttachmentPointer(pointer.getId(), avatar = new SignalServiceAttachmentPointer(pointer.getCdnNumber(),
SignalServiceAttachmentRemoteId.from(pointer),
pointer.getContentType(), pointer.getContentType(),
pointer.getKey().toByteArray(), pointer.getKey().toByteArray(),
Optional.of(pointer.getSize()), Optional.of(pointer.getSize()),

View File

@ -11,6 +11,7 @@ public final class SignalServiceConfiguration {
private final SignalServiceUrl[] signalServiceUrls; private final SignalServiceUrl[] signalServiceUrls;
private final SignalCdnUrl[] signalCdnUrls; private final SignalCdnUrl[] signalCdnUrls;
private final SignalCdnUrl[] signalCdn2Urls;
private final SignalContactDiscoveryUrl[] signalContactDiscoveryUrls; private final SignalContactDiscoveryUrl[] signalContactDiscoveryUrls;
private final SignalKeyBackupServiceUrl[] signalKeyBackupServiceUrls; private final SignalKeyBackupServiceUrl[] signalKeyBackupServiceUrls;
private final SignalStorageUrl[] signalStorageUrls; private final SignalStorageUrl[] signalStorageUrls;
@ -20,6 +21,7 @@ public final class SignalServiceConfiguration {
public SignalServiceConfiguration(SignalServiceUrl[] signalServiceUrls, public SignalServiceConfiguration(SignalServiceUrl[] signalServiceUrls,
SignalCdnUrl[] signalCdnUrls, SignalCdnUrl[] signalCdnUrls,
SignalCdnUrl[] signalCdn2Urls,
SignalContactDiscoveryUrl[] signalContactDiscoveryUrls, SignalContactDiscoveryUrl[] signalContactDiscoveryUrls,
SignalKeyBackupServiceUrl[] signalKeyBackupServiceUrls, SignalKeyBackupServiceUrl[] signalKeyBackupServiceUrls,
SignalStorageUrl[] signalStorageUrls, SignalStorageUrl[] signalStorageUrls,
@ -29,6 +31,7 @@ public final class SignalServiceConfiguration {
{ {
this.signalServiceUrls = signalServiceUrls; this.signalServiceUrls = signalServiceUrls;
this.signalCdnUrls = signalCdnUrls; this.signalCdnUrls = signalCdnUrls;
this.signalCdn2Urls = signalCdn2Urls;
this.signalContactDiscoveryUrls = signalContactDiscoveryUrls; this.signalContactDiscoveryUrls = signalContactDiscoveryUrls;
this.signalKeyBackupServiceUrls = signalKeyBackupServiceUrls; this.signalKeyBackupServiceUrls = signalKeyBackupServiceUrls;
this.signalStorageUrls = signalStorageUrls; this.signalStorageUrls = signalStorageUrls;
@ -45,6 +48,10 @@ public final class SignalServiceConfiguration {
return signalCdnUrls; return signalCdnUrls;
} }
public SignalCdnUrl[] getSignalCdn2Urls() {
return signalCdn2Urls;
}
public SignalContactDiscoveryUrl[] getSignalContactDiscoveryUrls() { public SignalContactDiscoveryUrl[] getSignalContactDiscoveryUrls() {
return signalContactDiscoveryUrls; return signalContactDiscoveryUrls;
} }

View File

@ -3,7 +3,7 @@ package org.whispersystems.signalservice.internal.push;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
public class AttachmentUploadAttributes { public class AttachmentV2UploadAttributes {
@JsonProperty @JsonProperty
private String url; private String url;
@ -34,7 +34,7 @@ public class AttachmentUploadAttributes {
@JsonProperty @JsonProperty
private String attachmentIdString; private String attachmentIdString;
public AttachmentUploadAttributes() {} public AttachmentV2UploadAttributes() {}
public String getUrl() { public String getUrl() {
return url; return url;

View File

@ -0,0 +1,38 @@
package org.whispersystems.signalservice.internal.push;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.Map;
public final class AttachmentV3UploadAttributes {
@JsonProperty
private int cdn;
@JsonProperty
private String key;
@JsonProperty
private Map<String, String> headers;
@JsonProperty
private String signedUploadLocation;
public AttachmentV3UploadAttributes() {
}
public int getCdn() {
return cdn;
}
public String getKey() {
return key;
}
public Map<String, String> getHeaders() {
return headers;
}
public String getSignedUploadLocation() {
return signedUploadLocation;
}
}

View File

@ -35,6 +35,7 @@ import org.whispersystems.signalservice.FeatureFlags;
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess; import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess;
import org.whispersystems.signalservice.api.groupsv2.CredentialResponse; import org.whispersystems.signalservice.api.groupsv2.CredentialResponse;
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment.ProgressListener; import org.whispersystems.signalservice.api.messages.SignalServiceAttachment.ProgressListener;
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentRemoteId;
import org.whispersystems.signalservice.api.messages.calls.TurnServerInfo; import org.whispersystems.signalservice.api.messages.calls.TurnServerInfo;
import org.whispersystems.signalservice.api.messages.multidevice.DeviceInfo; import org.whispersystems.signalservice.api.messages.multidevice.DeviceInfo;
import org.whispersystems.signalservice.api.profiles.ProfileAndCredential; import org.whispersystems.signalservice.api.profiles.ProfileAndCredential;
@ -114,6 +115,7 @@ import okhttp3.Call;
import okhttp3.ConnectionSpec; import okhttp3.ConnectionSpec;
import okhttp3.Credentials; import okhttp3.Credentials;
import okhttp3.Dns; import okhttp3.Dns;
import okhttp3.HttpUrl;
import okhttp3.Interceptor; import okhttp3.Interceptor;
import okhttp3.MediaType; import okhttp3.MediaType;
import okhttp3.MultipartBody; import okhttp3.MultipartBody;
@ -159,7 +161,8 @@ public class PushServiceSocket {
private static final String MESSAGE_PATH = "/v1/messages/%s"; private static final String MESSAGE_PATH = "/v1/messages/%s";
private static final String SENDER_ACK_MESSAGE_PATH = "/v1/messages/%s/%d"; private static final String SENDER_ACK_MESSAGE_PATH = "/v1/messages/%s/%d";
private static final String UUID_ACK_MESSAGE_PATH = "/v1/messages/uuid/%s"; private static final String UUID_ACK_MESSAGE_PATH = "/v1/messages/uuid/%s";
private static final String ATTACHMENT_PATH = "/v2/attachments/form/upload"; private static final String ATTACHMENT_V2_PATH = "/v2/attachments/form/upload";
private static final String ATTACHMENT_V3_PATH = "/v3/attachments/form/upload";
private static final String PROFILE_PATH = "/v1/profile/%s"; private static final String PROFILE_PATH = "/v1/profile/%s";
private static final String PROFILE_USERNAME_PATH = "/v1/profile/username/%s"; private static final String PROFILE_USERNAME_PATH = "/v1/profile/username/%s";
@ -169,7 +172,8 @@ public class PushServiceSocket {
private static final String KBS_AUTH_PATH = "/v1/backup/auth"; private static final String KBS_AUTH_PATH = "/v1/backup/auth";
private static final String ATTACHMENT_DOWNLOAD_PATH = "attachments/%d"; private static final String ATTACHMENT_KEY_DOWNLOAD_PATH = "attachments/%s";
private static final String ATTACHMENT_ID_DOWNLOAD_PATH = "attachments/%d";
private static final String ATTACHMENT_UPLOAD_PATH = "attachments/"; private static final String ATTACHMENT_UPLOAD_PATH = "attachments/";
private static final String AVATAR_UPLOAD_PATH = ""; private static final String AVATAR_UPLOAD_PATH = "";
@ -189,6 +193,7 @@ public class PushServiceSocket {
private final ServiceConnectionHolder[] serviceClients; private final ServiceConnectionHolder[] serviceClients;
private final ConnectionHolder[] cdnClients; private final ConnectionHolder[] cdnClients;
private final ConnectionHolder[] cdn2Clients;
private final ConnectionHolder[] contactDiscoveryClients; private final ConnectionHolder[] contactDiscoveryClients;
private final ConnectionHolder[] keyBackupServiceClients; private final ConnectionHolder[] keyBackupServiceClients;
private final ConnectionHolder[] storageClients; private final ConnectionHolder[] storageClients;
@ -207,6 +212,7 @@ public class PushServiceSocket {
this.signalAgent = signalAgent; this.signalAgent = signalAgent;
this.serviceClients = createServiceConnectionHolders(configuration.getSignalServiceUrls(), configuration.getNetworkInterceptors(), configuration.getDns()); this.serviceClients = createServiceConnectionHolders(configuration.getSignalServiceUrls(), configuration.getNetworkInterceptors(), configuration.getDns());
this.cdnClients = createConnectionHolders(configuration.getSignalCdnUrls(), configuration.getNetworkInterceptors(), configuration.getDns()); this.cdnClients = createConnectionHolders(configuration.getSignalCdnUrls(), configuration.getNetworkInterceptors(), configuration.getDns());
this.cdn2Clients = createConnectionHolders(configuration.getSignalCdn2Urls(), configuration.getNetworkInterceptors(), configuration.getDns());
this.contactDiscoveryClients = createConnectionHolders(configuration.getSignalContactDiscoveryUrls(), configuration.getNetworkInterceptors(), configuration.getDns()); this.contactDiscoveryClients = createConnectionHolders(configuration.getSignalContactDiscoveryUrls(), configuration.getNetworkInterceptors(), configuration.getDns());
this.keyBackupServiceClients = createConnectionHolders(configuration.getSignalKeyBackupServiceUrls(), configuration.getNetworkInterceptors(), configuration.getDns()); this.keyBackupServiceClients = createConnectionHolders(configuration.getSignalKeyBackupServiceUrls(), configuration.getNetworkInterceptors(), configuration.getDns());
this.storageClients = createConnectionHolders(configuration.getSignalStorageUrls(), configuration.getNetworkInterceptors(), configuration.getDns()); this.storageClients = createConnectionHolders(configuration.getSignalStorageUrls(), configuration.getNetworkInterceptors(), configuration.getDns());
@ -516,17 +522,23 @@ public class PushServiceSocket {
makeServiceRequest(SIGNED_PREKEY_PATH, "PUT", JsonUtil.toJson(signedPreKeyEntity)); makeServiceRequest(SIGNED_PREKEY_PATH, "PUT", JsonUtil.toJson(signedPreKeyEntity));
} }
public void retrieveAttachment(long attachmentId, File destination, long maxSizeBytes, ProgressListener listener) public void retrieveAttachment(int cdnNumber, SignalServiceAttachmentRemoteId cdnPath, File destination, long maxSizeBytes, ProgressListener listener)
throws NonSuccessfulResponseCodeException, PushNetworkException throws NonSuccessfulResponseCodeException, PushNetworkException
{ {
downloadFromCdn(destination, String.format(Locale.US, ATTACHMENT_DOWNLOAD_PATH, attachmentId), maxSizeBytes, listener); final String path;
if (cdnPath.getV2().isPresent()) {
path = String.format(Locale.US, ATTACHMENT_ID_DOWNLOAD_PATH, cdnPath.getV2().get());
} else {
path = String.format(Locale.US, ATTACHMENT_KEY_DOWNLOAD_PATH, cdnPath.getV3().get());
}
downloadFromCdn(destination, cdnNumber, path, maxSizeBytes, listener);
} }
public void retrieveSticker(File destination, byte[] packId, int stickerId) public void retrieveSticker(File destination, byte[] packId, int stickerId)
throws NonSuccessfulResponseCodeException, PushNetworkException throws NonSuccessfulResponseCodeException, PushNetworkException
{ {
String hexPackId = Hex.toStringCondensed(packId); String hexPackId = Hex.toStringCondensed(packId);
downloadFromCdn(destination, String.format(Locale.US, STICKER_PATH, hexPackId, stickerId), 1024 * 1024, null); downloadFromCdn(destination, 0, String.format(Locale.US, STICKER_PATH, hexPackId, stickerId), 1024 * 1024, null);
} }
public byte[] retrieveSticker(byte[] packId, int stickerId) public byte[] retrieveSticker(byte[] packId, int stickerId)
@ -535,7 +547,7 @@ public class PushServiceSocket {
String hexPackId = Hex.toStringCondensed(packId); String hexPackId = Hex.toStringCondensed(packId);
ByteArrayOutputStream output = new ByteArrayOutputStream(); ByteArrayOutputStream output = new ByteArrayOutputStream();
downloadFromCdn(output, 0, String.format(Locale.US, STICKER_PATH, hexPackId, stickerId), 1024 * 1024, null); downloadFromCdn(output, 0, 0, String.format(Locale.US, STICKER_PATH, hexPackId, stickerId), 1024 * 1024, null);
return output.toByteArray(); return output.toByteArray();
} }
@ -546,7 +558,7 @@ public class PushServiceSocket {
String hexPackId = Hex.toStringCondensed(packId); String hexPackId = Hex.toStringCondensed(packId);
ByteArrayOutputStream output = new ByteArrayOutputStream(); ByteArrayOutputStream output = new ByteArrayOutputStream();
downloadFromCdn(output, 0, String.format(STICKER_MANIFEST_PATH, hexPackId), 1024 * 1024, null); downloadFromCdn(output, 0, 0, String.format(STICKER_MANIFEST_PATH, hexPackId), 1024 * 1024, null);
return output.toByteArray(); return output.toByteArray();
} }
@ -611,7 +623,7 @@ public class PushServiceSocket {
public void retrieveProfileAvatar(String path, File destination, long maxSizeBytes) public void retrieveProfileAvatar(String path, File destination, long maxSizeBytes)
throws NonSuccessfulResponseCodeException, PushNetworkException throws NonSuccessfulResponseCodeException, PushNetworkException
{ {
downloadFromCdn(destination, path, maxSizeBytes, null); downloadFromCdn(destination, 0, path, maxSizeBytes, null);
} }
public void setProfileName(String name) throws NonSuccessfulResponseCodeException, PushNetworkException { public void setProfileName(String name) throws NonSuccessfulResponseCodeException, PushNetworkException {
@ -867,10 +879,20 @@ public class PushServiceSocket {
} }
} }
public AttachmentUploadAttributes getAttachmentUploadAttributes() throws NonSuccessfulResponseCodeException, PushNetworkException { public AttachmentV2UploadAttributes getAttachmentV2UploadAttributes() throws NonSuccessfulResponseCodeException, PushNetworkException {
String response = makeServiceRequest(ATTACHMENT_PATH, "GET", null); String response = makeServiceRequest(ATTACHMENT_V2_PATH, "GET", null);
try { try {
return JsonUtil.fromJson(response, AttachmentUploadAttributes.class); return JsonUtil.fromJson(response, AttachmentV2UploadAttributes.class);
} catch (IOException e) {
Log.w(TAG, e);
throw new NonSuccessfulResponseCodeException("Unable to parse entity");
}
}
public AttachmentV3UploadAttributes getAttachmentV3UploadAttributes() throws NonSuccessfulResponseCodeException, PushNetworkException {
String response = makeServiceRequest(ATTACHMENT_V3_PATH, "GET", null);
try {
return JsonUtil.fromJson(response, AttachmentV3UploadAttributes.class);
} catch (IOException e) { } catch (IOException e) {
Log.w(TAG, e); Log.w(TAG, e);
throw new NonSuccessfulResponseCodeException("Unable to parse entity"); throw new NonSuccessfulResponseCodeException("Unable to parse entity");
@ -890,7 +912,7 @@ public class PushServiceSocket {
null, null); null, null);
} }
public Pair<Long, byte[]> uploadAttachment(PushAttachmentData attachment, AttachmentUploadAttributes uploadAttributes) public Pair<Long, byte[]> uploadAttachment(PushAttachmentData attachment, AttachmentV2UploadAttributes uploadAttributes)
throws PushNetworkException, NonSuccessfulResponseCodeException throws PushNetworkException, NonSuccessfulResponseCodeException
{ {
long id = Long.parseLong(uploadAttributes.getAttachmentId()); long id = Long.parseLong(uploadAttributes.getAttachmentId());
@ -905,20 +927,31 @@ public class PushServiceSocket {
return new Pair<>(id, digest); return new Pair<>(id, digest);
} }
private void downloadFromCdn(File destination, String path, long maxSizeBytes, ProgressListener listener) public byte[] uploadAttachment(PushAttachmentData attachment, AttachmentV3UploadAttributes uploadAttributes) throws IOException {
String resumableUploadUrl = getResumableUploadUrl(uploadAttributes.getSignedUploadLocation(), uploadAttributes.getHeaders());
return uploadToCdn2(resumableUploadUrl,
attachment.getData(),
"application/octet-stream",
attachment.getDataSize(),
attachment.getOutputStreamFactory(),
attachment.getListener(),
attachment.getCancelationSignal());
}
private void downloadFromCdn(File destination, int cdnNumber, String path, long maxSizeBytes, ProgressListener listener)
throws PushNetworkException, NonSuccessfulResponseCodeException throws PushNetworkException, NonSuccessfulResponseCodeException
{ {
try (FileOutputStream outputStream = new FileOutputStream(destination, true)) { try (FileOutputStream outputStream = new FileOutputStream(destination, true)) {
downloadFromCdn(outputStream, destination.length(), path, maxSizeBytes, listener); downloadFromCdn(outputStream, destination.length(), cdnNumber, path, maxSizeBytes, listener);
} catch (IOException e) { } catch (IOException e) {
throw new PushNetworkException(e); throw new PushNetworkException(e);
} }
} }
private void downloadFromCdn(OutputStream outputStream, long offset, String path, long maxSizeBytes, ProgressListener listener) private void downloadFromCdn(OutputStream outputStream, long offset, int cdnNumber, String path, long maxSizeBytes, ProgressListener listener)
throws PushNetworkException, NonSuccessfulResponseCodeException throws PushNetworkException, NonSuccessfulResponseCodeException
{ {
ConnectionHolder connectionHolder = getRandom(cdnClients, random); ConnectionHolder connectionHolder = getRandom(cdnNumber == 2 ? cdn2Clients : cdnClients, random);
OkHttpClient okHttpClient = connectionHolder.getClient() OkHttpClient okHttpClient = connectionHolder.getClient()
.newBuilder() .newBuilder()
.connectTimeout(soTimeoutMillis, TimeUnit.MILLISECONDS) .connectTimeout(soTimeoutMillis, TimeUnit.MILLISECONDS)
@ -1046,6 +1079,107 @@ public class PushServiceSocket {
} }
} }
private String getResumableUploadUrl(String signedUrl, Map<String, String> headers) throws IOException {
ConnectionHolder connectionHolder = getRandom(cdn2Clients, random);
OkHttpClient okHttpClient = connectionHolder.getClient()
.newBuilder()
.connectTimeout(soTimeoutMillis, TimeUnit.MILLISECONDS)
.readTimeout(soTimeoutMillis, TimeUnit.MILLISECONDS)
.build();
final HttpUrl endpointUrl = HttpUrl.get(connectionHolder.url);
final HttpUrl signedHttpUrl;
try {
signedHttpUrl = HttpUrl.get(signedUrl);
} catch (IllegalArgumentException e) {
Log.w(TAG, "Server returned a malformed signed url: " + signedUrl);
throw new IOException("Server returned a malformed signed url", e);
}
final HttpUrl.Builder urlBuilder = new HttpUrl.Builder().scheme(endpointUrl.scheme())
.host(endpointUrl.host())
.port(endpointUrl.port())
.encodedPath(endpointUrl.encodedPath())
.addEncodedPathSegments(signedHttpUrl.encodedPath().substring(1))
.encodedQuery(signedHttpUrl.encodedQuery())
.encodedFragment(signedHttpUrl.encodedFragment());
Request.Builder request = new Request.Builder().url(urlBuilder.build())
.post(RequestBody.create(null, ""));
for (Map.Entry<String, String> header : headers.entrySet()) {
request.header(header.getKey(), header.getValue());
}
if (connectionHolder.getHostHeader().isPresent()) {
request.header("host", connectionHolder.getHostHeader().get());
}
Call call = okHttpClient.newCall(request.build());
synchronized (connections) {
connections.add(call);
}
try {
Response response;
try {
response = call.execute();
} catch (IOException e) {
throw new PushNetworkException(e);
}
if (response.isSuccessful()) {
return response.header("location");
} else {
throw new NonSuccessfulResponseCodeException("Response: " + response);
}
} finally {
synchronized (connections) {
connections.remove(call);
}
}
}
private byte[] uploadToCdn2(String resumableUrl, InputStream data, String contentType, long length, OutputStreamFactory outputStreamFactory, ProgressListener progressListener, CancelationSignal cancelationSignal) throws IOException {
ConnectionHolder connectionHolder = getRandom(cdn2Clients, random);
OkHttpClient okHttpClient = connectionHolder.getClient()
.newBuilder()
.connectTimeout(soTimeoutMillis, TimeUnit.MILLISECONDS)
.readTimeout(soTimeoutMillis, TimeUnit.MILLISECONDS)
.build();
DigestingRequestBody file = new DigestingRequestBody(data, outputStreamFactory, contentType, length, progressListener, cancelationSignal);
Request.Builder request = new Request.Builder().url(resumableUrl)
.put(file);
if (connectionHolder.getHostHeader().isPresent()) {
request.header("host", connectionHolder.getHostHeader().get());
}
Call call = okHttpClient.newCall(request.build());
synchronized (connections) {
connections.add(call);
}
try {
Response response;
try {
response = call.execute();
} catch (IOException e) {
throw new PushNetworkException(e);
}
if (response.isSuccessful()) return file.getTransmittedDigest();
else throw new NonSuccessfulResponseCodeException("Response: " + response);
} finally {
synchronized (connections) {
connections.remove(call);
}
}
}
private String makeServiceRequest(String urlFragment, String method, String jsonBody) private String makeServiceRequest(String urlFragment, String method, String jsonBody)
throws NonSuccessfulResponseCodeException, PushNetworkException throws NonSuccessfulResponseCodeException, PushNetworkException
{ {

View File

@ -377,7 +377,10 @@ message AttachmentPointer {
VOICE_MESSAGE = 1; VOICE_MESSAGE = 1;
} }
optional fixed64 id = 1; oneof attachment_identifier {
fixed64 cdnId = 1;
string cdnKey = 15;
}
optional string contentType = 2; optional string contentType = 2;
optional bytes key = 3; optional bytes key = 3;
optional uint32 size = 4; optional uint32 size = 4;
@ -390,6 +393,8 @@ message AttachmentPointer {
optional string caption = 11; optional string caption = 11;
optional string blurHash = 12; optional string blurHash = 12;
optional uint64 uploadTimestamp = 13; optional uint64 uploadTimestamp = 13;
optional uint32 cdnNumber = 14;
// Next ID: 16
} }
message GroupContext { message GroupContext {