import com.fasterxml.jackson.databind.ObjectMapper;
-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.asamk.signal.UserAlreadyExists;
import org.asamk.signal.storage.SignalAccount;
import org.asamk.signal.storage.contacts.ContactInfo;
import org.asamk.signal.storage.groups.GroupInfo;
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.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
-public class Manager implements Signal {
+public class Manager implements Closeable {
- private final String settingsPath;
- private final String dataPath;
- 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 final SignalAccount account;
+ private final PathConfig pathConfig;
private SignalServiceAccountManager accountManager;
private SignalServiceMessagePipe messagePipe = null;
private SignalServiceMessagePipe unidentifiedMessagePipe = null;
- public Manager(String username, String settingsPath) {
- this.username = username;
- this.settingsPath = settingsPath;
- this.dataPath = this.settingsPath + "/data";
- this.attachmentsPath = this.settingsPath + "/attachments";
- this.avatarsPath = this.settingsPath + "/avatars";
+ public Manager(SignalAccount account, PathConfig pathConfig, SignalServiceConfiguration serviceConfiguration, String userAgent) {
+ this.account = account;
+ this.pathConfig = pathConfig;
+ this.serviceConfiguration = serviceConfiguration;
+ this.userAgent = userAgent;
+ this.accountManager = createSignalServiceAccountManager();
+ this.account.setResolver(this::resolveSignalServiceAddress);
}
public String getUsername() {
- return username;
+ return account.getUsername();
}
public SignalServiceAddress getSelfAddress() {
return account.getSelfAddress();
}
- private SignalServiceAccountManager getSignalServiceAccountManager() {
- return new SignalServiceAccountManager(BaseConfig.serviceConfiguration, account.getUuid(), account.getUsername(), account.getPassword(), account.getDeviceId(), BaseConfig.USER_AGENT, timer);
+ private SignalServiceAccountManager createSignalServiceAccountManager() {
+ return new SignalServiceAccountManager(serviceConfiguration, account.getUuid(), account.getUsername(), account.getPassword(), account.getDeviceId(), userAgent, timer);
}
- private IdentityKey getIdentity() {
- return account.getSignalProtocolStore().getIdentityKeyPair().getPublicKey();
+ private IdentityKeyPair getIdentityKeyPair() {
+ return account.getSignalProtocolStore().getIdentityKeyPair();
}
public int getDeviceId() {
}
private String getMessageCachePath() {
- return this.dataPath + "/" + username + ".d/msg-cache";
+ return pathConfig.getDataPath() + "/" + account.getUsername() + ".d/msg-cache";
}
private String getMessageCachePath(String sender) {
return new File(cachePath + "/" + now + "_" + timestamp);
}
- public boolean userHasKeys() {
- return account != null && account.getSignalProtocolStore() != null;
- }
+ public static Manager init(String username, String settingsPath, SignalServiceConfiguration serviceConfiguration, String userAgent) throws IOException {
+ PathConfig pathConfig = PathConfig.createDefault(settingsPath);
- public void init() throws IOException {
- if (!SignalAccount.userExists(dataPath, username)) {
- return;
- }
- account = SignalAccount.load(dataPath, username);
- account.setResolver(this::resolveSignalServiceAddress);
+ if (!SignalAccount.userExists(pathConfig.getDataPath(), username)) {
+ IdentityKeyPair identityKey = KeyHelper.generateIdentityKeyPair();
+ int registrationId = KeyHelper.generateRegistrationId(false);
- migrateLegacyConfigs();
+ ProfileKey profileKey = KeyUtils.createProfileKey();
+ SignalAccount account = SignalAccount.create(pathConfig.getDataPath(), username, identityKey, registrationId, profileKey);
+ account.save();
- accountManager = getSignalServiceAccountManager();
- if (account.isRegistered()) {
- if (accountManager.getPreKeysCount() < BaseConfig.PREKEY_MINIMUM_COUNT) {
- refreshPreKeys();
- account.save();
- }
- if (account.getUuid() == null) {
- account.setUuid(accountManager.getOwnUuid());
- account.save();
- }
+ return new Manager(account, pathConfig, serviceConfiguration, userAgent);
}
+
+ SignalAccount account = SignalAccount.load(pathConfig.getDataPath(), username);
+
+ Manager m = new Manager(account, pathConfig, serviceConfiguration, userAgent);
+
+ m.migrateLegacyConfigs();
+
+ return m;
}
private void migrateLegacyConfigs() {
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);
+ IOUtils.createPrivateDirectories(pathConfig.getAvatarsPath());
Files.copy(attachmentFile.toPath(), avatarFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
} catch (Exception e) {
// Ignore
}
}
- private void createNewIdentity() throws IOException {
- IdentityKeyPair identityKey = KeyHelper.generateIdentityKeyPair();
- int registrationId = KeyHelper.generateRegistrationId(false);
- if (username == null) {
- account = SignalAccount.createTemporaryAccount(identityKey, registrationId);
- account.setResolver(this::resolveSignalServiceAddress);
- } else {
- ProfileKey profileKey = KeyUtils.createProfileKey();
- account = SignalAccount.create(dataPath, username, identityKey, registrationId, profileKey);
- account.setResolver(this::resolveSignalServiceAddress);
- account.save();
+ public void checkAccountState() throws IOException {
+ if (account.isRegistered()) {
+ if (accountManager.getPreKeysCount() < ServiceConfig.PREKEY_MINIMUM_COUNT) {
+ refreshPreKeys();
+ account.save();
+ }
+ if (account.getUuid() == null) {
+ account.setUuid(accountManager.getOwnUuid());
+ account.save();
+ }
}
}
public boolean isRegistered() {
- return account != null && account.isRegistered();
+ return account.isRegistered();
}
public void register(boolean voiceVerification) throws IOException {
- if (account == null) {
- createNewIdentity();
- }
account.setPassword(KeyUtils.createPassword());
+
+ // Resetting UUID, because registering doesn't work otherwise
account.setUuid(null);
- accountManager = getSignalServiceAccountManager();
+ accountManager = createSignalServiceAccountManager();
if (voiceVerification) {
accountManager.requestVoiceVerificationCode(Locale.getDefault(), Optional.absent(), Optional.absent());
}
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 {
account.save();
}
- public String getDeviceLinkUri() throws TimeoutException, IOException {
- if (account == null) {
- createNewIdentity();
- }
- account.setPassword(KeyUtils.createPassword());
- accountManager = getSignalServiceAccountManager();
- String uuid = accountManager.getNewDeviceUuid();
-
- return Utils.createDeviceLinkUri(new Utils.DeviceLinkInfo(uuid, getIdentity().getPublicKey()));
- }
-
- public void finishDeviceLink(String deviceName) throws IOException, InvalidKeyException, TimeoutException, UserAlreadyExists {
- account.setSignalingKey(KeyUtils.createSignalingKey());
- SignalServiceAccountManager.NewDeviceRegistrationReturn ret = accountManager.finishNewDeviceRegistration(account.getSignalProtocolStore().getIdentityKeyPair(), account.getSignalingKey(), false, true, account.getSignalProtocolStore().getLocalRegistrationId(), deviceName);
-
- username = ret.getNumber();
- // TODO do this check before actually registering
- if (SignalAccount.userExists(dataPath, username)) {
- throw new UserAlreadyExists(username, SignalAccount.getFileName(dataPath, username));
- }
-
- // Create new account with the synced identity
- byte[] profileKeyBytes = ret.getProfileKey();
- ProfileKey profileKey;
- if (profileKeyBytes == null) {
- profileKey = KeyUtils.createProfileKey();
- } else {
- try {
- profileKey = new ProfileKey(profileKeyBytes);
- } catch (InvalidInputException e) {
- throw new IOException("Received invalid profileKey", e);
- }
- }
- account = SignalAccount.createLinkedAccount(dataPath, username, ret.getUuid(), account.getPassword(), ret.getDeviceId(), ret.getIdentity(), account.getSignalProtocolStore().getLocalRegistrationId(), account.getSignalingKey(), profileKey);
- account.setResolver(this::resolveSignalServiceAddress);
-
- refreshPreKeys();
-
- requestSyncGroups();
- requestSyncContacts();
- requestSyncBlocked();
- requestSyncConfiguration();
-
- account.save();
- }
-
public List<DeviceInfo> getLinkedDevices() throws IOException {
List<DeviceInfo> devices = accountManager.getDevices();
account.setMultiDevice(devices.size() > 1);
}
private void addDevice(String deviceIdentifier, ECPublicKey deviceKey) throws IOException, InvalidKeyException {
- IdentityKeyPair identityKeyPair = account.getSignalProtocolStore().getIdentityKeyPair();
+ IdentityKeyPair identityKeyPair = getIdentityKeyPair();
String verificationCode = accountManager.getNewDeviceVerificationCode();
accountManager.addDevice(deviceIdentifier, deviceKey, identityKeyPair, Optional.of(account.getProfileKey().serialize()), verificationCode);
}
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);
account.setRegistrationLockPin(pin);
- account.getSignalProtocolStore().saveIdentity(account.getSelfAddress(), account.getSignalProtocolStore().getIdentityKeyPair().getPublicKey(), TrustLevel.TRUSTED_VERIFIED);
+ account.getSignalProtocolStore().saveIdentity(account.getSelfAddress(), getIdentityKeyPair().getPublicKey(), TrustLevel.TRUSTED_VERIFIED);
refreshPreKeys();
account.save();
throw new RuntimeException("Not implemented anymore, will be replaced with KBS");
} else {
account.setRegistrationLockPin(null);
- accountManager.removeV1Pin();
+ accountManager.removeRegistrationLockV1();
}
account.save();
}
- private void refreshPreKeys() throws IOException {
+ void refreshPreKeys() throws IOException {
List<PreKeyRecord> oneTimePreKeys = generatePreKeys();
- final IdentityKeyPair identityKeyPair = account.getSignalProtocolStore().getIdentityKeyPair();
+ final IdentityKeyPair identityKeyPair = getIdentityKeyPair();
SignedPreKeyRecord signedPreKeyRecord = generateSignedPreKey(identityKeyPair);
- accountManager.setPreKeys(getIdentity(), signedPreKeyRecord, oneTimePreKeys);
+ accountManager.setPreKeys(identityKeyPair.getPublicKey(), signedPreKeyRecord, oneTimePreKeys);
}
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 {
return account.getGroupStore().getGroups();
}
- @Override
- public void sendGroupMessage(String messageText, List<String> attachments,
+ public long sendGroupMessage(String messageText, List<String> attachments,
byte[] groupId)
- throws IOException, EncapsulatedExceptions, GroupNotFoundException, AttachmentInvalidException {
+ throws IOException, EncapsulatedExceptions, GroupNotFoundException, AttachmentInvalidException, NotAGroupMemberException {
final SignalServiceDataMessage.Builder messageBuilder = SignalServiceDataMessage.newBuilder().withBody(messageText);
if (attachments != null) {
messageBuilder.withAttachments(Utils.getSignalServiceAttachments(attachments));
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,
long targetSentTimestamp, byte[] groupId)
- throws IOException, EncapsulatedExceptions, AttachmentInvalidException, InvalidNumberException {
+ throws IOException, EncapsulatedExceptions, InvalidNumberException, NotAGroupMemberException, GroupNotFoundException {
SignalServiceDataMessage.Reaction reaction = new SignalServiceDataMessage.Reaction(emoji, remove, canonicalizeAndResolveSignalServiceAddress(targetAuthor), targetSentTimestamp);
final SignalServiceDataMessage.Builder messageBuilder = SignalServiceDataMessage.newBuilder()
.withReaction(reaction);
sendMessageLegacy(messageBuilder, g.getMembersWithout(account.getSelfAddress()));
}
- public void sendQuitGroupMessage(byte[] groupId) throws GroupNotFoundException, IOException, EncapsulatedExceptions {
+ public void sendQuitGroupMessage(byte[] groupId) throws GroupNotFoundException, IOException, EncapsulatedExceptions, NotAGroupMemberException {
SignalServiceGroup group = SignalServiceGroup.newBuilder(SignalServiceGroup.Type.QUIT)
.withId(groupId)
.build();
sendMessageLegacy(messageBuilder, g.getMembersWithout(account.getSelfAddress()));
}
- private byte[] sendUpdateGroupMessage(byte[] groupId, String name, Collection<SignalServiceAddress> members, String avatarFile) throws IOException, EncapsulatedExceptions, GroupNotFoundException, AttachmentInvalidException {
+ private byte[] sendUpdateGroupMessage(byte[] groupId, String name, Collection<SignalServiceAddress> members, String avatarFile) throws IOException, EncapsulatedExceptions, GroupNotFoundException, AttachmentInvalidException, NotAGroupMemberException {
GroupInfo g;
if (groupId == null) {
// Create new group
}
if (avatarFile != null) {
- IOUtils.createPrivateDirectories(avatarsPath);
+ IOUtils.createPrivateDirectories(pathConfig.getAvatarsPath());
File aFile = getGroupAvatarFile(g.groupId);
Files.copy(Paths.get(avatarFile), aFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
}
return g.groupId;
}
- private void sendUpdateGroupMessage(byte[] groupId, SignalServiceAddress recipient) throws IOException, EncapsulatedExceptions {
+ private void sendUpdateGroupMessage(byte[] groupId, SignalServiceAddress recipient) throws IOException, EncapsulatedExceptions, NotAGroupMemberException, GroupNotFoundException, AttachmentInvalidException {
if (groupId == null) {
return;
}
sendMessageLegacy(messageBuilder, Collections.singleton(recipient));
}
- private SignalServiceDataMessage.Builder getGroupUpdateMessageBuilder(GroupInfo g) {
+ private SignalServiceDataMessage.Builder getGroupUpdateMessageBuilder(GroupInfo g) throws AttachmentInvalidException {
SignalServiceGroup.Builder group = SignalServiceGroup.newBuilder(SignalServiceGroup.Type.UPDATE)
.withId(g.groupId)
.withName(g.name)
getMessageSender().sendReceipt(remoteAddress, getAccessFor(remoteAddress), receiptMessage);
}
- @Override
- public void 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);
- }
-
- @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,
long targetSentTimestamp, List<String> recipients)
- throws IOException, EncapsulatedExceptions, AttachmentInvalidException, InvalidNumberException {
+ throws IOException, EncapsulatedExceptions, InvalidNumberException {
SignalServiceDataMessage.Reaction reaction = new SignalServiceDataMessage.Reaction(emoji, remove, canonicalizeAndResolveSignalServiceAddress(targetAuthor), targetSentTimestamp);
final SignalServiceDataMessage.Builder messageBuilder = SignalServiceDataMessage.newBuilder()
.withReaction(reaction);
sendMessageLegacy(messageBuilder, getSignalServiceAddresses(recipients));
}
- @Override
public void sendEndSessionMessage(List<String> recipients) throws IOException, EncapsulatedExceptions, InvalidNumberException {
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);
+ }
+ account.save();
+ throw e;
+ }
}
- @Override
public String getContactName(String number) throws InvalidNumberException {
ContactInfo contact = account.getContactStore().getContact(canonicalizeAndResolveSignalServiceAddress(number));
if (contact == null) {
}
}
- @Override
public void setContactName(String number, String name) throws InvalidNumberException {
final SignalServiceAddress address = canonicalizeAndResolveSignalServiceAddress(number);
ContactInfo contact = account.getContactStore().getContact(address);
account.save();
}
- @Override
public void setContactBlocked(String number, boolean blocked) throws InvalidNumberException {
setContactBlocked(canonicalizeAndResolveSignalServiceAddress(number), blocked);
}
account.save();
}
- @Override
public void setGroupBlocked(final byte[] groupId, final boolean blocked) throws GroupNotFoundException {
GroupInfo group = getGroup(groupId);
if (group == null) {
}
}
- @Override
- public List<byte[]> getGroupIds() {
- List<GroupInfo> groups = getGroups();
- List<byte[]> ids = new ArrayList<>(groups.size());
- for (GroupInfo group : groups) {
- ids.add(group.groupId);
- }
- return ids;
- }
-
- @Override
- public String getGroupName(byte[] groupId) {
- GroupInfo group = getGroup(groupId);
- if (group == null) {
- return "";
- } else {
- return group.name;
- }
- }
-
- @Override
- public List<String> getGroupMembers(byte[] groupId) {
- GroupInfo group = getGroup(groupId);
- if (group == null) {
- return Collections.emptyList();
- } else {
- return new ArrayList<>(group.getMembersE164());
- }
- }
-
- @Override
- public byte[] updateGroup(byte[] groupId, String name, List<String> members, String avatar) throws IOException, EncapsulatedExceptions, GroupNotFoundException, AttachmentInvalidException, InvalidNumberException {
+ public byte[] updateGroup(byte[] groupId, String name, List<String> members, String avatar) throws IOException, EncapsulatedExceptions, GroupNotFoundException, AttachmentInvalidException, InvalidNumberException, NotAGroupMemberException {
if (groupId.length == 0) {
groupId = null;
}
}
}
- private void requestSyncGroups() throws IOException {
+ void requestSyncGroups() throws IOException {
SignalServiceProtos.SyncMessage.Request r = SignalServiceProtos.SyncMessage.Request.newBuilder().setType(SignalServiceProtos.SyncMessage.Request.Type.GROUPS).build();
SignalServiceSyncMessage message = SignalServiceSyncMessage.forRequest(new RequestMessage(r));
try {
}
}
- private void requestSyncContacts() throws IOException {
+ void requestSyncContacts() throws IOException {
SignalServiceProtos.SyncMessage.Request r = SignalServiceProtos.SyncMessage.Request.newBuilder().setType(SignalServiceProtos.SyncMessage.Request.Type.CONTACTS).build();
SignalServiceSyncMessage message = SignalServiceSyncMessage.forRequest(new RequestMessage(r));
try {
}
}
- private void requestSyncBlocked() throws IOException {
+ void requestSyncBlocked() throws IOException {
SignalServiceProtos.SyncMessage.Request r = SignalServiceProtos.SyncMessage.Request.newBuilder().setType(SignalServiceProtos.SyncMessage.Request.Type.BLOCKED).build();
SignalServiceSyncMessage message = SignalServiceSyncMessage.forRequest(new RequestMessage(r));
try {
}
}
- private void requestSyncConfiguration() throws IOException {
+ void requestSyncConfiguration() throws IOException {
SignalServiceProtos.SyncMessage.Request r = SignalServiceProtos.SyncMessage.Request.newBuilder().setType(SignalServiceProtos.SyncMessage.Request.Type.CONFIGURATION).build();
SignalServiceSyncMessage message = SignalServiceSyncMessage.forRequest(new RequestMessage(r));
try {
/**
* 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());
}
}
}
}
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);
}
if (group != null) {
try {
sendUpdateGroupMessage(groupInfo.getGroupId(), source);
- } catch (IOException | EncapsulatedExceptions e) {
+ } catch (IOException | EncapsulatedExceptions | AttachmentInvalidException e) {
e.printStackTrace();
- } catch (NotAGroupMemberException e) {
+ } catch (GroupNotFoundException | NotAGroupMemberException e) {
// We have left this group, so don't send a group update message
}
}
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());
}
}
}
retryFailedReceivedMessages(handler, ignoreAttachments);
final SignalServiceMessageReceiver messageReceiver = getMessageReceiver();
- try {
- if (messagePipe == null) {
- messagePipe = messageReceiver.createMessagePipe();
- }
+ if (messagePipe == null) {
+ messagePipe = messageReceiver.createMessagePipe();
+ }
- while (true) {
- SignalServiceEnvelope envelope;
- SignalServiceContent content = null;
- Exception exception = null;
- final long now = new Date().getTime();
- try {
- 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());
- Utils.storeEnvelope(envelope1, cacheFile);
- } catch (IOException e) {
- System.err.println("Failed to store encrypted message in disk cache, ignoring: " + e.getMessage());
- }
- });
- } catch (TimeoutException e) {
- if (returnOnTimeout)
- return;
- continue;
- } catch (InvalidVersionException e) {
- System.err.println("Ignoring error: " + e.getMessage());
- continue;
- }
- if (!envelope.isReceipt()) {
- try {
- content = decryptMessage(envelope);
- } catch (Exception e) {
- exception = e;
- }
- handleMessage(envelope, content, ignoreAttachments);
- }
- account.save();
- if (!isMessageBlocked(envelope, content)) {
- handler.handleMessage(envelope, content, exception);
- }
- if (!(exception instanceof org.whispersystems.libsignal.UntrustedIdentityException)) {
- File cacheFile = null;
+ while (true) {
+ SignalServiceEnvelope envelope;
+ SignalServiceContent content = null;
+ Exception exception = null;
+ final long now = new Date().getTime();
+ try {
+ envelope = messagePipe.read(timeout, unit, envelope1 -> {
+ // store message on disk, before acknowledging receipt to the server
try {
- cacheFile = getMessageCacheFile(envelope.getSourceE164().get(), now, envelope.getTimestamp());
- Files.delete(cacheFile.toPath());
- // Try to delete directory if empty
- new File(getMessageCachePath()).delete();
+ 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 delete cached message file “" + cacheFile + "”: " + e.getMessage());
+ System.err.println("Failed to store encrypted message in disk cache, ignoring: " + e.getMessage());
}
+ });
+ } catch (TimeoutException e) {
+ if (returnOnTimeout)
+ return;
+ continue;
+ } catch (InvalidVersionException e) {
+ System.err.println("Ignoring error: " + e.getMessage());
+ continue;
+ }
+ if (!envelope.isReceipt()) {
+ try {
+ content = decryptMessage(envelope);
+ } catch (Exception e) {
+ exception = e;
}
+ handleMessage(envelope, content, ignoreAttachments);
}
- } finally {
- if (messagePipe != null) {
- messagePipe.shutdown();
- messagePipe = null;
+ account.save();
+ if (!isMessageBlocked(envelope, content)) {
+ handler.handleMessage(envelope, content, exception);
+ }
+ if (!(exception instanceof org.whispersystems.libsignal.UntrustedIdentityException)) {
+ File cacheFile = null;
+ try {
+ cacheFile = getMessageCacheFile(envelope.getSourceE164().get(), now, envelope.getTimestamp());
+ Files.delete(cacheFile.toPath());
+ // Try to delete directory if empty
+ new File(getMessageCachePath()).delete();
+ } catch (IOException e) {
+ System.err.println("Failed to delete cached message file “" + cacheFile + "”: " + 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();
}
}
}
private File getContactAvatarFile(String number) {
- return new File(avatarsPath, "contact-" + number);
+ return new File(pathConfig.getAvatarsPath(), "contact-" + number);
}
- private File retrieveContactAvatarAttachment(SignalServiceAttachment attachment, String number) throws IOException, InvalidMessageException {
- IOUtils.createPrivateDirectories(avatarsPath);
+ private File retrieveContactAvatarAttachment(SignalServiceAttachment attachment, String number) throws IOException, InvalidMessageException, MissingConfigurationException {
+ IOUtils.createPrivateDirectories(pathConfig.getAvatarsPath());
if (attachment.isPointer()) {
SignalServiceAttachmentPointer pointer = attachment.asPointer();
return retrieveAttachment(pointer, getContactAvatarFile(number), false);
}
private File getGroupAvatarFile(byte[] groupId) {
- return new File(avatarsPath, "group-" + Base64.encodeBytes(groupId).replace("/", "_"));
+ return new File(pathConfig.getAvatarsPath(), "group-" + Base64.encodeBytes(groupId).replace("/", "_"));
}
- private File retrieveGroupAvatarAttachment(SignalServiceAttachment attachment, byte[] groupId) throws IOException, InvalidMessageException {
- IOUtils.createPrivateDirectories(avatarsPath);
+ private File retrieveGroupAvatarAttachment(SignalServiceAttachment attachment, byte[] groupId) throws IOException, InvalidMessageException, MissingConfigurationException {
+ IOUtils.createPrivateDirectories(pathConfig.getAvatarsPath());
if (attachment.isPointer()) {
SignalServiceAttachmentPointer pointer = attachment.asPointer();
return retrieveAttachment(pointer, getGroupAvatarFile(groupId), false);
}
}
- public File getAttachmentFile(long attachmentId) {
- return new File(attachmentsPath, attachmentId + "");
+ public File getAttachmentFile(SignalServiceAttachmentRemoteId attachmentId) {
+ return new File(pathConfig.getAttachmentsPath(), attachmentId.toString());
}
- private File retrieveAttachment(SignalServiceAttachmentPointer pointer) throws IOException, InvalidMessageException {
- IOUtils.createPrivateDirectories(attachmentsPath);
- return retrieveAttachment(pointer, getAttachmentFile(pointer.getId()), true);
+ private File retrieveAttachment(SignalServiceAttachmentPointer pointer) throws IOException, InvalidMessageException, MissingConfigurationException {
+ IOUtils.createPrivateDirectories(pathConfig.getAttachmentsPath());
+ 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);
- }
-
- @Override
- public boolean isRemote() {
- return false;
+ return messageReceiver.retrieveAttachment(pointer, tmpFile, ServiceConfig.MAX_ATTACHMENT_SIZE);
}
private void sendGroups() throws IOException, UntrustedIdentityException {
}
public String computeSafetyNumber(SignalServiceAddress theirAddress, IdentityKey theirIdentityKey) {
- return Utils.computeSafetyNumber(account.getSelfAddress(), getIdentity(), theirAddress, theirIdentityKey);
+ return Utils.computeSafetyNumber(account.getSelfAddress(), getIdentityKeyPair().getPublicKey(), theirAddress, theirIdentityKey);
+ }
+
+ void saveAccount() {
+ account.save();
}
public SignalServiceAddress canonicalizeAndResolveSignalServiceAddress(String identifier) throws InvalidNumberException {
return account.getRecipientStore().resolveServiceAddress(address);
}
+ @Override
+ public void close() throws IOException {
+ if (messagePipe != null) {
+ messagePipe.shutdown();
+ messagePipe = null;
+ }
+
+ if (unidentifiedMessagePipe != null) {
+ unidentifiedMessagePipe.shutdown();
+ unidentifiedMessagePipe = null;
+ }
+
+ account.close();
+ }
+
public interface ReceiveMessageHandler {
void handleMessage(SignalServiceEnvelope envelope, SignalServiceContent decryptedContent, Throwable e);