Make send behavior more deterministic if there are unregistered recipients

Fixes #803
This commit is contained in:
AsamK 2021-11-14 14:42:17 +01:00
parent fa5c09d23b
commit 382d8d22d0
10 changed files with 192 additions and 136 deletions

View file

@ -14,7 +14,7 @@ repositories {
} }
dependencies { dependencies {
implementation("com.github.turasa:signal-service-java:2.15.3_unofficial_32") implementation("com.github.turasa:signal-service-java:2.15.3_unofficial_33")
api("com.fasterxml.jackson.core", "jackson-databind", "2.13.0") api("com.fasterxml.jackson.core", "jackson-databind", "2.13.0")
implementation("com.google.protobuf:protobuf-javalite:3.11.4") implementation("com.google.protobuf:protobuf-javalite:3.11.4")
implementation("org.bouncycastle:bcprov-jdk15on:1.69") implementation("org.bouncycastle:bcprov-jdk15on:1.69")

View file

@ -144,17 +144,17 @@ public interface Manager extends Closeable {
GroupInviteLinkUrl inviteLinkUrl GroupInviteLinkUrl inviteLinkUrl
) throws IOException, InactiveGroupLinkException; ) throws IOException, InactiveGroupLinkException;
void sendTypingMessage( SendMessageResults sendTypingMessage(
TypingAction action, Set<RecipientIdentifier> recipients TypingAction action, Set<RecipientIdentifier> recipients
) throws IOException, UntrustedIdentityException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException; ) throws IOException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException;
void sendReadReceipt( SendMessageResults sendReadReceipt(
RecipientIdentifier.Single sender, List<Long> messageIds RecipientIdentifier.Single sender, List<Long> messageIds
) throws IOException, UntrustedIdentityException; ) throws IOException;
void sendViewedReceipt( SendMessageResults sendViewedReceipt(
RecipientIdentifier.Single sender, List<Long> messageIds RecipientIdentifier.Single sender, List<Long> messageIds
) throws IOException, UntrustedIdentityException; ) throws IOException;
SendMessageResults sendMessage( SendMessageResults sendMessage(
Message message, Set<RecipientIdentifier> recipients Message message, Set<RecipientIdentifier> recipients

View file

@ -620,50 +620,74 @@ public class ManagerImpl implements Manager {
return new SendMessageResults(timestamp, results); return new SendMessageResults(timestamp, results);
} }
private void sendTypingMessage( private SendMessageResults sendTypingMessage(
SignalServiceTypingMessage.Action action, Set<RecipientIdentifier> recipients SignalServiceTypingMessage.Action action, Set<RecipientIdentifier> recipients
) throws IOException, UntrustedIdentityException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException { ) throws IOException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException {
var results = new HashMap<RecipientIdentifier, List<SendMessageResult>>();
final var timestamp = System.currentTimeMillis(); final var timestamp = System.currentTimeMillis();
for (var recipient : recipients) { for (var recipient : recipients) {
if (recipient instanceof RecipientIdentifier.Single) { if (recipient instanceof RecipientIdentifier.Single) {
final var message = new SignalServiceTypingMessage(action, timestamp, Optional.absent()); final var message = new SignalServiceTypingMessage(action, timestamp, Optional.absent());
final var recipientId = resolveRecipient((RecipientIdentifier.Single) recipient); final var recipientId = resolveRecipient((RecipientIdentifier.Single) recipient);
sendHelper.sendTypingMessage(message, recipientId); final var result = sendHelper.sendTypingMessage(message, recipientId);
results.put(recipient,
List.of(SendMessageResult.from(result,
account.getRecipientStore(),
account.getRecipientStore()::resolveRecipientAddress)));
} else if (recipient instanceof RecipientIdentifier.Group) { } else if (recipient instanceof RecipientIdentifier.Group) {
final var groupId = ((RecipientIdentifier.Group) recipient).groupId(); final var groupId = ((RecipientIdentifier.Group) recipient).groupId();
final var message = new SignalServiceTypingMessage(action, timestamp, Optional.of(groupId.serialize())); final var message = new SignalServiceTypingMessage(action, timestamp, Optional.of(groupId.serialize()));
sendHelper.sendGroupTypingMessage(message, groupId); final var result = sendHelper.sendGroupTypingMessage(message, groupId);
results.put(recipient,
result.stream()
.map(r -> SendMessageResult.from(r,
account.getRecipientStore(),
account.getRecipientStore()::resolveRecipientAddress))
.collect(Collectors.toList()));
} }
} }
return new SendMessageResults(timestamp, results);
} }
@Override @Override
public void sendTypingMessage( public SendMessageResults sendTypingMessage(
TypingAction action, Set<RecipientIdentifier> recipients TypingAction action, Set<RecipientIdentifier> recipients
) throws IOException, UntrustedIdentityException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException { ) throws IOException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException {
sendTypingMessage(action.toSignalService(), recipients); return sendTypingMessage(action.toSignalService(), recipients);
} }
@Override @Override
public void sendReadReceipt( public SendMessageResults sendReadReceipt(
RecipientIdentifier.Single sender, List<Long> messageIds RecipientIdentifier.Single sender, List<Long> messageIds
) throws IOException, UntrustedIdentityException { ) throws IOException {
final var timestamp = System.currentTimeMillis();
var receiptMessage = new SignalServiceReceiptMessage(SignalServiceReceiptMessage.Type.READ, var receiptMessage = new SignalServiceReceiptMessage(SignalServiceReceiptMessage.Type.READ,
messageIds, messageIds,
System.currentTimeMillis()); timestamp);
sendHelper.sendReceiptMessage(receiptMessage, resolveRecipient(sender)); final var result = sendHelper.sendReceiptMessage(receiptMessage, resolveRecipient(sender));
return new SendMessageResults(timestamp,
Map.of(sender,
List.of(SendMessageResult.from(result,
account.getRecipientStore(),
account.getRecipientStore()::resolveRecipientAddress))));
} }
@Override @Override
public void sendViewedReceipt( public SendMessageResults sendViewedReceipt(
RecipientIdentifier.Single sender, List<Long> messageIds RecipientIdentifier.Single sender, List<Long> messageIds
) throws IOException, UntrustedIdentityException { ) throws IOException {
final var timestamp = System.currentTimeMillis();
var receiptMessage = new SignalServiceReceiptMessage(SignalServiceReceiptMessage.Type.VIEWED, var receiptMessage = new SignalServiceReceiptMessage(SignalServiceReceiptMessage.Type.VIEWED,
messageIds, messageIds,
System.currentTimeMillis()); timestamp);
sendHelper.sendReceiptMessage(receiptMessage, resolveRecipient(sender)); final var result = sendHelper.sendReceiptMessage(receiptMessage, resolveRecipient(sender));
return new SendMessageResults(timestamp,
Map.of(sender,
List.of(SendMessageResult.from(result,
account.getRecipientStore(),
account.getRecipientStore()::resolveRecipientAddress))));
} }
@Override @Override

View file

@ -5,13 +5,9 @@ import org.asamk.signal.manager.storage.recipients.RecipientAddress;
public class UntrustedIdentityException extends Exception { public class UntrustedIdentityException extends Exception {
private final RecipientAddress sender; private final RecipientAddress sender;
private final Integer senderDevice; private final int senderDevice;
public UntrustedIdentityException(final RecipientAddress sender) { public UntrustedIdentityException(final RecipientAddress sender, final int senderDevice) {
this(sender, null);
}
public UntrustedIdentityException(final RecipientAddress sender, final Integer senderDevice) {
super("Untrusted identity: " + sender.getIdentifier()); super("Untrusted identity: " + sender.getIdentifier());
this.sender = sender; this.sender = sender;
this.senderDevice = senderDevice; this.senderDevice = senderDevice;
@ -21,7 +17,7 @@ public class UntrustedIdentityException extends Exception {
return sender; return sender;
} }
public Integer getSenderDevice() { public int getSenderDevice() {
return senderDevice; return senderDevice;
} }
} }

View file

@ -1,7 +1,6 @@
package org.asamk.signal.manager.helper; package org.asamk.signal.manager.helper;
import org.asamk.signal.manager.SignalDependencies; import org.asamk.signal.manager.SignalDependencies;
import org.asamk.signal.manager.UntrustedIdentityException;
import org.asamk.signal.manager.groups.GroupId; import org.asamk.signal.manager.groups.GroupId;
import org.asamk.signal.manager.groups.GroupNotFoundException; import org.asamk.signal.manager.groups.GroupNotFoundException;
import org.asamk.signal.manager.groups.GroupSendingNotAllowedException; import org.asamk.signal.manager.groups.GroupSendingNotAllowedException;
@ -17,12 +16,14 @@ import org.whispersystems.libsignal.protocol.DecryptionErrorMessage;
import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.SignalServiceMessageSender; import org.whispersystems.signalservice.api.SignalServiceMessageSender;
import org.whispersystems.signalservice.api.crypto.ContentHint; import org.whispersystems.signalservice.api.crypto.ContentHint;
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair;
import org.whispersystems.signalservice.api.messages.SendMessageResult; import org.whispersystems.signalservice.api.messages.SendMessageResult;
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage; import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
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.SentTranscriptMessage; 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.push.SignalServiceAddress;
import org.whispersystems.signalservice.api.push.exceptions.ProofRequiredException; import org.whispersystems.signalservice.api.push.exceptions.ProofRequiredException;
import org.whispersystems.signalservice.api.push.exceptions.RateLimitException; import org.whispersystems.signalservice.api.push.exceptions.RateLimitException;
import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException; import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException;
@ -68,8 +69,8 @@ public class SendHelper {
} }
/** /**
* Send a single message to one or multiple recipients. * Send a single message to one recipient.
* The message is extended with the current expiration timer for each recipient. * The message is extended with the current expiration timer.
*/ */
public SendMessageResult sendMessage( public SendMessageResult sendMessage(
final SignalServiceDataMessage.Builder messageBuilder, final RecipientId recipientId final SignalServiceDataMessage.Builder messageBuilder, final RecipientId recipientId
@ -135,67 +136,46 @@ public class SendHelper {
return result; return result;
} }
public void sendDeliveryReceipt( public SendMessageResult sendDeliveryReceipt(
RecipientId recipientId, List<Long> messageIds RecipientId recipientId, List<Long> messageIds
) throws IOException, UntrustedIdentityException { ) {
var receiptMessage = new SignalServiceReceiptMessage(SignalServiceReceiptMessage.Type.DELIVERY, var receiptMessage = new SignalServiceReceiptMessage(SignalServiceReceiptMessage.Type.DELIVERY,
messageIds, messageIds,
System.currentTimeMillis()); System.currentTimeMillis());
sendReceiptMessage(receiptMessage, recipientId); return sendReceiptMessage(receiptMessage, recipientId);
} }
public void sendReceiptMessage( public SendMessageResult sendReceiptMessage(
final SignalServiceReceiptMessage receiptMessage, final RecipientId recipientId final SignalServiceReceiptMessage receiptMessage, final RecipientId recipientId
) throws IOException, UntrustedIdentityException { ) {
final var messageSender = dependencies.getMessageSender(); return handleSendMessage(recipientId,
final var address = addressResolver.resolveSignalServiceAddress(recipientId); (messageSender, address, unidentifiedAccess) -> messageSender.sendReceipt(address,
try { unidentifiedAccess,
messageSender.sendReceipt(address, unidentifiedAccessHelper.getAccessFor(recipientId), receiptMessage); receiptMessage));
} catch (org.whispersystems.signalservice.api.crypto.UntrustedIdentityException e) {
throw new UntrustedIdentityException(account.getRecipientStore().resolveRecipientAddress(recipientId));
}
} }
public void sendRetryReceipt( public SendMessageResult sendRetryReceipt(
DecryptionErrorMessage errorMessage, RecipientId recipientId, Optional<GroupId> groupId DecryptionErrorMessage errorMessage, RecipientId recipientId, Optional<GroupId> groupId
) throws IOException, UntrustedIdentityException { ) {
var messageSender = dependencies.getMessageSender();
final var address = addressResolver.resolveSignalServiceAddress(recipientId);
logger.debug("Sending retry receipt for {} to {}, device: {}", logger.debug("Sending retry receipt for {} to {}, device: {}",
errorMessage.getTimestamp(), errorMessage.getTimestamp(),
recipientId, recipientId,
errorMessage.getDeviceId()); errorMessage.getDeviceId());
try { return handleSendMessage(recipientId,
messageSender.sendRetryReceipt(address, (messageSender, address, unidentifiedAccess) -> messageSender.sendRetryReceipt(address,
unidentifiedAccessHelper.getAccessFor(recipientId), unidentifiedAccess,
groupId.transform(GroupId::serialize), groupId.transform(GroupId::serialize),
errorMessage); errorMessage));
} catch (org.whispersystems.signalservice.api.crypto.UntrustedIdentityException e) {
throw new UntrustedIdentityException(account.getRecipientStore().resolveRecipientAddress(recipientId));
}
} }
public SendMessageResult sendNullMessage(RecipientId recipientId) throws IOException { public SendMessageResult sendNullMessage(RecipientId recipientId) {
var messageSender = dependencies.getMessageSender(); return handleSendMessage(recipientId, SignalServiceMessageSender::sendNullMessage);
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 (org.whispersystems.signalservice.api.crypto.UntrustedIdentityException e) {
return SendMessageResult.identityFailure(address, e.getIdentityKey());
}
} }
public SendMessageResult sendSelfMessage( public SendMessageResult sendSelfMessage(
SignalServiceDataMessage.Builder messageBuilder SignalServiceDataMessage.Builder messageBuilder
) throws IOException { ) {
final var recipientId = account.getSelfRecipientId(); final var recipientId = account.getSelfRecipientId();
final var contact = account.getContactStore().getContact(recipientId); final var contact = account.getContactStore().getContact(recipientId);
final var expirationTime = contact != null ? contact.getMessageExpirationTime() : 0; final var expirationTime = contact != null ? contact.getMessageExpirationTime() : 0;
@ -205,35 +185,40 @@ public class SendHelper {
return sendSelfMessage(message); return sendSelfMessage(message);
} }
public SendMessageResult sendSyncMessage(SignalServiceSyncMessage message) throws IOException { public SendMessageResult sendSyncMessage(SignalServiceSyncMessage message) {
var messageSender = dependencies.getMessageSender(); var messageSender = dependencies.getMessageSender();
try { try {
return messageSender.sendSyncMessage(message, unidentifiedAccessHelper.getAccessForSync()); return messageSender.sendSyncMessage(message, unidentifiedAccessHelper.getAccessForSync());
} catch (UnregisteredUserException e) {
var address = addressResolver.resolveSignalServiceAddress(account.getSelfRecipientId());
return SendMessageResult.unregisteredFailure(address);
} catch (ProofRequiredException e) {
var address = addressResolver.resolveSignalServiceAddress(account.getSelfRecipientId());
return SendMessageResult.proofRequiredFailure(address, e);
} catch (RateLimitException e) {
var address = addressResolver.resolveSignalServiceAddress(account.getSelfRecipientId());
logger.warn("Sending failed due to rate limiting from the signal server: {}", e.getMessage());
return SendMessageResult.networkFailure(address);
} catch (org.whispersystems.signalservice.api.crypto.UntrustedIdentityException e) { } catch (org.whispersystems.signalservice.api.crypto.UntrustedIdentityException e) {
var address = addressResolver.resolveSignalServiceAddress(account.getSelfRecipientId()); var address = addressResolver.resolveSignalServiceAddress(account.getSelfRecipientId());
return SendMessageResult.identityFailure(address, e.getIdentityKey()); return SendMessageResult.identityFailure(address, e.getIdentityKey());
} catch (IOException e) {
var address = addressResolver.resolveSignalServiceAddress(account.getSelfRecipientId());
logger.warn("Failed to send message due to IO exception: {}", e.getMessage());
return SendMessageResult.networkFailure(address);
} }
} }
public void sendTypingMessage( public SendMessageResult sendTypingMessage(
SignalServiceTypingMessage message, RecipientId recipientId SignalServiceTypingMessage message, RecipientId recipientId
) throws IOException, UntrustedIdentityException { ) {
var messageSender = dependencies.getMessageSender(); return handleSendMessage(recipientId,
final var address = addressResolver.resolveSignalServiceAddress(recipientId); (messageSender, address, unidentifiedAccess) -> messageSender.sendTyping(address,
try { unidentifiedAccess,
try { message));
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);
}
} catch (org.whispersystems.signalservice.api.crypto.UntrustedIdentityException e) {
throw new UntrustedIdentityException(account.getRecipientStore().resolveRecipientAddress(recipientId));
}
} }
public void sendGroupTypingMessage( public List<SendMessageResult> sendGroupTypingMessage(
SignalServiceTypingMessage message, GroupId groupId SignalServiceTypingMessage message, GroupId groupId
) throws IOException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException { ) throws IOException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException {
final var g = getGroupForSending(groupId); final var g = getGroupForSending(groupId);
@ -245,7 +230,10 @@ public class SendHelper {
final var addresses = recipientIdList.stream() final var addresses = recipientIdList.stream()
.map(addressResolver::resolveSignalServiceAddress) .map(addressResolver::resolveSignalServiceAddress)
.collect(Collectors.toList()); .collect(Collectors.toList());
messageSender.sendTyping(addresses, unidentifiedAccessHelper.getAccessFor(recipientIdList), message, null); return messageSender.sendTyping(addresses,
unidentifiedAccessHelper.getAccessFor(recipientIdList),
message,
null);
} }
private GroupInfo getGroupForSending(GroupId groupId) throws GroupNotFoundException, NotAGroupMemberException { private GroupInfo getGroupForSending(GroupId groupId) throws GroupNotFoundException, NotAGroupMemberException {
@ -285,25 +273,29 @@ public class SendHelper {
private SendMessageResult sendMessage( private SendMessageResult sendMessage(
SignalServiceDataMessage message, RecipientId recipientId SignalServiceDataMessage message, RecipientId recipientId
) throws IOException { ) {
return handleSendMessage(recipientId,
(messageSender, address, unidentifiedAccess) -> messageSender.sendDataMessage(address,
unidentifiedAccess,
ContentHint.DEFAULT,
message,
SignalServiceMessageSender.IndividualSendEvents.EMPTY));
}
private SendMessageResult handleSendMessage(RecipientId recipientId, SenderHandler s) {
var messageSender = dependencies.getMessageSender(); var messageSender = dependencies.getMessageSender();
final var address = addressResolver.resolveSignalServiceAddress(recipientId); var address = addressResolver.resolveSignalServiceAddress(recipientId);
try { try {
try { try {
return messageSender.sendDataMessage(address, return s.send(messageSender, address, unidentifiedAccessHelper.getAccessFor(recipientId));
unidentifiedAccessHelper.getAccessFor(recipientId),
ContentHint.DEFAULT,
message,
SignalServiceMessageSender.IndividualSendEvents.EMPTY);
} catch (UnregisteredUserException e) { } catch (UnregisteredUserException e) {
final var newRecipientId = recipientRegistrationRefresher.refreshRecipientRegistration(recipientId); final var newRecipientId = recipientRegistrationRefresher.refreshRecipientRegistration(recipientId);
return messageSender.sendDataMessage(addressResolver.resolveSignalServiceAddress(newRecipientId), address = addressResolver.resolveSignalServiceAddress(newRecipientId);
unidentifiedAccessHelper.getAccessFor(newRecipientId), return s.send(messageSender, address, unidentifiedAccessHelper.getAccessFor(newRecipientId));
ContentHint.DEFAULT,
message,
SignalServiceMessageSender.IndividualSendEvents.EMPTY);
} }
} catch (UnregisteredUserException e) {
return SendMessageResult.unregisteredFailure(address);
} catch (ProofRequiredException e) { } catch (ProofRequiredException e) {
return SendMessageResult.proofRequiredFailure(address, e); return SendMessageResult.proofRequiredFailure(address, e);
} catch (RateLimitException e) { } catch (RateLimitException e) {
@ -311,10 +303,13 @@ public class SendHelper {
return SendMessageResult.networkFailure(address); return SendMessageResult.networkFailure(address);
} catch (org.whispersystems.signalservice.api.crypto.UntrustedIdentityException e) { } catch (org.whispersystems.signalservice.api.crypto.UntrustedIdentityException e) {
return SendMessageResult.identityFailure(address, e.getIdentityKey()); return SendMessageResult.identityFailure(address, e.getIdentityKey());
} catch (IOException e) {
logger.warn("Failed to send message due to IO exception: {}", e.getMessage());
return SendMessageResult.networkFailure(address);
} }
} }
private SendMessageResult sendSelfMessage(SignalServiceDataMessage message) throws IOException { private SendMessageResult sendSelfMessage(SignalServiceDataMessage message) {
var address = account.getSelfAddress(); var address = account.getSelfAddress();
var transcript = new SentTranscriptMessage(Optional.of(address), var transcript = new SentTranscriptMessage(Optional.of(address),
message.getTimestamp(), message.getTimestamp(),
@ -333,4 +328,13 @@ public class SendHelper {
identityFailureHandler.handleIdentityFailure(recipientId, r.getIdentityFailure()); identityFailureHandler.handleIdentityFailure(recipientId, r.getIdentityFailure());
} }
} }
interface SenderHandler {
SendMessageResult send(
SignalServiceMessageSender messageSender,
SignalServiceAddress address,
Optional<UnidentifiedAccessPair> unidentifiedAccess
) throws IOException, UnregisteredUserException, ProofRequiredException, RateLimitException, org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
}
} }

View file

@ -83,7 +83,9 @@ public class SendCommand implements JsonRpcLocalCommand {
} }
try { try {
m.sendEndSessionMessage(singleRecipients); final var results = m.sendEndSessionMessage(singleRecipients);
outputResult(outputWriter, results.timestamp());
ErrorUtils.handleSendMessageResults(results.results());
return; return;
} catch (IOException e) { } catch (IOException e) {
throw new UnexpectedErrorException("Failed to send message: " + e.getMessage() + " (" + e.getClass() throw new UnexpectedErrorException("Failed to send message: " + e.getMessage() + " (" + e.getClass()

View file

@ -3,14 +3,18 @@ package org.asamk.signal.commands;
import net.sourceforge.argparse4j.inf.Namespace; import net.sourceforge.argparse4j.inf.Namespace;
import net.sourceforge.argparse4j.inf.Subparser; import net.sourceforge.argparse4j.inf.Subparser;
import org.asamk.signal.JsonWriter;
import org.asamk.signal.OutputWriter; import org.asamk.signal.OutputWriter;
import org.asamk.signal.PlainTextWriter;
import org.asamk.signal.commands.exceptions.CommandException; import org.asamk.signal.commands.exceptions.CommandException;
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.UntrustedIdentityException; import org.asamk.signal.manager.api.SendMessageResults;
import org.asamk.signal.util.CommandUtil; import org.asamk.signal.util.CommandUtil;
import org.asamk.signal.util.ErrorUtils;
import java.io.IOException; import java.io.IOException;
import java.util.Map;
public class SendReceiptCommand implements JsonRpcLocalCommand { public class SendReceiptCommand implements JsonRpcLocalCommand {
@ -43,16 +47,28 @@ public class SendReceiptCommand implements JsonRpcLocalCommand {
final var type = ns.getString("type"); final var type = ns.getString("type");
try { try {
final SendMessageResults results;
if (type == null || "read".equals(type)) { if (type == null || "read".equals(type)) {
m.sendReadReceipt(recipient, targetTimestamps); results = m.sendReadReceipt(recipient, targetTimestamps);
} else if ("viewed".equals(type)) { } else if ("viewed".equals(type)) {
m.sendViewedReceipt(recipient, targetTimestamps); results = m.sendViewedReceipt(recipient, targetTimestamps);
} else { } else {
throw new UserErrorException("Unknown receipt type: " + type); throw new UserErrorException("Unknown receipt type: " + type);
} }
} catch (IOException | UntrustedIdentityException e) { outputResult(outputWriter, results.timestamp());
ErrorUtils.handleSendMessageResults(results.results());
} catch (IOException e) {
throw new UserErrorException("Failed to send message: " + e.getMessage() + " (" + e.getClass() throw new UserErrorException("Failed to send message: " + e.getMessage() + " (" + e.getClass()
.getSimpleName() + ")"); .getSimpleName() + ")");
} }
} }
private void outputResult(final OutputWriter outputWriter, final long timestamp) {
if (outputWriter instanceof PlainTextWriter writer) {
writer.println("{}", timestamp);
} else {
final var writer = (JsonWriter) outputWriter;
writer.write(Map.of("timestamp", timestamp));
}
}
} }

View file

@ -4,20 +4,23 @@ import net.sourceforge.argparse4j.impl.Arguments;
import net.sourceforge.argparse4j.inf.Namespace; import net.sourceforge.argparse4j.inf.Namespace;
import net.sourceforge.argparse4j.inf.Subparser; import net.sourceforge.argparse4j.inf.Subparser;
import org.asamk.signal.JsonWriter;
import org.asamk.signal.OutputWriter; import org.asamk.signal.OutputWriter;
import org.asamk.signal.PlainTextWriter;
import org.asamk.signal.commands.exceptions.CommandException; import org.asamk.signal.commands.exceptions.CommandException;
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.UntrustedIdentityException;
import org.asamk.signal.manager.api.RecipientIdentifier; import org.asamk.signal.manager.api.RecipientIdentifier;
import org.asamk.signal.manager.api.TypingAction; import org.asamk.signal.manager.api.TypingAction;
import org.asamk.signal.manager.groups.GroupNotFoundException; import org.asamk.signal.manager.groups.GroupNotFoundException;
import org.asamk.signal.manager.groups.GroupSendingNotAllowedException; import org.asamk.signal.manager.groups.GroupSendingNotAllowedException;
import org.asamk.signal.manager.groups.NotAGroupMemberException; import org.asamk.signal.manager.groups.NotAGroupMemberException;
import org.asamk.signal.util.CommandUtil; import org.asamk.signal.util.CommandUtil;
import org.asamk.signal.util.ErrorUtils;
import java.io.IOException; import java.io.IOException;
import java.util.HashSet; import java.util.HashSet;
import java.util.Map;
public class SendTypingCommand implements JsonRpcLocalCommand { public class SendTypingCommand implements JsonRpcLocalCommand {
@ -57,12 +60,23 @@ public class SendTypingCommand implements JsonRpcLocalCommand {
} }
try { try {
m.sendTypingMessage(action, recipientIdentifiers); final var results = m.sendTypingMessage(action, recipientIdentifiers);
} catch (IOException | UntrustedIdentityException e) { outputResult(outputWriter, results.timestamp());
ErrorUtils.handleSendMessageResults(results.results());
} catch (IOException e) {
throw new UserErrorException("Failed to send message: " + e.getMessage() + " (" + e.getClass() throw new UserErrorException("Failed to send message: " + e.getMessage() + " (" + e.getClass()
.getSimpleName() + ")"); .getSimpleName() + ")");
} catch (GroupNotFoundException | NotAGroupMemberException | GroupSendingNotAllowedException e) { } catch (GroupNotFoundException | NotAGroupMemberException | GroupSendingNotAllowedException e) {
throw new UserErrorException("Failed to send to group: " + e.getMessage()); throw new UserErrorException("Failed to send to group: " + e.getMessage());
} }
} }
private void outputResult(final OutputWriter outputWriter, final long timestamp) {
if (outputWriter instanceof PlainTextWriter writer) {
writer.println("{}", timestamp);
} else {
final var writer = (JsonWriter) outputWriter;
writer.write(Map.of("timestamp", timestamp));
}
}
} }

View file

@ -6,7 +6,6 @@ import org.asamk.signal.manager.AttachmentInvalidException;
import org.asamk.signal.manager.Manager; import org.asamk.signal.manager.Manager;
import org.asamk.signal.manager.NotMasterDeviceException; import org.asamk.signal.manager.NotMasterDeviceException;
import org.asamk.signal.manager.StickerPackInvalidException; import org.asamk.signal.manager.StickerPackInvalidException;
import org.asamk.signal.manager.UntrustedIdentityException;
import org.asamk.signal.manager.api.Configuration; import org.asamk.signal.manager.api.Configuration;
import org.asamk.signal.manager.api.Device; import org.asamk.signal.manager.api.Device;
import org.asamk.signal.manager.api.Group; import org.asamk.signal.manager.api.Group;
@ -298,31 +297,34 @@ public class DbusManagerImpl implements Manager {
} }
@Override @Override
public void sendTypingMessage( public SendMessageResults sendTypingMessage(
final TypingAction action, final Set<RecipientIdentifier> recipients final TypingAction action, final Set<RecipientIdentifier> recipients
) throws IOException, UntrustedIdentityException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException { ) throws IOException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException {
for (final var recipient : recipients) { return handleMessage(recipients, numbers -> {
if (recipient instanceof RecipientIdentifier.Single) { numbers.forEach(n -> signal.sendTyping(n, action == TypingAction.STOP));
signal.sendTyping(((RecipientIdentifier.Single) recipient).getIdentifier(), return 0L;
action == TypingAction.STOP); }, () -> {
} else if (recipient instanceof RecipientIdentifier.Group) { signal.sendTyping(signal.getSelfNumber(), action == TypingAction.STOP);
return 0L;
}, groupId -> {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} });
}
} }
@Override @Override
public void sendReadReceipt( public SendMessageResults sendReadReceipt(
final RecipientIdentifier.Single sender, final List<Long> messageIds final RecipientIdentifier.Single sender, final List<Long> messageIds
) throws IOException, UntrustedIdentityException { ) {
signal.sendReadReceipt(sender.getIdentifier(), messageIds); signal.sendReadReceipt(sender.getIdentifier(), messageIds);
return new SendMessageResults(0, Map.of());
} }
@Override @Override
public void sendViewedReceipt( public SendMessageResults sendViewedReceipt(
final RecipientIdentifier.Single sender, final List<Long> messageIds final RecipientIdentifier.Single sender, final List<Long> messageIds
) throws IOException, UntrustedIdentityException { ) {
signal.sendViewedReceipt(sender.getIdentifier(), messageIds); signal.sendViewedReceipt(sender.getIdentifier(), messageIds);
return new SendMessageResults(0, Map.of());
} }
@Override @Override

View file

@ -7,7 +7,6 @@ import org.asamk.signal.manager.AttachmentInvalidException;
import org.asamk.signal.manager.Manager; import org.asamk.signal.manager.Manager;
import org.asamk.signal.manager.NotMasterDeviceException; import org.asamk.signal.manager.NotMasterDeviceException;
import org.asamk.signal.manager.StickerPackInvalidException; import org.asamk.signal.manager.StickerPackInvalidException;
import org.asamk.signal.manager.UntrustedIdentityException;
import org.asamk.signal.manager.api.Identity; import org.asamk.signal.manager.api.Identity;
import org.asamk.signal.manager.api.InactiveGroupLinkException; import org.asamk.signal.manager.api.InactiveGroupLinkException;
import org.asamk.signal.manager.api.InvalidDeviceLinkException; import org.asamk.signal.manager.api.InvalidDeviceLinkException;
@ -305,16 +304,15 @@ public class DbusSignalImpl implements Signal {
try { try {
var recipients = new ArrayList<String>(1); var recipients = new ArrayList<String>(1);
recipients.add(recipient); recipients.add(recipient);
m.sendTypingMessage(stop ? TypingAction.STOP : TypingAction.START, final var results = m.sendTypingMessage(stop ? TypingAction.STOP : TypingAction.START,
getSingleRecipientIdentifiers(recipients, m.getSelfNumber()).stream() getSingleRecipientIdentifiers(recipients, m.getSelfNumber()).stream()
.map(RecipientIdentifier.class::cast) .map(RecipientIdentifier.class::cast)
.collect(Collectors.toSet())); .collect(Collectors.toSet()));
checkSendMessageResults(results.timestamp(), results.results());
} catch (IOException e) { } catch (IOException e) {
throw new Error.Failure(e.getMessage()); throw new Error.Failure(e.getMessage());
} catch (GroupNotFoundException | NotAGroupMemberException | GroupSendingNotAllowedException e) { } catch (GroupNotFoundException | NotAGroupMemberException | GroupSendingNotAllowedException e) {
throw new Error.GroupNotFound(e.getMessage()); throw new Error.GroupNotFound(e.getMessage());
} catch (UntrustedIdentityException e) {
throw new Error.UntrustedIdentity(e.getMessage());
} }
} }
@ -323,11 +321,11 @@ public class DbusSignalImpl implements Signal {
final String recipient, final List<Long> messageIds final String recipient, final List<Long> messageIds
) throws Error.Failure, Error.UntrustedIdentity { ) throws Error.Failure, Error.UntrustedIdentity {
try { try {
m.sendReadReceipt(getSingleRecipientIdentifier(recipient, m.getSelfNumber()), messageIds); final var results = m.sendReadReceipt(getSingleRecipientIdentifier(recipient, m.getSelfNumber()),
messageIds);
checkSendMessageResults(results.timestamp(), results.results());
} catch (IOException e) { } catch (IOException e) {
throw new Error.Failure(e.getMessage()); throw new Error.Failure(e.getMessage());
} catch (UntrustedIdentityException e) {
throw new Error.UntrustedIdentity(e.getMessage());
} }
} }
@ -336,11 +334,11 @@ public class DbusSignalImpl implements Signal {
final String recipient, final List<Long> messageIds final String recipient, final List<Long> messageIds
) throws Error.Failure, Error.UntrustedIdentity { ) throws Error.Failure, Error.UntrustedIdentity {
try { try {
m.sendViewedReceipt(getSingleRecipientIdentifier(recipient, m.getSelfNumber()), messageIds); final var results = m.sendViewedReceipt(getSingleRecipientIdentifier(recipient, m.getSelfNumber()),
messageIds);
checkSendMessageResults(results.timestamp(), results.results());
} catch (IOException e) { } catch (IOException e) {
throw new Error.Failure(e.getMessage()); throw new Error.Failure(e.getMessage());
} catch (UntrustedIdentityException e) {
throw new Error.UntrustedIdentity(e.getMessage());
} }
} }