Remove libsignal-service from manager lib API

This commit is contained in:
AsamK 2021-11-06 13:29:26 +01:00
parent 9075cc1a30
commit 2ab42ca547
31 changed files with 269 additions and 96 deletions

View file

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

View file

@ -67,6 +67,14 @@ public interface Manager extends Closeable {
return new ManagerImpl(account, pathConfig, serviceEnvironmentConfig, userAgent);
}
static void initLogger() {
LibSignalLogger.initLogger();
}
static boolean isValidNumber(final String e164Number, final String countryCode) {
return PhoneNumberFormatter.isValidNumber(e164Number, countryCode);
}
static List<String> getAllLocalNumbers(File settingsPath) {
var pathConfig = PathConfig.createDefault(settingsPath);
final var dataPath = pathConfig.dataPath();

View file

@ -26,6 +26,7 @@ import org.asamk.signal.manager.api.Message;
import org.asamk.signal.manager.api.Pair;
import org.asamk.signal.manager.api.RecipientIdentifier;
import org.asamk.signal.manager.api.SendGroupMessageResults;
import org.asamk.signal.manager.api.SendMessageResult;
import org.asamk.signal.manager.api.SendMessageResults;
import org.asamk.signal.manager.api.TypingAction;
import org.asamk.signal.manager.api.UpdateGroup;
@ -69,7 +70,6 @@ import org.whispersystems.libsignal.InvalidKeyException;
import org.whispersystems.libsignal.ecc.ECPublicKey;
import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.SignalSessionLock;
import org.whispersystems.signalservice.api.messages.SendMessageResult;
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
import org.whispersystems.signalservice.api.messages.SignalServiceReceiptMessage;
@ -581,13 +581,24 @@ public class ManagerImpl implements Manager {
if (recipient instanceof RecipientIdentifier.Single single) {
final var recipientId = resolveRecipient(single);
final var result = sendHelper.sendMessage(messageBuilder, recipientId);
results.put(recipient, List.of(result));
results.put(recipient,
List.of(SendMessageResult.from(result,
account.getRecipientStore(),
account.getRecipientStore()::resolveRecipientAddress)));
} else if (recipient instanceof RecipientIdentifier.NoteToSelf) {
final var result = sendHelper.sendSelfMessage(messageBuilder);
results.put(recipient, List.of(result));
results.put(recipient,
List.of(SendMessageResult.from(result,
account.getRecipientStore(),
account.getRecipientStore()::resolveRecipientAddress)));
} else if (recipient instanceof RecipientIdentifier.Group group) {
final var result = sendHelper.sendAsGroupMessage(messageBuilder, group.groupId());
results.put(recipient, result);
results.put(recipient,
result.stream()
.map(sendMessageResult -> SendMessageResult.from(sendMessageResult,
account.getRecipientStore(),
account.getRecipientStore()::resolveRecipientAddress))
.collect(Collectors.toList()));
}
}
return new SendMessageResults(timestamp, results);
@ -1292,7 +1303,8 @@ public class ManagerImpl implements Manager {
}
private void handleIdentityFailure(
final RecipientId recipientId, final SendMessageResult.IdentityFailure identityFailure
final RecipientId recipientId,
final org.whispersystems.signalservice.api.messages.SendMessageResult.IdentityFailure identityFailure
) {
this.identityHelper.handleIdentityFailure(recipientId, identityFailure);
}

View file

@ -16,6 +16,9 @@
*/
package org.asamk.signal.manager;
import org.asamk.signal.manager.api.CaptchaRequiredException;
import org.asamk.signal.manager.api.IncorrectPinException;
import org.asamk.signal.manager.api.PinLockedException;
import org.asamk.signal.manager.config.ServiceConfig;
import org.asamk.signal.manager.config.ServiceEnvironment;
import org.asamk.signal.manager.config.ServiceEnvironmentConfig;
@ -27,6 +30,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.libsignal.util.KeyHelper;
import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.KbsPinData;
import org.whispersystems.signalservice.api.KeyBackupServicePinException;
import org.whispersystems.signalservice.api.KeyBackupSystemNoDataException;
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
@ -116,7 +120,7 @@ public class RegistrationManager implements Closeable {
return new RegistrationManager(account, pathConfig, serviceConfiguration, userAgent);
}
public void register(boolean voiceVerification, String captcha) throws IOException {
public void register(boolean voiceVerification, String captcha) throws IOException, CaptchaRequiredException {
final ServiceResponse<RequestVerificationCodeResponse> response;
if (voiceVerification) {
response = accountManager.requestVoiceVerificationCode(getDefaultLocale(),
@ -129,7 +133,11 @@ public class RegistrationManager implements Closeable {
Optional.absent(),
Optional.absent());
}
handleResponseException(response);
try {
handleResponseException(response);
} catch (org.whispersystems.signalservice.api.push.exceptions.CaptchaRequiredException e) {
throw new CaptchaRequiredException(e.getMessage(), e);
}
}
private Locale getDefaultLocale() {
@ -146,7 +154,7 @@ public class RegistrationManager implements Closeable {
public Manager verifyAccount(
String verificationCode, String pin
) throws IOException, LockedException, KeyBackupSystemNoDataException, KeyBackupServicePinException {
) throws IOException, PinLockedException, IncorrectPinException {
verificationCode = verificationCode.replace("-", "");
VerifyAccountResponse response;
MasterKey masterKey;
@ -157,10 +165,17 @@ public class RegistrationManager implements Closeable {
pin = null;
} catch (LockedException e) {
if (pin == null) {
throw e;
throw new PinLockedException(e.getTimeRemaining());
}
var registrationLockData = pinHelper.getRegistrationLockData(pin, e);
KbsPinData registrationLockData;
try {
registrationLockData = pinHelper.getRegistrationLockData(pin, e);
} catch (KeyBackupSystemNoDataException ex) {
throw new IOException(e);
} catch (KeyBackupServicePinException ex) {
throw new IncorrectPinException(ex.getTriesRemaining());
}
if (registrationLockData == null) {
throw e;
}

View file

@ -0,0 +1,12 @@
package org.asamk.signal.manager.api;
public class CaptchaRequiredException extends Exception {
public CaptchaRequiredException(final String message) {
super(message);
}
public CaptchaRequiredException(final String message, final Throwable cause) {
super(message, cause);
}
}

View file

@ -0,0 +1,14 @@
package org.asamk.signal.manager.api;
public class IncorrectPinException extends Exception {
private final int triesRemaining;
public IncorrectPinException(int triesRemaining) {
this.triesRemaining = triesRemaining;
}
public int getTriesRemaining() {
return triesRemaining;
}
}

View file

@ -0,0 +1,8 @@
package org.asamk.signal.manager.api;
public class InvalidNumberException extends Exception {
InvalidNumberException(String message, Throwable e) {
super(message, e);
}
}

View file

@ -48,7 +48,7 @@ public record MessageEnvelope(
Optional<Call> call
) {
public static final record Receipt(long when, Type type, List<Long> timestamps) {
public record Receipt(long when, Type type, List<Long> timestamps) {
static Receipt from(final SignalServiceReceiptMessage receiptMessage) {
return new Receipt(receiptMessage.getWhen(),
@ -73,7 +73,7 @@ public record MessageEnvelope(
}
}
public static final record Typing(long timestamp, Type type, Optional<GroupId> groupId) {
public record Typing(long timestamp, Type type, Optional<GroupId> groupId) {
public static Typing from(final SignalServiceTypingMessage typingMessage) {
return new Typing(typingMessage.getTimestamp(),
@ -87,7 +87,7 @@ public record MessageEnvelope(
}
}
public static final record Data(
public record Data(
long timestamp,
Optional<GroupContext> groupContext,
Optional<GroupCallUpdate> groupCallUpdate,
@ -204,11 +204,15 @@ public record MessageEnvelope(
return new Quote(quote.getId(),
addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(quote.getAuthor())),
Optional.ofNullable(quote.getText()),
quote.getMentions()
.stream()
.map(m -> Mention.from(m, recipientResolver, addressResolver))
.collect(Collectors.toList()),
quote.getAttachments().stream().map(Attachment::from).collect(Collectors.toList()));
quote.getMentions() == null
? List.of()
: quote.getMentions()
.stream()
.map(m -> Mention.from(m, recipientResolver, addressResolver))
.collect(Collectors.toList()),
quote.getAttachments() == null
? List.of()
: quote.getAttachments().stream().map(Attachment::from).collect(Collectors.toList()));
}
}
@ -455,7 +459,7 @@ public record MessageEnvelope(
}
}
public static final record Sync(
public record Sync(
Optional<Sent> sent,
Optional<Blocked> blocked,
List<Read> read,
@ -631,7 +635,7 @@ public record MessageEnvelope(
}
}
public static final record Call(
public record Call(
Optional<Integer> destinationDeviceId,
Optional<GroupId> groupId,
Optional<Long> timestamp,

View file

@ -0,0 +1,14 @@
package org.asamk.signal.manager.api;
public class PinLockedException extends Exception {
private final long timeRemaining;
public PinLockedException(long timeRemaining) {
this.timeRemaining = timeRemaining;
}
public long getTimeRemaining() {
return timeRemaining;
}
}

View file

@ -0,0 +1,44 @@
package org.asamk.signal.manager.api;
import java.util.Set;
import java.util.stream.Collectors;
/**
* Thrown when rate-limited by the server and proof of humanity is required to continue messaging.
*/
public class ProofRequiredException extends Exception {
private final String token;
private final Set<Option> options;
private final long retryAfterSeconds;
public ProofRequiredException(org.whispersystems.signalservice.api.push.exceptions.ProofRequiredException e) {
this.token = e.getToken();
this.options = e.getOptions().stream().map(Option::from).collect(Collectors.toSet());
this.retryAfterSeconds = e.getRetryAfterSeconds();
}
public String getToken() {
return token;
}
public Set<Option> getOptions() {
return options;
}
public long getRetryAfterSeconds() {
return retryAfterSeconds;
}
public enum Option {
RECAPTCHA,
PUSH_CHALLENGE;
static Option from(org.whispersystems.signalservice.api.push.exceptions.ProofRequiredException.Option option) {
return switch (option) {
case RECAPTCHA -> RECAPTCHA;
case PUSH_CHALLENGE -> PUSH_CHALLENGE;
};
}
}
}

View file

@ -2,8 +2,6 @@ package org.asamk.signal.manager.api;
import org.asamk.signal.manager.groups.GroupId;
import org.asamk.signal.manager.storage.recipients.RecipientAddress;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.api.util.InvalidNumberException;
import org.whispersystems.signalservice.api.util.PhoneNumberFormatter;
import org.whispersystems.signalservice.api.util.UuidUtil;
@ -19,13 +17,13 @@ public sealed interface RecipientIdentifier {
sealed interface Single extends RecipientIdentifier {
static Single fromString(String identifier, String localNumber) throws InvalidNumberException {
return UuidUtil.isUuid(identifier)
? new Uuid(UUID.fromString(identifier))
: new Number(PhoneNumberFormatter.formatNumber(identifier, localNumber));
}
static Single fromAddress(SignalServiceAddress address) {
return new Uuid(address.getUuid());
try {
return UuidUtil.isUuid(identifier)
? new Uuid(UUID.fromString(identifier))
: new Number(PhoneNumberFormatter.formatNumber(identifier, localNumber));
} catch (org.whispersystems.signalservice.api.util.InvalidNumberException e) {
throw new InvalidNumberException(e.getMessage(), e);
}
}
static Single fromAddress(RecipientAddress address) {

View file

@ -1,7 +1,5 @@
package org.asamk.signal.manager.api;
import org.whispersystems.signalservice.api.messages.SendMessageResult;
import java.util.List;
public record SendGroupMessageResults(long timestamp, List<SendMessageResult> results) {}

View file

@ -0,0 +1,56 @@
package org.asamk.signal.manager.api;
import org.asamk.signal.manager.helper.RecipientAddressResolver;
import org.asamk.signal.manager.storage.recipients.RecipientAddress;
import org.asamk.signal.manager.storage.recipients.RecipientResolver;
import org.whispersystems.libsignal.IdentityKey;
public record SendMessageResult(
RecipientAddress address,
boolean isSuccess,
boolean isNetworkFailure,
boolean isUnregisteredFailure,
boolean isIdentityFailure,
ProofRequiredException proofRequiredFailure
) {
public static SendMessageResult success(RecipientAddress address) {
return new SendMessageResult(address, true, false, false, false, null);
}
public static SendMessageResult networkFailure(RecipientAddress address) {
return new SendMessageResult(address, false, true, false, false, null);
}
public static SendMessageResult unregisteredFailure(RecipientAddress address) {
return new SendMessageResult(address, false, false, true, false, null);
}
public static SendMessageResult identityFailure(RecipientAddress address, IdentityKey identityKey) {
return new SendMessageResult(address, false, false, false, true, null);
}
public static SendMessageResult proofRequiredFailure(
RecipientAddress address, ProofRequiredException proofRequiredException
) {
return new SendMessageResult(address, false, true, false, false, proofRequiredException);
}
public static SendMessageResult from(
final org.whispersystems.signalservice.api.messages.SendMessageResult sendMessageResult,
RecipientResolver recipientResolver,
RecipientAddressResolver addressResolver
) {
return new SendMessageResult(addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(
sendMessageResult.getAddress())),
sendMessageResult.isSuccess(),
sendMessageResult.isNetworkFailure(),
sendMessageResult.isUnregisteredFailure(),
sendMessageResult.getIdentityFailure() != null,
sendMessageResult.getProofRequiredFailure() == null
? null
: new ProofRequiredException(sendMessageResult.getProofRequiredFailure()));
}
public record IdentityFailure(IdentityKey identityKey) {}
}

View file

@ -1,7 +1,5 @@
package org.asamk.signal.manager.api;
import org.whispersystems.signalservice.api.messages.SendMessageResult;
import java.util.List;
import java.util.Map;

View file

@ -60,6 +60,10 @@ public class ServiceConfig {
}
}
public static boolean isZkgroupAvailable() {
return ServiceConfig.getCapabilities().isGv2();
}
public static boolean isSignalClientAvailable() {
try {
org.signal.client.internal.Native.DeviceTransfer_GeneratePrivateKey();

View file

@ -6,6 +6,7 @@ import org.asamk.signal.manager.SignalDependencies;
import org.asamk.signal.manager.api.InactiveGroupLinkException;
import org.asamk.signal.manager.api.Pair;
import org.asamk.signal.manager.api.SendGroupMessageResults;
import org.asamk.signal.manager.api.SendMessageResult;
import org.asamk.signal.manager.config.ServiceConfig;
import org.asamk.signal.manager.groups.GroupId;
import org.asamk.signal.manager.groups.GroupIdV1;
@ -676,6 +677,11 @@ public class GroupHelper {
final var timestamp = System.currentTimeMillis();
messageBuilder.withTimestamp(timestamp);
final var results = sendHelper.sendGroupMessage(messageBuilder.build(), members);
return new SendGroupMessageResults(timestamp, results);
return new SendGroupMessageResults(timestamp,
results.stream()
.map(sendMessageResult -> SendMessageResult.from(sendMessageResult,
recipientResolver,
account.getRecipientStore()::resolveRecipientAddress))
.collect(Collectors.toList()));
}
}

View file

@ -1,13 +1,14 @@
package org.asamk.signal.manager.storage.recipients;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.api.util.UuidUtil;
import java.util.Optional;
import java.util.UUID;
public class RecipientAddress {
public static final UUID UNKNOWN_UUID = new UUID(0, 0);
private final Optional<UUID> uuid;
private final Optional<String> e164;
@ -18,7 +19,7 @@ public class RecipientAddress {
* @param e164 The phone number of the user, if available.
*/
public RecipientAddress(Optional<UUID> uuid, Optional<String> e164) {
uuid = uuid.isPresent() && uuid.get().equals(UuidUtil.UNKNOWN_UUID) ? Optional.empty() : uuid;
uuid = uuid.isPresent() && uuid.get().equals(UNKNOWN_UUID) ? Optional.empty() : uuid;
if (uuid.isEmpty() && e164.isEmpty()) {
throw new AssertionError("Must have either a UUID or E164 number!");
}
@ -74,7 +75,7 @@ public class RecipientAddress {
}
public SignalServiceAddress toSignalServiceAddress() {
return new SignalServiceAddress(uuid.orElse(UuidUtil.UNKNOWN_UUID),
return new SignalServiceAddress(uuid.orElse(UNKNOWN_UUID),
org.whispersystems.libsignal.util.guava.Optional.fromNullable(e164.orElse(null)));
}