import org.asamk.signal.manager.storage.groups.GroupInfo;
import org.asamk.signal.manager.storage.groups.GroupInfoV1;
import org.asamk.signal.manager.storage.groups.GroupInfoV2;
+import org.asamk.signal.manager.storage.messageCache.CachedMessage;
import org.asamk.signal.manager.storage.profiles.SignalProfile;
import org.asamk.signal.manager.storage.profiles.SignalProfileEntry;
import org.asamk.signal.manager.storage.protocol.IdentityInfo;
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.MessageCacheUtils;
import org.asamk.signal.manager.util.Utils;
import org.signal.libsignal.metadata.InvalidMetadataMessageException;
import org.signal.libsignal.metadata.InvalidMetadataVersionException;
import org.whispersystems.libsignal.InvalidKeyException;
import org.whispersystems.libsignal.InvalidMessageException;
import org.whispersystems.libsignal.InvalidVersionException;
-import org.whispersystems.libsignal.ecc.Curve;
-import org.whispersystems.libsignal.ecc.ECKeyPair;
import org.whispersystems.libsignal.ecc.ECPublicKey;
import org.whispersystems.libsignal.state.PreKeyRecord;
import org.whispersystems.libsignal.state.SignedPreKeyRecord;
-import org.whispersystems.libsignal.util.Medium;
import org.whispersystems.libsignal.util.Pair;
import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.KeyBackupService;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
-import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
public class Manager implements Closeable {
- final static Logger logger = LoggerFactory.getLogger(Manager.class);
+ private final static Logger logger = LoggerFactory.getLogger(Manager.class);
- private final SleepTimer timer = new UptimeSleepTimer();
private final CertificateValidator certificateValidator = new CertificateValidator(ServiceConfig.getUnidentifiedSenderTrustRoot());
private final SignalServiceConfiguration serviceConfiguration;
this.userAgent = userAgent;
this.groupsV2Operations = capabilities.isGv2() ? new GroupsV2Operations(ClientZkOperations.create(
serviceConfiguration)) : null;
+ final SleepTimer timer = new UptimeSleepTimer();
this.accountManager = new SignalServiceAccountManager(serviceConfiguration,
new DynamicCredentialsProvider(account.getUuid(),
account.getUsername(),
return account.getDeviceId();
}
- private File getMessageCachePath() {
- return SignalAccount.getMessageCachePath(pathConfig.getDataPath(), account.getUsername());
- }
-
- private File getMessageCachePath(String sender) {
- if (sender == null || sender.isEmpty()) {
- return getMessageCachePath();
- }
-
- return new File(getMessageCachePath(), sender.replace("/", "_"));
- }
-
- private File getMessageCacheFile(String sender, long now, long timestamp) throws IOException {
- File cachePath = getMessageCachePath(sender);
- IOUtils.createPrivateDirectories(cachePath);
- return new File(cachePath, now + "_" + timestamp);
- }
-
public static Manager init(
String username, File settingsPath, SignalServiceConfiguration serviceConfiguration, String userAgent
) throws IOException, NotRegisteredException {
}
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();
- }
- updateAccountAttributes();
+ if (accountManager.getPreKeysCount() < ServiceConfig.PREKEY_MINIMUM_COUNT) {
+ refreshPreKeys();
+ account.save();
}
- }
-
- public boolean isRegistered() {
- return account.isRegistered();
+ if (account.getUuid() == null) {
+ account.setUuid(accountManager.getOwnUuid());
+ account.save();
+ }
+ updateAccountAttributes();
}
/**
*
* @param numbers The set of phone number in question
* @return A map of numbers to booleans. True if registered, false otherwise. Should never be null
- * @throws IOException if its unable to check if the users are registered
+ * @throws IOException if its unable to get the contacts to check if they're registered
*/
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
account.save();
}
- private List<PreKeyRecord> generatePreKeys() {
- List<PreKeyRecord> records = new ArrayList<>(ServiceConfig.PREKEY_BATCH_SIZE);
-
- final int offset = account.getPreKeyIdOffset();
- 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);
-
- records.add(record);
- }
-
- account.addPreKeys(records);
- account.save();
-
- return records;
- }
-
- private SignedPreKeyRecord generateSignedPreKey(IdentityKeyPair identityKeyPair) {
- try {
- ECKeyPair keyPair = Curve.generateKeyPair();
- byte[] signature = Curve.calculateSignature(identityKeyPair.getPrivateKey(),
- keyPair.getPublicKey().serialize());
- SignedPreKeyRecord record = new SignedPreKeyRecord(account.getNextSignedPreKeyId(),
- System.currentTimeMillis(),
- keyPair,
- signature);
-
- account.addSignedPreKey(record);
- account.save();
-
- return record;
- } catch (InvalidKeyException e) {
- throw new AssertionError(e);
- }
- }
-
public void setRegistrationLockPin(Optional<String> pin) throws IOException, UnauthenticatedResponseException {
if (pin.isPresent()) {
final MasterKey masterKey = account.getPinMasterKey() != null
accountManager.setPreKeys(identityKeyPair.getPublicKey(), signedPreKeyRecord, oneTimePreKeys);
}
+ private List<PreKeyRecord> generatePreKeys() {
+ final int offset = account.getPreKeyIdOffset();
+
+ List<PreKeyRecord> records = KeyUtils.generatePreKeyRecords(offset, ServiceConfig.PREKEY_BATCH_SIZE);
+ account.addPreKeys(records);
+ account.save();
+
+ return records;
+ }
+
+ private SignedPreKeyRecord generateSignedPreKey(IdentityKeyPair identityKeyPair) {
+ final int signedPreKeyId = account.getNextSignedPreKeyId();
+
+ SignedPreKeyRecord record = KeyUtils.generateSignedPreKeyRecord(identityKeyPair, signedPreKeyId);
+ account.addSignedPreKey(record);
+ account.save();
+
+ return record;
+ }
+
private SignalServiceMessagePipe getOrCreateMessagePipe() {
if (messagePipe == null) {
messagePipe = messageReceiver.createMessagePipe();
ServiceConfig.MAX_ENVELOPE_SIZE);
}
- private SignalServiceProfile getEncryptedRecipientProfile(SignalServiceAddress address) throws IOException {
- return profileHelper.retrieveProfileSync(address, SignalServiceProfile.RequestType.PROFILE).getProfile();
- }
-
private SignalProfile getRecipientProfile(
SignalServiceAddress address
) {
private SignalProfile retrieveRecipientProfile(
SignalServiceAddress address, ProfileKey profileKey
) throws IOException {
- final SignalServiceProfile encryptedProfile = getEncryptedRecipientProfile(address);
+ final SignalServiceProfile encryptedProfile = profileHelper.retrieveProfileSync(address,
+ SignalServiceProfile.RequestType.PROFILE).getProfile();
return decryptProfile(address, profileKey, encryptedProfile);
}
}
}
- private void retryFailedReceivedMessages(
- ReceiveMessageHandler handler, boolean ignoreAttachments
- ) {
- final File cachePath = getMessageCachePath();
- if (!cachePath.exists()) {
- return;
- }
- for (final File dir : Objects.requireNonNull(cachePath.listFiles())) {
- if (!dir.isDirectory()) {
- retryFailedReceivedMessage(handler, ignoreAttachments, dir);
- continue;
- }
-
- for (final File fileEntry : Objects.requireNonNull(dir.listFiles())) {
- if (!fileEntry.isFile()) {
- continue;
- }
- retryFailedReceivedMessage(handler, ignoreAttachments, fileEntry);
- }
- // Try to delete directory if empty
- dir.delete();
+ private void retryFailedReceivedMessages(ReceiveMessageHandler handler, boolean ignoreAttachments) {
+ for (CachedMessage cachedMessage : account.getMessageCache().getCachedMessages()) {
+ retryFailedReceivedMessage(handler, ignoreAttachments, cachedMessage);
}
}
private void retryFailedReceivedMessage(
- final ReceiveMessageHandler handler, final boolean ignoreAttachments, final File fileEntry
+ final ReceiveMessageHandler handler, final boolean ignoreAttachments, final CachedMessage cachedMessage
) {
- SignalServiceEnvelope envelope;
- try {
- envelope = MessageCacheUtils.loadEnvelope(fileEntry);
- if (envelope == null) {
- return;
- }
- } catch (IOException e) {
- e.printStackTrace();
+ SignalServiceEnvelope envelope = cachedMessage.loadEnvelope();
+ if (envelope == null) {
return;
}
SignalServiceContent content = null;
return;
} catch (Exception er) {
// All other errors are not recoverable, so delete the cached message
- try {
- Files.delete(fileEntry.toPath());
- } catch (IOException e) {
- logger.warn("Failed to delete cached message file “{}”, ignoring: {}", fileEntry, e.getMessage());
- }
+ cachedMessage.delete();
return;
}
List<HandleAction> actions = handleMessage(envelope, content, ignoreAttachments);
}
account.save();
handler.handleMessage(envelope, content, null);
- try {
- Files.delete(fileEntry.toPath());
- } catch (IOException e) {
- logger.warn("Failed to delete cached message file “{}”, ignoring: {}", fileEntry, e.getMessage());
- }
+ cachedMessage.delete();
}
public void receiveMessages(
Set<HandleAction> queuedActions = null;
- getOrCreateMessagePipe();
+ final SignalServiceMessagePipe messagePipe = getOrCreateMessagePipe();
boolean hasCaughtUpWithOldMessages = false;
SignalServiceEnvelope envelope;
SignalServiceContent content = null;
Exception exception = null;
- final long now = new Date().getTime();
+ final CachedMessage[] cachedMessage = {null};
try {
Optional<SignalServiceEnvelope> result = messagePipe.readOrEmpty(timeout, unit, envelope1 -> {
// store message on disk, before acknowledging receipt to the server
- try {
- String source = envelope1.getSourceE164().isPresent() ? envelope1.getSourceE164().get() : "";
- File cacheFile = getMessageCacheFile(source, now, envelope1.getTimestamp());
- MessageCacheUtils.storeEnvelope(envelope1, cacheFile);
- } catch (IOException e) {
- logger.warn("Failed to store encrypted message in disk cache, ignoring: {}", e.getMessage());
- }
+ cachedMessage[0] = account.getMessageCache().cacheMessage(envelope1);
});
if (result.isPresent()) {
envelope = result.get();
}
}
account.save();
- if (!isMessageBlocked(envelope, content)) {
+ if (isMessageBlocked(envelope, content)) {
+ logger.info("Ignoring a message from blocked user/group: {}", envelope.getTimestamp());
+ } else if (isNotAGroupMember(envelope, content)) {
+ logger.info("Ignoring a message from a non group member: {}", envelope.getTimestamp());
+ } else {
handler.handleMessage(envelope, content, exception);
}
if (!(exception instanceof org.whispersystems.libsignal.UntrustedIdentityException)) {
- File cacheFile = null;
- try {
- String source = envelope.getSourceE164().isPresent() ? envelope.getSourceE164().get() : "";
- cacheFile = getMessageCacheFile(source, now, envelope.getTimestamp());
- Files.delete(cacheFile.toPath());
- // Try to delete directory if empty
- getMessageCachePath().delete();
- } catch (IOException e) {
- logger.warn("Failed to delete cached message file “{}”, ignoring: {}", cacheFile, e.getMessage());
+ if (cachedMessage[0] != null) {
+ cachedMessage[0].delete();
}
}
}
return true;
}
+ if (content != null && content.getDataMessage().isPresent()) {
+ SignalServiceDataMessage message = content.getDataMessage().get();
+ if (message.getGroupContext().isPresent()) {
+ GroupId groupId = GroupUtils.getGroupId(message.getGroupContext().get());
+ GroupInfo group = getGroup(groupId);
+ if (group != null && group.isBlocked()) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private boolean isNotAGroupMember(
+ SignalServiceEnvelope envelope, SignalServiceContent content
+ ) {
+ SignalServiceAddress source;
+ if (!envelope.isUnidentifiedSender() && envelope.hasSource()) {
+ source = envelope.getSourceAddress();
+ } else if (content != null) {
+ source = content.getSender();
+ } else {
+ return false;
+ }
+
if (content != null && content.getDataMessage().isPresent()) {
SignalServiceDataMessage message = content.getDataMessage().get();
if (message.getGroupContext().isPresent()) {
if (message.getGroupContext().get().getGroupV1().isPresent()) {
SignalServiceGroup groupInfo = message.getGroupContext().get().getGroupV1().get();
- if (groupInfo.getType() != SignalServiceGroup.Type.DELIVER) {
+ if (groupInfo.getType() == SignalServiceGroup.Type.QUIT) {
return false;
}
}
GroupId groupId = GroupUtils.getGroupId(message.getGroupContext().get());
GroupInfo group = getGroup(groupId);
- if (group != null && group.isBlocked()) {
+ if (group != null && !group.isMember(source)) {
return true;
}
}