Refactor message sending

This commit is contained in:
AsamK 2021-08-21 15:02:14 +02:00
parent b77d820661
commit 893b7f7f9d
6 changed files with 404 additions and 295 deletions

View file

@ -33,6 +33,7 @@ import org.asamk.signal.manager.groups.NotAGroupMemberException;
import org.asamk.signal.manager.helper.GroupV2Helper; import org.asamk.signal.manager.helper.GroupV2Helper;
import org.asamk.signal.manager.helper.PinHelper; import org.asamk.signal.manager.helper.PinHelper;
import org.asamk.signal.manager.helper.ProfileHelper; import org.asamk.signal.manager.helper.ProfileHelper;
import org.asamk.signal.manager.helper.SendHelper;
import org.asamk.signal.manager.helper.UnidentifiedAccessHelper; import org.asamk.signal.manager.helper.UnidentifiedAccessHelper;
import org.asamk.signal.manager.jobs.Context; import org.asamk.signal.manager.jobs.Context;
import org.asamk.signal.manager.jobs.Job; import org.asamk.signal.manager.jobs.Job;
@ -86,7 +87,6 @@ import org.whispersystems.libsignal.util.Pair;
import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.InvalidMessageStructureException; import org.whispersystems.signalservice.api.InvalidMessageStructureException;
import org.whispersystems.signalservice.api.SignalSessionLock; import org.whispersystems.signalservice.api.SignalSessionLock;
import org.whispersystems.signalservice.api.crypto.ContentHint;
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException; import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
import org.whispersystems.signalservice.api.groupsv2.GroupLinkNotActiveException; import org.whispersystems.signalservice.api.groupsv2.GroupLinkNotActiveException;
import org.whispersystems.signalservice.api.groupsv2.GroupsV2AuthorizationString; import org.whispersystems.signalservice.api.groupsv2.GroupsV2AuthorizationString;
@ -111,7 +111,6 @@ import org.whispersystems.signalservice.api.messages.multidevice.DeviceGroup;
import org.whispersystems.signalservice.api.messages.multidevice.DeviceGroupsInputStream; import org.whispersystems.signalservice.api.messages.multidevice.DeviceGroupsInputStream;
import org.whispersystems.signalservice.api.messages.multidevice.DeviceGroupsOutputStream; import org.whispersystems.signalservice.api.messages.multidevice.DeviceGroupsOutputStream;
import org.whispersystems.signalservice.api.messages.multidevice.RequestMessage; import org.whispersystems.signalservice.api.messages.multidevice.RequestMessage;
import org.whispersystems.signalservice.api.messages.multidevice.SentTranscriptMessage;
import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage; import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage;
import org.whispersystems.signalservice.api.messages.multidevice.StickerPackOperationMessage; import org.whispersystems.signalservice.api.messages.multidevice.StickerPackOperationMessage;
import org.whispersystems.signalservice.api.messages.multidevice.VerifiedMessage; import org.whispersystems.signalservice.api.messages.multidevice.VerifiedMessage;
@ -120,7 +119,6 @@ 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.push.exceptions.ConflictException; import org.whispersystems.signalservice.api.push.exceptions.ConflictException;
import org.whispersystems.signalservice.api.push.exceptions.MissingConfigurationException; import org.whispersystems.signalservice.api.push.exceptions.MissingConfigurationException;
import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException;
import org.whispersystems.signalservice.api.util.DeviceNameUtil; import org.whispersystems.signalservice.api.util.DeviceNameUtil;
import org.whispersystems.signalservice.api.util.InvalidNumberException; import org.whispersystems.signalservice.api.util.InvalidNumberException;
import org.whispersystems.signalservice.api.util.PhoneNumberFormatter; import org.whispersystems.signalservice.api.util.PhoneNumberFormatter;
@ -179,10 +177,11 @@ public class Manager implements Closeable {
private final ExecutorService executor = Executors.newCachedThreadPool(); private final ExecutorService executor = Executors.newCachedThreadPool();
private final UnidentifiedAccessHelper unidentifiedAccessHelper;
private final ProfileHelper profileHelper; private final ProfileHelper profileHelper;
private final GroupV2Helper groupV2Helper; private final GroupV2Helper groupV2Helper;
private final PinHelper pinHelper; private final PinHelper pinHelper;
private final SendHelper sendHelper;
private final AvatarStore avatarStore; private final AvatarStore avatarStore;
private final AttachmentStore attachmentStore; private final AttachmentStore attachmentStore;
private final StickerPackStore stickerPackStore; private final StickerPackStore stickerPackStore;
@ -218,7 +217,7 @@ public class Manager implements Closeable {
sessionLock); sessionLock);
this.pinHelper = new PinHelper(dependencies.getKeyBackupService()); this.pinHelper = new PinHelper(dependencies.getKeyBackupService());
this.unidentifiedAccessHelper = new UnidentifiedAccessHelper(account::getProfileKey, final var unidentifiedAccessHelper = new UnidentifiedAccessHelper(account::getProfileKey,
account.getProfileStore()::getProfileKey, account.getProfileStore()::getProfileKey,
this::getRecipientProfile, this::getRecipientProfile,
this::getSenderCertificate); this::getSenderCertificate);
@ -237,6 +236,14 @@ public class Manager implements Closeable {
this.avatarStore = new AvatarStore(pathConfig.getAvatarsPath()); this.avatarStore = new AvatarStore(pathConfig.getAvatarsPath());
this.attachmentStore = new AttachmentStore(pathConfig.getAttachmentsPath()); this.attachmentStore = new AttachmentStore(pathConfig.getAttachmentsPath());
this.stickerPackStore = new StickerPackStore(pathConfig.getStickerPacksPath()); this.stickerPackStore = new StickerPackStore(pathConfig.getStickerPacksPath());
this.sendHelper = new SendHelper(account,
dependencies,
unidentifiedAccessHelper,
this::resolveSignalServiceAddress,
this::resolveRecipient,
this::handleIdentityFailure,
this::getGroup,
this::refreshRegisteredUser);
} }
public String getUsername() { public String getUsername() {
@ -396,10 +403,7 @@ public class Manager implements Closeable {
} }
account.getProfileStore().storeProfile(account.getSelfRecipientId(), newProfile); account.getProfileStore().storeProfile(account.getSelfRecipientId(), newProfile);
try { sendHelper.sendSyncMessage(SignalServiceSyncMessage.forFetchLatest(SignalServiceSyncMessage.FetchType.LOCAL_PROFILE));
sendSyncMessage(SignalServiceSyncMessage.forFetchLatest(SignalServiceSyncMessage.FetchType.LOCAL_PROFILE));
} catch (UntrustedIdentityException ignored) {
}
} }
public void unregister() throws IOException { public void unregister() throws IOException {
@ -653,17 +657,6 @@ public class Manager implements Closeable {
return Optional.of(AttachmentUtils.createAttachment(streamDetails, Optional.absent())); return Optional.of(AttachmentUtils.createAttachment(streamDetails, Optional.absent()));
} }
private GroupInfo getGroupForSending(GroupId groupId) throws GroupNotFoundException, NotAGroupMemberException {
var g = getGroup(groupId);
if (g == null) {
throw new GroupNotFoundException(groupId);
}
if (!g.isMember(account.getSelfRecipientId())) {
throw new NotAGroupMemberException(groupId, g.getTitle());
}
return g;
}
private GroupInfo getGroupForUpdating(GroupId groupId) throws GroupNotFoundException, NotAGroupMemberException { private GroupInfo getGroupForUpdating(GroupId groupId) throws GroupNotFoundException, NotAGroupMemberException {
var g = getGroup(groupId); var g = getGroup(groupId);
if (g == null) { if (g == null) {
@ -682,12 +675,12 @@ public class Manager implements Closeable {
public Pair<Long, List<SendMessageResult>> sendGroupMessage( public Pair<Long, List<SendMessageResult>> sendGroupMessage(
String messageText, List<String> attachments, GroupId groupId String messageText, List<String> attachments, GroupId groupId
) throws IOException, GroupNotFoundException, AttachmentInvalidException, NotAGroupMemberException { ) throws IOException, GroupNotFoundException, AttachmentInvalidException, NotAGroupMemberException {
final var messageBuilder = SignalServiceDataMessage.newBuilder().withBody(messageText); final var messageBuilder = createMessageBuilder().withBody(messageText);
if (attachments != null) { if (attachments != null) {
messageBuilder.withAttachments(AttachmentUtils.getSignalServiceAttachments(attachments)); messageBuilder.withAttachments(AttachmentUtils.getSignalServiceAttachments(attachments));
} }
return sendGroupMessage(messageBuilder, groupId); return sendHelper.sendAsGroupMessage(messageBuilder, groupId);
} }
public Pair<Long, List<SendMessageResult>> sendGroupMessageReaction( public Pair<Long, List<SendMessageResult>> sendGroupMessageReaction(
@ -698,20 +691,9 @@ public class Manager implements Closeable {
remove, remove,
resolveSignalServiceAddress(targetAuthorRecipientId), resolveSignalServiceAddress(targetAuthorRecipientId),
targetSentTimestamp); targetSentTimestamp);
final var messageBuilder = SignalServiceDataMessage.newBuilder().withReaction(reaction); final var messageBuilder = createMessageBuilder().withReaction(reaction);
return sendGroupMessage(messageBuilder, groupId); return sendHelper.sendAsGroupMessage(messageBuilder, groupId);
}
public Pair<Long, List<SendMessageResult>> sendGroupMessage(
SignalServiceDataMessage.Builder messageBuilder, GroupId groupId
) throws IOException, GroupNotFoundException, NotAGroupMemberException {
final var g = getGroupForSending(groupId);
GroupUtils.setGroupContext(messageBuilder, g);
messageBuilder.withExpiration(g.getMessageExpirationTime());
return sendMessage(messageBuilder, g.getMembersWithout(account.getSelfRecipientId()));
} }
public Pair<Long, List<SendMessageResult>> sendQuitGroupMessage( public Pair<Long, List<SendMessageResult>> sendQuitGroupMessage(
@ -722,7 +704,7 @@ public class Manager implements Closeable {
return quitGroupV1((GroupInfoV1) group); return quitGroupV1((GroupInfoV1) group);
} }
final var newAdmins = getSignalServiceAddresses(groupAdmins); final var newAdmins = getRecipientIds(groupAdmins);
try { try {
return quitGroupV2((GroupInfoV2) group, newAdmins); return quitGroupV2((GroupInfoV2) group, newAdmins);
} catch (ConflictException e) { } catch (ConflictException e) {
@ -737,10 +719,11 @@ public class Manager implements Closeable {
.withId(groupInfoV1.getGroupId().serialize()) .withId(groupInfoV1.getGroupId().serialize())
.build(); .build();
var messageBuilder = SignalServiceDataMessage.newBuilder().asGroupMessage(group); var messageBuilder = createMessageBuilder().asGroupMessage(group);
groupInfoV1.removeMember(account.getSelfRecipientId()); groupInfoV1.removeMember(account.getSelfRecipientId());
account.getGroupStore().updateGroup(groupInfoV1); account.getGroupStore().updateGroup(groupInfoV1);
return sendMessage(messageBuilder, groupInfoV1.getMembersWithout(account.getSelfRecipientId())); return sendHelper.sendGroupMessage(messageBuilder.build(),
groupInfoV1.getMembersIncludingPendingWithout(account.getSelfRecipientId()));
} }
private Pair<Long, List<SendMessageResult>> quitGroupV2( private Pair<Long, List<SendMessageResult>> quitGroupV2(
@ -760,7 +743,8 @@ public class Manager implements Closeable {
groupInfoV2.setGroup(groupGroupChangePair.first(), this::resolveRecipient); groupInfoV2.setGroup(groupGroupChangePair.first(), this::resolveRecipient);
var messageBuilder = getGroupUpdateMessageBuilder(groupInfoV2, groupGroupChangePair.second().toByteArray()); var messageBuilder = getGroupUpdateMessageBuilder(groupInfoV2, groupGroupChangePair.second().toByteArray());
account.getGroupStore().updateGroup(groupInfoV2); account.getGroupStore().updateGroup(groupInfoV2);
return sendMessage(messageBuilder, groupInfoV2.getMembersWithout(account.getSelfRecipientId())); return sendHelper.sendGroupMessage(messageBuilder.build(),
groupInfoV2.getMembersIncludingPendingWithout(account.getSelfRecipientId()));
} }
public void deleteGroup(GroupId groupId) throws IOException { public void deleteGroup(GroupId groupId) throws IOException {
@ -771,7 +755,7 @@ public class Manager implements Closeable {
public Pair<GroupId, List<SendMessageResult>> createGroup( public Pair<GroupId, List<SendMessageResult>> createGroup(
String name, List<String> members, File avatarFile String name, List<String> members, File avatarFile
) throws IOException, AttachmentInvalidException, InvalidNumberException { ) throws IOException, AttachmentInvalidException, InvalidNumberException {
return createGroup(name, members == null ? null : getSignalServiceAddresses(members), avatarFile); return createGroup(name, members == null ? null : getRecipientIds(members), avatarFile);
} }
private Pair<GroupId, List<SendMessageResult>> createGroup( private Pair<GroupId, List<SendMessageResult>> createGroup(
@ -807,7 +791,8 @@ public class Manager implements Closeable {
messageBuilder = getGroupUpdateMessageBuilder(gv2, null); messageBuilder = getGroupUpdateMessageBuilder(gv2, null);
account.getGroupStore().updateGroup(gv2); account.getGroupStore().updateGroup(gv2);
final var result = sendMessage(messageBuilder, gv2.getMembersIncludingPendingWithout(selfRecipientId)); final var result = sendHelper.sendGroupMessage(messageBuilder.build(),
gv2.getMembersIncludingPendingWithout(selfRecipientId));
return new Pair<>(gv2.getGroupId(), result.second()); return new Pair<>(gv2.getGroupId(), result.second());
} }
@ -829,10 +814,10 @@ public class Manager implements Closeable {
return updateGroup(groupId, return updateGroup(groupId,
name, name,
description, description,
members == null ? null : getSignalServiceAddresses(members), members == null ? null : getRecipientIds(members),
removeMembers == null ? null : getSignalServiceAddresses(removeMembers), removeMembers == null ? null : getRecipientIds(removeMembers),
admins == null ? null : getSignalServiceAddresses(admins), admins == null ? null : getRecipientIds(admins),
removeAdmins == null ? null : getSignalServiceAddresses(removeAdmins), removeAdmins == null ? null : getRecipientIds(removeAdmins),
resetGroupLink, resetGroupLink,
groupLinkState, groupLinkState,
addMemberPermission, addMemberPermission,
@ -908,7 +893,8 @@ public class Manager implements Closeable {
account.getGroupStore().updateGroup(gv1); account.getGroupStore().updateGroup(gv1);
return sendMessage(messageBuilder, gv1.getMembersIncludingPendingWithout(account.getSelfRecipientId())); return sendHelper.sendGroupMessage(messageBuilder.build(),
gv1.getMembersIncludingPendingWithout(account.getSelfRecipientId()));
} }
private void updateGroupV1Details( private void updateGroupV1Details(
@ -1092,7 +1078,7 @@ public class Manager implements Closeable {
final var messageBuilder = getGroupUpdateMessageBuilder(group, groupChange.toByteArray()); final var messageBuilder = getGroupUpdateMessageBuilder(group, groupChange.toByteArray());
account.getGroupStore().updateGroup(group); account.getGroupStore().updateGroup(group);
return sendMessage(messageBuilder, members); return sendHelper.sendGroupMessage(messageBuilder.build(), members);
} }
private static int currentTimeDays() { private static int currentTimeDays() {
@ -1122,9 +1108,9 @@ public class Manager implements Closeable {
GroupIdV1 groupId, SignalServiceAddress recipient GroupIdV1 groupId, SignalServiceAddress recipient
) throws IOException, NotAGroupMemberException, GroupNotFoundException, AttachmentInvalidException { ) throws IOException, NotAGroupMemberException, GroupNotFoundException, AttachmentInvalidException {
GroupInfoV1 g; GroupInfoV1 g;
var group = getGroupForSending(groupId); var group = getGroupForUpdating(groupId);
if (!(group instanceof GroupInfoV1)) { if (!(group instanceof GroupInfoV1)) {
throw new RuntimeException("Received an invalid group request for a v2 group!"); throw new IOException("Received an invalid group request for a v2 group!");
} }
g = (GroupInfoV1) group; g = (GroupInfoV1) group;
@ -1136,7 +1122,7 @@ public class Manager implements Closeable {
var messageBuilder = getGroupUpdateMessageBuilder(g); var messageBuilder = getGroupUpdateMessageBuilder(g);
// Send group message only to the recipient who requested it // Send group message only to the recipient who requested it
return sendMessage(messageBuilder, Set.of(recipientId)); return sendHelper.sendGroupMessage(messageBuilder.build(), Set.of(recipientId));
} }
private SignalServiceDataMessage.Builder getGroupUpdateMessageBuilder(GroupInfoV1 g) throws AttachmentInvalidException { private SignalServiceDataMessage.Builder getGroupUpdateMessageBuilder(GroupInfoV1 g) throws AttachmentInvalidException {
@ -1157,18 +1143,14 @@ public class Manager implements Closeable {
throw new AttachmentInvalidException(g.getGroupId().toBase64(), e); throw new AttachmentInvalidException(g.getGroupId().toBase64(), e);
} }
return SignalServiceDataMessage.newBuilder() return createMessageBuilder().asGroupMessage(group.build()).withExpiration(g.getMessageExpirationTime());
.asGroupMessage(group.build())
.withExpiration(g.getMessageExpirationTime());
} }
private SignalServiceDataMessage.Builder getGroupUpdateMessageBuilder(GroupInfoV2 g, byte[] signedGroupChange) { private SignalServiceDataMessage.Builder getGroupUpdateMessageBuilder(GroupInfoV2 g, byte[] signedGroupChange) {
var group = SignalServiceGroupV2.newBuilder(g.getMasterKey()) var group = SignalServiceGroupV2.newBuilder(g.getMasterKey())
.withRevision(g.getGroup().getRevision()) .withRevision(g.getGroup().getRevision())
.withSignedGroupChange(signedGroupChange); .withSignedGroupChange(signedGroupChange);
return SignalServiceDataMessage.newBuilder() return createMessageBuilder().asGroupMessage(group.build()).withExpiration(g.getMessageExpirationTime());
.asGroupMessage(group.build())
.withExpiration(g.getMessageExpirationTime());
} }
Pair<Long, List<SendMessageResult>> sendGroupInfoRequest( Pair<Long, List<SendMessageResult>> sendGroupInfoRequest(
@ -1176,10 +1158,10 @@ public class Manager implements Closeable {
) throws IOException { ) throws IOException {
var group = SignalServiceGroup.newBuilder(SignalServiceGroup.Type.REQUEST_INFO).withId(groupId.serialize()); var group = SignalServiceGroup.newBuilder(SignalServiceGroup.Type.REQUEST_INFO).withId(groupId.serialize());
var messageBuilder = SignalServiceDataMessage.newBuilder().asGroupMessage(group.build()); var messageBuilder = createMessageBuilder().asGroupMessage(group.build());
// Send group info request message to the recipient who sent us a message with this groupId // Send group info request message to the recipient who sent us a message with this groupId
return sendMessage(messageBuilder, Set.of(resolveRecipient(recipient))); return sendHelper.sendGroupMessage(messageBuilder.build(), Set.of(resolveRecipient(recipient)));
} }
void sendReceipt( void sendReceipt(
@ -1189,16 +1171,13 @@ public class Manager implements Closeable {
List.of(messageId), List.of(messageId),
System.currentTimeMillis()); System.currentTimeMillis());
dependencies.getMessageSender() sendHelper.sendReceiptMessage(receiptMessage, resolveRecipient(remoteAddress));
.sendReceipt(remoteAddress,
unidentifiedAccessHelper.getAccessFor(resolveRecipient(remoteAddress)),
receiptMessage);
} }
public Pair<Long, List<SendMessageResult>> sendMessage( public Pair<Long, List<SendMessageResult>> sendMessage(
String messageText, List<String> attachments, List<String> recipients String messageText, List<String> attachments, List<String> recipients
) throws IOException, AttachmentInvalidException, InvalidNumberException { ) throws IOException, AttachmentInvalidException, InvalidNumberException {
final var messageBuilder = SignalServiceDataMessage.newBuilder().withBody(messageText); final var messageBuilder = createMessageBuilder().withBody(messageText);
if (attachments != null) { if (attachments != null) {
var attachmentStreams = AttachmentUtils.getSignalServiceAttachments(attachments); var attachmentStreams = AttachmentUtils.getSignalServiceAttachments(attachments);
@ -1215,33 +1194,33 @@ public class Manager implements Closeable {
messageBuilder.withAttachments(attachmentPointers); messageBuilder.withAttachments(attachmentPointers);
} }
return sendMessage(messageBuilder, getSignalServiceAddresses(recipients)); return sendHelper.sendMessage(messageBuilder, getRecipientIds(recipients));
} }
public Pair<Long, SendMessageResult> sendSelfMessage( public Pair<Long, SendMessageResult> sendSelfMessage(
String messageText, List<String> attachments String messageText, List<String> attachments
) throws IOException, AttachmentInvalidException { ) throws IOException, AttachmentInvalidException {
final var messageBuilder = SignalServiceDataMessage.newBuilder().withBody(messageText); final var messageBuilder = createMessageBuilder().withBody(messageText);
if (attachments != null) { if (attachments != null) {
messageBuilder.withAttachments(AttachmentUtils.getSignalServiceAttachments(attachments)); messageBuilder.withAttachments(AttachmentUtils.getSignalServiceAttachments(attachments));
} }
return sendSelfMessage(messageBuilder); return sendHelper.sendSelfMessage(messageBuilder);
} }
public Pair<Long, List<SendMessageResult>> sendRemoteDeleteMessage( public Pair<Long, List<SendMessageResult>> sendRemoteDeleteMessage(
long targetSentTimestamp, List<String> recipients long targetSentTimestamp, List<String> recipients
) throws IOException, InvalidNumberException { ) throws IOException, InvalidNumberException {
var delete = new SignalServiceDataMessage.RemoteDelete(targetSentTimestamp); var delete = new SignalServiceDataMessage.RemoteDelete(targetSentTimestamp);
final var messageBuilder = SignalServiceDataMessage.newBuilder().withRemoteDelete(delete); final var messageBuilder = createMessageBuilder().withRemoteDelete(delete);
return sendMessage(messageBuilder, getSignalServiceAddresses(recipients)); return sendHelper.sendMessage(messageBuilder, getRecipientIds(recipients));
} }
public Pair<Long, List<SendMessageResult>> sendGroupRemoteDeleteMessage( public Pair<Long, List<SendMessageResult>> sendGroupRemoteDeleteMessage(
long targetSentTimestamp, GroupId groupId long targetSentTimestamp, GroupId groupId
) throws IOException, NotAGroupMemberException, GroupNotFoundException { ) throws IOException, NotAGroupMemberException, GroupNotFoundException {
var delete = new SignalServiceDataMessage.RemoteDelete(targetSentTimestamp); var delete = new SignalServiceDataMessage.RemoteDelete(targetSentTimestamp);
final var messageBuilder = SignalServiceDataMessage.newBuilder().withRemoteDelete(delete); final var messageBuilder = createMessageBuilder().withRemoteDelete(delete);
return sendGroupMessage(messageBuilder, groupId); return sendHelper.sendAsGroupMessage(messageBuilder, groupId);
} }
public Pair<Long, List<SendMessageResult>> sendMessageReaction( public Pair<Long, List<SendMessageResult>> sendMessageReaction(
@ -1252,28 +1231,27 @@ public class Manager implements Closeable {
remove, remove,
resolveSignalServiceAddress(targetAuthorRecipientId), resolveSignalServiceAddress(targetAuthorRecipientId),
targetSentTimestamp); targetSentTimestamp);
final var messageBuilder = SignalServiceDataMessage.newBuilder().withReaction(reaction); final var messageBuilder = createMessageBuilder().withReaction(reaction);
return sendMessage(messageBuilder, getSignalServiceAddresses(recipients)); return sendHelper.sendMessage(messageBuilder, getRecipientIds(recipients));
} }
public Pair<Long, List<SendMessageResult>> sendEndSessionMessage(List<String> recipients) throws IOException, InvalidNumberException { public Pair<Long, List<SendMessageResult>> sendEndSessionMessage(List<String> recipients) throws IOException, InvalidNumberException {
var messageBuilder = SignalServiceDataMessage.newBuilder().asEndSessionMessage(); var messageBuilder = createMessageBuilder().asEndSessionMessage();
final var signalServiceAddresses = getSignalServiceAddresses(recipients); final var recipientIds = getRecipientIds(recipients);
try { try {
return sendMessage(messageBuilder, signalServiceAddresses); return sendHelper.sendMessage(messageBuilder, recipientIds);
} catch (Exception e) { } finally {
for (var address : signalServiceAddresses) { for (var recipientId : recipientIds) {
handleEndSession(address); handleEndSession(recipientId);
} }
throw e;
} }
} }
void renewSession(RecipientId recipientId) throws IOException { void renewSession(RecipientId recipientId) throws IOException {
account.getSessionStore().archiveSessions(recipientId); account.getSessionStore().archiveSessions(recipientId);
if (!recipientId.equals(getSelfRecipientId())) { if (!recipientId.equals(getSelfRecipientId())) {
sendNullMessage(recipientId); sendHelper.sendNullMessage(recipientId);
} }
} }
@ -1323,8 +1301,8 @@ public class Manager implements Closeable {
} }
private void sendExpirationTimerUpdate(RecipientId recipientId) throws IOException { private void sendExpirationTimerUpdate(RecipientId recipientId) throws IOException {
final var messageBuilder = SignalServiceDataMessage.newBuilder().asExpirationUpdate(); final var messageBuilder = createMessageBuilder().asExpirationUpdate();
sendMessage(messageBuilder, Set.of(recipientId)); sendHelper.sendMessage(messageBuilder, Set.of(recipientId));
} }
/** /**
@ -1350,8 +1328,8 @@ public class Manager implements Closeable {
} }
private void sendExpirationTimerUpdate(GroupIdV1 groupId) throws IOException, NotAGroupMemberException, GroupNotFoundException { private void sendExpirationTimerUpdate(GroupIdV1 groupId) throws IOException, NotAGroupMemberException, GroupNotFoundException {
final var messageBuilder = SignalServiceDataMessage.newBuilder().asExpirationUpdate(); final var messageBuilder = createMessageBuilder().asExpirationUpdate();
sendGroupMessage(messageBuilder, groupId); sendHelper.sendAsGroupMessage(messageBuilder, groupId);
} }
/** /**
@ -1398,11 +1376,7 @@ public class Manager implements Closeable {
.setType(SignalServiceProtos.SyncMessage.Request.Type.GROUPS) .setType(SignalServiceProtos.SyncMessage.Request.Type.GROUPS)
.build(); .build();
var message = SignalServiceSyncMessage.forRequest(new RequestMessage(r)); var message = SignalServiceSyncMessage.forRequest(new RequestMessage(r));
try { sendHelper.sendSyncMessage(message);
sendSyncMessage(message);
} catch (UntrustedIdentityException e) {
throw new AssertionError(e);
}
} }
private void requestSyncContacts() throws IOException { private void requestSyncContacts() throws IOException {
@ -1410,11 +1384,7 @@ public class Manager implements Closeable {
.setType(SignalServiceProtos.SyncMessage.Request.Type.CONTACTS) .setType(SignalServiceProtos.SyncMessage.Request.Type.CONTACTS)
.build(); .build();
var message = SignalServiceSyncMessage.forRequest(new RequestMessage(r)); var message = SignalServiceSyncMessage.forRequest(new RequestMessage(r));
try { sendHelper.sendSyncMessage(message);
sendSyncMessage(message);
} catch (UntrustedIdentityException e) {
throw new AssertionError(e);
}
} }
private void requestSyncBlocked() throws IOException { private void requestSyncBlocked() throws IOException {
@ -1422,11 +1392,7 @@ public class Manager implements Closeable {
.setType(SignalServiceProtos.SyncMessage.Request.Type.BLOCKED) .setType(SignalServiceProtos.SyncMessage.Request.Type.BLOCKED)
.build(); .build();
var message = SignalServiceSyncMessage.forRequest(new RequestMessage(r)); var message = SignalServiceSyncMessage.forRequest(new RequestMessage(r));
try { sendHelper.sendSyncMessage(message);
sendSyncMessage(message);
} catch (UntrustedIdentityException e) {
throw new AssertionError(e);
}
} }
private void requestSyncConfiguration() throws IOException { private void requestSyncConfiguration() throws IOException {
@ -1434,11 +1400,7 @@ public class Manager implements Closeable {
.setType(SignalServiceProtos.SyncMessage.Request.Type.CONFIGURATION) .setType(SignalServiceProtos.SyncMessage.Request.Type.CONFIGURATION)
.build(); .build();
var message = SignalServiceSyncMessage.forRequest(new RequestMessage(r)); var message = SignalServiceSyncMessage.forRequest(new RequestMessage(r));
try { sendHelper.sendSyncMessage(message);
sendSyncMessage(message);
} catch (UntrustedIdentityException e) {
throw new AssertionError(e);
}
} }
private void requestSyncKeys() throws IOException { private void requestSyncKeys() throws IOException {
@ -1446,11 +1408,7 @@ public class Manager implements Closeable {
.setType(SignalServiceProtos.SyncMessage.Request.Type.KEYS) .setType(SignalServiceProtos.SyncMessage.Request.Type.KEYS)
.build(); .build();
var message = SignalServiceSyncMessage.forRequest(new RequestMessage(r)); var message = SignalServiceSyncMessage.forRequest(new RequestMessage(r));
try { sendHelper.sendSyncMessage(message);
sendSyncMessage(message);
} catch (UntrustedIdentityException e) {
throw new AssertionError(e);
}
} }
private byte[] getSenderCertificate() { private byte[] getSenderCertificate() {
@ -1469,12 +1427,7 @@ public class Manager implements Closeable {
return certificate; return certificate;
} }
private void sendSyncMessage(SignalServiceSyncMessage message) throws IOException, UntrustedIdentityException { private Set<RecipientId> getRecipientIds(Collection<String> numbers) throws InvalidNumberException {
var messageSender = dependencies.getMessageSender();
messageSender.sendSyncMessage(message, unidentifiedAccessHelper.getAccessForSync());
}
private Set<RecipientId> getSignalServiceAddresses(Collection<String> numbers) throws InvalidNumberException {
final var signalServiceAddresses = new HashSet<SignalServiceAddress>(numbers.size()); final var signalServiceAddresses = new HashSet<SignalServiceAddress>(numbers.size());
final var addressesMissingUuid = new HashSet<SignalServiceAddress>(); final var addressesMissingUuid = new HashSet<SignalServiceAddress>();
@ -1537,188 +1490,27 @@ public class Manager implements Closeable {
public void sendTypingMessage( public void sendTypingMessage(
TypingAction action, Set<String> recipients TypingAction action, Set<String> recipients
) throws IOException, UntrustedIdentityException, InvalidNumberException { ) throws IOException, UntrustedIdentityException, InvalidNumberException {
sendTypingMessageInternal(action, getSignalServiceAddresses(recipients));
}
private void sendTypingMessageInternal(
TypingAction action, Set<RecipientId> recipientIds
) throws IOException, UntrustedIdentityException {
final var timestamp = System.currentTimeMillis(); final var timestamp = System.currentTimeMillis();
var message = new SignalServiceTypingMessage(action.toSignalService(), timestamp, Optional.absent()); var message = new SignalServiceTypingMessage(action.toSignalService(), timestamp, Optional.absent());
var messageSender = dependencies.getMessageSender(); sendHelper.sendTypingMessage(message, getRecipientIds(recipients));
for (var recipientId : recipientIds) {
final var address = resolveSignalServiceAddress(recipientId);
messageSender.sendTyping(address, unidentifiedAccessHelper.getAccessFor(recipientId), message);
}
} }
public void sendGroupTypingMessage( public void sendGroupTypingMessage(
TypingAction action, GroupId groupId TypingAction action, GroupId groupId
) throws IOException, NotAGroupMemberException, GroupNotFoundException { ) throws IOException, NotAGroupMemberException, GroupNotFoundException {
final var timestamp = System.currentTimeMillis(); final var timestamp = System.currentTimeMillis();
final var g = getGroupForSending(groupId);
final var message = new SignalServiceTypingMessage(action.toSignalService(), final var message = new SignalServiceTypingMessage(action.toSignalService(),
timestamp, timestamp,
Optional.of(groupId.serialize())); Optional.of(groupId.serialize()));
final var messageSender = dependencies.getMessageSender(); sendHelper.sendGroupTypingMessage(message, groupId);
final var recipientIdList = new ArrayList<>(g.getMembersWithout(account.getSelfRecipientId()));
final var addresses = recipientIdList.stream()
.map(this::resolveSignalServiceAddress)
.collect(Collectors.toList());
messageSender.sendTyping(addresses, unidentifiedAccessHelper.getAccessFor(recipientIdList), message, null);
} }
private Pair<Long, List<SendMessageResult>> sendMessage( private SignalServiceDataMessage.Builder createMessageBuilder() {
SignalServiceDataMessage.Builder messageBuilder, Set<RecipientId> recipientIds
) throws IOException {
final var timestamp = System.currentTimeMillis(); final var timestamp = System.currentTimeMillis();
var messageBuilder = SignalServiceDataMessage.newBuilder();
messageBuilder.withTimestamp(timestamp); messageBuilder.withTimestamp(timestamp);
return messageBuilder;
SignalServiceDataMessage message = null;
try {
message = messageBuilder.build();
if (message.getGroupContext().isPresent()) {
try {
var messageSender = dependencies.getMessageSender();
final var isRecipientUpdate = false;
final var recipientIdList = new ArrayList<>(recipientIds);
final var addresses = recipientIdList.stream()
.map(this::resolveSignalServiceAddress)
.collect(Collectors.toList());
var result = messageSender.sendDataMessage(addresses,
unidentifiedAccessHelper.getAccessFor(recipientIdList),
isRecipientUpdate,
ContentHint.DEFAULT,
message,
sendResult -> logger.trace("Partial message send result: {}", sendResult.isSuccess()),
() -> false);
for (var r : result) {
handlePossibleIdentityFailure(r);
}
return new Pair<>(timestamp, result);
} catch (UntrustedIdentityException e) {
return new Pair<>(timestamp, List.of());
}
} else {
// Send to all individually, so sync messages are sent correctly
messageBuilder.withProfileKey(account.getProfileKey().serialize());
var results = new ArrayList<SendMessageResult>(recipientIds.size());
for (var recipientId : recipientIds) {
final var contact = account.getContactStore().getContact(recipientId);
final var expirationTime = contact != null ? contact.getMessageExpirationTime() : 0;
messageBuilder.withExpiration(expirationTime);
message = messageBuilder.build();
final var result = sendMessage(recipientId, message);
handlePossibleIdentityFailure(result);
results.add(result);
}
return new Pair<>(timestamp, results);
}
} finally {
if (message != null && message.isEndSession()) {
for (var recipient : recipientIds) {
handleEndSession(recipient);
}
}
}
}
private void handlePossibleIdentityFailure(final SendMessageResult r) {
if (r.getIdentityFailure() != null) {
final var recipientId = resolveRecipient(r.getAddress());
final var identityKey = r.getIdentityFailure().getIdentityKey();
if (identityKey != null) {
final var newIdentity = account.getIdentityKeyStore()
.saveIdentity(recipientId, identityKey, new Date());
if (newIdentity) {
account.getSessionStore().archiveSessions(recipientId);
}
} else {
// Retrieve profile to get the current identity key from the server
retrieveEncryptedProfile(recipientId);
}
}
}
private Pair<Long, SendMessageResult> sendSelfMessage(
SignalServiceDataMessage.Builder messageBuilder
) throws IOException {
final var timestamp = System.currentTimeMillis();
messageBuilder.withTimestamp(timestamp);
final var recipientId = account.getSelfRecipientId();
final var contact = account.getContactStore().getContact(recipientId);
final var expirationTime = contact != null ? contact.getMessageExpirationTime() : 0;
messageBuilder.withExpiration(expirationTime);
var message = messageBuilder.build();
final var result = sendSelfMessage(message);
return new Pair<>(timestamp, result);
}
private SendMessageResult sendSelfMessage(SignalServiceDataMessage message) throws IOException {
var messageSender = dependencies.getMessageSender();
var recipientId = account.getSelfRecipientId();
final var unidentifiedAccess = unidentifiedAccessHelper.getAccessFor(recipientId);
var recipient = resolveSignalServiceAddress(recipientId);
var transcript = new SentTranscriptMessage(Optional.of(recipient),
message.getTimestamp(),
message,
message.getExpiresInSeconds(),
Map.of(recipient, unidentifiedAccess.isPresent()),
false);
var syncMessage = SignalServiceSyncMessage.forSentTranscript(transcript);
try {
return messageSender.sendSyncMessage(syncMessage, unidentifiedAccess);
} catch (UntrustedIdentityException e) {
return SendMessageResult.identityFailure(recipient, e.getIdentityKey());
}
}
private SendMessageResult sendMessage(
RecipientId recipientId, SignalServiceDataMessage message
) throws IOException {
var messageSender = dependencies.getMessageSender();
final var address = resolveSignalServiceAddress(recipientId);
try {
try {
return messageSender.sendDataMessage(address,
unidentifiedAccessHelper.getAccessFor(recipientId),
ContentHint.DEFAULT,
message);
} catch (UnregisteredUserException e) {
final var newRecipientId = refreshRegisteredUser(recipientId);
return messageSender.sendDataMessage(resolveSignalServiceAddress(newRecipientId),
unidentifiedAccessHelper.getAccessFor(newRecipientId),
ContentHint.DEFAULT,
message);
}
} catch (UntrustedIdentityException e) {
return SendMessageResult.identityFailure(address, e.getIdentityKey());
}
}
private SendMessageResult sendNullMessage(RecipientId recipientId) throws IOException {
var messageSender = dependencies.getMessageSender();
final var address = resolveSignalServiceAddress(recipientId);
try {
try {
return messageSender.sendNullMessage(address, unidentifiedAccessHelper.getAccessFor(recipientId));
} catch (UnregisteredUserException e) {
final var newRecipientId = refreshRegisteredUser(recipientId);
final var newAddress = resolveSignalServiceAddress(newRecipientId);
return messageSender.sendNullMessage(newAddress, unidentifiedAccessHelper.getAccessFor(newRecipientId));
}
} catch (UntrustedIdentityException e) {
return SendMessageResult.identityFailure(address, e.getIdentityKey());
}
} }
private SignalServiceContent decryptMessage(SignalServiceEnvelope envelope) throws InvalidMetadataMessageException, ProtocolInvalidMessageException, ProtocolDuplicateMessageException, ProtocolLegacyMessageException, ProtocolInvalidKeyIdException, InvalidMetadataVersionException, ProtocolInvalidVersionException, ProtocolNoSessionException, ProtocolInvalidKeyException, SelfSendException, UnsupportedDataMessageException, ProtocolUntrustedIdentityException, InvalidMessageStructureException { private SignalServiceContent decryptMessage(SignalServiceEnvelope envelope) throws InvalidMetadataMessageException, ProtocolInvalidMessageException, ProtocolDuplicateMessageException, ProtocolLegacyMessageException, ProtocolInvalidKeyIdException, InvalidMetadataVersionException, ProtocolInvalidVersionException, ProtocolNoSessionException, ProtocolInvalidKeyException, SelfSendException, UnsupportedDataMessageException, ProtocolUntrustedIdentityException, InvalidMessageStructureException {
@ -2611,7 +2403,7 @@ public class Manager implements Closeable {
.retrieveAttachment(pointer, tmpFile, ServiceConfig.MAX_ATTACHMENT_SIZE); .retrieveAttachment(pointer, tmpFile, ServiceConfig.MAX_ATTACHMENT_SIZE);
} }
void sendGroups() throws IOException, UntrustedIdentityException { void sendGroups() throws IOException {
var groupsFile = IOUtils.createTempFile(); var groupsFile = IOUtils.createTempFile();
try { try {
@ -2645,7 +2437,7 @@ public class Manager implements Closeable {
.withLength(groupsFile.length()) .withLength(groupsFile.length())
.build(); .build();
sendSyncMessage(SignalServiceSyncMessage.forGroups(attachmentStream)); sendHelper.sendSyncMessage(SignalServiceSyncMessage.forGroups(attachmentStream));
} }
} }
} finally { } finally {
@ -2657,7 +2449,7 @@ public class Manager implements Closeable {
} }
} }
public void sendContacts() throws IOException, UntrustedIdentityException { public void sendContacts() throws IOException {
var contactsFile = IOUtils.createTempFile(); var contactsFile = IOUtils.createTempFile();
try { try {
@ -2713,7 +2505,8 @@ public class Manager implements Closeable {
.withLength(contactsFile.length()) .withLength(contactsFile.length())
.build(); .build();
sendSyncMessage(SignalServiceSyncMessage.forContacts(new ContactsMessage(attachmentStream, true))); sendHelper.sendSyncMessage(SignalServiceSyncMessage.forContacts(new ContactsMessage(attachmentStream,
true)));
} }
} }
} finally { } finally {
@ -2725,7 +2518,7 @@ public class Manager implements Closeable {
} }
} }
void sendBlockedList() throws IOException, UntrustedIdentityException { void sendBlockedList() throws IOException {
var addresses = new ArrayList<SignalServiceAddress>(); var addresses = new ArrayList<SignalServiceAddress>();
for (var record : account.getContactStore().getContacts()) { for (var record : account.getContactStore().getContacts()) {
if (record.second().isBlocked()) { if (record.second().isBlocked()) {
@ -2738,17 +2531,17 @@ public class Manager implements Closeable {
groupIds.add(record.getGroupId().serialize()); groupIds.add(record.getGroupId().serialize());
} }
} }
sendSyncMessage(SignalServiceSyncMessage.forBlocked(new BlockedListMessage(addresses, groupIds))); sendHelper.sendSyncMessage(SignalServiceSyncMessage.forBlocked(new BlockedListMessage(addresses, groupIds)));
} }
private void sendVerifiedMessage( private void sendVerifiedMessage(
SignalServiceAddress destination, IdentityKey identityKey, TrustLevel trustLevel SignalServiceAddress destination, IdentityKey identityKey, TrustLevel trustLevel
) throws IOException, UntrustedIdentityException { ) throws IOException {
var verifiedMessage = new VerifiedMessage(destination, var verifiedMessage = new VerifiedMessage(destination,
identityKey, identityKey,
trustLevel.toVerifiedState(), trustLevel.toVerifiedState(),
System.currentTimeMillis()); System.currentTimeMillis());
sendSyncMessage(SignalServiceSyncMessage.forVerified(verifiedMessage)); sendHelper.sendSyncMessage(SignalServiceSyncMessage.forVerified(verifiedMessage));
} }
public List<Pair<RecipientId, Contact>> getContacts() { public List<Pair<RecipientId, Contact>> getContacts() {
@ -2849,13 +2642,28 @@ public class Manager implements Closeable {
try { try {
var address = account.getRecipientStore().resolveServiceAddress(recipientId); var address = account.getRecipientStore().resolveServiceAddress(recipientId);
sendVerifiedMessage(address, identity.getIdentityKey(), trustLevel); sendVerifiedMessage(address, identity.getIdentityKey(), trustLevel);
} catch (IOException | UntrustedIdentityException e) { } catch (IOException e) {
logger.warn("Failed to send verification sync message: {}", e.getMessage()); logger.warn("Failed to send verification sync message: {}", e.getMessage());
} }
return true; return true;
} }
private void handleIdentityFailure(
final RecipientId recipientId, final SendMessageResult.IdentityFailure identityFailure
) {
final var identityKey = identityFailure.getIdentityKey();
if (identityKey != null) {
final var newIdentity = account.getIdentityKeyStore().saveIdentity(recipientId, identityKey, new Date());
if (newIdentity) {
account.getSessionStore().archiveSessions(recipientId);
}
} else {
// Retrieve profile to get the current identity key from the server
retrieveEncryptedProfile(recipientId);
}
}
public String computeSafetyNumber( public String computeSafetyNumber(
SignalServiceAddress theirAddress, IdentityKey theirIdentityKey SignalServiceAddress theirAddress, IdentityKey theirIdentityKey
) { ) {

View file

@ -0,0 +1,9 @@
package org.asamk.signal.manager.helper;
import org.asamk.signal.manager.groups.GroupId;
import org.asamk.signal.manager.storage.groups.GroupInfo;
public interface GroupProvider {
GroupInfo getGroup(GroupId groupId);
}

View file

@ -0,0 +1,9 @@
package org.asamk.signal.manager.helper;
import org.asamk.signal.manager.storage.recipients.RecipientId;
import org.whispersystems.signalservice.api.messages.SendMessageResult;
public interface IdentityFailureHandler {
void handleIdentityFailure(RecipientId recipientId, SendMessageResult.IdentityFailure identityFailure);
}

View file

@ -0,0 +1,10 @@
package org.asamk.signal.manager.helper;
import org.asamk.signal.manager.storage.recipients.RecipientId;
import java.io.IOException;
public interface RecipientRegistrationRefresher {
RecipientId refreshRecipientRegistration(RecipientId recipientId) throws IOException;
}

View file

@ -0,0 +1,277 @@
package org.asamk.signal.manager.helper;
import org.asamk.signal.manager.SignalDependencies;
import org.asamk.signal.manager.groups.GroupId;
import org.asamk.signal.manager.groups.GroupNotFoundException;
import org.asamk.signal.manager.groups.GroupUtils;
import org.asamk.signal.manager.groups.NotAGroupMemberException;
import org.asamk.signal.manager.storage.SignalAccount;
import org.asamk.signal.manager.storage.groups.GroupInfo;
import org.asamk.signal.manager.storage.recipients.RecipientId;
import org.asamk.signal.manager.storage.recipients.RecipientResolver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.libsignal.util.Pair;
import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.crypto.ContentHint;
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
import org.whispersystems.signalservice.api.messages.SendMessageResult;
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
import org.whispersystems.signalservice.api.messages.SignalServiceReceiptMessage;
import org.whispersystems.signalservice.api.messages.SignalServiceTypingMessage;
import org.whispersystems.signalservice.api.messages.multidevice.SentTranscriptMessage;
import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage;
import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
public class SendHelper {
private final static Logger logger = LoggerFactory.getLogger(SendHelper.class);
private final SignalAccount account;
private final SignalDependencies dependencies;
private final UnidentifiedAccessHelper unidentifiedAccessHelper;
private final SignalServiceAddressResolver addressResolver;
private final RecipientResolver recipientResolver;
private final IdentityFailureHandler identityFailureHandler;
private final GroupProvider groupProvider;
private final RecipientRegistrationRefresher recipientRegistrationRefresher;
public SendHelper(
final SignalAccount account,
final SignalDependencies dependencies,
final UnidentifiedAccessHelper unidentifiedAccessHelper,
final SignalServiceAddressResolver addressResolver,
final RecipientResolver recipientResolver,
final IdentityFailureHandler identityFailureHandler,
final GroupProvider groupProvider,
final RecipientRegistrationRefresher recipientRegistrationRefresher
) {
this.account = account;
this.dependencies = dependencies;
this.unidentifiedAccessHelper = unidentifiedAccessHelper;
this.addressResolver = addressResolver;
this.recipientResolver = recipientResolver;
this.identityFailureHandler = identityFailureHandler;
this.groupProvider = groupProvider;
this.recipientRegistrationRefresher = recipientRegistrationRefresher;
}
/**
* Send a single message to one or multiple recipients.
* The message is extended with the current expiration timer for each recipient.
*/
public Pair<Long, List<SendMessageResult>> sendMessage(
SignalServiceDataMessage.Builder messageBuilder, Set<RecipientId> recipientIds
) throws IOException {
// Send to all individually, so sync messages are sent correctly
messageBuilder.withProfileKey(account.getProfileKey().serialize());
var results = new ArrayList<SendMessageResult>(recipientIds.size());
long timestamp = 0;
for (var recipientId : recipientIds) {
final var contact = account.getContactStore().getContact(recipientId);
final var expirationTime = contact != null ? contact.getMessageExpirationTime() : 0;
messageBuilder.withExpiration(expirationTime);
final var singleMessage = messageBuilder.build();
timestamp = singleMessage.getTimestamp();
final var result = sendMessage(singleMessage, recipientId);
handlePossibleIdentityFailure(result);
results.add(result);
}
return new Pair<>(timestamp, results);
}
/**
* Send a group message to the given group
* The message is extended with the current expiration timer for the group and the group context.
*/
public Pair<Long, List<SendMessageResult>> sendAsGroupMessage(
SignalServiceDataMessage.Builder messageBuilder, GroupId groupId
) throws IOException, GroupNotFoundException, NotAGroupMemberException {
final var g = getGroupForSending(groupId);
GroupUtils.setGroupContext(messageBuilder, g);
messageBuilder.withExpiration(g.getMessageExpirationTime());
final var recipients = g.getMembersWithout(account.getSelfRecipientId());
return sendGroupMessage(messageBuilder.build(), recipients);
}
/**
* Send a complete group message to the given recipients (should be current/old/new members)
* This method should only be used for create/update/quit group messages.
*/
public Pair<Long, List<SendMessageResult>> sendGroupMessage(
final SignalServiceDataMessage message, final Set<RecipientId> recipientIds
) throws IOException {
List<SendMessageResult> result = sendGroupMessageInternal(message, recipientIds);
for (var r : result) {
handlePossibleIdentityFailure(r);
}
return new Pair<>(message.getTimestamp(), result);
}
public void sendReceiptMessage(
final SignalServiceReceiptMessage receiptMessage, final RecipientId recipientId
) throws IOException, UntrustedIdentityException {
final var messageSender = dependencies.getMessageSender();
messageSender.sendReceipt(addressResolver.resolveSignalServiceAddress(recipientId),
unidentifiedAccessHelper.getAccessFor(recipientId),
receiptMessage);
}
public SendMessageResult sendNullMessage(RecipientId recipientId) throws IOException {
var messageSender = dependencies.getMessageSender();
final var address = addressResolver.resolveSignalServiceAddress(recipientId);
try {
try {
return messageSender.sendNullMessage(address, unidentifiedAccessHelper.getAccessFor(recipientId));
} catch (UnregisteredUserException e) {
final var newRecipientId = recipientRegistrationRefresher.refreshRecipientRegistration(recipientId);
final var newAddress = addressResolver.resolveSignalServiceAddress(newRecipientId);
return messageSender.sendNullMessage(newAddress, unidentifiedAccessHelper.getAccessFor(newRecipientId));
}
} catch (UntrustedIdentityException e) {
return SendMessageResult.identityFailure(address, e.getIdentityKey());
}
}
public Pair<Long, SendMessageResult> sendSelfMessage(
SignalServiceDataMessage.Builder messageBuilder
) throws IOException {
final var recipientId = account.getSelfRecipientId();
final var contact = account.getContactStore().getContact(recipientId);
final var expirationTime = contact != null ? contact.getMessageExpirationTime() : 0;
messageBuilder.withExpiration(expirationTime);
var message = messageBuilder.build();
final var result = sendSelfMessage(message);
return new Pair<>(message.getTimestamp(), result);
}
public SendMessageResult sendSyncMessage(SignalServiceSyncMessage message) throws IOException {
var messageSender = dependencies.getMessageSender();
try {
return messageSender.sendSyncMessage(message, unidentifiedAccessHelper.getAccessForSync());
} catch (UntrustedIdentityException e) {
var address = addressResolver.resolveSignalServiceAddress(account.getSelfRecipientId());
return SendMessageResult.identityFailure(address, e.getIdentityKey());
}
}
public void sendTypingMessage(
SignalServiceTypingMessage message, Set<RecipientId> recipientIds
) throws IOException, UntrustedIdentityException {
var messageSender = dependencies.getMessageSender();
for (var recipientId : recipientIds) {
final var address = addressResolver.resolveSignalServiceAddress(recipientId);
try {
messageSender.sendTyping(address, unidentifiedAccessHelper.getAccessFor(recipientId), message);
} catch (UnregisteredUserException e) {
final var newRecipientId = recipientRegistrationRefresher.refreshRecipientRegistration(recipientId);
final var newAddress = addressResolver.resolveSignalServiceAddress(newRecipientId);
messageSender.sendTyping(newAddress, unidentifiedAccessHelper.getAccessFor(newRecipientId), message);
}
}
}
public void sendGroupTypingMessage(
SignalServiceTypingMessage message, GroupId groupId
) throws IOException, NotAGroupMemberException, GroupNotFoundException {
final var g = getGroupForSending(groupId);
final var messageSender = dependencies.getMessageSender();
final var recipientIdList = new ArrayList<>(g.getMembersWithout(account.getSelfRecipientId()));
final var addresses = recipientIdList.stream()
.map(addressResolver::resolveSignalServiceAddress)
.collect(Collectors.toList());
messageSender.sendTyping(addresses, unidentifiedAccessHelper.getAccessFor(recipientIdList), message, null);
}
private GroupInfo getGroupForSending(GroupId groupId) throws GroupNotFoundException, NotAGroupMemberException {
var g = groupProvider.getGroup(groupId);
if (g == null) {
throw new GroupNotFoundException(groupId);
}
if (!g.isMember(account.getSelfRecipientId())) {
throw new NotAGroupMemberException(groupId, g.getTitle());
}
return g;
}
private List<SendMessageResult> sendGroupMessageInternal(
final SignalServiceDataMessage message, final Set<RecipientId> recipientIds
) throws IOException {
try {
var messageSender = dependencies.getMessageSender();
// isRecipientUpdate is true if we've already sent this message to some recipients in the past, otherwise false.
final var isRecipientUpdate = false;
final var recipientIdList = new ArrayList<>(recipientIds);
final var addresses = recipientIdList.stream()
.map(addressResolver::resolveSignalServiceAddress)
.collect(Collectors.toList());
return messageSender.sendDataMessage(addresses,
unidentifiedAccessHelper.getAccessFor(recipientIdList),
isRecipientUpdate,
ContentHint.DEFAULT,
message,
sendResult -> logger.trace("Partial message send result: {}", sendResult.isSuccess()),
() -> false);
} catch (UntrustedIdentityException e) {
return List.of();
}
}
private SendMessageResult sendMessage(
SignalServiceDataMessage message, RecipientId recipientId
) throws IOException {
var messageSender = dependencies.getMessageSender();
final var address = addressResolver.resolveSignalServiceAddress(recipientId);
try {
try {
return messageSender.sendDataMessage(address,
unidentifiedAccessHelper.getAccessFor(recipientId),
ContentHint.DEFAULT,
message);
} catch (UnregisteredUserException e) {
final var newRecipientId = recipientRegistrationRefresher.refreshRecipientRegistration(recipientId);
return messageSender.sendDataMessage(addressResolver.resolveSignalServiceAddress(newRecipientId),
unidentifiedAccessHelper.getAccessFor(newRecipientId),
ContentHint.DEFAULT,
message);
}
} catch (UntrustedIdentityException e) {
return SendMessageResult.identityFailure(address, e.getIdentityKey());
}
}
private SendMessageResult sendSelfMessage(SignalServiceDataMessage message) throws IOException {
var address = account.getSelfAddress();
var transcript = new SentTranscriptMessage(Optional.of(address),
message.getTimestamp(),
message,
message.getExpiresInSeconds(),
Map.of(address, true),
false);
var syncMessage = SignalServiceSyncMessage.forSentTranscript(transcript);
return sendSyncMessage(syncMessage);
}
private void handlePossibleIdentityFailure(final SendMessageResult r) {
if (r.getIdentityFailure() != null) {
final var recipientId = recipientResolver.resolveRecipient(r.getAddress());
identityFailureHandler.handleIdentityFailure(recipientId, r.getIdentityFailure());
}
}
}

View file

@ -6,9 +6,7 @@ import net.sourceforge.argparse4j.inf.Subparser;
import org.asamk.signal.OutputWriter; import org.asamk.signal.OutputWriter;
import org.asamk.signal.commands.exceptions.CommandException; import org.asamk.signal.commands.exceptions.CommandException;
import org.asamk.signal.commands.exceptions.IOErrorException; import org.asamk.signal.commands.exceptions.IOErrorException;
import org.asamk.signal.commands.exceptions.UntrustedKeyErrorException;
import org.asamk.signal.manager.Manager; import org.asamk.signal.manager.Manager;
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
import java.io.IOException; import java.io.IOException;
@ -30,8 +28,6 @@ public class SendContactsCommand implements JsonRpcLocalCommand {
) throws CommandException { ) throws CommandException {
try { try {
m.sendContacts(); m.sendContacts();
} catch (UntrustedIdentityException e) {
throw new UntrustedKeyErrorException("SendContacts error: " + e.getMessage());
} catch (IOException e) { } catch (IOException e) {
throw new IOErrorException("SendContacts error: " + e.getMessage()); throw new IOErrorException("SendContacts error: " + e.getMessage());
} }