*/
package org.asamk.signal.manager;
-import com.fasterxml.jackson.databind.ObjectMapper;
-
import org.asamk.signal.manager.groups.GroupId;
import org.asamk.signal.manager.groups.GroupIdV1;
import org.asamk.signal.manager.groups.GroupIdV2;
import org.asamk.signal.manager.util.AttachmentUtils;
import org.asamk.signal.manager.util.IOUtils;
import org.asamk.signal.manager.util.KeyUtils;
+import org.asamk.signal.manager.util.ProfileUtils;
+import org.asamk.signal.manager.util.StickerUtils;
import org.asamk.signal.manager.util.Utils;
import org.signal.libsignal.metadata.InvalidMetadataMessageException;
import org.signal.libsignal.metadata.InvalidMetadataVersionException;
import org.whispersystems.signalservice.api.SignalServiceMessagePipe;
import org.whispersystems.signalservice.api.SignalServiceMessageReceiver;
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
-import org.whispersystems.signalservice.api.crypto.InvalidCiphertextException;
-import org.whispersystems.signalservice.api.crypto.ProfileCipher;
import org.whispersystems.signalservice.api.crypto.SignalServiceCipher;
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair;
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
import org.whispersystems.signalservice.api.messages.SignalServiceGroupV2;
import org.whispersystems.signalservice.api.messages.SignalServiceReceiptMessage;
import org.whispersystems.signalservice.api.messages.SignalServiceStickerManifestUpload;
-import org.whispersystems.signalservice.api.messages.SignalServiceStickerManifestUpload.StickerInfo;
import org.whispersystems.signalservice.api.messages.multidevice.BlockedListMessage;
import org.whispersystems.signalservice.api.messages.multidevice.ContactsMessage;
import org.whispersystems.signalservice.api.messages.multidevice.DeviceContact;
import org.whispersystems.signalservice.api.messages.multidevice.DeviceGroupsInputStream;
import org.whispersystems.signalservice.api.messages.multidevice.DeviceGroupsOutputStream;
import org.whispersystems.signalservice.api.messages.multidevice.DeviceInfo;
+import org.whispersystems.signalservice.api.messages.multidevice.KeysMessage;
import org.whispersystems.signalservice.api.messages.multidevice.RequestMessage;
import org.whispersystems.signalservice.api.messages.multidevice.SentTranscriptMessage;
import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage;
import org.whispersystems.signalservice.api.messages.multidevice.VerifiedMessage;
import org.whispersystems.signalservice.api.profiles.ProfileAndCredential;
import org.whispersystems.signalservice.api.profiles.SignalServiceProfile;
-import org.whispersystems.signalservice.api.push.ContactTokenDetails;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.api.push.exceptions.MissingConfigurationException;
+import org.whispersystems.signalservice.api.storage.StorageKey;
import org.whispersystems.signalservice.api.util.InvalidNumberException;
import org.whispersystems.signalservice.api.util.PhoneNumberFormatter;
import org.whispersystems.signalservice.api.util.SleepTimer;
import org.whispersystems.signalservice.internal.push.UnsupportedDataMessageException;
import org.whispersystems.signalservice.internal.util.DynamicCredentialsProvider;
import org.whispersystems.signalservice.internal.util.Hex;
-import org.whispersystems.util.Base64;
+import org.whispersystems.signalservice.internal.util.Util;
import java.io.Closeable;
import java.io.File;
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;
import static org.asamk.signal.manager.ServiceConfig.CDS_MRENCLAVE;
import static org.asamk.signal.manager.ServiceConfig.capabilities;
account.getDeviceId()),
userAgent,
groupsV2Operations,
+ ServiceConfig.AUTOMATIC_NETWORK_RETRY,
timer);
this.groupsV2Api = accountManager.getGroupsV2Api();
final KeyBackupService keyBackupService = ServiceConfig.createKeyBackupService(accountManager);
userAgent,
null,
timer,
- clientZkProfileOperations);
+ clientZkProfileOperations,
+ ServiceConfig.AUTOMATIC_NETWORK_RETRY);
this.account.setResolver(this::resolveSignalServiceAddress);
return new Manager(account, pathConfig, serviceConfiguration, userAgent);
}
+ public static List<String> getAllLocalUsernames(File settingsPath) {
+ PathConfig pathConfig = PathConfig.createDefault(settingsPath);
+ final File dataPath = pathConfig.getDataPath();
+ final File[] files = dataPath.listFiles();
+
+ if (files == null) {
+ return List.of();
+ }
+
+ return Arrays.stream(files)
+ .filter(File::isFile)
+ .map(File::getName)
+ .filter(file -> PhoneNumberFormatter.isValidNumber(file, null))
+ .collect(Collectors.toList());
+ }
+
public void checkAccountState() throws IOException {
if (accountManager.getPreKeysCount() < ServiceConfig.PREKEY_MINIMUM_COUNT) {
refreshPreKeys();
*/
public Map<String, Boolean> areUsersRegistered(Set<String> numbers) throws IOException {
// Note "contactDetails" has no optionals. It only gives us info on users who are registered
- List<ContactTokenDetails> contactDetails = this.accountManager.getContacts(numbers);
+ Map<String, UUID> contactDetails = getRegisteredUsers(numbers);
- Set<String> registeredUsers = contactDetails.stream()
- .map(ContactTokenDetails::getNumber)
- .collect(Collectors.toSet());
+ Set<String> registeredUsers = contactDetails.keySet();
return numbers.stream().collect(Collectors.toMap(x -> x, registeredUsers::contains));
}
* if it's Optional.absent(), the avatar will be removed
*/
public void setProfile(String name, Optional<File> avatar) throws IOException {
+ // TODO
+ String about = null;
+ String aboutEmoji = null;
+
try (final StreamDetails streamDetails = avatar == null
? avatarStore.retrieveProfileAvatar(getSelfAddress())
: avatar.isPresent() ? Utils.createStreamDetailsFromFile(avatar.get()) : null) {
- accountManager.setVersionedProfile(account.getUuid(), account.getProfileKey(), name, streamDetails);
+ accountManager.setVersionedProfile(account.getUuid(),
+ account.getProfileKey(),
+ name,
+ about,
+ aboutEmoji,
+ streamDetails);
}
if (avatar != null) {
avatarStore.deleteProfileAvatar(getSelfAddress());
}
}
+
+ try {
+ sendSyncMessage(SignalServiceSyncMessage.forFetchLatest(SignalServiceSyncMessage.FetchType.LOCAL_PROFILE));
+ } catch (UntrustedIdentityException ignored) {
+ }
}
public void unregister() throws IOException {
// If this is the master device, other users can't send messages to this number anymore.
// If this is a linked device, other users can still send messages, but this device doesn't receive them anymore.
accountManager.setGcmId(Optional.absent());
+ accountManager.deleteAccount();
account.setRegistered(false);
account.save();
}
public void setRegistrationLockPin(Optional<String> pin) throws IOException, UnauthenticatedResponseException {
+ if (!account.isMasterDevice()) {
+ throw new RuntimeException("Only master device can set a PIN");
+ }
if (pin.isPresent()) {
final MasterKey masterKey = account.getPinMasterKey() != null
? account.getPinMasterKey()
Optional.absent(),
clientZkProfileOperations,
executor,
- ServiceConfig.MAX_ENVELOPE_SIZE);
+ ServiceConfig.MAX_ENVELOPE_SIZE,
+ ServiceConfig.AUTOMATIC_NETWORK_RETRY);
}
private SignalProfile getRecipientProfile(
SignalServiceAddress address
+ ) {
+ return getRecipientProfile(address, false);
+ }
+
+ private SignalProfile getRecipientProfile(
+ SignalServiceAddress address, boolean force
) {
SignalProfileEntry profileEntry = account.getProfileStore().getProfileEntry(address);
if (profileEntry == null) {
return null;
}
long now = new Date().getTime();
- // Profiles are cache for 24h before retrieving them again
+ // Profiles are cached for 24h before retrieving them again
if (!profileEntry.isRequestPending() && (
- profileEntry.getProfile() == null || now - profileEntry.getLastUpdateTimestamp() > 24 * 60 * 60 * 1000
+ force
+ || profileEntry.getProfile() == null
+ || now - profileEntry.getLastUpdateTimestamp() > 24 * 60 * 60 * 1000
)) {
profileEntry.setRequestPending(true);
final SignalServiceProfile encryptedProfile;
profileEntry.setRequestPending(false);
}
- ProfileKey profileKey = profileEntry.getProfileKey();
- SignalProfile profile = decryptProfile(address, profileKey, encryptedProfile);
+ final ProfileKey profileKey = profileEntry.getProfileKey();
+ final SignalProfile profile = decryptProfileAndDownloadAvatar(address, profileKey, encryptedProfile);
account.getProfileStore()
.updateProfile(address, profileKey, now, profile, profileEntry.getProfileKeyCredential());
return profile;
long now = new Date().getTime();
final ProfileKeyCredential profileKeyCredential = profileAndCredential.getProfileKeyCredential().orNull();
- final SignalProfile profile = decryptProfile(address,
+ final SignalProfile profile = decryptProfileAndDownloadAvatar(address,
profileEntry.getProfileKey(),
profileAndCredential.getProfile());
account.getProfileStore()
return profileEntry.getProfileKeyCredential();
}
- private SignalProfile decryptProfile(
+ private SignalProfile decryptProfileAndDownloadAvatar(
final SignalServiceAddress address, final ProfileKey profileKey, final SignalServiceProfile encryptedProfile
) {
if (encryptedProfile.getAvatar() != null) {
downloadProfileAvatar(address, encryptedProfile.getAvatar(), profileKey);
}
- ProfileCipher profileCipher = new ProfileCipher(profileKey);
- try {
- String name;
- try {
- name = encryptedProfile.getName() == null
- ? null
- : new String(profileCipher.decryptName(Base64.decode(encryptedProfile.getName())));
- } catch (IOException e) {
- name = null;
- }
- String unidentifiedAccess;
- try {
- unidentifiedAccess = encryptedProfile.getUnidentifiedAccess() == null
- || !profileCipher.verifyUnidentifiedAccess(Base64.decode(encryptedProfile.getUnidentifiedAccess()))
- ? null
- : encryptedProfile.getUnidentifiedAccess();
- } catch (IOException e) {
- unidentifiedAccess = null;
- }
- return new SignalProfile(encryptedProfile.getIdentityKey(),
- name,
- unidentifiedAccess,
- encryptedProfile.isUnrestrictedUnidentifiedAccess(),
- encryptedProfile.getCapabilities());
- } catch (InvalidCiphertextException e) {
- return null;
- }
+ return ProfileUtils.decryptProfile(profileKey, encryptedProfile);
}
private Optional<SignalServiceAttachmentStream> createGroupAvatarAttachment(GroupId groupId) throws IOException {
return account.getGroupStore().getGroups();
}
- public Pair<Long, List<SendMessageResult>> sendGroupMessage(
- SignalServiceDataMessage.Builder messageBuilder, GroupId groupId
- ) throws IOException, GroupNotFoundException, NotAGroupMemberException {
- final GroupInfo g = getGroupForSending(groupId);
-
- GroupUtils.setGroupContext(messageBuilder, g);
- messageBuilder.withExpiration(g.getMessageExpirationTime());
-
- return sendMessage(messageBuilder, g.getMembersWithout(account.getSelfAddress()));
- }
-
public Pair<Long, List<SendMessageResult>> sendGroupMessage(
String messageText, List<String> attachments, GroupId groupId
) throws IOException, GroupNotFoundException, AttachmentInvalidException, NotAGroupMemberException {
return sendGroupMessage(messageBuilder, groupId);
}
- public Pair<Long, List<SendMessageResult>> sendQuitGroupMessage(GroupId groupId) throws GroupNotFoundException, IOException, NotAGroupMemberException {
+ public Pair<Long, List<SendMessageResult>> sendGroupMessage(
+ SignalServiceDataMessage.Builder messageBuilder, GroupId groupId
+ ) throws IOException, GroupNotFoundException, NotAGroupMemberException {
+ final GroupInfo g = getGroupForSending(groupId);
+
+ GroupUtils.setGroupContext(messageBuilder, g);
+ messageBuilder.withExpiration(g.getMessageExpirationTime());
+ return sendMessage(messageBuilder, g.getMembersWithout(account.getSelfAddress()));
+ }
+
+ public Pair<Long, List<SendMessageResult>> sendQuitGroupMessage(GroupId groupId) throws GroupNotFoundException, IOException, NotAGroupMemberException {
SignalServiceDataMessage.Builder messageBuilder;
final GroupInfo g = getGroupForUpdating(groupId);
return sendMessage(messageBuilder, g.getMembersWithout(account.getSelfAddress()));
}
+ public Pair<GroupId, List<SendMessageResult>> updateGroup(
+ GroupId groupId, String name, List<String> members, File avatarFile
+ ) throws IOException, GroupNotFoundException, AttachmentInvalidException, InvalidNumberException, NotAGroupMemberException {
+ return sendUpdateGroupMessage(groupId,
+ name,
+ members == null ? null : getSignalServiceAddresses(members),
+ avatarFile);
+ }
+
private Pair<GroupId, List<SendMessageResult>> sendUpdateGroupMessage(
GroupId groupId, String name, Collection<SignalServiceAddress> members, File avatarFile
) throws IOException, GroupNotFoundException, AttachmentInvalidException, NotAGroupMemberException {
return new Pair<>(g.getGroupId(), result.second());
}
+ private void updateGroupV1(
+ final GroupInfoV1 g,
+ final String name,
+ final Collection<SignalServiceAddress> members,
+ final File avatarFile
+ ) throws IOException {
+ if (name != null) {
+ g.name = name;
+ }
+
+ if (members != null) {
+ final Set<String> newE164Members = new HashSet<>();
+ for (SignalServiceAddress member : members) {
+ if (g.isMember(member) || !member.getNumber().isPresent()) {
+ continue;
+ }
+ newE164Members.add(member.getNumber().get());
+ }
+
+ final Map<String, UUID> registeredUsers = getRegisteredUsers(newE164Members);
+ if (registeredUsers.size() != newE164Members.size()) {
+ // Some of the new members are not registered on Signal
+ newE164Members.removeAll(registeredUsers.keySet());
+ throw new IOException("Failed to add members "
+ + String.join(", ", newE164Members)
+ + " to group: Not registered on Signal");
+ }
+
+ g.addMembers(members);
+ }
+
+ if (avatarFile != null) {
+ avatarStore.storeGroupAvatar(g.getGroupId(),
+ outputStream -> IOUtils.copyFileToStream(avatarFile, outputStream));
+ }
+ }
+
public Pair<GroupId, List<SendMessageResult>> joinGroup(
GroupInviteLinkUrl inviteLinkUrl
) throws IOException, GroupLinkNotActiveException {
return new Pair<>(group.getGroupId(), result.second());
}
+ private static int currentTimeDays() {
+ return (int) TimeUnit.MILLISECONDS.toDays(System.currentTimeMillis());
+ }
+
+ private GroupsV2AuthorizationString getGroupAuthForToday(
+ final GroupSecretParams groupSecretParams
+ ) throws IOException {
+ final int today = currentTimeDays();
+ // Returns credentials for the next 7 days
+ final HashMap<Integer, AuthCredentialResponse> credentials = groupsV2Api.getCredentials(today);
+ // TODO cache credentials until they expire
+ AuthCredentialResponse authCredentialResponse = credentials.get(today);
+ try {
+ return groupsV2Api.getGroupsV2AuthorizationString(account.getUuid(),
+ today,
+ groupSecretParams,
+ authCredentialResponse);
+ } catch (VerificationFailedException e) {
+ throw new IOException(e);
+ }
+ }
+
private Pair<Long, List<SendMessageResult>> sendUpdateGroupMessage(
GroupInfoV2 group, DecryptedGroup newDecryptedGroup, GroupChange groupChange
) throws IOException {
return sendMessage(messageBuilder, group.getMembersIncludingPendingWithout(account.getSelfAddress()));
}
- private void updateGroupV1(
- final GroupInfoV1 g,
- final String name,
- final Collection<SignalServiceAddress> members,
- final File avatarFile
- ) throws IOException {
- if (name != null) {
- g.name = name;
- }
-
- if (members != null) {
- final Set<String> newE164Members = new HashSet<>();
- for (SignalServiceAddress member : members) {
- if (g.isMember(member) || !member.getNumber().isPresent()) {
- continue;
- }
- newE164Members.add(member.getNumber().get());
- }
-
- final List<ContactTokenDetails> contacts = accountManager.getContacts(newE164Members);
- if (contacts.size() != newE164Members.size()) {
- // Some of the new members are not registered on Signal
- for (ContactTokenDetails contact : contacts) {
- newE164Members.remove(contact.getNumber());
- }
- throw new IOException("Failed to add members "
- + String.join(", ", newE164Members)
- + " to group: Not registered on Signal");
- }
-
- g.addMembers(members);
- }
-
- if (avatarFile != null) {
- avatarStore.storeGroupAvatar(g.getGroupId(),
- outputStream -> IOUtils.copyFileToStream(avatarFile, outputStream));
- }
- }
-
- Pair<Long, List<SendMessageResult>> sendUpdateGroupMessage(
+ Pair<Long, List<SendMessageResult>> sendGroupInfoMessage(
GroupIdV1 groupId, SignalServiceAddress recipient
) throws IOException, NotAGroupMemberException, GroupNotFoundException, AttachmentInvalidException {
GroupInfoV1 g;
return sendMessage(messageBuilder, getSignalServiceAddresses(recipients));
}
+ public Pair<Long, SendMessageResult> sendSelfMessage(
+ String messageText, List<String> attachments
+ ) throws IOException, AttachmentInvalidException {
+ final SignalServiceDataMessage.Builder messageBuilder = SignalServiceDataMessage.newBuilder()
+ .withBody(messageText);
+ if (attachments != null) {
+ messageBuilder.withAttachments(AttachmentUtils.getSignalServiceAttachments(attachments));
+ }
+ return sendSelfMessage(messageBuilder);
+ }
+
public Pair<Long, List<SendMessageResult>> sendMessageReaction(
String emoji, boolean remove, String targetAuthor, long targetSentTimestamp, List<String> recipients
) throws IOException, InvalidNumberException {
account.save();
}
- public Pair<GroupId, List<SendMessageResult>> updateGroup(
- GroupId groupId, String name, List<String> members, File avatarFile
- ) throws IOException, GroupNotFoundException, AttachmentInvalidException, InvalidNumberException, NotAGroupMemberException {
- return sendUpdateGroupMessage(groupId,
- name,
- members == null ? null : getSignalServiceAddresses(members),
- avatarFile);
- }
-
/**
* Change the expiration timer for a contact
*/
* @return if successful, returns the URL to install the sticker pack in the signal app
*/
public String uploadStickerPack(File path) throws IOException, StickerPackInvalidException {
- SignalServiceStickerManifestUpload manifest = getSignalServiceStickerManifestUpload(path);
+ SignalServiceStickerManifestUpload manifest = StickerUtils.getSignalServiceStickerManifestUpload(path);
SignalServiceMessageSender messageSender = createMessageSender();
}
}
- private SignalServiceStickerManifestUpload getSignalServiceStickerManifestUpload(
- final File file
- ) throws IOException, StickerPackInvalidException {
- ZipFile zip = null;
- String rootPath = null;
-
- if (file.getName().endsWith(".zip")) {
- zip = new ZipFile(file);
- } else if (file.getName().equals("manifest.json")) {
- rootPath = file.getParent();
- } else {
- throw new StickerPackInvalidException("Could not find manifest.json");
- }
-
- JsonStickerPack pack = parseStickerPack(rootPath, zip);
-
- if (pack.stickers == null) {
- throw new StickerPackInvalidException("Must set a 'stickers' field.");
- }
-
- if (pack.stickers.isEmpty()) {
- throw new StickerPackInvalidException("Must include stickers.");
- }
-
- List<StickerInfo> stickers = new ArrayList<>(pack.stickers.size());
- for (JsonStickerPack.JsonSticker sticker : pack.stickers) {
- if (sticker.file == null) {
- throw new StickerPackInvalidException("Must set a 'file' field on each sticker.");
- }
-
- Pair<InputStream, Long> data;
- try {
- data = getInputStreamAndLength(rootPath, zip, sticker.file);
- } catch (IOException ignored) {
- throw new StickerPackInvalidException("Could not find find " + sticker.file);
- }
-
- String contentType = Utils.getFileMimeType(new File(sticker.file), null);
- StickerInfo stickerInfo = new StickerInfo(data.first(),
- data.second(),
- Optional.fromNullable(sticker.emoji).or(""),
- contentType);
- stickers.add(stickerInfo);
- }
-
- StickerInfo cover = null;
- if (pack.cover != null) {
- if (pack.cover.file == null) {
- throw new StickerPackInvalidException("Must set a 'file' field on the cover.");
- }
-
- Pair<InputStream, Long> data;
- try {
- data = getInputStreamAndLength(rootPath, zip, pack.cover.file);
- } catch (IOException ignored) {
- throw new StickerPackInvalidException("Could not find find " + pack.cover.file);
- }
-
- String contentType = Utils.getFileMimeType(new File(pack.cover.file), null);
- cover = new StickerInfo(data.first(),
- data.second(),
- Optional.fromNullable(pack.cover.emoji).or(""),
- contentType);
- }
-
- return new SignalServiceStickerManifestUpload(pack.title, pack.author, cover, stickers);
- }
-
- private static JsonStickerPack parseStickerPack(String rootPath, ZipFile zip) throws IOException {
- InputStream inputStream;
- if (zip != null) {
- inputStream = zip.getInputStream(zip.getEntry("manifest.json"));
- } else {
- inputStream = new FileInputStream((new File(rootPath, "manifest.json")));
- }
- return new ObjectMapper().readValue(inputStream, JsonStickerPack.class);
- }
-
- private static Pair<InputStream, Long> getInputStreamAndLength(
- final String rootPath, final ZipFile zip, final String subfile
- ) throws IOException {
- if (zip != null) {
- final ZipEntry entry = zip.getEntry(subfile);
- return new Pair<>(zip.getInputStream(entry), entry.getSize());
- } else {
- final File file = new File(rootPath, subfile);
- return new Pair<>(new FileInputStream(file), file.length());
- }
- }
-
void requestSyncGroups() throws IOException {
SignalServiceProtos.SyncMessage.Request r = SignalServiceProtos.SyncMessage.Request.newBuilder()
.setType(SignalServiceProtos.SyncMessage.Request.Type.GROUPS)
try {
sendSyncMessage(message);
} catch (UntrustedIdentityException e) {
- e.printStackTrace();
+ throw new AssertionError(e);
}
}
try {
sendSyncMessage(message);
} catch (UntrustedIdentityException e) {
- e.printStackTrace();
+ throw new AssertionError(e);
}
}
try {
sendSyncMessage(message);
} catch (UntrustedIdentityException e) {
- e.printStackTrace();
+ throw new AssertionError(e);
}
}
try {
sendSyncMessage(message);
} catch (UntrustedIdentityException e) {
- e.printStackTrace();
+ throw new AssertionError(e);
+ }
+ }
+
+ void requestSyncKeys() throws IOException {
+ SignalServiceProtos.SyncMessage.Request r = SignalServiceProtos.SyncMessage.Request.newBuilder()
+ .setType(SignalServiceProtos.SyncMessage.Request.Type.KEYS)
+ .build();
+ SignalServiceSyncMessage message = SignalServiceSyncMessage.forRequest(new RequestMessage(r));
+ try {
+ sendSyncMessage(message);
+ } catch (UntrustedIdentityException e) {
+ throw new AssertionError(e);
}
}
private Collection<SignalServiceAddress> getSignalServiceAddresses(Collection<String> numbers) throws InvalidNumberException {
final Set<SignalServiceAddress> signalServiceAddresses = new HashSet<>(numbers.size());
- final Set<SignalServiceAddress> missingUuids = new HashSet<>();
+ final Set<SignalServiceAddress> addressesMissingUuid = new HashSet<>();
for (String number : numbers) {
final SignalServiceAddress resolvedAddress = canonicalizeAndResolveSignalServiceAddress(number);
if (resolvedAddress.getUuid().isPresent()) {
signalServiceAddresses.add(resolvedAddress);
} else {
- missingUuids.add(resolvedAddress);
+ addressesMissingUuid.add(resolvedAddress);
}
}
+ final Set<String> numbersMissingUuid = addressesMissingUuid.stream()
+ .map(a -> a.getNumber().get())
+ .collect(Collectors.toSet());
Map<String, UUID> registeredUsers;
try {
- registeredUsers = accountManager.getRegisteredUsers(getIasKeyStore(),
- missingUuids.stream().map(a -> a.getNumber().get()).collect(Collectors.toSet()),
- CDS_MRENCLAVE);
- } catch (IOException | Quote.InvalidQuoteFormatException | UnauthenticatedQuoteException | SignatureException | UnauthenticatedResponseException e) {
+ registeredUsers = getRegisteredUsers(numbersMissingUuid);
+ } catch (IOException e) {
logger.warn("Failed to resolve uuids from server, ignoring: {}", e.getMessage());
- registeredUsers = new HashMap<>();
+ registeredUsers = Map.of();
}
- for (SignalServiceAddress address : missingUuids) {
+ for (SignalServiceAddress address : addressesMissingUuid) {
final String number = address.getNumber().get();
if (registeredUsers.containsKey(number)) {
final SignalServiceAddress newAddress = resolveSignalServiceAddress(new SignalServiceAddress(
return signalServiceAddresses;
}
+ private Map<String, UUID> getRegisteredUsers(final Set<String> numbersMissingUuid) throws IOException {
+ try {
+ return accountManager.getRegisteredUsers(getIasKeyStore(), numbersMissingUuid, CDS_MRENCLAVE);
+ } catch (Quote.InvalidQuoteFormatException | UnauthenticatedQuoteException | SignatureException | UnauthenticatedResponseException | InvalidKeyException e) {
+ throw new IOException(e);
+ }
+ }
+
private Pair<Long, List<SendMessageResult>> sendMessage(
SignalServiceDataMessage.Builder messageBuilder, Collection<SignalServiceAddress> recipients
) throws IOException {
final int expirationTime = contact != null ? contact.messageExpirationTime : 0;
messageBuilder.withExpiration(expirationTime);
message = messageBuilder.build();
- if (address.matches(account.getSelfAddress())) {
- results.add(sendSelfMessage(message));
- } else {
- results.add(sendMessage(address, message));
- }
+ results.add(sendMessage(address, message));
}
return new Pair<>(timestamp, results);
}
}
}
+ private Pair<Long, SendMessageResult> sendSelfMessage(
+ SignalServiceDataMessage.Builder messageBuilder
+ ) throws IOException {
+ final long timestamp = System.currentTimeMillis();
+ messageBuilder.withTimestamp(timestamp);
+ getOrCreateMessagePipe();
+ getOrCreateUnidentifiedMessagePipe();
+ try {
+ final SignalServiceAddress address = getSelfAddress();
+
+ final ContactInfo contact = account.getContactStore().getContact(address);
+ final int expirationTime = contact != null ? contact.messageExpirationTime : 0;
+ messageBuilder.withExpiration(expirationTime);
+
+ SignalServiceDataMessage message = messageBuilder.build();
+ final SendMessageResult result = sendSelfMessage(message);
+ return new Pair<>(timestamp, result);
+ } finally {
+ account.save();
+ }
+ }
+
private SendMessageResult sendSelfMessage(SignalServiceDataMessage message) throws IOException {
SignalServiceMessageSender messageSender = createMessageSender();
if (e.getCause() instanceof org.whispersystems.libsignal.UntrustedIdentityException) {
org.whispersystems.libsignal.UntrustedIdentityException identityException = (org.whispersystems.libsignal.UntrustedIdentityException) e
.getCause();
- account.getSignalProtocolStore()
- .saveIdentity(resolveSignalServiceAddress(identityException.getName()),
- identityException.getUntrustedIdentity(),
- TrustLevel.UNTRUSTED);
+ final IdentityKey untrustedIdentity = identityException.getUntrustedIdentity();
+ if (untrustedIdentity != null) {
+ account.getSignalProtocolStore()
+ .saveIdentity(resolveSignalServiceAddress(identityException.getName()),
+ untrustedIdentity,
+ TrustLevel.UNTRUSTED);
+ }
throw identityException;
}
throw new AssertionError(e);
account.getSignalProtocolStore().deleteAllSessions(source);
}
- private static int currentTimeDays() {
- return (int) TimeUnit.MILLISECONDS.toDays(System.currentTimeMillis());
- }
-
- private GroupsV2AuthorizationString getGroupAuthForToday(
- final GroupSecretParams groupSecretParams
- ) throws IOException {
- final int today = currentTimeDays();
- // Returns credentials for the next 7 days
- final HashMap<Integer, AuthCredentialResponse> credentials = groupsV2Api.getCredentials(today);
- // TODO cache credentials until they expire
- AuthCredentialResponse authCredentialResponse = credentials.get(today);
- try {
- return groupsV2Api.getGroupsV2AuthorizationString(account.getUuid(),
- today,
- groupSecretParams,
- authCredentialResponse);
- } catch (VerificationFailedException e) {
- throw new IOException(e);
- }
- }
-
private List<HandleAction> handleSignalServiceDataMessage(
SignalServiceDataMessage message,
boolean isSync,
}
case REQUEST_INFO:
if (groupV1 != null && !isSync) {
- actions.add(new SendGroupUpdateAction(source, groupV1.getGroupId()));
+ actions.add(new SendGroupInfoAction(source, groupV1.getGroupId()));
}
break;
}
try {
action.execute(this);
} catch (Throwable e) {
- e.printStackTrace();
+ logger.warn("Message action failed.", e);
}
}
}
try {
action.execute(this);
} catch (Throwable e) {
- e.printStackTrace();
+ logger.warn("Message action failed.", e);
}
}
account.save();
try {
action.execute(this);
} catch (Throwable e) {
- e.printStackTrace();
+ logger.warn("Message action failed.", e);
}
}
} else {
logger.warn("Failed to handle received sync groups “{}”, ignoring: {}",
tmpFile,
e.getMessage());
- e.printStackTrace();
} finally {
if (tmpFile != null) {
try {
}
}
} catch (Exception e) {
- e.printStackTrace();
+ logger.warn("Failed to handle received sync contacts “{}”, ignoring: {}",
+ tmpFile,
+ e.getMessage());
} finally {
if (tmpFile != null) {
try {
account.getStickerStore().updateSticker(sticker);
}
}
+ if (syncMessage.getFetchType().isPresent()) {
+ switch (syncMessage.getFetchType().get()) {
+ case LOCAL_PROFILE:
+ getRecipientProfile(getSelfAddress(), true);
+ case STORAGE_MANIFEST:
+ // TODO
+ }
+ }
+ if (syncMessage.getKeys().isPresent()) {
+ final KeysMessage keysMessage = syncMessage.getKeys().get();
+ if (keysMessage.getStorageService().isPresent()) {
+ final StorageKey storageKey = keysMessage.getStorageService().get();
+ account.setStorageKey(storageKey);
+ }
+ }
if (syncMessage.getConfiguration().isPresent()) {
// TODO
}
return account.getContactStore().getContacts();
}
- public ContactInfo getContact(String number) {
- return account.getContactStore().getContact(Utils.getSignalServiceAddressFromIdentifier(number));
+ public String getContactOrProfileName(String number) {
+ final SignalServiceAddress address = Utils.getSignalServiceAddressFromIdentifier(number);
+
+ final ContactInfo contact = account.getContactStore().getContact(address);
+ if (contact != null && !Util.isEmpty(contact.name)) {
+ return contact.name;
+ }
+
+ final SignalProfileEntry profileEntry = account.getProfileStore().getProfileEntry(address);
+ if (profileEntry != null && profileEntry.getProfile() != null) {
+ return profileEntry.getProfile().getName();
+ }
+
+ return null;
}
public GroupInfo getGroup(GroupId groupId) {
try {
sendVerifiedMessage(address, id.getIdentityKey(), TrustLevel.TRUSTED_VERIFIED);
} catch (IOException | UntrustedIdentityException e) {
- e.printStackTrace();
+ logger.warn("Failed to send verification sync message: {}", e.getMessage());
}
account.save();
return true;
try {
sendVerifiedMessage(address, id.getIdentityKey(), TrustLevel.TRUSTED_VERIFIED);
} catch (IOException | UntrustedIdentityException e) {
- e.printStackTrace();
+ logger.warn("Failed to send verification sync message: {}", e.getMessage());
}
account.save();
return true;
try {
sendVerifiedMessage(address, id.getIdentityKey(), TrustLevel.TRUSTED_UNVERIFIED);
} catch (IOException | UntrustedIdentityException e) {
- e.printStackTrace();
+ logger.warn("Failed to send verification sync message: {}", e.getMessage());
}
}
}