Add streaming video support for attachment files.

This commit is contained in:
Clark 2024-05-21 21:00:36 -04:00 committed by Cody Henthorne
parent bc5cb454bf
commit 5c3ea712fe
4 changed files with 84 additions and 12 deletions

View file

@ -116,6 +116,7 @@ import org.thoughtcrime.securesms.giph.mp4.GiphyMp4PlaybackPolicy;
import org.thoughtcrime.securesms.giph.mp4.GiphyMp4PlaybackPolicyEnforcer;
import org.thoughtcrime.securesms.jobmanager.JobManager;
import org.thoughtcrime.securesms.jobs.AttachmentDownloadJob;
import org.thoughtcrime.securesms.jobs.RestoreAttachmentJob;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.linkpreview.LinkPreview;
import org.thoughtcrime.securesms.mediapreview.MediaIntentFactory;
@ -2489,7 +2490,22 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
}
if (MediaUtil.isInstantVideoSupported(slide)) {
final DatabaseAttachment databaseAttachment = (DatabaseAttachment) slide.asAttachment();
if (databaseAttachment.transferState != AttachmentTable.TRANSFER_PROGRESS_STARTED) {
if (databaseAttachment.transferState == AttachmentTable.TRANSFER_RESTORE_OFFLOADED) {
final AttachmentId attachmentId = databaseAttachment.attachmentId;
final JobManager jobManager = ApplicationDependencies.getJobManager();
final String queue = RestoreAttachmentJob.constructQueueString(attachmentId);
setup(v, slide);
jobManager.add(new RestoreAttachmentJob(messageRecord.getId(),
attachmentId,
true,
false,
RestoreAttachmentJob.RestoreMode.ORIGINAL));
jobManager.addListener(queue, (job, jobState) -> {
if (jobState.isComplete()) {
cleanup();
}
});
} else if (databaseAttachment.transferState != AttachmentTable.TRANSFER_PROGRESS_STARTED) {
final AttachmentId attachmentId = databaseAttachment.attachmentId;
final JobManager jobManager = ApplicationDependencies.getJobManager();
final String queue = AttachmentDownloadJob.constructQueueString(attachmentId);

View file

@ -16,9 +16,14 @@ import org.signal.libsignal.protocol.InvalidMessageException;
import org.thoughtcrime.securesms.attachments.DatabaseAttachment;
import org.thoughtcrime.securesms.database.AttachmentTable;
import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.mms.PartUriParser;
import org.signal.core.util.Base64;
import org.whispersystems.signalservice.api.backup.BackupKey;
import org.whispersystems.signalservice.api.backup.MediaId;
import org.whispersystems.signalservice.api.crypto.AttachmentCipherInputStream;
import org.whispersystems.signalservice.api.crypto.AttachmentCipherStreamUtil;
import org.whispersystems.signalservice.internal.crypto.PaddingInputStream;
import java.io.EOFException;
import java.io.File;
@ -62,20 +67,30 @@ class PartDataSource implements DataSource {
if (inProgress && !hasData && hasIncrementalDigest && attachmentKey != null) {
final byte[] decode = Base64.decode(attachmentKey);
final File transferFile = attachmentDatabase.getOrCreateTransferFile(attachment.attachmentId);
try {
this.inputStream = AttachmentCipherInputStream.createForAttachment(transferFile, attachment.size, decode, attachment.remoteDigest, attachment.getIncrementalDigest(), attachment.incrementalMacChunkSize);
if (attachment.transferState == AttachmentTable.TRANSFER_RESTORE_IN_PROGRESS && attachment.archiveMediaId != null) {
final File archiveFile = attachmentDatabase.getOrCreateArchiveTransferFile(attachment.attachmentId);
try {
BackupKey.MediaKeyMaterial mediaKeyMaterial = SignalStore.svr().getOrCreateMasterKey().deriveBackupKey().deriveMediaSecretsFromMediaId(attachment.archiveMediaId);
long originalCipherLength = AttachmentCipherStreamUtil.getCiphertextLength(PaddingInputStream.getPaddedSize(attachment.size));
long skipped = 0;
while (skipped < dataSpec.position) {
skipped += this.inputStream.read();
this.inputStream = AttachmentCipherInputStream.createStreamingForArchivedAttachment(mediaKeyMaterial, archiveFile, originalCipherLength, attachment.size, attachment.remoteDigest, decode, attachment.getIncrementalDigest(), attachment.incrementalMacChunkSize);
} catch (InvalidMessageException e) {
throw new IOException("Error decrypting attachment stream!", e);
}
} else {
final File transferFile = attachmentDatabase.getOrCreateTransferFile(attachment.attachmentId);
try {
this.inputStream = AttachmentCipherInputStream.createForAttachment(transferFile, attachment.size, decode, attachment.remoteDigest, attachment.getIncrementalDigest(), attachment.incrementalMacChunkSize);
} catch (InvalidMessageException e) {
throw new IOException("Error decrypting attachment stream!", e);
}
Log.d(TAG, "Successfully loaded partial attachment file.");
} catch (InvalidMessageException e) {
throw new IOException("Error decrypting attachment stream!", e);
}
long skipped = 0;
while (skipped < dataSpec.position) {
skipped += this.inputStream.read();
}
Log.d(TAG, "Successfully loaded partial attachment file.");
} else if (!inProgress || hasData) {
this.inputStream = attachmentDatabase.getAttachmentStream(partUri.getPartId(), dataSpec.position);

View file

@ -42,6 +42,10 @@ class BackupKey(val value: ByteArray) {
return deriveMediaSecrets(deriveMediaId(mediaName))
}
fun deriveMediaSecretsFromMediaId(base64MediaId: String): MediaKeyMaterial {
return deriveMediaSecrets(MediaId(base64MediaId))
}
fun deriveThumbnailTransitKey(thumbnailMediaName: MediaName): ByteArray {
return HKDF.deriveSecrets(value, deriveMediaId(thumbnailMediaName).value, "20240513_Signal_Backups_EncryptThumbnail".toByteArray(), 64)
}

View file

@ -138,6 +138,43 @@ public class AttachmentCipherInputStream extends FilterInputStream {
return inputStream;
}
public static InputStream createStreamingForArchivedAttachment(BackupKey.MediaKeyMaterial archivedMediaKeyMaterial, File file, long originalCipherTextLength, long plaintextLength, byte[] combinedKeyMaterial, byte[] digest, byte[] incrementalDigest, int incrementalMacChunkSize)
throws InvalidMessageException, IOException
{
final InputStream archiveStream = createForArchivedMedia(archivedMediaKeyMaterial, file, originalCipherTextLength);
byte[][] parts = Util.split(combinedKeyMaterial, CIPHER_KEY_SIZE, MAC_KEY_SIZE);
Mac mac = initMac(parts[1]);
if (originalCipherTextLength <= BLOCK_SIZE + mac.getMacLength()) {
throw new InvalidMessageException("Message shorter than crypto overhead!");
}
if (digest == null) {
throw new InvalidMessageException("Missing digest!");
}
final InputStream wrappedStream;
wrappedStream = new IncrementalMacInputStream(
new IncrementalMacAdditionalValidationsInputStream(
archiveStream,
file.length(),
mac,
digest
),
parts[1],
ChunkSizeChoice.everyNthByte(incrementalMacChunkSize),
incrementalDigest);
InputStream inputStream = new AttachmentCipherInputStream(wrappedStream, parts[0], file.length() - BLOCK_SIZE - mac.getMacLength());
if (plaintextLength != 0) {
inputStream = new ContentLengthInputStream(inputStream, plaintextLength);
}
return inputStream;
}
public static InputStream createForStickerData(byte[] data, byte[] packKey)
throws InvalidMessageException, IOException
{