Update to the new attachment upload form libsignal method.

This commit is contained in:
Greyson Parrelli
2026-04-02 11:36:37 -04:00
parent 58e8ea08c2
commit ce6f39ae68
9 changed files with 87 additions and 135 deletions

View File

@@ -17,6 +17,7 @@ import org.signal.libsignal.net.MultiRecipientSendAuthorization;
import org.signal.libsignal.net.MultiRecipientSendFailure;
import org.signal.libsignal.net.RequestResult;
import org.signal.libsignal.net.RequestUnauthorizedException;
import org.signal.libsignal.net.UploadTooLargeException;
import org.signal.libsignal.net.RetryLaterException;
import org.signal.libsignal.protocol.IdentityKey;
import org.signal.libsignal.protocol.IdentityKeyPair;
@@ -832,11 +833,22 @@ public class SignalServiceMessageSender {
return uploadAttachmentV4(attachment, attachmentKey, attachmentData);
}
public ResumableUploadSpec getResumableUploadSpec() throws IOException {
public ResumableUploadSpec getResumableUploadSpec(long uploadSizeBytes) throws IOException {
Log.d(TAG, "Using pipe to retrieve attachment upload attributes...");
AttachmentUploadForm v4UploadAttributes = NetworkResultUtil.toBasicLegacy(attachmentApi.getAttachmentV4UploadForm());
RequestResult<AttachmentUploadForm, UploadTooLargeException> result = attachmentApi.getAttachmentV4UploadForm(uploadSizeBytes);
return socket.getResumableUploadSpec(v4UploadAttributes);
if (result instanceof RequestResult.Success) {
AttachmentUploadForm v4UploadAttributes = ((RequestResult.Success<AttachmentUploadForm>) result).getResult();
return socket.getResumableUploadSpec(v4UploadAttributes);
} else if (result instanceof RequestResult.NonSuccess) {
throw ((RequestResult.NonSuccess<UploadTooLargeException>) result).getError();
} else if (result instanceof RequestResult.RetryableNetworkError) {
throw new PushNetworkException(((RequestResult.RetryableNetworkError) result).getNetworkError());
} else if (result instanceof RequestResult.ApplicationError) {
throw new RuntimeException(((RequestResult.ApplicationError) result).getCause());
} else {
throw new IOException("Unexpected RequestResult type: " + result.getClass().getSimpleName());
}
}
private SignalServiceAttachmentPointer uploadAttachmentV4(SignalServiceAttachmentStream attachment, byte[] attachmentKey, PushAttachmentData attachmentData) throws IOException {

View File

@@ -5,20 +5,25 @@
package org.whispersystems.signalservice.api.attachment
import kotlinx.coroutines.runBlocking
import org.signal.core.util.logging.Log
import org.signal.libsignal.net.AuthMessagesService
import org.signal.libsignal.net.AuthenticatedChatConnection
import org.signal.libsignal.net.RequestResult
import org.signal.libsignal.net.UploadTooLargeException
import org.signal.libsignal.net.getOrError
import org.whispersystems.signalservice.api.NetworkResult
import org.whispersystems.signalservice.api.crypto.AttachmentCipherStreamUtil
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentRemoteId
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentStream
import org.whispersystems.signalservice.api.websocket.SignalWebSocket
import org.whispersystems.signalservice.internal.crypto.PaddingInputStream
import org.whispersystems.signalservice.internal.get
import org.whispersystems.signalservice.internal.push.AttachmentUploadForm
import org.whispersystems.signalservice.internal.push.PushAttachmentData
import org.whispersystems.signalservice.internal.push.PushServiceSocket
import org.whispersystems.signalservice.internal.push.http.AttachmentCipherOutputStreamFactory
import org.whispersystems.signalservice.internal.push.http.ResumableUploadSpec
import org.whispersystems.signalservice.internal.websocket.WebSocketRequestMessage
import java.io.IOException
import java.io.InputStream
import kotlin.jvm.optionals.getOrNull
@@ -36,56 +41,25 @@ class AttachmentApi(
/**
* Gets a v4 attachment upload form, which provides the necessary information to upload an attachment.
*
* GET /v4/attachments/form/upload
* - 200: Success
* - 413: Too many attempts
* - 429: Too many attempts
*/
fun getAttachmentV4UploadForm(): NetworkResult<AttachmentUploadForm> {
val request = WebSocketRequestMessage.get("/v4/attachments/form/upload")
return NetworkResult.fromWebSocketRequest(authWebSocket, request, AttachmentUploadForm::class)
}
/**
* Uploads an attachment using the v4 upload scheme.
*/
fun uploadAttachmentV4(attachmentStream: SignalServiceAttachmentStream): NetworkResult<AttachmentUploadResult> {
if (attachmentStream.resumableUploadSpec.isEmpty) {
throw IllegalStateException("Attachment must have a resumable upload spec!")
}
return NetworkResult.fromFetch {
val resumableUploadSpec = attachmentStream.resumableUploadSpec.get()
val paddedLength = PaddingInputStream.getPaddedSize(attachmentStream.length)
val dataStream: InputStream = PaddingInputStream(attachmentStream.inputStream, attachmentStream.length)
val ciphertextLength = AttachmentCipherStreamUtil.getCiphertextLength(paddedLength)
val attachmentData = PushAttachmentData(
contentType = attachmentStream.contentType,
data = dataStream,
dataSize = ciphertextLength,
incremental = attachmentStream.isFaststart,
outputStreamFactory = AttachmentCipherOutputStreamFactory(resumableUploadSpec.attachmentKey, resumableUploadSpec.attachmentIv),
listener = attachmentStream.listener,
cancelationSignal = attachmentStream.cancelationSignal,
resumableUploadSpec = attachmentStream.resumableUploadSpec.get()
)
val digestInfo = pushServiceSocket.uploadAttachment(attachmentData)
AttachmentUploadResult(
remoteId = SignalServiceAttachmentRemoteId.V4(resumableUploadSpec.cdnKey),
cdnNumber = resumableUploadSpec.cdnNumber,
key = resumableUploadSpec.attachmentKey,
digest = digestInfo.digest,
incrementalDigest = digestInfo.incrementalDigest,
incrementalDigestChunkSize = digestInfo.incrementalMacChunkSize,
uploadTimestamp = attachmentStream.uploadTimestamp,
dataSize = attachmentStream.length,
blurHash = attachmentStream.blurHash.getOrNull()
)
fun getAttachmentV4UploadForm(uploadSizeBytes: Long): RequestResult<AttachmentUploadForm, UploadTooLargeException> {
return try {
runBlocking {
authWebSocket.runWithChatConnection { chatConnection ->
AuthMessagesService(chatConnection as AuthenticatedChatConnection).getUploadForm(uploadSizeBytes)
}
}.getOrError().map { form ->
AttachmentUploadForm(
cdn = form.cdn,
key = form.key,
headers = form.headers,
signedUploadLocation = form.signedUploadUrl.toString()
)
}
} catch (e: IOException) {
RequestResult.RetryableNetworkError(e)
} catch (e: Throwable) {
RequestResult.ApplicationError(e)
}
}