import org.asamk.Signal;
import org.asamk.signal.AttachmentInvalidException;
import org.asamk.signal.GroupNotFoundException;
-import org.asamk.signal.JsonStickerPack;
import org.asamk.signal.NotAGroupMemberException;
import org.asamk.signal.StickerPackInvalidException;
import org.asamk.signal.TrustLevel;
import org.signal.libsignal.metadata.certificate.InvalidCertificateException;
import org.signal.zkgroup.InvalidInputException;
import org.signal.zkgroup.VerificationFailedException;
+import org.signal.zkgroup.profiles.ClientZkProfileOperations;
import org.signal.zkgroup.profiles.ProfileKey;
import org.whispersystems.libsignal.IdentityKey;
import org.whispersystems.libsignal.IdentityKeyPair;
import org.whispersystems.signalservice.api.messages.SendMessageResult;
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer;
+import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentRemoteId;
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentStream;
import org.whispersystems.signalservice.api.messages.SignalServiceContent;
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
import org.whispersystems.signalservice.api.push.ContactTokenDetails;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.api.push.exceptions.EncapsulatedExceptions;
+import org.whispersystems.signalservice.api.push.exceptions.MissingConfigurationException;
import org.whispersystems.signalservice.api.push.exceptions.NetworkFailureException;
import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException;
import org.whispersystems.signalservice.api.util.InvalidNumberException;
import org.whispersystems.signalservice.api.util.StreamDetails;
import org.whispersystems.signalservice.api.util.UptimeSleepTimer;
import org.whispersystems.signalservice.api.util.UuidUtil;
+import org.whispersystems.signalservice.internal.configuration.SignalServiceConfiguration;
import org.whispersystems.signalservice.internal.push.SignalServiceProtos;
import org.whispersystems.signalservice.internal.push.UnsupportedDataMessageException;
+import org.whispersystems.signalservice.internal.push.VerifyAccountResponse;
import org.whispersystems.signalservice.internal.util.Hex;
import org.whispersystems.util.Base64;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
+import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
private final String attachmentsPath;
private final String avatarsPath;
private final SleepTimer timer = new UptimeSleepTimer();
+ private final SignalServiceConfiguration serviceConfiguration;
+ private final String userAgent;
private SignalAccount account;
private String username;
private SignalServiceMessagePipe messagePipe = null;
private SignalServiceMessagePipe unidentifiedMessagePipe = null;
- public Manager(String username, String settingsPath) {
+ public Manager(String username, String settingsPath, SignalServiceConfiguration serviceConfiguration, String userAgent) {
this.username = username;
this.settingsPath = settingsPath;
this.dataPath = this.settingsPath + "/data";
this.attachmentsPath = this.settingsPath + "/attachments";
this.avatarsPath = this.settingsPath + "/avatars";
-
+ this.serviceConfiguration = serviceConfiguration;
+ this.userAgent = userAgent;
}
public String getUsername() {
}
private SignalServiceAccountManager getSignalServiceAccountManager() {
- return new SignalServiceAccountManager(BaseConfig.serviceConfiguration, account.getUuid(), account.getUsername(), account.getPassword(), account.getDeviceId(), BaseConfig.USER_AGENT, timer);
+ return new SignalServiceAccountManager(serviceConfiguration, account.getUuid(), account.getUsername(), account.getPassword(), account.getDeviceId(), userAgent, timer);
}
private IdentityKey getIdentity() {
accountManager = getSignalServiceAccountManager();
if (account.isRegistered()) {
- if (accountManager.getPreKeysCount() < BaseConfig.PREKEY_MINIMUM_COUNT) {
+ if (accountManager.getPreKeysCount() < ServiceConfig.PREKEY_MINIMUM_COUNT) {
refreshPreKeys();
account.save();
}
if (JsonGroupStore.groupsWithLegacyAvatarId.size() > 0) {
for (GroupInfo g : JsonGroupStore.groupsWithLegacyAvatarId) {
File avatarFile = getGroupAvatarFile(g.groupId);
- File attachmentFile = getAttachmentFile(g.getAvatarId());
+ File attachmentFile = getAttachmentFile(new SignalServiceAttachmentRemoteId(g.getAvatarId()));
if (!avatarFile.exists() && attachmentFile.exists()) {
try {
IOUtils.createPrivateDirectories(avatarsPath);
}
public void updateAccountAttributes() throws IOException {
- accountManager.setAccountAttributes(account.getSignalingKey(), account.getSignalProtocolStore().getLocalRegistrationId(), true, account.getRegistrationLockPin(), account.getRegistrationLock(), getSelfUnidentifiedAccessKey(), false, BaseConfig.capabilities);
+ accountManager.setAccountAttributes(account.getSignalingKey(), account.getSignalProtocolStore().getLocalRegistrationId(), true, account.getRegistrationLockPin(), account.getRegistrationLock(), getSelfUnidentifiedAccessKey(), false, ServiceConfig.capabilities);
}
public void setProfileName(String name) throws IOException {
}
private List<PreKeyRecord> generatePreKeys() {
- List<PreKeyRecord> records = new ArrayList<>(BaseConfig.PREKEY_BATCH_SIZE);
+ List<PreKeyRecord> records = new ArrayList<>(ServiceConfig.PREKEY_BATCH_SIZE);
final int offset = account.getPreKeyIdOffset();
- for (int i = 0; i < BaseConfig.PREKEY_BATCH_SIZE; i++) {
+ for (int i = 0; i < ServiceConfig.PREKEY_BATCH_SIZE; i++) {
int preKeyId = (offset + i) % Medium.MAX_VALUE;
ECKeyPair keyPair = Curve.generateKeyPair();
PreKeyRecord record = new PreKeyRecord(preKeyId, keyPair);
verificationCode = verificationCode.replace("-", "");
account.setSignalingKey(KeyUtils.createSignalingKey());
// TODO make unrestricted unidentified access configurable
- UUID uuid = accountManager.verifyAccountWithCode(verificationCode, account.getSignalingKey(), account.getSignalProtocolStore().getLocalRegistrationId(), true, pin, null, getSelfUnidentifiedAccessKey(), false, BaseConfig.capabilities);
+ VerifyAccountResponse response = accountManager.verifyAccountWithCode(verificationCode, account.getSignalingKey(), account.getSignalProtocolStore().getLocalRegistrationId(), true, pin, null, getSelfUnidentifiedAccessKey(), false, ServiceConfig.capabilities);
+ UUID uuid = UuidUtil.parseOrNull(response.getUuid());
+ // TODO response.isStorageCapable()
//accountManager.setGcmId(Optional.of(GoogleCloudMessaging.getInstance(this).register(REGISTRATION_ID)));
account.setRegistered(true);
account.setUuid(uuid);
throw new RuntimeException("Not implemented anymore, will be replaced with KBS");
} else {
account.setRegistrationLockPin(null);
- accountManager.removeV1Pin();
+ accountManager.removeRegistrationLockV1();
}
account.save();
}
}
private SignalServiceMessageReceiver getMessageReceiver() {
- return new SignalServiceMessageReceiver(BaseConfig.serviceConfiguration, account.getUuid(), account.getUsername(), account.getPassword(), account.getDeviceId(), account.getSignalingKey(), BaseConfig.USER_AGENT, null, timer);
+ // TODO implement ZkGroup support
+ final ClientZkProfileOperations clientZkProfileOperations = null;
+ return new SignalServiceMessageReceiver(serviceConfiguration, account.getUuid(), account.getUsername(), account.getPassword(), account.getDeviceId(), account.getSignalingKey(), userAgent, null, timer, clientZkProfileOperations);
}
private SignalServiceMessageSender getMessageSender() {
- return new SignalServiceMessageSender(BaseConfig.serviceConfiguration, account.getUuid(), account.getUsername(), account.getPassword(),
- account.getDeviceId(), account.getSignalProtocolStore(), BaseConfig.USER_AGENT, account.isMultiDevice(), Optional.fromNullable(messagePipe), Optional.fromNullable(unidentifiedMessagePipe), Optional.absent());
+ // TODO implement ZkGroup support
+ final ClientZkProfileOperations clientZkProfileOperations = null;
+ final boolean attachmentsV3 = false;
+ return new SignalServiceMessageSender(serviceConfiguration, account.getUuid(), account.getUsername(), account.getPassword(),
+ account.getDeviceId(), account.getSignalProtocolStore(), userAgent, account.isMultiDevice(), attachmentsV3, Optional.fromNullable(messagePipe), Optional.fromNullable(unidentifiedMessagePipe), Optional.absent(), clientZkProfileOperations);
}
private SignalServiceProfile getRecipientProfile(SignalServiceAddress address, Optional<UnidentifiedAccess> unidentifiedAccess) throws IOException {
}
@Override
- public void sendGroupMessage(String messageText, List<String> attachments,
+ public long sendGroupMessage(String messageText, List<String> attachments,
byte[] groupId)
throws IOException, EncapsulatedExceptions, GroupNotFoundException, AttachmentInvalidException {
final SignalServiceDataMessage.Builder messageBuilder = SignalServiceDataMessage.newBuilder().withBody(messageText);
messageBuilder.withExpiration(g.messageExpirationTime);
- sendMessageLegacy(messageBuilder, g.getMembersWithout(account.getSelfAddress()));
+ return sendMessageLegacy(messageBuilder, g.getMembersWithout(account.getSelfAddress()));
}
public void sendGroupMessageReaction(String emoji, boolean remove, String targetAuthor,
}
@Override
- public void sendMessage(String message, List<String> attachments, String recipient)
+ public long sendMessage(String message, List<String> attachments, String recipient)
throws EncapsulatedExceptions, AttachmentInvalidException, IOException, InvalidNumberException {
List<String> recipients = new ArrayList<>(1);
recipients.add(recipient);
- sendMessage(message, attachments, recipients);
+ return sendMessage(message, attachments, recipients);
}
@Override
- public void sendMessage(String messageText, List<String> attachments,
+ public long sendMessage(String messageText, List<String> attachments,
List<String> recipients)
throws IOException, EncapsulatedExceptions, AttachmentInvalidException, InvalidNumberException {
final SignalServiceDataMessage.Builder messageBuilder = SignalServiceDataMessage.newBuilder().withBody(messageText);
messageBuilder.withAttachments(attachmentPointers);
}
- sendMessageLegacy(messageBuilder, getSignalServiceAddresses(recipients));
+ return sendMessageLegacy(messageBuilder, getSignalServiceAddresses(recipients));
}
public void sendMessageReaction(String emoji, boolean remove, String targetAuthor,
SignalServiceDataMessage.Builder messageBuilder = SignalServiceDataMessage.newBuilder()
.asEndSessionMessage();
- sendMessageLegacy(messageBuilder, getSignalServiceAddresses(recipients));
+ final Collection<SignalServiceAddress> signalServiceAddresses = getSignalServiceAddresses(recipients);
+ try {
+ sendMessageLegacy(messageBuilder, signalServiceAddresses);
+ } catch (Exception e) {
+ for (SignalServiceAddress address : signalServiceAddresses) {
+ handleEndSession(address);
+ }
+ throw e;
+ }
}
@Override
/**
* This method throws an EncapsulatedExceptions exception instead of returning a list of SendMessageResult.
*/
- private void sendMessageLegacy(SignalServiceDataMessage.Builder messageBuilder, Collection<SignalServiceAddress> recipients)
+ private long sendMessageLegacy(SignalServiceDataMessage.Builder messageBuilder, Collection<SignalServiceAddress> recipients)
throws EncapsulatedExceptions, IOException {
+ final long timestamp = System.currentTimeMillis();
+ messageBuilder.withTimestamp(timestamp);
List<SendMessageResult> results = sendMessage(messageBuilder, recipients);
List<UntrustedIdentityException> untrustedIdentities = new LinkedList<>();
if (!untrustedIdentities.isEmpty() || !unregisteredUsers.isEmpty() || !networkExceptions.isEmpty()) {
throw new EncapsulatedExceptions(untrustedIdentities, unregisteredUsers, networkExceptions);
}
+ return timestamp;
}
private Collection<SignalServiceAddress> getSignalServiceAddresses(Collection<String> numbers) throws InvalidNumberException {
if (avatar.isPointer()) {
try {
retrieveGroupAvatarAttachment(avatar.asPointer(), group.groupId);
- } catch (IOException | InvalidMessageException e) {
- System.err.println("Failed to retrieve group avatar (" + avatar.asPointer().getId() + "): " + e.getMessage());
+ } catch (IOException | InvalidMessageException | MissingConfigurationException e) {
+ System.err.println("Failed to retrieve group avatar (" + avatar.asPointer().getRemoteId() + "): " + e.getMessage());
}
}
}
}
if (groupInfo.getMembers().isPresent()) {
- group.addMembers(groupInfo.getMembers().get());
+ group.addMembers(groupInfo.getMembers().get()
+ .stream()
+ .map(this::resolveSignalServiceAddress)
+ .collect(Collectors.toSet()));
}
account.getGroupStore().updateGroup(group);
}
break;
case QUIT:
- if (group == null) {
- try {
- sendGroupInfoRequest(groupInfo.getGroupId(), source);
- } catch (IOException | EncapsulatedExceptions e) {
- e.printStackTrace();
- }
- } else {
+ if (group != null) {
group.removeMember(source);
account.getGroupStore().updateGroup(group);
}
break;
}
}
+ final SignalServiceAddress conversationPartnerAddress = isSync ? destination : source;
if (message.isEndSession()) {
- handleEndSession(isSync ? destination : source);
+ handleEndSession(conversationPartnerAddress);
}
if (message.isExpirationUpdate() || message.getBody().isPresent()) {
if (message.getGroupContext().isPresent() && message.getGroupContext().get().getGroupV1().isPresent()) {
account.getGroupStore().updateGroup(group);
}
} else {
- ContactInfo contact = account.getContactStore().getContact(isSync ? destination : source);
+ ContactInfo contact = account.getContactStore().getContact(conversationPartnerAddress);
if (contact == null) {
- contact = new ContactInfo(isSync ? destination : source);
+ contact = new ContactInfo(conversationPartnerAddress);
}
if (contact.messageExpirationTime != message.getExpiresInSeconds()) {
contact.messageExpirationTime = message.getExpiresInSeconds();
if (attachment.isPointer()) {
try {
retrieveAttachment(attachment.asPointer());
- } catch (IOException | InvalidMessageException e) {
- System.err.println("Failed to retrieve attachment (" + attachment.asPointer().getId() + "): " + e.getMessage());
+ } catch (IOException | InvalidMessageException | MissingConfigurationException e) {
+ System.err.println("Failed to retrieve attachment (" + attachment.asPointer().getRemoteId() + "): " + e.getMessage());
}
}
}
SignalServiceAttachmentPointer attachment = preview.getImage().get().asPointer();
try {
retrieveAttachment(attachment);
- } catch (IOException | InvalidMessageException e) {
- System.err.println("Failed to retrieve attachment (" + attachment.getId() + "): " + e.getMessage());
+ } catch (IOException | InvalidMessageException | MissingConfigurationException e) {
+ System.err.println("Failed to retrieve attachment (" + attachment.getRemoteId() + "): " + e.getMessage());
}
}
}
envelope = messagePipe.read(timeout, unit, envelope1 -> {
// store message on disk, before acknowledging receipt to the server
try {
- File cacheFile = getMessageCacheFile(envelope1.getSourceE164().get(), now, envelope1.getTimestamp());
+ String source = envelope1.getSourceE164().isPresent() ? envelope1.getSourceE164().get() : "";
+ File cacheFile = getMessageCacheFile(source, now, envelope1.getTimestamp());
Utils.storeEnvelope(envelope1, cacheFile);
} catch (IOException e) {
System.err.println("Failed to store encrypted message in disk cache, ignoring: " + e.getMessage());
if (content.isNeedsReceipt()) {
try {
sendReceipt(sender, message.getTimestamp());
- } catch (IOException | UntrustedIdentityException e) {
+ } catch (IOException | UntrustedIdentityException | IllegalArgumentException e) {
e.printStackTrace();
}
}
if (rm.isContactsRequest()) {
try {
sendContacts();
- } catch (UntrustedIdentityException | IOException e) {
+ } catch (UntrustedIdentityException | IOException | IllegalArgumentException e) {
e.printStackTrace();
}
}
if (rm.isGroupsRequest()) {
try {
sendGroups();
- } catch (UntrustedIdentityException | IOException e) {
+ } catch (UntrustedIdentityException | IOException | IllegalArgumentException e) {
e.printStackTrace();
}
}
if (rm.isBlockedListRequest()) {
try {
sendBlockedList();
- } catch (UntrustedIdentityException | IOException e) {
+ } catch (UntrustedIdentityException | IOException | IllegalArgumentException e) {
e.printStackTrace();
}
}
if (g.getName().isPresent()) {
syncGroup.name = g.getName().get();
}
- syncGroup.addMembers(g.getMembers());
+ syncGroup.addMembers(g.getMembers()
+ .stream()
+ .map(this::resolveSignalServiceAddress)
+ .collect(Collectors.toSet()));
if (!g.isActive()) {
syncGroup.removeMember(account.getSelfAddress());
} else {
if (syncMessage.getBlockedList().isPresent()) {
final BlockedListMessage blockedListMessage = syncMessage.getBlockedList().get();
for (SignalServiceAddress address : blockedListMessage.getAddresses()) {
- setContactBlocked(address, true);
+ setContactBlocked(resolveSignalServiceAddress(address), true);
}
for (byte[] groupId : blockedListMessage.getGroupIds()) {
try {
if (c.getAddress().matches(account.getSelfAddress()) && c.getProfileKey().isPresent()) {
account.setProfileKey(c.getProfileKey().get());
}
- ContactInfo contact = account.getContactStore().getContact(c.getAddress());
+ final SignalServiceAddress address = resolveSignalServiceAddress(c.getAddress());
+ ContactInfo contact = account.getContactStore().getContact(address);
if (contact == null) {
- contact = new ContactInfo(c.getAddress());
+ contact = new ContactInfo(address);
}
if (c.getName().isPresent()) {
contact.name = c.getName().get();
}
if (syncMessage.getVerified().isPresent()) {
final VerifiedMessage verifiedMessage = syncMessage.getVerified().get();
- account.getSignalProtocolStore().setIdentityTrustLevel(verifiedMessage.getDestination(), verifiedMessage.getIdentityKey(), TrustLevel.fromVerifiedState(verifiedMessage.getVerified()));
+ account.getSignalProtocolStore().setIdentityTrustLevel(resolveSignalServiceAddress(verifiedMessage.getDestination()), verifiedMessage.getIdentityKey(), TrustLevel.fromVerifiedState(verifiedMessage.getVerified()));
}
if (syncMessage.getConfiguration().isPresent()) {
// TODO
return new File(avatarsPath, "contact-" + number);
}
- private File retrieveContactAvatarAttachment(SignalServiceAttachment attachment, String number) throws IOException, InvalidMessageException {
+ private File retrieveContactAvatarAttachment(SignalServiceAttachment attachment, String number) throws IOException, InvalidMessageException, MissingConfigurationException {
IOUtils.createPrivateDirectories(avatarsPath);
if (attachment.isPointer()) {
SignalServiceAttachmentPointer pointer = attachment.asPointer();
return new File(avatarsPath, "group-" + Base64.encodeBytes(groupId).replace("/", "_"));
}
- private File retrieveGroupAvatarAttachment(SignalServiceAttachment attachment, byte[] groupId) throws IOException, InvalidMessageException {
+ private File retrieveGroupAvatarAttachment(SignalServiceAttachment attachment, byte[] groupId) throws IOException, InvalidMessageException, MissingConfigurationException {
IOUtils.createPrivateDirectories(avatarsPath);
if (attachment.isPointer()) {
SignalServiceAttachmentPointer pointer = attachment.asPointer();
}
}
- public File getAttachmentFile(long attachmentId) {
- return new File(attachmentsPath, attachmentId + "");
+ public File getAttachmentFile(SignalServiceAttachmentRemoteId attachmentId) {
+ return new File(attachmentsPath, attachmentId.toString());
}
- private File retrieveAttachment(SignalServiceAttachmentPointer pointer) throws IOException, InvalidMessageException {
+ private File retrieveAttachment(SignalServiceAttachmentPointer pointer) throws IOException, InvalidMessageException, MissingConfigurationException {
IOUtils.createPrivateDirectories(attachmentsPath);
- return retrieveAttachment(pointer, getAttachmentFile(pointer.getId()), true);
+ return retrieveAttachment(pointer, getAttachmentFile(pointer.getRemoteId()), true);
}
- private File retrieveAttachment(SignalServiceAttachmentPointer pointer, File outputFile, boolean storePreview) throws IOException, InvalidMessageException {
+ private File retrieveAttachment(SignalServiceAttachmentPointer pointer, File outputFile, boolean storePreview) throws IOException, InvalidMessageException, MissingConfigurationException {
if (storePreview && pointer.getPreview().isPresent()) {
File previewFile = new File(outputFile + ".preview");
try (OutputStream output = new FileOutputStream(previewFile)) {
final SignalServiceMessageReceiver messageReceiver = getMessageReceiver();
File tmpFile = IOUtils.createTempFile();
- try (InputStream input = messageReceiver.retrieveAttachment(pointer, tmpFile, BaseConfig.MAX_ATTACHMENT_SIZE)) {
+ try (InputStream input = messageReceiver.retrieveAttachment(pointer, tmpFile, ServiceConfig.MAX_ATTACHMENT_SIZE)) {
try (OutputStream output = new FileOutputStream(outputFile)) {
byte[] buffer = new byte[4096];
int read;
return outputFile;
}
- private InputStream retrieveAttachmentAsStream(SignalServiceAttachmentPointer pointer, File tmpFile) throws IOException, InvalidMessageException {
+ private InputStream retrieveAttachmentAsStream(SignalServiceAttachmentPointer pointer, File tmpFile) throws IOException, InvalidMessageException, MissingConfigurationException {
final SignalServiceMessageReceiver messageReceiver = getMessageReceiver();
- return messageReceiver.retrieveAttachment(pointer, tmpFile, BaseConfig.MAX_ATTACHMENT_SIZE);
+ return messageReceiver.retrieveAttachment(pointer, tmpFile, ServiceConfig.MAX_ATTACHMENT_SIZE);
}
@Override
return false;
}
+ @Override
+ public String getObjectPath() {
+ return null;
+ }
+
private void sendGroups() throws IOException, UntrustedIdentityException {
File groupsFile = IOUtils.createTempFile();
public SignalServiceAddress resolveSignalServiceAddress(String identifier) {
SignalServiceAddress address = Util.getSignalServiceAddressFromIdentifier(identifier);
+
+ return resolveSignalServiceAddress(address);
+ }
+
+ public SignalServiceAddress resolveSignalServiceAddress(SignalServiceAddress address) {
if (address.matches(account.getSelfAddress())) {
return account.getSelfAddress();
}
- ContactInfo contactInfo = account.getContactStore().getContact(address);
- if (contactInfo == null) {
- return address;
- }
- return contactInfo.getAddress();
+ return account.getRecipientStore().resolveServiceAddress(address);
}
public interface ReceiveMessageHandler {