Extract ContactHelper and IncomingMessageHandler

This commit is contained in:
AsamK 2021-08-26 15:25:02 +02:00
parent debbaa81ba
commit 8bc6c0abcb
21 changed files with 954 additions and 780 deletions

View file

@ -1,219 +0,0 @@
package org.asamk.signal.manager;
import org.asamk.signal.manager.groups.GroupIdV1;
import org.asamk.signal.manager.storage.recipients.RecipientId;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import java.util.List;
import java.util.Objects;
interface HandleAction {
void execute(Manager m) throws Throwable;
}
class SendReceiptAction implements HandleAction {
private final SignalServiceAddress address;
private final long timestamp;
public SendReceiptAction(final SignalServiceAddress address, final long timestamp) {
this.address = address;
this.timestamp = timestamp;
}
@Override
public void execute(Manager m) throws Throwable {
m.sendDeliveryReceipt(address, List.of(timestamp));
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final var that = (SendReceiptAction) o;
return timestamp == that.timestamp && address.equals(that.address);
}
@Override
public int hashCode() {
return Objects.hash(address, timestamp);
}
}
class SendSyncContactsAction implements HandleAction {
private static final SendSyncContactsAction INSTANCE = new SendSyncContactsAction();
private SendSyncContactsAction() {
}
public static SendSyncContactsAction create() {
return INSTANCE;
}
@Override
public void execute(Manager m) throws Throwable {
m.sendContacts();
}
}
class SendSyncGroupsAction implements HandleAction {
private static final SendSyncGroupsAction INSTANCE = new SendSyncGroupsAction();
private SendSyncGroupsAction() {
}
public static SendSyncGroupsAction create() {
return INSTANCE;
}
@Override
public void execute(Manager m) throws Throwable {
m.sendGroups();
}
}
class SendSyncBlockedListAction implements HandleAction {
private static final SendSyncBlockedListAction INSTANCE = new SendSyncBlockedListAction();
private SendSyncBlockedListAction() {
}
public static SendSyncBlockedListAction create() {
return INSTANCE;
}
@Override
public void execute(Manager m) throws Throwable {
m.sendBlockedList();
}
}
class SendGroupInfoRequestAction implements HandleAction {
private final SignalServiceAddress address;
private final GroupIdV1 groupId;
public SendGroupInfoRequestAction(final SignalServiceAddress address, final GroupIdV1 groupId) {
this.address = address;
this.groupId = groupId;
}
@Override
public void execute(Manager m) throws Throwable {
m.sendGroupInfoRequest(groupId, address);
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final var that = (SendGroupInfoRequestAction) o;
if (!address.equals(that.address)) return false;
return groupId.equals(that.groupId);
}
@Override
public int hashCode() {
var result = address.hashCode();
result = 31 * result + groupId.hashCode();
return result;
}
}
class SendGroupInfoAction implements HandleAction {
private final SignalServiceAddress address;
private final GroupIdV1 groupId;
public SendGroupInfoAction(final SignalServiceAddress address, final GroupIdV1 groupId) {
this.address = address;
this.groupId = groupId;
}
@Override
public void execute(Manager m) throws Throwable {
m.sendGroupInfoMessage(groupId, address);
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final var that = (SendGroupInfoAction) o;
if (!address.equals(that.address)) return false;
return groupId.equals(that.groupId);
}
@Override
public int hashCode() {
var result = address.hashCode();
result = 31 * result + groupId.hashCode();
return result;
}
}
class RetrieveProfileAction implements HandleAction {
private final RecipientId recipientId;
public RetrieveProfileAction(final RecipientId recipientId) {
this.recipientId = recipientId;
}
@Override
public void execute(Manager m) throws Throwable {
m.refreshRecipientProfile(recipientId);
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final RetrieveProfileAction that = (RetrieveProfileAction) o;
return recipientId.equals(that.recipientId);
}
@Override
public int hashCode() {
return recipientId.hashCode();
}
}
class RenewSessionAction implements HandleAction {
private final RecipientId recipientId;
public RenewSessionAction(final RecipientId recipientId) {
this.recipientId = recipientId;
}
@Override
public void execute(Manager m) throws Throwable {
m.renewSession(recipientId);
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final RenewSessionAction that = (RenewSessionAction) o;
return recipientId.equals(that.recipientId);
}
@Override
public int hashCode() {
return recipientId.hashCode();
}
}

View file

@ -0,0 +1,17 @@
package org.asamk.signal.manager;
import org.asamk.signal.manager.jobs.Context;
import org.asamk.signal.manager.jobs.Job;
public class JobExecutor {
private final Context context;
public JobExecutor(final Context context) {
this.context = context;
}
public void enqueueJob(Job job) {
job.run(context);
}
}

View file

