From 10cf4315373f2c85615d1f5a67ab30a85f81766a Mon Sep 17 00:00:00 2001 From: Cody Henthorne Date: Thu, 8 Dec 2022 13:07:24 -0500 Subject: [PATCH] Revert " Enable kotlin for libsignal-service project and convert SignalServiceDataMessage." This reverts commit fc2b67aa0fe7d9adaa9666092cc6d9d7012e2a37. --- build.gradle | 1 - gradle/verification-metadata.xml | 128 --- libsignal/service/build.gradle | 4 - .../api/messages/SignalServiceContent.java | 44 +- .../messages/SignalServiceDataMessage.java | 729 ++++++++++++++++++ .../api/messages/SignalServiceDataMessage.kt | 288 ------- .../signalservice/api/util/OptionalUtil.java | 53 ++ .../signalservice/api/util/OptionalUtil.kt | 67 -- 8 files changed, 803 insertions(+), 511 deletions(-) create mode 100644 libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/SignalServiceDataMessage.java delete mode 100644 libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/SignalServiceDataMessage.kt create mode 100644 libsignal/service/src/main/java/org/whispersystems/signalservice/api/util/OptionalUtil.java delete mode 100644 libsignal/service/src/main/java/org/whispersystems/signalservice/api/util/OptionalUtil.kt diff --git a/build.gradle b/build.gradle index cd53100e3c..946d118de2 100644 --- a/build.gradle +++ b/build.gradle @@ -79,7 +79,6 @@ task qa { ':Signal-Android:lintPlayProdRelease', 'Signal-Android:ktlintCheck', ':libsignal-service:test', - ':libsignal-service:ktlintCheck', ':Signal-Android:assemblePlayProdRelease' } diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index fc25766bbd..b6a80016e0 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -2292,14 +2292,6 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - - - - @@ -2308,14 +2300,6 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - - - - @@ -2324,14 +2308,6 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - - - - @@ -2340,14 +2316,6 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - - - - @@ -2356,14 +2324,6 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - - - - @@ -2372,14 +2332,6 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - - - - @@ -2388,14 +2340,6 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - - - - @@ -2404,14 +2348,6 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - - - - @@ -2420,14 +2356,6 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - - - - @@ -2436,14 +2364,6 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - - - - @@ -2452,14 +2372,6 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - - - - @@ -3512,11 +3424,6 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - @@ -3552,11 +3459,6 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - @@ -3697,11 +3599,6 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - @@ -3722,11 +3619,6 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - @@ -3807,11 +3699,6 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - @@ -3862,11 +3749,6 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - @@ -3902,11 +3784,6 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - @@ -3947,11 +3824,6 @@ https://docs.gradle.org/current/userguide/dependency_verification.html - - - - - diff --git a/libsignal/service/build.gradle b/libsignal/service/build.gradle index 5d99b0ef21..2216625f7d 100644 --- a/libsignal/service/build.gradle +++ b/libsignal/service/build.gradle @@ -1,11 +1,9 @@ apply plugin: 'java-library' -apply plugin: 'org.jetbrains.kotlin.jvm' apply plugin: 'java-test-fixtures' apply plugin: 'com.google.protobuf' apply plugin: 'maven-publish' apply plugin: 'signing' apply plugin: 'idea' -apply plugin: 'org.jlleitschuh.gradle.ktlint' sourceCompatibility = 1.8 archivesBaseName = "signal-service-java" @@ -43,8 +41,6 @@ dependencies { api libs.rxjava3.rxjava - implementation libs.kotlin.stdlib.jdk8 - testImplementation testLibs.junit.junit testImplementation testLibs.assertj.core testImplementation testLibs.conscrypt.openjdk.uber diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/SignalServiceContent.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/SignalServiceContent.java index c59f66e2c8..6cdda86785 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/SignalServiceContent.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/SignalServiceContent.java @@ -721,29 +721,27 @@ public final class SignalServiceContent { metadata.getSenderDevice()); } - return SignalServiceDataMessage.newBuilder() - .withTimestamp(metadata.getTimestamp()) - .asGroupMessage(groupInfoV2) - .withAttachments(attachments) - .withBody(content.hasBody() ? content.getBody() : null) - .asEndSessionMessage(endSession) - .withExpiration(content.getExpireTimer()) - .asExpirationUpdate(expirationUpdate) - .withProfileKey(content.hasProfileKey() ? content.getProfileKey().toByteArray() : null) - .asProfileKeyUpdate(profileKeyUpdate) - .withQuote(quote) - .withSharedContacts(sharedContacts) - .withPreviews(previews) - .withMentions(mentions) - .withSticker(sticker) - .withViewOnce(content.getIsViewOnce()) - .withReaction(reaction) - .withRemoteDelete(remoteDelete) - .withGroupCallUpdate(groupCallUpdate) - .withPayment(payment) - .withStoryContext(storyContext) - .withGiftBadge(giftBadge) - .build(); + return new SignalServiceDataMessage(metadata.getTimestamp(), + groupInfoV2, + attachments, + content.hasBody() ? content.getBody() : null, + endSession, + content.getExpireTimer(), + expirationUpdate, + content.hasProfileKey() ? content.getProfileKey().toByteArray() : null, + profileKeyUpdate, + quote, + sharedContacts, + previews, + mentions, + sticker, + content.getIsViewOnce(), + reaction, + remoteDelete, + groupCallUpdate, + payment, + storyContext, + giftBadge); } private static SignalServiceSyncMessage createSynchronizeMessage(SignalServiceMetadata metadata, diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/SignalServiceDataMessage.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/SignalServiceDataMessage.java new file mode 100644 index 0000000000..3c7b0273fa --- /dev/null +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/SignalServiceDataMessage.java @@ -0,0 +1,729 @@ +/* + * Copyright (C) 2014-2016 Open Whisper Systems + * + * Licensed according to the LICENSE file in this repository. + */ + +package org.whispersystems.signalservice.api.messages; + +import org.signal.libsignal.protocol.InvalidMessageException; +import org.signal.libsignal.zkgroup.groups.GroupSecretParams; +import org.signal.libsignal.zkgroup.receipts.ReceiptCredentialPresentation; +import org.whispersystems.signalservice.api.messages.shared.SharedContact; +import org.whispersystems.signalservice.api.push.ServiceId; +import org.whispersystems.signalservice.api.push.SignalServiceAddress; +import org.whispersystems.signalservice.api.util.OptionalUtil; +import org.whispersystems.signalservice.internal.push.SignalServiceProtos; + +import java.util.LinkedList; +import java.util.List; +import java.util.Optional; + +/** + * Represents a decrypted Signal Service data message. + */ +public class SignalServiceDataMessage { + + private final long timestamp; + private final Optional> attachments; + private final Optional body; + private final Optional group; + private final Optional profileKey; + private final boolean endSession; + private final boolean expirationUpdate; + private final int expiresInSeconds; + private final boolean profileKeyUpdate; + private final Optional quote; + private final Optional> contacts; + private final Optional> previews; + private final Optional> mentions; + private final Optional sticker; + private final boolean viewOnce; + private final Optional reaction; + private final Optional remoteDelete; + private final Optional groupCallUpdate; + private final Optional payment; + private final Optional storyContext; + private final Optional giftBadge; + + /** + * Construct a SignalServiceDataMessage. + * + * @param timestamp The sent timestamp. + * @param groupV2 The group information (or null if none). + * @param attachments The attachments (or null if none). + * @param body The message contents. + * @param endSession Flag indicating whether this message should close a session. + * @param expiresInSeconds Number of seconds in which the message should disappear after being seen. + */ + SignalServiceDataMessage(long timestamp, + SignalServiceGroupV2 groupV2, + List attachments, + String body, + boolean endSession, + int expiresInSeconds, + boolean expirationUpdate, + byte[] profileKey, + boolean profileKeyUpdate, + Quote quote, + List sharedContacts, + List previews, + List mentions, + Sticker sticker, + boolean viewOnce, + Reaction reaction, + RemoteDelete remoteDelete, + GroupCallUpdate groupCallUpdate, + Payment payment, + StoryContext storyContext, + GiftBadge giftBadge) + { + this.group = Optional.ofNullable(groupV2); + this.timestamp = timestamp; + this.body = OptionalUtil.absentIfEmpty(body); + this.endSession = endSession; + this.expiresInSeconds = expiresInSeconds; + this.expirationUpdate = expirationUpdate; + this.profileKey = Optional.ofNullable(profileKey); + this.profileKeyUpdate = profileKeyUpdate; + this.quote = Optional.ofNullable(quote); + this.sticker = Optional.ofNullable(sticker); + this.viewOnce = viewOnce; + this.reaction = Optional.ofNullable(reaction); + this.remoteDelete = Optional.ofNullable(remoteDelete); + this.groupCallUpdate = Optional.ofNullable(groupCallUpdate); + this.payment = Optional.ofNullable(payment); + this.storyContext = Optional.ofNullable(storyContext); + this.giftBadge = Optional.ofNullable(giftBadge); + + if (attachments != null && !attachments.isEmpty()) { + this.attachments = Optional.of(attachments); + } else { + this.attachments = Optional.empty(); + } + + if (sharedContacts != null && !sharedContacts.isEmpty()) { + this.contacts = Optional.of(sharedContacts); + } else { + this.contacts = Optional.empty(); + } + + if (previews != null && !previews.isEmpty()) { + this.previews = Optional.of(previews); + } else { + this.previews = Optional.empty(); + } + + if (mentions != null && !mentions.isEmpty()) { + this.mentions = Optional.of(mentions); + } else { + this.mentions = Optional.empty(); + } + } + + public static Builder newBuilder() { + return new Builder(); + } + + /** + * @return The message timestamp. + */ + public long getTimestamp() { + return timestamp; + } + + /** + * @return The message attachments (if any). + */ + public Optional> getAttachments() { + return attachments; + } + + /** + * @return The message body (if any). + */ + public Optional getBody() { + return body; + } + + /** + * @return The message group context (if any). + */ + public Optional getGroupContext() { + return group; + } + + public boolean isEndSession() { + return endSession; + } + + public boolean isExpirationUpdate() { + return expirationUpdate; + } + + public boolean isActivatePaymentsRequest() { + return getPayment().isPresent() && + getPayment().get().getPaymentActivation().isPresent() && + getPayment().get().getPaymentActivation().get().getType().equals(SignalServiceProtos.DataMessage.Payment.Activation.Type.REQUEST); + } + + public boolean isPaymentsActivated() { + return getPayment().isPresent() && + getPayment().get().getPaymentActivation().isPresent() && + getPayment().get().getPaymentActivation().get().getType().equals(SignalServiceProtos.DataMessage.Payment.Activation.Type.ACTIVATED); + } + + public boolean isProfileKeyUpdate() { + return profileKeyUpdate; + } + + public boolean isGroupV2Message() { + return group.isPresent(); + } + + public boolean isGroupV2Update() { + return group.isPresent() && + group.get().hasSignedGroupChange() && + !hasRenderableContent(); + } + + public boolean isEmptyGroupV2Message() { + return isGroupV2Message() && !isGroupV2Update() && !hasRenderableContent(); + } + + /** Contains some user data that affects the conversation */ + public boolean hasRenderableContent() { + return attachments.isPresent() || + body.isPresent() || + quote.isPresent() || + contacts.isPresent() || + previews.isPresent() || + mentions.isPresent() || + sticker.isPresent() || + reaction.isPresent() || + remoteDelete.isPresent(); + } + + public int getExpiresInSeconds() { + return expiresInSeconds; + } + + public Optional getProfileKey() { + return profileKey; + } + + public Optional getQuote() { + return quote; + } + + public Optional> getSharedContacts() { + return contacts; + } + + public Optional> getPreviews() { + return previews; + } + + public Optional> getMentions() { + return mentions; + } + + public Optional getSticker() { + return sticker; + } + + public boolean isViewOnce() { + return viewOnce; + } + + public Optional getReaction() { + return reaction; + } + + public Optional getRemoteDelete() { + return remoteDelete; + } + + public Optional getGroupCallUpdate() { + return groupCallUpdate; + } + + public Optional getPayment() { + return payment; + } + + public Optional getStoryContext() { + return storyContext; + } + + public Optional getGiftBadge() { + return giftBadge; + } + + public Optional getGroupId() { + byte[] groupId = null; + + if (getGroupContext().isPresent() && getGroupContext().isPresent()) { + SignalServiceGroupV2 gv2 = getGroupContext().get(); + groupId = GroupSecretParams.deriveFromMasterKey(gv2.getMasterKey()) + .getPublicParams() + .getGroupIdentifier() + .serialize(); + } + + return Optional.ofNullable(groupId); + } + + public static class Builder { + + private List attachments = new LinkedList<>(); + private List sharedContacts = new LinkedList<>(); + private List previews = new LinkedList<>(); + private List mentions = new LinkedList<>(); + + private long timestamp; + private SignalServiceGroupV2 groupV2; + private String body; + private boolean endSession; + private int expiresInSeconds; + private boolean expirationUpdate; + private byte[] profileKey; + private boolean profileKeyUpdate; + private Quote quote; + private Sticker sticker; + private boolean viewOnce; + private Reaction reaction; + private RemoteDelete remoteDelete; + private GroupCallUpdate groupCallUpdate; + private Payment payment; + private StoryContext storyContext; + private GiftBadge giftBadge; + + private Builder() {} + + public Builder withTimestamp(long timestamp) { + this.timestamp = timestamp; + return this; + } + + public Builder asGroupMessage(SignalServiceGroupV2 group) { + this.groupV2 = group; + return this; + } + + public Builder withAttachment(SignalServiceAttachment attachment) { + this.attachments.add(attachment); + return this; + } + + public Builder withAttachments(List attachments) { + this.attachments.addAll(attachments); + return this; + } + + public Builder withBody(String body) { + this.body = body; + return this; + } + + public Builder asEndSessionMessage() { + return asEndSessionMessage(true); + } + + public Builder asEndSessionMessage(boolean endSession) { + this.endSession = endSession; + return this; + } + + public Builder asExpirationUpdate() { + return asExpirationUpdate(true); + } + + public Builder asExpirationUpdate(boolean expirationUpdate) { + this.expirationUpdate = expirationUpdate; + return this; + } + + public Builder withExpiration(int expiresInSeconds) { + this.expiresInSeconds = expiresInSeconds; + return this; + } + + public Builder withProfileKey(byte[] profileKey) { + this.profileKey = profileKey; + return this; + } + + public Builder asProfileKeyUpdate(boolean profileKeyUpdate) { + this.profileKeyUpdate = profileKeyUpdate; + return this; + } + + public Builder withQuote(Quote quote) { + this.quote = quote; + return this; + } + + public Builder withSharedContact(SharedContact contact) { + this.sharedContacts.add(contact); + return this; + } + + public Builder withSharedContacts(List contacts) { + this.sharedContacts.addAll(contacts); + return this; + } + + public Builder withPreviews(List previews) { + this.previews.addAll(previews); + return this; + } + + public Builder withMentions(List mentions) { + this.mentions.addAll(mentions); + return this; + } + + public Builder withSticker(Sticker sticker) { + this.sticker = sticker; + return this; + } + + public Builder withViewOnce(boolean viewOnce) { + this.viewOnce = viewOnce; + return this; + } + + public Builder withReaction(Reaction reaction) { + this.reaction = reaction; + return this; + } + + public Builder withRemoteDelete(RemoteDelete remoteDelete) { + this.remoteDelete = remoteDelete; + return this; + } + + public Builder withGroupCallUpdate(GroupCallUpdate groupCallUpdate) { + this.groupCallUpdate = groupCallUpdate; + return this; + } + + public Builder withPayment(Payment payment) { + this.payment = payment; + return this; + } + + public Builder withStoryContext(StoryContext storyContext) { + this.storyContext = storyContext; + return this; + } + + public Builder withGiftBadge(GiftBadge giftBadge) { + this.giftBadge = giftBadge; + return this; + } + + public SignalServiceDataMessage build() { + if (timestamp == 0) timestamp = System.currentTimeMillis(); + return new SignalServiceDataMessage(timestamp, groupV2, attachments, body, endSession, + expiresInSeconds, expirationUpdate, profileKey, + profileKeyUpdate, quote, sharedContacts, previews, + mentions, sticker, viewOnce, reaction, remoteDelete, + groupCallUpdate, + payment, + storyContext, + giftBadge); + } + } + + public static class Quote { + private final long id; + private final ServiceId author; + private final String text; + private final List attachments; + private final List mentions; + private final Type type; + + public Quote(long id, + ServiceId author, + String text, + List attachments, + List mentions, + Type type) + { + this.id = id; + this.author = author; + this.text = text; + this.attachments = attachments; + this.mentions = mentions; + this.type = type; + } + + public long getId() { + return id; + } + + public ServiceId getAuthor() { + return author; + } + + public String getText() { + return text; + } + + public List getAttachments() { + return attachments; + } + + public List getMentions() { + return mentions; + } + + public Type getType() { + return type; + } + + public enum Type { + NORMAL(SignalServiceProtos.DataMessage.Quote.Type.NORMAL), + GIFT_BADGE(SignalServiceProtos.DataMessage.Quote.Type.GIFT_BADGE); + + private final SignalServiceProtos.DataMessage.Quote.Type protoType; + + Type(SignalServiceProtos.DataMessage.Quote.Type protoType) { + this.protoType = protoType; + } + + public SignalServiceProtos.DataMessage.Quote.Type getProtoType() { + return protoType; + } + + public static Type fromProto(SignalServiceProtos.DataMessage.Quote.Type protoType) { + for (final Type value : values()) { + if (value.protoType == protoType) { + return value; + } + } + + return NORMAL; + } + } + + public static class QuotedAttachment { + private final String contentType; + private final String fileName; + private final SignalServiceAttachment thumbnail; + + public QuotedAttachment(String contentType, String fileName, SignalServiceAttachment thumbnail) { + this.contentType = contentType; + this.fileName = fileName; + this.thumbnail = thumbnail; + } + + public String getContentType() { + return contentType; + } + + public String getFileName() { + return fileName; + } + + public SignalServiceAttachment getThumbnail() { + return thumbnail; + } + } + } + + public static class Sticker { + private final byte[] packId; + private final byte[] packKey; + private final int stickerId; + private final String emoji; + private final SignalServiceAttachment attachment; + + public Sticker(byte[] packId, byte[] packKey, int stickerId, String emoji, SignalServiceAttachment attachment) { + this.packId = packId; + this.packKey = packKey; + this.stickerId = stickerId; + this.emoji = emoji; + this.attachment = attachment; + } + + public byte[] getPackId() { + return packId; + } + + public byte[] getPackKey() { + return packKey; + } + + public int getStickerId() { + return stickerId; + } + + public String getEmoji() { + return emoji; + } + + public SignalServiceAttachment getAttachment() { + return attachment; + } + } + + public static class Reaction { + private final String emoji; + private final boolean remove; + private final ServiceId targetAuthor; + private final long targetSentTimestamp; + + public Reaction(String emoji, boolean remove, ServiceId targetAuthor, long targetSentTimestamp) { + this.emoji = emoji; + this.remove = remove; + this.targetAuthor = targetAuthor; + this.targetSentTimestamp = targetSentTimestamp; + } + + public String getEmoji() { + return emoji; + } + + public boolean isRemove() { + return remove; + } + + public ServiceId getTargetAuthor() { + return targetAuthor; + } + + public long getTargetSentTimestamp() { + return targetSentTimestamp; + } + } + + public static class RemoteDelete { + private final long targetSentTimestamp; + + public RemoteDelete(long targetSentTimestamp) { + this.targetSentTimestamp = targetSentTimestamp; + } + + public long getTargetSentTimestamp() { + return targetSentTimestamp; + } + } + + public static class Mention { + private final ServiceId serviceId; + private final int start; + private final int length; + + public Mention(ServiceId serviceId, int start, int length) { + this.serviceId = serviceId; + this.start = start; + this.length = length; + } + + public ServiceId getServiceId() { + return serviceId; + } + + public int getStart() { + return start; + } + + public int getLength() { + return length; + } + } + + public static class GroupCallUpdate { + private final String eraId; + + public GroupCallUpdate(String eraId) { + this.eraId = eraId; + } + + public String getEraId() { + return eraId; + } + } + + public static class PaymentNotification { + + private final byte[] receipt; + private final String note; + + public PaymentNotification(byte[] receipt, String note) { + this.receipt = receipt; + this.note = note; + } + + public byte[] getReceipt() { + return receipt; + } + + public String getNote() { + return note; + } + } + + public static class PaymentActivation { + private final SignalServiceProtos.DataMessage.Payment.Activation.Type type; + + public PaymentActivation(SignalServiceProtos.DataMessage.Payment.Activation.Type type) { + this.type = type; + } + + public SignalServiceProtos.DataMessage.Payment.Activation.Type getType() { + return type; + } + } + + public static class Payment { + private final Optional paymentNotification; + private final Optional paymentActivation; + + public Payment(PaymentNotification paymentNotification, PaymentActivation paymentActivation) { + this.paymentNotification = Optional.ofNullable(paymentNotification); + this.paymentActivation = Optional.ofNullable(paymentActivation); + } + + public Optional getPaymentNotification() { + return paymentNotification; + } + + public Optional getPaymentActivation() { + return paymentActivation; + } + } + + public static class StoryContext { + private final ServiceId authorServiceId; + private final long sentTimestamp; + + public StoryContext(ServiceId authorServiceId, long sentTimestamp) { + this.authorServiceId = authorServiceId; + this.sentTimestamp = sentTimestamp; + } + + public ServiceId getAuthorServiceId() { + return authorServiceId; + } + + public long getSentTimestamp() { + return sentTimestamp; + } + } + + public static class GiftBadge { + private final ReceiptCredentialPresentation receiptCredentialPresentation; + + public GiftBadge(ReceiptCredentialPresentation receiptCredentialPresentation) { + this.receiptCredentialPresentation = receiptCredentialPresentation; + } + + public ReceiptCredentialPresentation getReceiptCredentialPresentation() { + return receiptCredentialPresentation; + } + } +} diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/SignalServiceDataMessage.kt b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/SignalServiceDataMessage.kt deleted file mode 100644 index 46b10ab935..0000000000 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/messages/SignalServiceDataMessage.kt +++ /dev/null @@ -1,288 +0,0 @@ -/* - * Copyright (C) 2014-2016 Open Whisper Systems - * - * Licensed according to the LICENSE file in this repository. - */ -package org.whispersystems.signalservice.api.messages - -import org.signal.libsignal.zkgroup.groups.GroupSecretParams -import org.signal.libsignal.zkgroup.receipts.ReceiptCredentialPresentation -import org.whispersystems.signalservice.api.messages.shared.SharedContact -import org.whispersystems.signalservice.api.push.ServiceId -import org.whispersystems.signalservice.api.util.OptionalUtil.asOptional -import org.whispersystems.signalservice.api.util.OptionalUtil.emptyIfStringEmpty -import java.util.LinkedList -import java.util.Optional -import org.whispersystems.signalservice.internal.push.SignalServiceProtos.DataMessage.Payment as PaymentProto -import org.whispersystems.signalservice.internal.push.SignalServiceProtos.DataMessage.Quote as QuoteProto - -/** - * Represents a decrypted Signal Service data message. - * - * @param timestamp The sent timestamp. - * @param groupContext The group information (or null if none). - * @param attachments The attachments (or null if none). - * @param body The message contents. - * @param isEndSession Flag indicating whether this message should close a session. - * @param expiresInSeconds Number of seconds in which the message should disappear after being seen. - */ -class SignalServiceDataMessage private constructor( - val timestamp: Long, - val groupContext: Optional, - val attachments: Optional>, - val body: Optional, - val isEndSession: Boolean, - val expiresInSeconds: Int, - val isExpirationUpdate: Boolean, - val profileKey: Optional, - val isProfileKeyUpdate: Boolean, - val quote: Optional, - val sharedContacts: Optional>, - val previews: Optional>, - val mentions: Optional>, - val sticker: Optional, - val isViewOnce: Boolean, - val reaction: Optional, - val remoteDelete: Optional, - val groupCallUpdate: Optional, - val payment: Optional, - val storyContext: Optional, - val giftBadge: Optional -) { - val isActivatePaymentsRequest: Boolean = payment.map { it.isActivationRequest }.orElse(false) - val isPaymentsActivated: Boolean = payment.map { it.isActivation }.orElse(false) - - val groupId: Optional = groupContext.map { GroupSecretParams.deriveFromMasterKey(it.masterKey).publicParams.groupIdentifier.serialize() } - val isGroupV2Message: Boolean = groupContext.isPresent - - /** Contains some user data that affects the conversation */ - private val hasRenderableContent: Boolean = - this.attachments.isPresent || - this.body.isPresent || - this.quote.isPresent || - this.sharedContacts.isPresent || - this.previews.isPresent || - this.mentions.isPresent || - this.sticker.isPresent || - this.reaction.isPresent || - this.remoteDelete.isPresent - - val isGroupV2Update: Boolean = groupContext.isPresent && groupContext.get().hasSignedGroupChange() && !hasRenderableContent - val isEmptyGroupV2Message: Boolean = isGroupV2Message && !isGroupV2Update && !hasRenderableContent - - class Builder { - private var timestamp: Long = 0 - private var groupV2: SignalServiceGroupV2? = null - private val attachments: MutableList = LinkedList() - private var body: String? = null - private var endSession: Boolean = false - private var expiresInSeconds: Int = 0 - private var expirationUpdate: Boolean = false - private var profileKey: ByteArray? = null - private var profileKeyUpdate: Boolean = false - private var quote: Quote? = null - private val sharedContacts: MutableList = LinkedList() - private val previews: MutableList = LinkedList() - private val mentions: MutableList = LinkedList() - private var sticker: Sticker? = null - private var viewOnce: Boolean = false - private var reaction: Reaction? = null - private var remoteDelete: RemoteDelete? = null - private var groupCallUpdate: GroupCallUpdate? = null - private var payment: Payment? = null - private var storyContext: StoryContext? = null - private var giftBadge: GiftBadge? = null - - fun withTimestamp(timestamp: Long): Builder { - this.timestamp = timestamp - return this - } - - fun asGroupMessage(group: SignalServiceGroupV2?): Builder { - groupV2 = group - return this - } - - fun withAttachment(attachment: SignalServiceAttachment?): Builder { - attachment?.let { attachments.add(attachment) } - return this - } - - fun withAttachments(attachments: List?): Builder { - attachments?.let { this.attachments.addAll(attachments) } - return this - } - - fun withBody(body: String?): Builder { - this.body = body - return this - } - - @JvmOverloads - fun asEndSessionMessage(endSession: Boolean = true): Builder { - this.endSession = endSession - return this - } - - @JvmOverloads - fun asExpirationUpdate(expirationUpdate: Boolean = true): Builder { - this.expirationUpdate = expirationUpdate - return this - } - - fun withExpiration(expiresInSeconds: Int): Builder { - this.expiresInSeconds = expiresInSeconds - return this - } - - fun withProfileKey(profileKey: ByteArray?): Builder { - this.profileKey = profileKey - return this - } - - fun asProfileKeyUpdate(profileKeyUpdate: Boolean): Builder { - this.profileKeyUpdate = profileKeyUpdate - return this - } - - fun withQuote(quote: Quote?): Builder { - this.quote = quote - return this - } - - fun withSharedContact(contact: SharedContact?): Builder { - contact?.let { sharedContacts.add(contact) } - return this - } - - fun withSharedContacts(contacts: List?): Builder { - contacts?.let { sharedContacts.addAll(contacts) } - return this - } - - fun withPreviews(previews: List?): Builder { - previews?.let { this.previews.addAll(previews) } - return this - } - - fun withMentions(mentions: List?): Builder { - mentions?.let { this.mentions.addAll(mentions) } - return this - } - - fun withSticker(sticker: Sticker?): Builder { - this.sticker = sticker - return this - } - - fun withViewOnce(viewOnce: Boolean): Builder { - this.viewOnce = viewOnce - return this - } - - fun withReaction(reaction: Reaction?): Builder { - this.reaction = reaction - return this - } - - fun withRemoteDelete(remoteDelete: RemoteDelete?): Builder { - this.remoteDelete = remoteDelete - return this - } - - fun withGroupCallUpdate(groupCallUpdate: GroupCallUpdate?): Builder { - this.groupCallUpdate = groupCallUpdate - return this - } - - fun withPayment(payment: Payment?): Builder { - this.payment = payment - return this - } - - fun withStoryContext(storyContext: StoryContext?): Builder { - this.storyContext = storyContext - return this - } - - fun withGiftBadge(giftBadge: GiftBadge?): Builder { - this.giftBadge = giftBadge - return this - } - - fun build(): SignalServiceDataMessage { - if (timestamp == 0L) { - timestamp = System.currentTimeMillis() - } - - return SignalServiceDataMessage( - timestamp = timestamp, - groupContext = groupV2.asOptional(), - attachments = attachments.asOptional(), - body = body.emptyIfStringEmpty(), - isEndSession = endSession, - expiresInSeconds = expiresInSeconds, - isExpirationUpdate = expirationUpdate, - profileKey = profileKey.asOptional(), - isProfileKeyUpdate = profileKeyUpdate, - quote = quote.asOptional(), - sharedContacts = sharedContacts.asOptional(), - previews = previews.asOptional(), - mentions = mentions.asOptional(), - sticker = sticker.asOptional(), - isViewOnce = viewOnce, - reaction = reaction.asOptional(), - remoteDelete = remoteDelete.asOptional(), - groupCallUpdate = groupCallUpdate.asOptional(), - payment = payment.asOptional(), - storyContext = storyContext.asOptional(), - giftBadge = giftBadge.asOptional() - ) - } - } - - data class Quote( - val id: Long, - val author: ServiceId, - val text: String, - val attachments: List, - val mentions: List, - val type: Type - ) { - enum class Type(val protoType: QuoteProto.Type) { - NORMAL(QuoteProto.Type.NORMAL), - GIFT_BADGE(QuoteProto.Type.GIFT_BADGE); - - companion object { - @JvmStatic - fun fromProto(protoType: QuoteProto.Type): Type { - return values().firstOrNull { it.protoType == protoType } ?: NORMAL - } - } - } - - data class QuotedAttachment(val contentType: String, val fileName: String, val thumbnail: SignalServiceAttachment) - } - class Sticker(val packId: ByteArray, val packKey: ByteArray, val stickerId: Int, val emoji: String, val attachment: SignalServiceAttachment) - data class Reaction(val emoji: String, val isRemove: Boolean, val targetAuthor: ServiceId, val targetSentTimestamp: Long) - data class RemoteDelete(val targetSentTimestamp: Long) - data class Mention(val serviceId: ServiceId, val start: Int, val length: Int) - data class GroupCallUpdate(val eraId: String) - class PaymentNotification(val receipt: ByteArray, val note: String) - data class PaymentActivation(val type: PaymentProto.Activation.Type) - class Payment(paymentNotification: PaymentNotification?, paymentActivation: PaymentActivation?) { - val paymentNotification: Optional = Optional.ofNullable(paymentNotification) - val paymentActivation: Optional = Optional.ofNullable(paymentActivation) - val isActivationRequest: Boolean = paymentActivation != null && paymentActivation.type == PaymentProto.Activation.Type.REQUEST - val isActivation: Boolean = paymentActivation != null && paymentActivation.type == PaymentProto.Activation.Type.ACTIVATED - } - data class StoryContext(val authorServiceId: ServiceId, val sentTimestamp: Long) - data class GiftBadge(val receiptCredentialPresentation: ReceiptCredentialPresentation) - - companion object { - @JvmStatic - fun newBuilder(): Builder { - return Builder() - } - } -} diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/util/OptionalUtil.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/util/OptionalUtil.java new file mode 100644 index 0000000000..52b56ae0e4 --- /dev/null +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/util/OptionalUtil.java @@ -0,0 +1,53 @@ +package org.whispersystems.signalservice.api.util; + +import com.google.protobuf.ByteString; + +import java.util.Arrays; +import java.util.Optional; + +public final class OptionalUtil { + + private OptionalUtil() { } + + @SafeVarargs + public static Optional or(Optional... optionals) { + return Arrays.stream(optionals) + .filter(Optional::isPresent) + .findFirst() + .orElse(Optional.empty()); + } + + public static boolean byteArrayEquals(Optional a, Optional b) { + if (a.isPresent() != b.isPresent()) { + return false; + } else if (a.isPresent()) { + return Arrays.equals(a.get(), b.get()); + } else { + return true; + } + } + + public static int byteArrayHashCode(Optional bytes) { + if (bytes.isPresent()) { + return Arrays.hashCode(bytes.get()); + } else { + return 0; + } + } + + public static Optional absentIfEmpty(String value) { + if (value == null || value.length() == 0) { + return Optional.empty(); + } else { + return Optional.of(value); + } + } + + public static Optional absentIfEmpty(ByteString value) { + if (value == null || value.isEmpty()) { + return Optional.empty(); + } else { + return Optional.of(value.toByteArray()); + } + } +} diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/util/OptionalUtil.kt b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/util/OptionalUtil.kt deleted file mode 100644 index 469d2419fd..0000000000 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/util/OptionalUtil.kt +++ /dev/null @@ -1,67 +0,0 @@ -package org.whispersystems.signalservice.api.util - -import com.google.protobuf.ByteString -import java.util.Optional - -object OptionalUtil { - @JvmStatic - @SafeVarargs - fun or(vararg optionals: Optional): Optional { - return optionals.firstOrNull { it.isPresent } ?: Optional.empty() - } - - @JvmStatic - fun byteArrayEquals(a: Optional, b: Optional): Boolean { - return if (a.isPresent != b.isPresent) { - false - } else if (a.isPresent) { - a.get().contentEquals(b.get()) - } else { - true - } - } - - @JvmStatic - fun byteArrayHashCode(bytes: Optional): Int { - return if (bytes.isPresent) { - bytes.get().contentHashCode() - } else { - 0 - } - } - - @JvmStatic - fun absentIfEmpty(value: String?): Optional { - return if (value == null || value.isEmpty()) { - Optional.empty() - } else { - Optional.of(value) - } - } - - @JvmStatic - fun absentIfEmpty(value: ByteString?): Optional { - return if (value == null || value.isEmpty) { - Optional.empty() - } else { - Optional.of(value.toByteArray()) - } - } - - @JvmStatic - fun emptyIfListEmpty(list: List?): Optional> { - return list.asOptional() - } - - fun E?.asOptional(): Optional { - return Optional.ofNullable(this) - } - - fun List?.asOptional(): Optional> { - return Optional.ofNullable(this?.takeIf { it.isNotEmpty() }) - } - - fun String?.emptyIfStringEmpty(): Optional { - return absentIfEmpty(this) - } -}