@ -16,6 +16,7 @@
*/ */
package org.asamk.signal.manager; package org.asamk.signal.manager;
import org.asamk.signal.manager.actions.HandleAction;
import org.asamk.signal.manager.api.Device; import org.asamk.signal.manager.api.Device;
import org.asamk.signal.manager.api.Message; import org.asamk.signal.manager.api.Message;
import org.asamk.signal.manager.api.RecipientIdentifier; import org.asamk.signal.manager.api.RecipientIdentifier;
@ -26,29 +27,26 @@ import org.asamk.signal.manager.config.ServiceConfig;
import org.asamk.signal.manager.config.ServiceEnvironment; import org.asamk.signal.manager.config.ServiceEnvironment;
import org.asamk.signal.manager.config.ServiceEnvironmentConfig; import org.asamk.signal.manager.config.ServiceEnvironmentConfig;
import org.asamk.signal.manager.groups.GroupId; import org.asamk.signal.manager.groups.GroupId;
import org.asamk.signal.manager.groups.GroupIdV1;
import org.asamk.signal.manager.groups.GroupInviteLinkUrl; import org.asamk.signal.manager.groups.GroupInviteLinkUrl;
import org.asamk.signal.manager.groups.GroupLinkState; import org.asamk.signal.manager.groups.GroupLinkState;
import org.asamk.signal.manager.groups.GroupNotFoundException; import org.asamk.signal.manager.groups.GroupNotFoundException;
import org.asamk.signal.manager.groups.GroupPermission; import org.asamk.signal.manager.groups.GroupPermission;
import org.asamk.signal.manager.groups.GroupSendingNotAllowedException; import org.asamk.signal.manager.groups.GroupSendingNotAllowedException;
import org.asamk.signal.manager.groups.GroupUtils;
import org.asamk.signal.manager.groups.LastGroupAdminException; import org.asamk.signal.manager.groups.LastGroupAdminException;
import org.asamk.signal.manager.groups.NotAGroupMemberException; import org.asamk.signal.manager.groups.NotAGroupMemberException;
import org.asamk.signal.manager.helper.AttachmentHelper; import org.asamk.signal.manager.helper.AttachmentHelper;
import org.asamk.signal.manager.helper.ContactHelper;
import org.asamk.signal.manager.helper.GroupHelper; import org.asamk.signal.manager.helper.GroupHelper;
import org.asamk.signal.manager.helper.GroupV2Helper; import org.asamk.signal.manager.helper.GroupV2Helper;
import org.asamk.signal.manager.helper.IncomingMessageHandler;
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.SendHelper;
import org.asamk.signal.manager.helper.SyncHelper; import org.asamk.signal.manager.helper.SyncHelper;
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.RetrieveStickerPackJob;
import org.asamk.signal.manager.storage.SignalAccount; import org.asamk.signal.manager.storage.SignalAccount;
import org.asamk.signal.manager.storage.groups.GroupInfo; import org.asamk.signal.manager.storage.groups.GroupInfo;
import org.asamk.signal.manager.storage.groups.GroupInfoV1;
import org.asamk.signal.manager.storage.identities.IdentityInfo; import org.asamk.signal.manager.storage.identities.IdentityInfo;
import org.asamk.signal.manager.storage.identities.TrustNewIdentity; import org.asamk.signal.manager.storage.identities.TrustNewIdentity;
import org.asamk.signal.manager.storage.messageCache.CachedMessage; import org.asamk.signal.manager.storage.messageCache.CachedMessage;
@ -60,10 +58,7 @@ import org.asamk.signal.manager.storage.stickers.StickerPackId;
import org.asamk.signal.manager.util.KeyUtils; import org.asamk.signal.manager.util.KeyUtils;
import org.asamk.signal.manager.util.StickerUtils; import org.asamk.signal.manager.util.StickerUtils;
import org.asamk.signal.manager.util.Utils; import org.asamk.signal.manager.util.Utils;
import org.signal.libsignal.metadata.ProtocolInvalidMessageException;
import org.signal.libsignal.metadata.ProtocolUntrustedIdentityException; import org.signal.libsignal.metadata.ProtocolUntrustedIdentityException;
import org.signal.zkgroup.InvalidInputException;
import org.signal.zkgroup.profiles.ProfileKey;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.whispersystems.libsignal.IdentityKey; import org.whispersystems.libsignal.IdentityKey;
@ -85,10 +80,8 @@ import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentRemo
import org.whispersystems.signalservice.api.messages.SignalServiceContent; import org.whispersystems.signalservice.api.messages.SignalServiceContent;
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage; import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope; import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
import org.whispersystems.signalservice.api.messages.SignalServiceGroup;
import org.whispersystems.signalservice.api.messages.SignalServiceReceiptMessage; import org.whispersystems.signalservice.api.messages.SignalServiceReceiptMessage;
import org.whispersystems.signalservice.api.messages.SignalServiceTypingMessage; import org.whispersystems.signalservice.api.messages.SignalServiceTypingMessage;
import org.whispersystems.signalservice.api.messages.multidevice.StickerPackOperationMessage;
import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.api.push.SignalServiceAddress;
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;
@ -109,7 +102,6 @@ import java.net.URISyntaxException;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.security.SignatureException; import java.security.SignatureException;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Date; import java.util.Date;
@ -146,19 +138,10 @@ public class Manager implements Closeable {
private final SyncHelper syncHelper; private final SyncHelper syncHelper;
private final AttachmentHelper attachmentHelper; private final AttachmentHelper attachmentHelper;
private final GroupHelper groupHelper; private final GroupHelper groupHelper;
private final ContactHelper contactHelper;
private final IncomingMessageHandler incomingMessageHandler;
private final AvatarStore avatarStore; private final Context context;
private final AttachmentStore attachmentStore;
private final StickerPackStore stickerPackStore;
private final SignalSessionLock sessionLock = new SignalSessionLock() {
private final ReentrantLock LEGACY_LOCK = new ReentrantLock();
@Override
public Lock acquire() {
LEGACY_LOCK.lock();
return LEGACY_LOCK::unlock;
}
};
Manager( Manager(
SignalAccount account, SignalAccount account,
@ -173,6 +156,15 @@ public class Manager implements Closeable {
account.getUsername(), account.getUsername(),
account.getPassword(), account.getPassword(),
account.getDeviceId()); account.getDeviceId());
final var sessionLock = new SignalSessionLock() {
private final ReentrantLock LEGACY_LOCK = new ReentrantLock();
@Override
public Lock acquire() {
LEGACY_LOCK.lock();
return LEGACY_LOCK::unlock;
}
};
this.dependencies = new SignalDependencies(account.getSelfAddress(), this.dependencies = new SignalDependencies(account.getSelfAddress(),
serviceEnvironmentConfig, serviceEnvironmentConfig,
userAgent, userAgent,
@ -180,9 +172,9 @@ public class Manager implements Closeable {
account.getSignalProtocolStore(), account.getSignalProtocolStore(),
executor, executor,
sessionLock); sessionLock);
this.avatarStore = new AvatarStore(pathConfig.getAvatarsPath()); final var avatarStore = new AvatarStore(pathConfig.getAvatarsPath());
this.attachmentStore = new AttachmentStore(pathConfig.getAttachmentsPath()); final var attachmentStore = new AttachmentStore(pathConfig.getAttachmentsPath());
this.stickerPackStore = new StickerPackStore(pathConfig.getStickerPacksPath()); final var stickerPackStore = new StickerPackStore(pathConfig.getStickerPacksPath());
this.attachmentHelper = new AttachmentHelper(dependencies, attachmentStore); this.attachmentHelper = new AttachmentHelper(dependencies, attachmentStore);
this.pinHelper = new PinHelper(dependencies.getKeyBackupService()); this.pinHelper = new PinHelper(dependencies.getKeyBackupService());
@ -220,6 +212,7 @@ public class Manager implements Closeable {
avatarStore, avatarStore,
this::resolveSignalServiceAddress, this::resolveSignalServiceAddress,
this::resolveRecipient); this::resolveRecipient);
this.contactHelper = new ContactHelper(account);
this.syncHelper = new SyncHelper(account, this.syncHelper = new SyncHelper(account,
attachmentHelper, attachmentHelper,
sendHelper, sendHelper,
@ -227,16 +220,31 @@ public class Manager implements Closeable {
avatarStore, avatarStore,
this::resolveSignalServiceAddress, this::resolveSignalServiceAddress,
this::resolveRecipient); this::resolveRecipient);
this.context = new Context(account,
dependencies.getAccountManager(),
dependencies.getMessageReceiver(),
stickerPackStore,
sendHelper,
groupHelper,
syncHelper,
profileHelper);
var jobExecutor = new JobExecutor(context);
this.incomingMessageHandler = new IncomingMessageHandler(account,
dependencies,
this::resolveRecipient,
groupHelper,
contactHelper,
attachmentHelper,
syncHelper,
jobExecutor);
} }
public String getUsername() { public String getUsername() {
return account.getUsername(); return account.getUsername();
} }
private SignalServiceAddress getSelfAddress() {
return account.getSelfAddress();
}
public RecipientId getSelfRecipientId() { public RecipientId getSelfRecipientId() {
return account.getSelfRecipientId(); return account.getSelfRecipientId();
} }
@ -326,15 +334,15 @@ public class Manager implements Closeable {
} }
})); }));
// Note "contactDetails" has no optionals. It only gives us info on users who are registered // Note "registeredUsers" has no optionals. It only gives us info on users who are registered
var contactDetails = getRegisteredUsers(canonicalizedNumbers.values() var registeredUsers = getRegisteredUsers(canonicalizedNumbers.values()
.stream() .stream()
.filter(s -> !s.isEmpty()) .filter(s -> !s.isEmpty())
.collect(Collectors.toSet())); .collect(Collectors.toSet()));
return numbers.stream().collect(Collectors.toMap(n -> n, n -> { return numbers.stream().collect(Collectors.toMap(n -> n, n -> {
final var number = canonicalizedNumbers.get(n); final var number = canonicalizedNumbers.get(n);
final var uuid = contactDetails.get(number); final var uuid = registeredUsers.get(number);
return new Pair<>(number.isEmpty() ? null : number, uuid); return new Pair<>(number.isEmpty() ? null : number, uuid);
})); }));
} }
@ -475,10 +483,6 @@ public class Manager implements Closeable {
return profileHelper.getRecipientProfile(recipientId); return profileHelper.getRecipientProfile(recipientId);
} }
public void refreshRecipientProfile(RecipientId recipientId) {
profileHelper.refreshRecipientProfile(recipientId);
}
public List<GroupInfo> getGroups() { public List<GroupInfo> getGroups() {
return account.getGroupStore().getGroups(); return account.getGroupStore().getGroups();
} }
@ -578,20 +582,6 @@ public class Manager implements Closeable {
} }
} }
SendGroupMessageResults sendGroupInfoMessage(
GroupIdV1 groupId, SignalServiceAddress recipient
) throws IOException, NotAGroupMemberException, GroupNotFoundException, AttachmentInvalidException {
final var recipientId = resolveRecipient(recipient);
return groupHelper.sendGroupInfoMessage(groupId, recipientId);
}
SendGroupMessageResults sendGroupInfoRequest(
GroupIdV1 groupId, SignalServiceAddress recipient
) throws IOException {
final var recipientId = resolveRecipient(recipient);
return groupHelper.sendGroupInfoRequest(groupId, recipientId);
}
public void sendReadReceipt( public void sendReadReceipt(
RecipientIdentifier.Single sender, List<Long> messageIds RecipientIdentifier.Single sender, List<Long> messageIds
) throws IOException, UntrustedIdentityException { ) throws IOException, UntrustedIdentityException {
@ -612,16 +602,6 @@ public class Manager implements Closeable {
sendHelper.sendReceiptMessage(receiptMessage, resolveRecipient(sender)); sendHelper.sendReceiptMessage(receiptMessage, resolveRecipient(sender));
} }
void sendDeliveryReceipt(
SignalServiceAddress remoteAddress, List<Long> messageIds
) throws IOException, UntrustedIdentityException {
var receiptMessage = new SignalServiceReceiptMessage(SignalServiceReceiptMessage.Type.DELIVERY,
messageIds,
System.currentTimeMillis());
sendHelper.sendReceiptMessage(receiptMessage, resolveRecipient(remoteAddress));
}
public SendMessageResults sendMessage( public SendMessageResults sendMessage(
Message message, Set<RecipientIdentifier> recipients Message message, Set<RecipientIdentifier> recipients
) throws IOException, AttachmentInvalidException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException { ) throws IOException, AttachmentInvalidException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException {
@ -674,56 +654,38 @@ public class Manager implements Closeable {
throw new AssertionError(e); throw new AssertionError(e);
} finally { } finally {
for (var recipient : recipients) { for (var recipient : recipients) {
final var recipientId = resolveRecipient((RecipientIdentifier.Single) recipient); final var recipientId = resolveRecipient(recipient);
handleEndSession(recipientId); account.getSessionStore().deleteAllSessions(recipientId);
} }
} }
} }
void renewSession(RecipientId recipientId) throws IOException {
account.getSessionStore().archiveSessions(recipientId);
if (!recipientId.equals(getSelfRecipientId())) {
sendHelper.sendNullMessage(recipientId);
}
}
public void setContactName( public void setContactName(
RecipientIdentifier.Single recipient, String name RecipientIdentifier.Single recipient, String name
) throws NotMasterDeviceException { ) throws NotMasterDeviceException {
if (!account.isMasterDevice()) { if (!account.isMasterDevice()) {
throw new NotMasterDeviceException(); throw new NotMasterDeviceException();
} }
final var recipientId = resolveRecipient(recipient); contactHelper.setContactName(resolveRecipient(recipient), name);
var contact = account.getContactStore().getContact(recipientId);
final var builder = contact == null ? Contact.newBuilder() : Contact.newBuilder(contact);
account.getContactStore().storeContact(recipientId, builder.withName(name).build());
} }
public void setContactBlocked( public void setContactBlocked(
RecipientIdentifier.Single recipient, boolean blocked RecipientIdentifier.Single recipient, boolean blocked
) throws NotMasterDeviceException { ) throws NotMasterDeviceException, IOException {
if (!account.isMasterDevice()) { if (!account.isMasterDevice()) {
throw new NotMasterDeviceException(); throw new NotMasterDeviceException();
} }
setContactBlocked(resolveRecipient(recipient), blocked); contactHelper.setContactBlocked(resolveRecipient(recipient), blocked);
// TODO cycle our profile key
syncHelper.sendBlockedList();
} }
private void setContactBlocked(RecipientId recipientId, boolean blocked) { public void setGroupBlocked(
var contact = account.getContactStore().getContact(recipientId); final GroupId groupId, final boolean blocked
final var builder = contact == null ? Contact.newBuilder() : Contact.newBuilder(contact); ) throws GroupNotFoundException, IOException {
groupHelper.setGroupBlocked(groupId, blocked);
// TODO cycle our profile key // TODO cycle our profile key
account.getContactStore().storeContact(recipientId, builder.withBlocked(blocked).build()); syncHelper.sendBlockedList();
}
public void setGroupBlocked(final GroupId groupId, final boolean blocked) throws GroupNotFoundException {
var group = getGroup(groupId);
if (group == null) {
throw new GroupNotFoundException(groupId);
}
group.setBlocked(blocked);
// TODO cycle our profile key
account.getGroupStore().updateGroup(group);
} }
/** /**
@ -733,7 +695,7 @@ public class Manager implements Closeable {
RecipientIdentifier.Single recipient, int messageExpirationTimer RecipientIdentifier.Single recipient, int messageExpirationTimer
) throws IOException { ) throws IOException {
var recipientId = resolveRecipient(recipient); var recipientId = resolveRecipient(recipient);
setExpirationTimer(recipientId, messageExpirationTimer); contactHelper.setExpirationTimer(recipientId, messageExpirationTimer);
final var messageBuilder = SignalServiceDataMessage.newBuilder().asExpirationUpdate(); final var messageBuilder = SignalServiceDataMessage.newBuilder().asExpirationUpdate();
try { try {
sendMessage(messageBuilder, Set.of(recipient)); sendMessage(messageBuilder, Set.of(recipient));
@ -742,16 +704,6 @@ public class Manager implements Closeable {
} }
} }
private void setExpirationTimer(RecipientId recipientId, int messageExpirationTimer) {
var contact = account.getContactStore().getContact(recipientId);
if (contact != null && contact.getMessageExpirationTime() == messageExpirationTimer) {
return;
}
final var builder = contact == null ? Contact.newBuilder() : Contact.newBuilder(contact);
account.getContactStore()
.storeContact(recipientId, builder.withMessageExpirationTime(messageExpirationTimer).build());
}
/** /**
* Upload the sticker pack from path. * Upload the sticker pack from path.
* *
@ -875,162 +827,6 @@ public class Manager implements Closeable {
sendTypingMessage(action.toSignalService(), recipients); sendTypingMessage(action.toSignalService(), recipients);
} }
private void handleEndSession(RecipientId recipientId) {
account.getSessionStore().deleteAllSessions(recipientId);
}
private List<HandleAction> handleSignalServiceDataMessage(
SignalServiceDataMessage message,
boolean isSync,
SignalServiceAddress source,
SignalServiceAddress destination,
boolean ignoreAttachments
) {
var actions = new ArrayList<HandleAction>();
if (message.getGroupContext().isPresent()) {
if (message.getGroupContext().get().getGroupV1().isPresent()) {
var groupInfo = message.getGroupContext().get().getGroupV1().get();
var groupId = GroupId.v1(groupInfo.getGroupId());
var group = getGroup(groupId);
if (group == null || group instanceof GroupInfoV1) {
var groupV1 = (GroupInfoV1) group;
switch (groupInfo.getType()) {
case UPDATE: {
if (groupV1 == null) {
groupV1 = new GroupInfoV1(groupId);
}
if (groupInfo.getAvatar().isPresent()) {
var avatar = groupInfo.getAvatar().get();
groupHelper.downloadGroupAvatar(groupV1.getGroupId(), avatar);
}
if (groupInfo.getName().isPresent()) {
groupV1.name = groupInfo.getName().get();
}
if (groupInfo.getMembers().isPresent()) {
groupV1.addMembers(groupInfo.getMembers()
.get()
.stream()
.map(this::resolveRecipient)
.collect(Collectors.toSet()));
}
account.getGroupStore().updateGroup(groupV1);
break;
}
case DELIVER:
if (groupV1 == null && !isSync) {
actions.add(new SendGroupInfoRequestAction(source, groupId));
}
break;
case QUIT: {
if (groupV1 != null) {
groupV1.removeMember(resolveRecipient(source));
account.getGroupStore().updateGroup(groupV1);
}
break;
}
case REQUEST_INFO:
if (groupV1 != null && !isSync) {
actions.add(new SendGroupInfoAction(source, groupV1.getGroupId()));
}
break;
}
} else {
// Received a group v1 message for a v2 group
}
}
if (message.getGroupContext().get().getGroupV2().isPresent()) {
final var groupContext = message.getGroupContext().get().getGroupV2().get();
final var groupMasterKey = groupContext.getMasterKey();
groupHelper.getOrMigrateGroup(groupMasterKey,
groupContext.getRevision(),
groupContext.hasSignedGroupChange() ? groupContext.getSignedGroupChange() : null);
}
}
final var conversationPartnerAddress = isSync ? destination : source;
if (conversationPartnerAddress != null && message.isEndSession()) {
handleEndSession(resolveRecipient(conversationPartnerAddress));
}
if (message.isExpirationUpdate() || message.getBody().isPresent()) {
if (message.getGroupContext().isPresent()) {
if (message.getGroupContext().get().getGroupV1().isPresent()) {
var groupInfo = message.getGroupContext().get().getGroupV1().get();
var group = account.getGroupStore().getOrCreateGroupV1(GroupId.v1(groupInfo.getGroupId()));
if (group != null) {
if (group.messageExpirationTime != message.getExpiresInSeconds()) {
group.messageExpirationTime = message.getExpiresInSeconds();
account.getGroupStore().updateGroup(group);
}
}
} else if (message.getGroupContext().get().getGroupV2().isPresent()) {
// disappearing message timer already stored in the DecryptedGroup
}
} else if (conversationPartnerAddress != null) {
setExpirationTimer(resolveRecipient(conversationPartnerAddress), message.getExpiresInSeconds());
}
}
if (!ignoreAttachments) {
if (message.getAttachments().isPresent()) {
for (var attachment : message.getAttachments().get()) {
attachmentHelper.downloadAttachment(attachment);
}
}
if (message.getSharedContacts().isPresent()) {
for (var contact : message.getSharedContacts().get()) {
if (contact.getAvatar().isPresent()) {
attachmentHelper.downloadAttachment(contact.getAvatar().get().getAttachment());
}
}
}
if (message.getPreviews().isPresent()) {
final var previews = message.getPreviews().get();
for (var preview : previews) {
if (preview.getImage().isPresent()) {
attachmentHelper.downloadAttachment(preview.getImage().get());
}
}
}
if (message.getQuote().isPresent()) {
final var quote = message.getQuote().get();
for (var quotedAttachment : quote.getAttachments()) {
final var thumbnail = quotedAttachment.getThumbnail();
if (thumbnail != null) {
attachmentHelper.downloadAttachment(thumbnail);
}
}
}
}
if (message.getProfileKey().isPresent() && message.getProfileKey().get().length == 32) {
final ProfileKey profileKey;
try {
profileKey = new ProfileKey(message.getProfileKey().get());
} catch (InvalidInputException e) {
throw new AssertionError(e);
}
if (source.matches(account.getSelfAddress())) {
this.account.setProfileKey(profileKey);
}
this.account.getProfileStore().storeProfileKey(resolveRecipient(source), profileKey);
}
if (message.getSticker().isPresent()) {
final var messageSticker = message.getSticker().get();
final var stickerPackId = StickerPackId.deserialize(messageSticker.getPackId());
var sticker = account.getStickerStore().getSticker(stickerPackId);
if (sticker == null) {
sticker = new Sticker(stickerPackId, messageSticker.getPackKey());
account.getStickerStore().updateSticker(sticker);
}
enqueueJob(new RetrieveStickerPackJob(stickerPackId, messageSticker.getPackKey()));
}
return actions;
}
private void retryFailedReceivedMessages(ReceiveMessageHandler handler, boolean ignoreAttachments) { private void retryFailedReceivedMessages(ReceiveMessageHandler handler, boolean ignoreAttachments) {
Set<HandleAction> queuedActions = new HashSet<>(); Set<HandleAction> queuedActions = new HashSet<>();
for (var cachedMessage : account.getMessageCache().getCachedMessages()) { for (var cachedMessage : account.getMessageCache().getCachedMessages()) {
@ -1070,7 +866,7 @@ public class Manager implements Closeable {
cachedMessage.delete(); cachedMessage.delete();
return null; return null;
} }
actions = handleMessage(envelope, content, ignoreAttachments); actions = incomingMessageHandler.handleMessage(envelope, content, ignoreAttachments);
} }
handler.handleMessage(envelope, content, null); handler.handleMessage(envelope, content, null);
cachedMessage.delete(); cachedMessage.delete();
@ -1095,8 +891,6 @@ public class Manager implements Closeable {
while (!Thread.interrupted()) { while (!Thread.interrupted()) {
SignalServiceEnvelope envelope; SignalServiceEnvelope envelope;
SignalServiceContent content = null;
Exception exception = null;
final CachedMessage[] cachedMessage = {null}; final CachedMessage[] cachedMessage = {null};
account.setLastReceiveTimestamp(System.currentTimeMillis()); account.setLastReceiveTimestamp(System.currentTimeMillis());
logger.debug("Checking for new message from server"); logger.debug("Checking for new message from server");
@ -1137,58 +931,17 @@ public class Manager implements Closeable {
continue; continue;
} }
if (envelope.hasSource()) { final var result = incomingMessageHandler.handleEnvelope(envelope, ignoreAttachments, handler);
// Store uuid if we don't have it already queuedActions.addAll(result.first());
// address/uuid in envelope is sent by server final var exception = result.second();
resolveRecipientTrusted(envelope.getSourceAddress());
} if (hasCaughtUpWithOldMessages) {
if (!envelope.isReceipt()) { handleQueuedActions(queuedActions);
try {
content = dependencies.getCipher().decrypt(envelope);
} catch (Exception e) {
exception = e;
}
if (!envelope.hasSource() && content != null) {
// Store uuid if we don't have it already
// address/uuid is validated by unidentified sender certificate
resolveRecipientTrusted(content.getSender());
}
var actions = handleMessage(envelope, content, ignoreAttachments);
if (exception instanceof ProtocolInvalidMessageException) {
final var sender = resolveRecipient(((ProtocolInvalidMessageException) exception).getSender());
logger.debug("Received invalid message, queuing renew session action.");
actions.add(new RenewSessionAction(sender));
}
if (hasCaughtUpWithOldMessages) {
for (var action : actions) {
try {
action.execute(this);
} catch (Throwable e) {
if (e instanceof AssertionError && e.getCause() instanceof InterruptedException) {
Thread.currentThread().interrupt();
}
logger.warn("Message action failed.", e);
}
}
} else {
queuedActions.addAll(actions);
}
}
final var notAllowedToSendToGroup = isNotAllowedToSendToGroup(envelope, content);
if (isMessageBlocked(envelope, content)) {
logger.info("Ignoring a message from blocked user/group: {}", envelope.getTimestamp());
} else if (notAllowedToSendToGroup) {
logger.info("Ignoring a group message from an unauthorized sender (no member or admin): {} {}",
(envelope.hasSource() ? envelope.getSourceAddress() : content.getSender()).getIdentifier(),
envelope.getTimestamp());
} else {
handler.handleMessage(envelope, content, exception);
} }
if (cachedMessage[0] != null) { if (cachedMessage[0] != null) {
if (exception instanceof ProtocolUntrustedIdentityException) { if (exception instanceof ProtocolUntrustedIdentityException) {
final var identifier = ((ProtocolUntrustedIdentityException) exception).getSender(); final var identifier = ((ProtocolUntrustedIdentityException) exception).getSender();
final var recipientId = resolveRecipient(identifier); final var recipientId = resolveRecipient(identifier);
queuedActions.add(new RetrieveProfileAction(recipientId));
if (!envelope.hasSource()) { if (!envelope.hasSource()) {
try { try {
cachedMessage[0] = account.getMessageCache().replaceSender(cachedMessage[0], recipientId); cachedMessage[0] = account.getMessageCache().replaceSender(cachedMessage[0], recipientId);
@ -1205,10 +958,10 @@ public class Manager implements Closeable {
handleQueuedActions(queuedActions); handleQueuedActions(queuedActions);
} }
private void handleQueuedActions(final Set<HandleAction> queuedActions) { private void handleQueuedActions(final Collection<HandleAction> queuedActions) {
for (var action : queuedActions) { for (var action : queuedActions) {
try { try {
action.execute(this); action.execute(context);
} catch (Throwable e) { } catch (Throwable e) {
if (e instanceof AssertionError && e.getCause() instanceof InterruptedException) { if (e instanceof AssertionError && e.getCause() instanceof InterruptedException) {
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();
@ -1218,252 +971,19 @@ public class Manager implements Closeable {
} }
} }
private boolean isMessageBlocked(
SignalServiceEnvelope envelope, SignalServiceContent content
) {
SignalServiceAddress source;
if (!envelope.isUnidentifiedSender() && envelope.hasSource()) {
source = envelope.getSourceAddress();
} else if (content != null) {
source = content.getSender();
} else {
return false;
}
final var recipientId = resolveRecipient(source);
if (isContactBlocked(recipientId)) {
return true;
}
if (content != null && content.getDataMessage().isPresent()) {
var message = content.getDataMessage().get();
if (message.getGroupContext().isPresent()) {
var groupId = GroupUtils.getGroupId(message.getGroupContext().get());
var group = getGroup(groupId);
if (group != null && group.isBlocked()) {
return true;
}
}
}
return false;
}
public boolean isContactBlocked(final RecipientIdentifier.Single recipient) { public boolean isContactBlocked(final RecipientIdentifier.Single recipient) {
final var recipientId = resolveRecipient(recipient); final var recipientId = resolveRecipient(recipient);
return isContactBlocked(recipientId); return contactHelper.isContactBlocked(recipientId);
}
private boolean isContactBlocked(final RecipientId recipientId) {
var sourceContact = account.getContactStore().getContact(recipientId);
return sourceContact != null && sourceContact.isBlocked();
}
private boolean isNotAllowedToSendToGroup(
SignalServiceEnvelope envelope, SignalServiceContent content
) {
SignalServiceAddress source;
if (!envelope.isUnidentifiedSender() && envelope.hasSource()) {
source = envelope.getSourceAddress();
} else if (content != null) {
source = content.getSender();
} else {
return false;
}
if (content == null || !content.getDataMessage().isPresent()) {
return false;
}
var message = content.getDataMessage().get();
if (!message.getGroupContext().isPresent()) {
return false;
}
if (message.getGroupContext().get().getGroupV1().isPresent()) {
var groupInfo = message.getGroupContext().get().getGroupV1().get();
if (groupInfo.getType() == SignalServiceGroup.Type.QUIT) {
return false;
}
}
var groupId = GroupUtils.getGroupId(message.getGroupContext().get());
var group = getGroup(groupId);
if (group == null) {
return false;
}
final var recipientId = resolveRecipient(source);
if (!group.isMember(recipientId)) {
return true;
}
if (group.isAnnouncementGroup() && !group.isAdmin(recipientId)) {
return message.getBody().isPresent()
|| message.getAttachments().isPresent()
|| message.getQuote()
.isPresent()
|| message.getPreviews().isPresent()
|| message.getMentions().isPresent()
|| message.getSticker().isPresent();
}
return false;
}
private List<HandleAction> handleMessage(
SignalServiceEnvelope envelope, SignalServiceContent content, boolean ignoreAttachments
) {
var actions = new ArrayList<HandleAction>();
if (content != null) {
final SignalServiceAddress sender;
if (!envelope.isUnidentifiedSender() && envelope.hasSource()) {
sender = envelope.getSourceAddress();
} else {
sender = content.getSender();
}
if (content.getDataMessage().isPresent()) {
var message = content.getDataMessage().get();
if (content.isNeedsReceipt()) {
actions.add(new SendReceiptAction(sender, message.getTimestamp()));
}
actions.addAll(handleSignalServiceDataMessage(message,
false,
sender,
account.getSelfAddress(),
ignoreAttachments));
}
if (content.getSyncMessage().isPresent()) {
account.setMultiDevice(true);
var syncMessage = content.getSyncMessage().get();
if (syncMessage.getSent().isPresent()) {
var message = syncMessage.getSent().get();
final var destination = message.getDestination().orNull();
actions.addAll(handleSignalServiceDataMessage(message.getMessage(),
true,
sender,
destination,
ignoreAttachments));
}
if (syncMessage.getRequest().isPresent() && account.isMasterDevice()) {
var rm = syncMessage.getRequest().get();
if (rm.isContactsRequest()) {
actions.add(SendSyncContactsAction.create());
}
if (rm.isGroupsRequest()) {
actions.add(SendSyncGroupsAction.create());
}
if (rm.isBlockedListRequest()) {
actions.add(SendSyncBlockedListAction.create());
}
// TODO Handle rm.isConfigurationRequest(); rm.isKeysRequest();
}
if (syncMessage.getGroups().isPresent()) {
try {
final var groupsMessage = syncMessage.getGroups().get();
attachmentHelper.retrieveAttachment(groupsMessage, syncHelper::handleSyncDeviceGroups);
} catch (Exception e) {
logger.warn("Failed to handle received sync groups, ignoring: {}", e.getMessage());
}
}
if (syncMessage.getBlockedList().isPresent()) {
final var blockedListMessage = syncMessage.getBlockedList().get();
for (var address : blockedListMessage.getAddresses()) {
setContactBlocked(resolveRecipient(address), true);
}
for (var groupId : blockedListMessage.getGroupIds()
.stream()
.map(GroupId::unknownVersion)
.collect(Collectors.toSet())) {
try {
setGroupBlocked(groupId, true);
} catch (GroupNotFoundException e) {
logger.warn("BlockedListMessage contained groupID that was not found in GroupStore: {}",
groupId.toBase64());
}
}
}
if (syncMessage.getContacts().isPresent()) {
try {
final var contactsMessage = syncMessage.getContacts().get();
attachmentHelper.retrieveAttachment(contactsMessage.getContactsStream(),
syncHelper::handleSyncDeviceContacts);
} catch (Exception e) {
logger.warn("Failed to handle received sync contacts, ignoring: {}", e.getMessage());
}
}
if (syncMessage.getVerified().isPresent()) {
final var verifiedMessage = syncMessage.getVerified().get();
account.getIdentityKeyStore()
.setIdentityTrustLevel(resolveRecipientTrusted(verifiedMessage.getDestination()),
verifiedMessage.getIdentityKey(),
TrustLevel.fromVerifiedState(verifiedMessage.getVerified()));
}
if (syncMessage.getStickerPackOperations().isPresent()) {
final var stickerPackOperationMessages = syncMessage.getStickerPackOperations().get();
for (var m : stickerPackOperationMessages) {
if (!m.getPackId().isPresent()) {
continue;
}
final var stickerPackId = StickerPackId.deserialize(m.getPackId().get());
final var installed = !m.getType().isPresent()
|| m.getType().get() == StickerPackOperationMessage.Type.INSTALL;
var sticker = account.getStickerStore().getSticker(stickerPackId);
if (m.getPackKey().isPresent()) {
if (sticker == null) {
sticker = new Sticker(stickerPackId, m.getPackKey().get());
}
if (installed) {
enqueueJob(new RetrieveStickerPackJob(stickerPackId, m.getPackKey().get()));
}
}
if (sticker != null) {
sticker.setInstalled(installed);
account.getStickerStore().updateSticker(sticker);
}
}
}
if (syncMessage.getFetchType().isPresent()) {
switch (syncMessage.getFetchType().get()) {
case LOCAL_PROFILE:
actions.add(new RetrieveProfileAction(account.getSelfRecipientId()));
case STORAGE_MANIFEST:
// TODO
}
}
if (syncMessage.getKeys().isPresent()) {
final var keysMessage = syncMessage.getKeys().get();
if (keysMessage.getStorageService().isPresent()) {
final var storageKey = keysMessage.getStorageService().get();
account.setStorageKey(storageKey);
}
}
if (syncMessage.getConfiguration().isPresent()) {
// TODO
}
}
}
return actions;
} }
public File getAttachmentFile(SignalServiceAttachmentRemoteId attachmentId) { public File getAttachmentFile(SignalServiceAttachmentRemoteId attachmentId) {
return attachmentStore.getAttachmentFile(attachmentId); return attachmentHelper.getAttachmentFile(attachmentId);
}
void sendGroups() throws IOException {
syncHelper.sendGroups();
} }
public void sendContacts() throws IOException { public void sendContacts() throws IOException {
syncHelper.sendContacts(); syncHelper.sendContacts();
} }
void sendBlockedList() throws IOException {
syncHelper.sendBlockedList();
}
public List<Pair<RecipientId, Contact>> getContacts() { public List<Pair<RecipientId, Contact>> getContacts() {
return account.getContactStore().getContacts(); return account.getContactStore().getContacts();
} }
@ -1471,7 +991,7 @@ public class Manager implements Closeable {
public String getContactOrProfileName(RecipientIdentifier.Single recipientIdentifier) { public String getContactOrProfileName(RecipientIdentifier.Single recipientIdentifier) {
final var recipientId = resolveRecipient(recipientIdentifier); final var recipientId = resolveRecipient(recipientIdentifier);
final var contact = account.getRecipientStore().getContact(recipientId); final var contact = account.getContactStore().getContact(recipientId);
if (contact != null && !Util.isEmpty(contact.getName())) { if (contact != null && !Util.isEmpty(contact.getName())) {
return contact.getName(); return contact.getName();
} }
@ -1587,7 +1107,7 @@ public class Manager implements Closeable {
} }
} else { } else {
// Retrieve profile to get the current identity key from the server // Retrieve profile to get the current identity key from the server
refreshRecipientProfile(recipientId); profileHelper.refreshRecipientProfile(recipientId);
} }
} }
@ -1660,20 +1180,12 @@ public class Manager implements Closeable {
return account.getRecipientStore().resolveRecipientTrusted(address); return account.getRecipientStore().resolveRecipientTrusted(address);
} }
private void enqueueJob(Job job) {
var context = new Context(account,
dependencies.getAccountManager(),
dependencies.getMessageReceiver(),
stickerPackStore);
job.run(context);
}
@Override @Override
public void close() throws IOException { public void close() throws IOException {
close(true); close(true);
} }
void close(boolean closeAccount) throws IOException { private void close(boolean closeAccount) throws IOException {
executor.shutdown(); executor.shutdown();
dependencies.getSignalWebSocket().disconnect(); dependencies.getSignalWebSocket().disconnect();

View file

@ -0,0 +1,8 @@
package org.asamk.signal.manager.actions;
import org.asamk.signal.manager.jobs.Context;
public interface HandleAction {
void execute(Context context) throws Throwable;
}

View file

@ -0,0 +1,36 @@
package org.asamk.signal.manager.actions;
import org.asamk.signal.manager.jobs.Context;
import org.asamk.signal.manager.storage.recipients.RecipientId;
public class RenewSessionAction implements HandleAction {
private final RecipientId recipientId;
public RenewSessionAction(final RecipientId recipientId) {
this.recipientId = recipientId;
}
@Override
public void execute(Context context) throws Throwable {
context.getAccount().getSessionStore().archiveSessions(recipientId);
if (!recipientId.equals(context.getAccount().getSelfRecipientId())) {
context.getSendHelper().sendNullMessage(recipientId);
}
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final RenewSessionAction that = (RenewSessionAction) o;
return recipientId.equals(that.recipientId);
}
@Override
public int hashCode() {
return recipientId.hashCode();
}
}

View file

@ -0,0 +1,33 @@
package org.asamk.signal.manager.actions;
import org.asamk.signal.manager.jobs.Context;
import org.asamk.signal.manager.storage.recipients.RecipientId;
public class RetrieveProfileAction implements HandleAction {
private final RecipientId recipientId;
public RetrieveProfileAction(final RecipientId recipientId) {
this.recipientId = recipientId;
}
@Override
public void execute(Context context) throws Throwable {
context.getProfileHelper().refreshRecipientProfile(recipientId);
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final RetrieveProfileAction that = (RetrieveProfileAction) o;
return recipientId.equals(that.recipientId);
}
@Override
public int hashCode() {
return recipientId.hashCode();
}
}

View file

@ -0,0 +1,39 @@
package org.asamk.signal.manager.actions;
import org.asamk.signal.manager.groups.GroupIdV1;
import org.asamk.signal.manager.jobs.Context;
import org.asamk.signal.manager.storage.recipients.RecipientId;
public class SendGroupInfoAction implements HandleAction {
private final RecipientId recipientId;
private final GroupIdV1 groupId;
public SendGroupInfoAction(final RecipientId recipientId, final GroupIdV1 groupId) {
this.recipientId = recipientId;
this.groupId = groupId;
}
@Override
public void execute(Context context) throws Throwable {
context.getGroupHelper().sendGroupInfoMessage(groupId, recipientId);
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final var that = (SendGroupInfoAction) o;
if (!recipientId.equals(that.recipientId)) return false;
return groupId.equals(that.groupId);
}
@Override
public int hashCode() {
var result = recipientId.hashCode();
result = 31 * result + groupId.hashCode();
return result;
}
}

View file

@ -0,0 +1,39 @@
package org.asamk.signal.manager.actions;
import org.asamk.signal.manager.groups.GroupIdV1;
import org.asamk.signal.manager.jobs.Context;
import org.asamk.signal.manager.storage.recipients.RecipientId;
public class SendGroupInfoRequestAction implements HandleAction {
private final RecipientId recipientId;
private final GroupIdV1 groupId;
public SendGroupInfoRequestAction(final RecipientId recipientId, final GroupIdV1 groupId) {
this.recipientId = recipientId;
this.groupId = groupId;
}
@Override
public void execute(Context context) throws Throwable {
context.getGroupHelper().sendGroupInfoRequest(groupId, recipientId);
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final var that = (SendGroupInfoRequestAction) o;
if (!recipientId.equals(that.recipientId)) return false;
return groupId.equals(that.groupId);
}
@Override
public int hashCode() {
var result = recipientId.hashCode();
result = 31 * result + groupId.hashCode();
return result;
}
}

View file

@ -0,0 +1,36 @@
package org.asamk.signal.manager.actions;
import org.asamk.signal.manager.jobs.Context;
import org.asamk.signal.manager.storage.recipients.RecipientId;
import java.util.List;
import java.util.Objects;
public class SendReceiptAction implements HandleAction {
private final RecipientId recipientId;
private final long timestamp;
public SendReceiptAction(final RecipientId recipientId, final long timestamp) {
this.recipientId = recipientId;
this.timestamp = timestamp;
}
@Override
public void execute(Context context) throws Throwable {
context.getSendHelper().sendDeliveryReceipt(recipientId, List.of(timestamp));
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final var that = (SendReceiptAction) o;
return timestamp == that.timestamp && recipientId.equals(that.recipientId);
}
@Override
public int hashCode() {
return Objects.hash(recipientId, timestamp);
}
}

View file

@ -0,0 +1,20 @@
package org.asamk.signal.manager.actions;
import org.asamk.signal.manager.jobs.Context;
public class SendSyncBlockedListAction implements HandleAction {
private static final SendSyncBlockedListAction INSTANCE = new SendSyncBlockedListAction();
private SendSyncBlockedListAction() {
}
public static SendSyncBlockedListAction create() {
return INSTANCE;
}
@Override
public void execute(Context context) throws Throwable {
context.getSyncHelper().sendBlockedList();
}
}

View file

@ -0,0 +1,20 @@
package org.asamk.signal.manager.actions;
import org.asamk.signal.manager.jobs.Context;
public class SendSyncContactsAction implements HandleAction {
private static final SendSyncContactsAction INSTANCE = new SendSyncContactsAction();
private SendSyncContactsAction() {
}
public static SendSyncContactsAction create() {
return INSTANCE;
}
@Override
public void execute(Context context) throws Throwable {
context.getSyncHelper().sendContacts();
}
}

View file

@ -0,0 +1,20 @@
package org.asamk.signal.manager.actions;
import org.asamk.signal.manager.jobs.Context;
public class SendSyncGroupsAction implements HandleAction {
private static final SendSyncGroupsAction INSTANCE = new SendSyncGroupsAction();
private SendSyncGroupsAction() {
}
public static SendSyncGroupsAction create() {
return INSTANCE;
}
@Override
public void execute(Context context) throws Throwable {
context.getSyncHelper().sendGroups();
}
}

View file

@ -11,6 +11,7 @@ import org.slf4j.LoggerFactory;
import org.whispersystems.libsignal.InvalidMessageException; import org.whispersystems.libsignal.InvalidMessageException;
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment; import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer; import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer;
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentRemoteId;
import org.whispersystems.signalservice.api.push.exceptions.MissingConfigurationException; import org.whispersystems.signalservice.api.push.exceptions.MissingConfigurationException;
import java.io.File; import java.io.File;
@ -35,6 +36,10 @@ public class AttachmentHelper {
this.attachmentStore = attachmentStore; this.attachmentStore = attachmentStore;
} }
public File getAttachmentFile(SignalServiceAttachmentRemoteId attachmentId) {
return attachmentStore.getAttachmentFile(attachmentId);
}
public List<SignalServiceAttachment> uploadAttachments(final List<String> attachments) throws AttachmentInvalidException, IOException { public List<SignalServiceAttachment> uploadAttachments(final List<String> attachments) throws AttachmentInvalidException, IOException {
var attachmentStreams = AttachmentUtils.getSignalServiceAttachments(attachments); var attachmentStreams = AttachmentUtils.getSignalServiceAttachments(attachments);

View file

@ -0,0 +1,41 @@
package org.asamk.signal.manager.helper;
import org.asamk.signal.manager.storage.SignalAccount;
import org.asamk.signal.manager.storage.recipients.Contact;
import org.asamk.signal.manager.storage.recipients.RecipientId;
public class ContactHelper {
private final SignalAccount account;
public ContactHelper(final SignalAccount account) {
this.account = account;
}
public boolean isContactBlocked(final RecipientId recipientId) {
var sourceContact = account.getContactStore().getContact(recipientId);
return sourceContact != null && sourceContact.isBlocked();
}
public void setContactName(final RecipientId recipientId, final String name) {
var contact = account.getContactStore().getContact(recipientId);
final var builder = contact == null ? Contact.newBuilder() : Contact.newBuilder(contact);
account.getContactStore().storeContact(recipientId, builder.withName(name).build());
}
public void setExpirationTimer(RecipientId recipientId, int messageExpirationTimer) {
var contact = account.getContactStore().getContact(recipientId);
if (contact != null && contact.getMessageExpirationTime() == messageExpirationTimer) {
return;
}
final var builder = contact == null ? Contact.newBuilder() : Contact.newBuilder(contact);
account.getContactStore()
.storeContact(recipientId, builder.withMessageExpirationTime(messageExpirationTimer).build());
}
public void setContactBlocked(RecipientId recipientId, boolean blocked) {
var contact = account.getContactStore().getContact(recipientId);
final var builder = contact == null ? Contact.newBuilder() : Contact.newBuilder(contact);
account.getContactStore().storeContact(recipientId, builder.withBlocked(blocked).build());
}
}

View file

@ -91,6 +91,11 @@ public class GroupHelper {
return getGroup(groupId, false); return getGroup(groupId, false);
} }
public boolean isGroupBlocked(final GroupId groupId) {
var group = getGroup(groupId);
return group != null && group.isBlocked();
}
public void downloadGroupAvatar(GroupIdV1 groupId, SignalServiceAttachment avatar) { public void downloadGroupAvatar(GroupIdV1 groupId, SignalServiceAttachment avatar) {
try { try {
avatarStore.storeGroupAvatar(groupId, avatarStore.storeGroupAvatar(groupId,
@ -300,6 +305,16 @@ public class GroupHelper {
avatarStore.deleteGroupAvatar(groupId); avatarStore.deleteGroupAvatar(groupId);
} }
public void setGroupBlocked(final GroupId groupId, final boolean blocked) throws GroupNotFoundException {
var group = getGroup(groupId);
if (group == null) {
throw new GroupNotFoundException(groupId);
}
group.setBlocked(blocked);
account.getGroupStore().updateGroup(group);
}
public SendGroupMessageResults sendGroupInfoRequest( public SendGroupMessageResults sendGroupInfoRequest(
GroupIdV1 groupId, RecipientId recipientId GroupIdV1 groupId, RecipientId recipientId
) throws IOException { ) throws IOException {

View file

@ -0,0 +1,492 @@
package org.asamk.signal.manager.helper;
import org.asamk.signal.manager.JobExecutor;
import org.asamk.signal.manager.Manager;
import org.asamk.signal.manager.SignalDependencies;
import org.asamk.signal.manager.TrustLevel;
import org.asamk.signal.manager.actions.HandleAction;
import org.asamk.signal.manager.actions.RenewSessionAction;
import org.asamk.signal.manager.actions.RetrieveProfileAction;
import org.asamk.signal.manager.actions.SendGroupInfoAction;
import org.asamk.signal.manager.actions.SendGroupInfoRequestAction;
import org.asamk.signal.manager.actions.SendReceiptAction;
import org.asamk.signal.manager.actions.SendSyncBlockedListAction;
import org.asamk.signal.manager.actions.SendSyncContactsAction;
import org.asamk.signal.manager.actions.SendSyncGroupsAction;
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.jobs.RetrieveStickerPackJob;
import org.asamk.signal.manager.storage.SignalAccount;
import org.asamk.signal.manager.storage.groups.GroupInfoV1;
import org.asamk.signal.manager.storage.recipients.RecipientId;
import org.asamk.signal.manager.storage.recipients.RecipientResolver;
import org.asamk.signal.manager.storage.stickers.Sticker;
import org.asamk.signal.manager.storage.stickers.StickerPackId;
import org.signal.libsignal.metadata.ProtocolInvalidMessageException;
import org.signal.libsignal.metadata.ProtocolUntrustedIdentityException;
import org.signal.zkgroup.InvalidInputException;
import org.signal.zkgroup.profiles.ProfileKey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.libsignal.util.Pair;
import org.whispersystems.signalservice.api.messages.SignalServiceContent;
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
import org.whispersystems.signalservice.api.messages.SignalServiceGroup;
import org.whispersystems.signalservice.api.messages.multidevice.StickerPackOperationMessage;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public final class IncomingMessageHandler {
private final static Logger logger = LoggerFactory.getLogger(IncomingMessageHandler.class);
private final SignalAccount account;
private final SignalDependencies dependencies;
private final RecipientResolver recipientResolver;
private final GroupHelper groupHelper;
private final ContactHelper contactHelper;
private final AttachmentHelper attachmentHelper;
private final SyncHelper syncHelper;
private final JobExecutor jobExecutor;
public IncomingMessageHandler(
final SignalAccount account,
final SignalDependencies dependencies,
final RecipientResolver recipientResolver,
final GroupHelper groupHelper,
final ContactHelper contactHelper,
final AttachmentHelper attachmentHelper,
final SyncHelper syncHelper,
final JobExecutor jobExecutor
) {
this.account = account;
this.dependencies = dependencies;
this.recipientResolver = recipientResolver;
this.groupHelper = groupHelper;
this.contactHelper = contactHelper;
this.attachmentHelper = attachmentHelper;
this.syncHelper = syncHelper;
this.jobExecutor = jobExecutor;
}
public Pair<List<HandleAction>, Exception> handleEnvelope(
final SignalServiceEnvelope envelope,
final boolean ignoreAttachments,
final Manager.ReceiveMessageHandler handler
) {
final var actions = new ArrayList<HandleAction>();
if (envelope.hasSource()) {
// Store uuid if we don't have it already
// address/uuid in envelope is sent by server
account.getRecipientStore().resolveRecipientTrusted(envelope.getSourceAddress());
}
SignalServiceContent content = null;
Exception exception = null;
if (!envelope.isReceipt()) {
try {
content = dependencies.getCipher().decrypt(envelope);
} catch (ProtocolUntrustedIdentityException e) {
final var recipientId = account.getRecipientStore().resolveRecipient(e.getSender());
actions.add(new RetrieveProfileAction(recipientId));
} catch (ProtocolInvalidMessageException e) {
final var sender = account.getRecipientStore().resolveRecipient(e.getSender());
logger.debug("Received invalid message, queuing renew session action.");
actions.add(new RenewSessionAction(sender));
exception = e;
} catch (Exception e) {
exception = e;
}
if (!envelope.hasSource() && content != null) {
// Store uuid if we don't have it already
// address/uuid is validated by unidentified sender certificate
account.getRecipientStore().resolveRecipientTrusted(content.getSender());
}
actions.addAll(handleMessage(envelope, content, ignoreAttachments));
}
if (isMessageBlocked(envelope, content)) {
logger.info("Ignoring a message from blocked user/group: {}", envelope.getTimestamp());
} else if (isNotAllowedToSendToGroup(envelope, content)) {
logger.info("Ignoring a group message from an unauthorized sender (no member or admin): {} {}",
(envelope.hasSource() ? envelope.getSourceAddress() : content.getSender()).getIdentifier(),
envelope.getTimestamp());
} else {
handler.handleMessage(envelope, content, exception);
}
return new Pair<>(actions, exception);
}
public List<HandleAction> handleMessage(
SignalServiceEnvelope envelope, SignalServiceContent content, boolean ignoreAttachments
) {
var actions = new ArrayList<HandleAction>();
if (content != null) {
final RecipientId sender;
if (!envelope.isUnidentifiedSender() && envelope.hasSource()) {
sender = recipientResolver.resolveRecipient(envelope.getSourceAddress());
} else {
sender = recipientResolver.resolveRecipient(content.getSender());
}
if (content.getDataMessage().isPresent()) {
var message = content.getDataMessage().get();
if (content.isNeedsReceipt()) {
actions.add(new SendReceiptAction(sender, message.getTimestamp()));
}
actions.addAll(handleSignalServiceDataMessage(message,
false,
sender,
account.getSelfRecipientId(),
ignoreAttachments));
}
if (content.getSyncMessage().isPresent()) {
account.setMultiDevice(true);
var syncMessage = content.getSyncMessage().get();
if (syncMessage.getSent().isPresent()) {
var message = syncMessage.getSent().get();
final var destination = message.getDestination().orNull();
actions.addAll(handleSignalServiceDataMessage(message.getMessage(),
true,
sender,
destination == null ? null : recipientResolver.resolveRecipient(destination),
ignoreAttachments));
}
if (syncMessage.getRequest().isPresent() && account.isMasterDevice()) {
var rm = syncMessage.getRequest().get();
if (rm.isContactsRequest()) {
actions.add(SendSyncContactsAction.create());
}
if (rm.isGroupsRequest()) {
actions.add(SendSyncGroupsAction.create());
}
if (rm.isBlockedListRequest()) {
actions.add(SendSyncBlockedListAction.create());
}
// TODO Handle rm.isConfigurationRequest(); rm.isKeysRequest();
}
if (syncMessage.getGroups().isPresent()) {
try {
final var groupsMessage = syncMessage.getGroups().get();
attachmentHelper.retrieveAttachment(groupsMessage, syncHelper::handleSyncDeviceGroups);
} catch (Exception e) {
logger.warn("Failed to handle received sync groups, ignoring: {}", e.getMessage());
}
}
if (syncMessage.getBlockedList().isPresent()) {
final var blockedListMessage = syncMessage.getBlockedList().get();
for (var address : blockedListMessage.getAddresses()) {
contactHelper.setContactBlocked(recipientResolver.resolveRecipient(address), true);
}
for (var groupId : blockedListMessage.getGroupIds()
.stream()
.map(GroupId::unknownVersion)
.collect(Collectors.toSet())) {
try {
groupHelper.setGroupBlocked(groupId, true);
} catch (GroupNotFoundException e) {
logger.warn("BlockedListMessage contained groupID that was not found in GroupStore: {}",
groupId.toBase64());
}
}
}
if (syncMessage.getContacts().isPresent()) {
try {
final var contactsMessage = syncMessage.getContacts().get();
attachmentHelper.retrieveAttachment(contactsMessage.getContactsStream(),
syncHelper::handleSyncDeviceContacts);
} catch (Exception e) {
logger.warn("Failed to handle received sync contacts, ignoring: {}", e.getMessage());
}
}
if (syncMessage.getVerified().isPresent()) {
final var verifiedMessage = syncMessage.getVerified().get();
account.getIdentityKeyStore()
.setIdentityTrustLevel(account.getRecipientStore()
.resolveRecipientTrusted(verifiedMessage.getDestination()),
verifiedMessage.getIdentityKey(),
TrustLevel.fromVerifiedState(verifiedMessage.getVerified()));
}
if (syncMessage.getStickerPackOperations().isPresent()) {
final var stickerPackOperationMessages = syncMessage.getStickerPackOperations().get();
for (var m : stickerPackOperationMessages) {
if (!m.getPackId().isPresent()) {
continue;
}
final var stickerPackId = StickerPackId.deserialize(m.getPackId().get());
final var installed = !m.getType().isPresent()
|| m.getType().get() == StickerPackOperationMessage.Type.INSTALL;
var sticker = account.getStickerStore().getSticker(stickerPackId);
if (m.getPackKey().isPresent()) {
if (sticker == null) {
sticker = new Sticker(stickerPackId, m.getPackKey().get());
}
if (installed) {
jobExecutor.enqueueJob(new RetrieveStickerPackJob(stickerPackId, m.getPackKey().get()));
}
}
if (sticker != null) {
sticker.setInstalled(installed);
account.getStickerStore().updateSticker(sticker);
}
}
}
if (syncMessage.getFetchType().isPresent()) {
switch (syncMessage.getFetchType().get()) {
case LOCAL_PROFILE:
actions.add(new RetrieveProfileAction(account.getSelfRecipientId()));
case STORAGE_MANIFEST:
// TODO
}
}
if (syncMessage.getKeys().isPresent()) {
final var keysMessage = syncMessage.getKeys().get();
if (keysMessage.getStorageService().isPresent()) {
final var storageKey = keysMessage.getStorageService().get();
account.setStorageKey(storageKey);
}
}
if (syncMessage.getConfiguration().isPresent()) {
// TODO
}
}
}
return actions;
}
private boolean isMessageBlocked(SignalServiceEnvelope envelope, SignalServiceContent content) {
SignalServiceAddress source;
if (!envelope.isUnidentifiedSender() && envelope.hasSource()) {
source = envelope.getSourceAddress();
} else if (content != null) {
source = content.getSender();
} else {
return false;
}
final var recipientId = recipientResolver.resolveRecipient(source);
if (contactHelper.isContactBlocked(recipientId)) {
return true;
}
if (content != null && content.getDataMessage().isPresent()) {
var message = content.getDataMessage().get();
if (message.getGroupContext().isPresent()) {
var groupId = GroupUtils.getGroupId(message.getGroupContext().get());
return groupHelper.isGroupBlocked(groupId);
}
}
return false;
}
private boolean isNotAllowedToSendToGroup(SignalServiceEnvelope envelope, SignalServiceContent content) {
SignalServiceAddress source;
if (!envelope.isUnidentifiedSender() && envelope.hasSource()) {
source = envelope.getSourceAddress();
} else if (content != null) {
source = content.getSender();
} else {
return false;
}
if (content == null || !content.getDataMessage().isPresent()) {
return false;
}
var message = content.getDataMessage().get();
if (!message.getGroupContext().isPresent()) {
return false;
}
if (message.getGroupContext().get().getGroupV1().isPresent()) {
var groupInfo = message.getGroupContext().get().getGroupV1().get();
if (groupInfo.getType() == SignalServiceGroup.Type.QUIT) {
return false;
}
}
var groupId = GroupUtils.getGroupId(message.getGroupContext().get());
var group = groupHelper.getGroup(groupId);
if (group == null) {
return false;
}
final var recipientId = recipientResolver.resolveRecipient(source);
if (!group.isMember(recipientId)) {
return true;
}
if (group.isAnnouncementGroup() && !group.isAdmin(recipientId)) {
return message.getBody().isPresent()
|| message.getAttachments().isPresent()
|| message.getQuote()
.isPresent()
|| message.getPreviews().isPresent()
|| message.getMentions().isPresent()
|| message.getSticker().isPresent();
}
return false;
}
private List<HandleAction> handleSignalServiceDataMessage(
SignalServiceDataMessage message,
boolean isSync,
RecipientId source,
RecipientId destination,
boolean ignoreAttachments
) {
var actions = new ArrayList<HandleAction>();
if (message.getGroupContext().isPresent()) {
if (message.getGroupContext().get().getGroupV1().isPresent()) {
var groupInfo = message.getGroupContext().get().getGroupV1().get();
var groupId = GroupId.v1(groupInfo.getGroupId());
var group = groupHelper.getGroup(groupId);
if (group == null || group instanceof GroupInfoV1) {
var groupV1 = (GroupInfoV1) group;
switch (groupInfo.getType()) {
case UPDATE: {
if (groupV1 == null) {
groupV1 = new GroupInfoV1(groupId);
}
if (groupInfo.getAvatar().isPresent()) {
var avatar = groupInfo.getAvatar().get();
groupHelper.downloadGroupAvatar(groupV1.getGroupId(), avatar);
}
if (groupInfo.getName().isPresent()) {
groupV1.name = groupInfo.getName().get();
}
if (groupInfo.getMembers().isPresent()) {
groupV1.addMembers(groupInfo.getMembers()
.get()
.stream()
.map(recipientResolver::resolveRecipient)
.collect(Collectors.toSet()));
}
account.getGroupStore().updateGroup(groupV1);
break;
}
case DELIVER:
if (groupV1 == null && !isSync) {
actions.add(new SendGroupInfoRequestAction(source, groupId));
}
break;
case QUIT: {
if (groupV1 != null) {
groupV1.removeMember(source);
account.getGroupStore().updateGroup(groupV1);
}
break;
}
case REQUEST_INFO:
if (groupV1 != null && !isSync) {
actions.add(new SendGroupInfoAction(source, groupV1.getGroupId()));
}
break;
}
} else {
// Received a group v1 message for a v2 group
}
}
if (message.getGroupContext().get().getGroupV2().isPresent()) {
final var groupContext = message.getGroupContext().get().getGroupV2().get();
final var groupMasterKey = groupContext.getMasterKey();
groupHelper.getOrMigrateGroup(groupMasterKey,
groupContext.getRevision(),
groupContext.hasSignedGroupChange() ? groupContext.getSignedGroupChange() : null);
}
}
final var conversationPartnerAddress = isSync ? destination : source;
if (conversationPartnerAddress != null && message.isEndSession()) {
account.getSessionStore().deleteAllSessions(conversationPartnerAddress);
}
if (message.isExpirationUpdate() || message.getBody().isPresent()) {
if (message.getGroupContext().isPresent()) {
if (message.getGroupContext().get().getGroupV1().isPresent()) {
var groupInfo = message.getGroupContext().get().getGroupV1().get();
var group = account.getGroupStore().getOrCreateGroupV1(GroupId.v1(groupInfo.getGroupId()));
if (group != null) {
if (group.messageExpirationTime != message.getExpiresInSeconds()) {
group.messageExpirationTime = message.getExpiresInSeconds();
account.getGroupStore().updateGroup(group);
}
}
} else if (message.getGroupContext().get().getGroupV2().isPresent()) {
// disappearing message timer already stored in the DecryptedGroup
}
} else if (conversationPartnerAddress != null) {
contactHelper.setExpirationTimer(conversationPartnerAddress, message.getExpiresInSeconds());
}
}
if (!ignoreAttachments) {
if (message.getAttachments().isPresent()) {
for (var attachment : message.getAttachments().get()) {
attachmentHelper.downloadAttachment(attachment);
}
}
if (message.getSharedContacts().isPresent()) {
for (var contact : message.getSharedContacts().get()) {
if (contact.getAvatar().isPresent()) {
attachmentHelper.downloadAttachment(contact.getAvatar().get().getAttachment());
}
}
}
if (message.getPreviews().isPresent()) {
final var previews = message.getPreviews().get();
for (var preview : previews) {
if (preview.getImage().isPresent()) {
attachmentHelper.downloadAttachment(preview.getImage().get());
}
}
}
if (message.getQuote().isPresent()) {
final var quote = message.getQuote().get();
for (var quotedAttachment : quote.getAttachments()) {
final var thumbnail = quotedAttachment.getThumbnail();
if (thumbnail != null) {
attachmentHelper.downloadAttachment(thumbnail);
}
}
}
}
if (message.getProfileKey().isPresent() && message.getProfileKey().get().length == 32) {
final ProfileKey profileKey;
try {
profileKey = new ProfileKey(message.getProfileKey().get());
} catch (InvalidInputException e) {
throw new AssertionError(e);
}
if (account.getSelfRecipientId().equals(source)) {
this.account.setProfileKey(profileKey);
}
this.account.getProfileStore().storeProfileKey(source, profileKey);
}
if (message.getSticker().isPresent()) {
final var messageSticker = message.getSticker().get();
final var stickerPackId = StickerPackId.deserialize(messageSticker.getPackId());
var sticker = account.getStickerStore().getSticker(stickerPackId);
if (sticker == null) {
sticker = new Sticker(stickerPackId, messageSticker.getPackKey());
account.getStickerStore().updateSticker(sticker);
}
jobExecutor.enqueueJob(new RetrieveStickerPackJob(stickerPackId, messageSticker.getPackKey()));
}
return actions;
}
}

View file

@ -131,6 +131,16 @@ public class SendHelper {
return result; return result;
} }
public void sendDeliveryReceipt(
RecipientId recipientId, List<Long> messageIds
) throws IOException, UntrustedIdentityException {
var receiptMessage = new SignalServiceReceiptMessage(SignalServiceReceiptMessage.Type.DELIVERY,
messageIds,
System.currentTimeMillis());
sendReceiptMessage(receiptMessage, recipientId);
}
public void sendReceiptMessage( public void sendReceiptMessage(
final SignalServiceReceiptMessage receiptMessage, final RecipientId recipientId final SignalServiceReceiptMessage receiptMessage, final RecipientId recipientId
) throws IOException, UntrustedIdentityException { ) throws IOException, UntrustedIdentityException {

View file

@ -1,27 +1,43 @@
package org.asamk.signal.manager.jobs; package org.asamk.signal.manager.jobs;
import org.asamk.signal.manager.StickerPackStore; import org.asamk.signal.manager.StickerPackStore;
import org.asamk.signal.manager.helper.GroupHelper;
import org.asamk.signal.manager.helper.ProfileHelper;
import org.asamk.signal.manager.helper.SendHelper;
import org.asamk.signal.manager.helper.SyncHelper;
import org.asamk.signal.manager.storage.SignalAccount; import org.asamk.signal.manager.storage.SignalAccount;
import org.whispersystems.signalservice.api.SignalServiceAccountManager; import org.whispersystems.signalservice.api.SignalServiceAccountManager;
import org.whispersystems.signalservice.api.SignalServiceMessageReceiver; import org.whispersystems.signalservice.api.SignalServiceMessageReceiver;
public class Context { public class Context {
private SignalAccount account; private final SignalAccount account;
private SignalServiceAccountManager accountManager; private final SignalServiceAccountManager accountManager;
private SignalServiceMessageReceiver messageReceiver; private final SignalServiceMessageReceiver messageReceiver;
private StickerPackStore stickerPackStore; private final StickerPackStore stickerPackStore;
private final SendHelper sendHelper;
private final GroupHelper groupHelper;
private final SyncHelper syncHelper;
private final ProfileHelper profileHelper;
public Context( public Context(
final SignalAccount account, final SignalAccount account,
final SignalServiceAccountManager accountManager, final SignalServiceAccountManager accountManager,
final SignalServiceMessageReceiver messageReceiver, final SignalServiceMessageReceiver messageReceiver,
final StickerPackStore stickerPackStore final StickerPackStore stickerPackStore,
final SendHelper sendHelper,
final GroupHelper groupHelper,
final SyncHelper syncHelper,
final ProfileHelper profileHelper
) { ) {
this.account = account; this.account = account;
this.accountManager = accountManager; this.accountManager = accountManager;
this.messageReceiver = messageReceiver; this.messageReceiver = messageReceiver;
this.stickerPackStore = stickerPackStore; this.stickerPackStore = stickerPackStore;
this.sendHelper = sendHelper;
this.groupHelper = groupHelper;
this.syncHelper = syncHelper;
this.profileHelper = profileHelper;
} }
public SignalAccount getAccount() { public SignalAccount getAccount() {
@ -39,4 +55,20 @@ public class Context {
public StickerPackStore getStickerPackStore() { public StickerPackStore getStickerPackStore() {
return stickerPackStore; return stickerPackStore;
} }
public SendHelper getSendHelper() {
return sendHelper;
}
public GroupHelper getGroupHelper() {
return groupHelper;
}
public SyncHelper getSyncHelper() {
return syncHelper;
}
public ProfileHelper getProfileHelper() {
return profileHelper;
}
} }

View file

@ -5,6 +5,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.UnexpectedErrorException;
import org.asamk.signal.commands.exceptions.UserErrorException; import org.asamk.signal.commands.exceptions.UserErrorException;
import org.asamk.signal.manager.Manager; import org.asamk.signal.manager.Manager;
import org.asamk.signal.manager.NotMasterDeviceException; import org.asamk.signal.manager.NotMasterDeviceException;
@ -13,6 +14,8 @@ import org.asamk.signal.util.CommandUtil;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.io.IOException;
public class BlockCommand implements JsonRpcLocalCommand { public class BlockCommand implements JsonRpcLocalCommand {
private final static Logger logger = LoggerFactory.getLogger(BlockCommand.class); private final static Logger logger = LoggerFactory.getLogger(BlockCommand.class);
@ -39,6 +42,8 @@ public class BlockCommand implements JsonRpcLocalCommand {
m.setContactBlocked(contact, true); m.setContactBlocked(contact, true);
} catch (NotMasterDeviceException e) { } catch (NotMasterDeviceException e) {
throw new UserErrorException("This command doesn't work on linked devices."); throw new UserErrorException("This command doesn't work on linked devices.");
} catch (IOException e) {
throw new UnexpectedErrorException("Failed to sync block to linked devices: " + e.getMessage());
} }
} }
@ -49,6 +54,8 @@ public class BlockCommand implements JsonRpcLocalCommand {
m.setGroupBlocked(groupId, true); m.setGroupBlocked(groupId, true);
} catch (GroupNotFoundException e) { } catch (GroupNotFoundException e) {
logger.warn("Group not found {}: {}", groupId.toBase64(), e.getMessage()); logger.warn("Group not found {}: {}", groupId.toBase64(), e.getMessage());
} catch (IOException e) {
throw new UnexpectedErrorException("Failed to sync block to linked devices: " + e.getMessage());
} }
} }
} }

View file

@ -5,6 +5,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.UnexpectedErrorException;
import org.asamk.signal.commands.exceptions.UserErrorException; import org.asamk.signal.commands.exceptions.UserErrorException;
import org.asamk.signal.manager.Manager; import org.asamk.signal.manager.Manager;
import org.asamk.signal.manager.NotMasterDeviceException; import org.asamk.signal.manager.NotMasterDeviceException;
@ -13,6 +14,8 @@ import org.asamk.signal.util.CommandUtil;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.io.IOException;
public class UnblockCommand implements JsonRpcLocalCommand { public class UnblockCommand implements JsonRpcLocalCommand {
private final static Logger logger = LoggerFactory.getLogger(UnblockCommand.class); private final static Logger logger = LoggerFactory.getLogger(UnblockCommand.class);
@ -38,6 +41,8 @@ public class UnblockCommand implements JsonRpcLocalCommand {
m.setContactBlocked(contactNumber, false); m.setContactBlocked(contactNumber, false);
} catch (NotMasterDeviceException e) { } catch (NotMasterDeviceException e) {
throw new UserErrorException("This command doesn't work on linked devices."); throw new UserErrorException("This command doesn't work on linked devices.");
} catch (IOException e) {
throw new UnexpectedErrorException("Failed to sync unblock to linked devices: " + e.getMessage());
} }
} }
@ -47,6 +52,8 @@ public class UnblockCommand implements JsonRpcLocalCommand {
m.setGroupBlocked(groupId, false); m.setGroupBlocked(groupId, false);
} catch (GroupNotFoundException e) { } catch (GroupNotFoundException e) {
logger.warn("Unknown group id: {}", groupId); logger.warn("Unknown group id: {}", groupId);
} catch (IOException e) {
throw new UnexpectedErrorException("Failed to sync unblock to linked devices: " + e.getMessage());
} }
} }
} }

View file

@ -253,6 +253,8 @@ public class DbusSignalImpl implements Signal {
m.setContactBlocked(getSingleRecipientIdentifier(number, m.getUsername()), blocked); m.setContactBlocked(getSingleRecipientIdentifier(number, m.getUsername()), blocked);
} catch (NotMasterDeviceException e) { } catch (NotMasterDeviceException e) {
throw new Error.Failure("This command doesn't work on linked devices."); throw new Error.Failure("This command doesn't work on linked devices.");
} catch (IOException e) {
throw new Error.Failure(e.getMessage());
} }
} }
@ -262,6 +264,8 @@ public class DbusSignalImpl implements Signal {
m.setGroupBlocked(getGroupId(groupId), blocked); m.setGroupBlocked(getGroupId(groupId), blocked);
} catch (GroupNotFoundException e) { } catch (GroupNotFoundException e) {
throw new Error.GroupNotFound(e.getMessage()); throw new Error.GroupNotFound(e.getMessage());
} catch (IOException e) {
throw new Error.Failure(e.getMessage());
} }
} }