Initial multi device support refactoring.

1) Store account data as a json type, which includes all
   devices in a single object.

2) Simplify message delivery logic.

3) Make federated calls a pass through to standard controllers.

4) Simplify key retrieval logic.
This commit is contained in:
Moxie Marlinspike
2014-01-18 23:45:07 -08:00
parent 6f9226dcf9
commit 74f71fd8a6
47 changed files with 961 additions and 1211 deletions

View File

@@ -17,35 +17,43 @@
package org.whispersystems.textsecuregcm.storage;
import org.whispersystems.textsecuregcm.auth.AuthenticationCredentials;
import org.whispersystems.textsecuregcm.util.Util;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.base.Optional;
import java.io.Serializable;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class Account implements Serializable {
private String number;
private boolean supportsSms;
private Map<Long, Device> devices = new HashMap<>();
private Account(String number, boolean supportsSms) {
public static final int MEMCACHE_VERION = 2;
@JsonProperty
private String number;
@JsonProperty
private boolean supportsSms;
@JsonProperty
private List<Device> devices = new LinkedList<>();
@JsonIgnore
private Optional<Device> authenticatedDevice;
public Account() {}
public Account(String number, boolean supportsSms) {
this.number = number;
this.supportsSms = supportsSms;
}
public Account(String number, boolean supportsSms, Device onlyDevice) {
this(number, supportsSms);
addDevice(onlyDevice);
public Optional<Device> getAuthenticatedDevice() {
return authenticatedDevice;
}
public Account(String number, boolean supportsSms, List<Device> devices) {
this(number, supportsSms);
for (Device device : devices)
addDevice(device);
public void setAuthenticatedDevice(Device device) {
this.authenticatedDevice = Optional.of(device);
}
public void setNumber(String number) {
@@ -64,30 +72,55 @@ public class Account implements Serializable {
this.supportsSms = supportsSms;
}
public boolean isActive() {
Device masterDevice = devices.get((long) 1);
return masterDevice != null && masterDevice.isActive();
public void addDevice(Device device) {
this.devices.add(device);
}
public Collection<Device> getDevices() {
return devices.values();
public void setDevices(List<Device> devices) {
this.devices = devices;
}
public Device getDevice(long destinationDeviceId) {
return devices.get(destinationDeviceId);
public List<Device> getDevices() {
return devices;
}
public boolean hasAllDeviceIds(Set<Long> deviceIds) {
if (devices.size() != deviceIds.size())
return false;
for (long deviceId : devices.keySet()) {
if (!deviceIds.contains(deviceId))
return false;
public Optional<Device> getMasterDevice() {
return getDevice(Device.MASTER_ID);
}
public Optional<Device> getDevice(long deviceId) {
for (Device device : devices) {
if (device.getId() == deviceId) {
return Optional.of(device);
}
}
return Optional.absent();
}
public boolean isActive() {
return
getMasterDevice().isPresent() &&
getMasterDevice().get().isActive();
}
public long getNextDeviceId() {
long highestDevice = Device.MASTER_ID;
for (Device device : devices) {
if (device.getId() > highestDevice) {
highestDevice = device.getId();
}
}
return highestDevice + 1;
}
public boolean isRateLimited() {
return true;
}
public void addDevice(Device device) {
devices.put(device.getDeviceId(), device);
public Optional<String> getRelay() {
return Optional.absent();
}
}

View File

@@ -16,6 +16,10 @@
*/
package org.whispersystems.textsecuregcm.storage;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.skife.jdbi.v2.SQLStatement;
import org.skife.jdbi.v2.StatementContext;
import org.skife.jdbi.v2.TransactionIsolationLevel;
@@ -28,10 +32,9 @@ import org.skife.jdbi.v2.sqlobject.SqlQuery;
import org.skife.jdbi.v2.sqlobject.SqlUpdate;
import org.skife.jdbi.v2.sqlobject.Transaction;
import org.skife.jdbi.v2.sqlobject.customizers.Mapper;
import org.skife.jdbi.v2.sqlobject.stringtemplate.UseStringTemplate3StatementLocator;
import org.skife.jdbi.v2.tweak.ResultSetMapper;
import org.skife.jdbi.v2.unstable.BindIn;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
@@ -39,91 +42,63 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
@UseStringTemplate3StatementLocator
public abstract class Accounts {
public static final String ID = "id";
public static final String NUMBER = "number";
public static final String DEVICE_ID = "device_id";
public static final String AUTH_TOKEN = "auth_token";
public static final String SALT = "salt";
public static final String SIGNALING_KEY = "signaling_key";
public static final String GCM_ID = "gcm_id";
public static final String APN_ID = "apn_id";
public static final String FETCHES_MESSAGES = "fetches_messages";
public static final String SUPPORTS_SMS = "supports_sms";
private static final String ID = "id";
private static final String NUMBER = "number";
private static final String DATA = "data";
@SqlUpdate("INSERT INTO accounts (" + NUMBER + ", " + DEVICE_ID + ", " + AUTH_TOKEN + ", " +
SALT + ", " + SIGNALING_KEY + ", " + FETCHES_MESSAGES + ", " +
GCM_ID + ", " + APN_ID + ", " + SUPPORTS_SMS + ") " +
"VALUES (:number, :device_id, :auth_token, :salt, :signaling_key, :fetches_messages, :gcm_id, :apn_id, :supports_sms)")
@GetGeneratedKeys
abstract long insertStep(@AccountBinder Device device);
private static final ObjectMapper mapper = new ObjectMapper();
@SqlQuery("SELECT " + DEVICE_ID + " FROM accounts WHERE " + NUMBER + " = :number ORDER BY " + DEVICE_ID + " DESC LIMIT 1 FOR UPDATE")
abstract long getHighestDeviceId(@Bind("number") String number);
@Transaction(TransactionIsolationLevel.SERIALIZABLE)
public long insert(@AccountBinder Device device) {
device.setDeviceId(getHighestDeviceId(device.getNumber()) + 1);
return insertStep(device);
static {
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE);
mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
}
@SqlUpdate("DELETE FROM accounts WHERE " + NUMBER + " = :number RETURNING id")
abstract void removeAccountsByNumber(@Bind("number") String number);
@SqlUpdate("INSERT INTO accounts (" + NUMBER + ", " + DATA + ") VALUES (:number, CAST(:data AS json))")
@GetGeneratedKeys
abstract long insertStep(@AccountBinder Account account);
@SqlUpdate("UPDATE accounts SET " + AUTH_TOKEN + " = :auth_token, " + SALT + " = :salt, " +
SIGNALING_KEY + " = :signaling_key, " + GCM_ID + " = :gcm_id, " + APN_ID + " = :apn_id, " +
FETCHES_MESSAGES + " = :fetches_messages, " + SUPPORTS_SMS + " = :supports_sms " +
"WHERE " + NUMBER + " = :number AND " + DEVICE_ID + " = :device_id")
abstract void update(@AccountBinder Device device);
@SqlUpdate("DELETE FROM accounts WHERE " + NUMBER + " = :number")
abstract void removeAccount(@Bind("number") String number);
@Mapper(DeviceMapper.class)
@SqlQuery("SELECT * FROM accounts WHERE " + NUMBER + " = :number AND " + DEVICE_ID + " = :device_id")
abstract Device get(@Bind("number") String number, @Bind("device_id") long deviceId);
@SqlUpdate("UPDATE accounts SET " + DATA + " = CAST(:data AS json) WHERE " + NUMBER + " = :number")
abstract void update(@AccountBinder Account account);
@Mapper(AccountMapper.class)
@SqlQuery("SELECT * FROM accounts WHERE " + NUMBER + " = :number")
abstract Account get(@Bind("number") String number);
@SqlQuery("SELECT COUNT(DISTINCT " + NUMBER + ") from accounts")
abstract long getNumberCount();
abstract long getCount();
@Mapper(DeviceMapper.class)
@SqlQuery("SELECT * FROM accounts WHERE " + DEVICE_ID + " = 1 OFFSET :offset LIMIT :limit")
abstract List<Device> getAllMasterDevices(@Bind("offset") int offset, @Bind("limit") int length);
@Mapper(AccountMapper.class)
@SqlQuery("SELECT * FROM accounts OFFSET :offset LIMIT :limit")
abstract List<Account> getAll(@Bind("offset") int offset, @Bind("limit") int length);
@Mapper(DeviceMapper.class)
@SqlQuery("SELECT * FROM accounts WHERE " + DEVICE_ID + " = 1")
public abstract Iterator<Device> getAllMasterDevices();
@Mapper(DeviceMapper.class)
@SqlQuery("SELECT * FROM accounts WHERE " + NUMBER + " = :number")
public abstract List<Device> getAllByNumber(@Bind("number") String number);
@Mapper(DeviceMapper.class)
@SqlQuery("SELECT * FROM accounts WHERE " + NUMBER + " IN ( <numbers> )")
public abstract List<Device> getAllByNumbers(@BindIn("numbers") List<String> numbers);
@Mapper(AccountMapper.class)
@SqlQuery("SELECT * FROM accounts")
public abstract Iterator<Account> getAll();
@Transaction(TransactionIsolationLevel.SERIALIZABLE)
public long insertClearingNumber(Device device) {
removeAccountsByNumber(device.getNumber());
device.setDeviceId(getHighestDeviceId(device.getNumber()) + 1);
return insertStep(device);
public long create(Account account) {
removeAccount(account.getNumber());
return insertStep(account);
}
public static class DeviceMapper implements ResultSetMapper<Device> {
public static class AccountMapper implements ResultSetMapper<Account> {
@Override
public Device map(int i, ResultSet resultSet, StatementContext statementContext)
public Account map(int i, ResultSet resultSet, StatementContext statementContext)
throws SQLException
{
return new Device(resultSet.getLong(ID), resultSet.getString(NUMBER), resultSet.getLong(DEVICE_ID),
resultSet.getString(AUTH_TOKEN), resultSet.getString(SALT),
resultSet.getString(SIGNALING_KEY), resultSet.getString(GCM_ID),
resultSet.getString(APN_ID),
resultSet.getInt(SUPPORTS_SMS) == 1, resultSet.getInt(FETCHES_MESSAGES) == 1);
try {
return mapper.readValue(resultSet.getString(DATA), Account.class);
} catch (IOException e) {
throw new SQLException(e);
}
}
}
@@ -134,23 +109,20 @@ public abstract class Accounts {
public static class AccountBinderFactory implements BinderFactory {
@Override
public Binder build(Annotation annotation) {
return new Binder<AccountBinder, Device>() {
return new Binder<AccountBinder, Account>() {
@Override
public void bind(SQLStatement<?> sql,
AccountBinder accountBinder,
Device device)
Account account)
{
sql.bind(ID, device.getId());
sql.bind(NUMBER, device.getNumber());
sql.bind(DEVICE_ID, device.getDeviceId());
sql.bind(AUTH_TOKEN, device.getAuthenticationCredentials()
.getHashedAuthenticationToken());
sql.bind(SALT, device.getAuthenticationCredentials().getSalt());
sql.bind(SIGNALING_KEY, device.getSignalingKey());
sql.bind(GCM_ID, device.getGcmRegistrationId());
sql.bind(APN_ID, device.getApnRegistrationId());
sql.bind(SUPPORTS_SMS, device.getSupportsSms() ? 1 : 0);
sql.bind(FETCHES_MESSAGES, device.getFetchesMessages() ? 1 : 0);
try {
String serialized = mapper.writeValueAsString(account);
sql.bind(NUMBER, account.getNumber());
sql.bind(DATA, serialized);
} catch (JsonProcessingException e) {
throw new IllegalArgumentException(e);
}
}
};
}

View File

@@ -49,131 +49,67 @@ public class AccountsManager {
}
public long getCount() {
return accounts.getNumberCount();
return accounts.getCount();
}
public List<Device> getAllMasterDevices(int offset, int length) {
return accounts.getAllMasterDevices(offset, length);
public List<Account> getAll(int offset, int length) {
return accounts.getAll(offset, length);
}
public Iterator<Device> getAllMasterDevices() {
return accounts.getAllMasterDevices();
public Iterator<Account> getAll() {
return accounts.getAll();
}
/** Creates a new Account (WITH ONE DEVICE), clearing all existing devices on the given number */
public void create(Account account) {
Device device = account.getDevices().iterator().next();
long id = accounts.insertClearingNumber(device);
device.setId(id);
accounts.create(account);
if (memcachedClient != null) {
memcachedClient.set(getKey(device.getNumber(), device.getDeviceId()), 0, device);
memcachedClient.set(getKey(account.getNumber()), 0, account);
}
updateDirectory(device);
updateDirectory(account);
}
/** Creates a new Device for an existing Account */
public void provisionDevice(Device device) {
long id = accounts.insert(device);
device.setId(id);
public void update(Account account) {
if (memcachedClient != null) {
memcachedClient.set(getKey(device.getNumber(), device.getDeviceId()), 0, device);
memcachedClient.set(getKey(account.getNumber()), 0, account);
}
updateDirectory(device);
accounts.update(account);
updateDirectory(account);
}
public void update(Device device) {
if (memcachedClient != null) {
memcachedClient.set(getKey(device.getNumber(), device.getDeviceId()), 0, device);
}
accounts.update(device);
updateDirectory(device);
}
public Optional<Device> get(String number, long deviceId) {
Device device = null;
public Optional<Account> get(String number) {
Account account = null;
if (memcachedClient != null) {
device = (Device)memcachedClient.get(getKey(number, deviceId));
account = (Account)memcachedClient.get(getKey(number));
}
if (device == null) {
device = accounts.get(number, deviceId);
if (account == null) {
account = accounts.get(number);
if (device != null && memcachedClient != null) {
memcachedClient.set(getKey(number, deviceId), 0, device);
if (account != null && memcachedClient != null) {
memcachedClient.set(getKey(number), 0, account);
}
}
if (device != null) return Optional.of(device);
if (account != null) return Optional.of(account);
else return Optional.absent();
}
public Optional<Account> getAccount(String number) {
List<Device> devices = accounts.getAllByNumber(number);
if (devices.isEmpty())
return Optional.absent();
return Optional.of(new Account(number, devices.get(0).getSupportsSms(), devices));
}
private List<Account> getAllAccounts(List<String> numbers) {
List<Device> devices = accounts.getAllByNumbers(numbers);
List<Account> accounts = new LinkedList<>();
for (Device device : devices) {
Account deviceAccount = null;
for (Account account : accounts) {
if (account.getNumber().equals(device.getNumber())) {
deviceAccount = account;
break;
}
}
if (deviceAccount == null) {
deviceAccount = new Account(device.getNumber(), false, device);
accounts.add(deviceAccount);
} else {
deviceAccount.addDevice(device);
}
if (device.getDeviceId() == 1)
deviceAccount.setSupportsSms(device.getSupportsSms());
}
return accounts;
}
public List<Account> getAccountsForDevices(Map<String, Set<Long>> destinations) throws MissingDevicesException {
Set<String> numbersMissingDevices = new HashSet<>(destinations.keySet());
List<Account> localAccounts = getAllAccounts(new LinkedList<>(destinations.keySet()));
for (Account account : localAccounts){
if (account.hasAllDeviceIds(destinations.get(account.getNumber())))
numbersMissingDevices.remove(account.getNumber());
}
if (!numbersMissingDevices.isEmpty())
throw new MissingDevicesException(numbersMissingDevices);
return localAccounts;
}
private void updateDirectory(Device device) {
if (device.getDeviceId() != 1)
return;
if (device.isActive()) {
byte[] token = Util.getContactToken(device.getNumber());
ClientContact clientContact = new ClientContact(token, null, device.getSupportsSms());
private void updateDirectory(Account account) {
if (account.isActive()) {
byte[] token = Util.getContactToken(account.getNumber());
ClientContact clientContact = new ClientContact(token, null, account.getSupportsSms());
directory.add(clientContact);
} else {
directory.remove(device.getNumber());
directory.remove(account.getNumber());
}
}
private String getKey(String number, long accountId) {
return Device.class.getSimpleName() + Device.MEMCACHE_VERION + number + accountId;
private String getKey(String number) {
return Account.class.getSimpleName() + Account.MEMCACHE_VERION + number;
}
}

View File

@@ -17,6 +17,7 @@
package org.whispersystems.textsecuregcm.storage;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.whispersystems.textsecuregcm.auth.AuthenticationCredentials;
import org.whispersystems.textsecuregcm.util.Util;
@@ -24,97 +25,58 @@ import java.io.Serializable;
public class Device implements Serializable {
public static final int MEMCACHE_VERION = 1;
public static final long MASTER_ID = 1;
@JsonProperty
private long id;
private String number;
private long deviceId;
private String hashedAuthenticationToken;
@JsonProperty
private String authToken;
@JsonProperty
private String salt;
@JsonProperty
private String signalingKey;
/**
* In order for us to tell a client that an account is "inactive" (ie go use SMS for transport), we check that all
* non-fetching Accounts don't have push registrations. In this way, we can ensure that we have some form of transport
* available for all Accounts on all "active" numbers.
*/
private String gcmRegistrationId;
private String apnRegistrationId;
private boolean supportsSms;
@JsonProperty
private String gcmId;
@JsonProperty
private String apnId;
@JsonProperty
private boolean fetchesMessages;
public Device() {}
public Device(long id, String number, long deviceId, String hashedAuthenticationToken, String salt,
String signalingKey, String gcmRegistrationId, String apnRegistrationId,
boolean supportsSms, boolean fetchesMessages)
public Device(long id, String authToken, String salt,
String signalingKey, String gcmId, String apnId,
boolean fetchesMessages)
{
this.id = id;
this.number = number;
this.deviceId = deviceId;
this.hashedAuthenticationToken = hashedAuthenticationToken;
this.salt = salt;
this.signalingKey = signalingKey;
this.gcmRegistrationId = gcmRegistrationId;
this.apnRegistrationId = apnRegistrationId;
this.supportsSms = supportsSms;
this.fetchesMessages = fetchesMessages;
this.id = id;
this.authToken = authToken;
this.salt = salt;
this.signalingKey = signalingKey;
this.gcmId = gcmId;
this.apnId = apnId;
this.fetchesMessages = fetchesMessages;
}
public String getApnRegistrationId() {
return apnRegistrationId;
public String getApnId() {
return apnId;
}
public void setApnRegistrationId(String apnRegistrationId) {
this.apnRegistrationId = apnRegistrationId;
public void setApnId(String apnId) {
this.apnId = apnId;
}
public String getGcmRegistrationId() {
return gcmRegistrationId;
public String getGcmId() {
return gcmId;
}
public void setGcmRegistrationId(String gcmRegistrationId) {
this.gcmRegistrationId = gcmRegistrationId;
}
public void setNumber(String number) {
this.number = number;
}
public String getNumber() {
return number;
}
public long getDeviceId() {
return deviceId;
}
public void setDeviceId(long deviceId) {
this.deviceId = deviceId;
}
public void setAuthenticationCredentials(AuthenticationCredentials credentials) {
this.hashedAuthenticationToken = credentials.getHashedAuthenticationToken();
this.salt = credentials.getSalt();
}
public AuthenticationCredentials getAuthenticationCredentials() {
return new AuthenticationCredentials(hashedAuthenticationToken, salt);
}
public String getSignalingKey() {
return signalingKey;
}
public void setSignalingKey(String signalingKey) {
this.signalingKey = signalingKey;
}
public boolean getSupportsSms() {
return supportsSms;
}
public void setSupportsSms(boolean supportsSms) {
this.supportsSms = supportsSms;
public void setGcmId(String gcmId) {
this.gcmId = gcmId;
}
public long getId() {
@@ -125,8 +87,25 @@ public class Device implements Serializable {
this.id = id;
}
public void setAuthenticationCredentials(AuthenticationCredentials credentials) {
this.authToken = credentials.getHashedAuthenticationToken();
this.salt = credentials.getSalt();
}
public AuthenticationCredentials getAuthenticationCredentials() {
return new AuthenticationCredentials(authToken, salt);
}
public String getSignalingKey() {
return signalingKey;
}
public void setSignalingKey(String signalingKey) {
this.signalingKey = signalingKey;
}
public boolean isActive() {
return fetchesMessages || !Util.isEmpty(getApnRegistrationId()) || !Util.isEmpty(getGcmRegistrationId());
return fetchesMessages || !Util.isEmpty(getApnId()) || !Util.isEmpty(getGcmId());
}
public boolean getFetchesMessages() {
@@ -137,7 +116,7 @@ public class Device implements Serializable {
this.fetchesMessages = fetchesMessages;
}
public String getBackwardsCompatibleNumberEncoding() {
return deviceId == 1 ? number : (number + "." + deviceId);
public boolean isMaster() {
return getId() == MASTER_ID;
}
}

View File

@@ -16,6 +16,7 @@
*/
package org.whispersystems.textsecuregcm.storage;
import com.google.common.base.Optional;
import org.skife.jdbi.v2.SQLStatement;
import org.skife.jdbi.v2.StatementContext;
import org.skife.jdbi.v2.TransactionIsolationLevel;
@@ -62,8 +63,11 @@ public abstract class Keys {
@Mapper(PreKeyMapper.class)
abstract PreKey retrieveFirst(@Bind("number") String number, @Bind("device_id") long deviceId);
@SqlQuery("SELECT DISTINCT ON (number, device_id) * FROM keys WHERE number = :number ORDER BY key_id ASC FOR UPDATE")
abstract List<PreKey> retrieveFirst(@Bind("number") String number);
@Transaction(TransactionIsolationLevel.SERIALIZABLE)
public void store(String number, long deviceId, PreKey lastResortKey, List<PreKey> keys) {
public void store(String number, long deviceId, List<PreKey> keys, PreKey lastResortKey) {
for (PreKey key : keys) {
key.setNumber(number);
key.setDeviceId(deviceId);
@@ -79,20 +83,31 @@ public abstract class Keys {
}
@Transaction(TransactionIsolationLevel.SERIALIZABLE)
public UnstructuredPreKeyList get(String number, Account account) {
List<PreKey> preKeys = new LinkedList<>();
for (Device device : account.getDevices()) {
PreKey preKey = retrieveFirst(number, device.getDeviceId());
if (preKey != null)
preKeys.add(preKey);
public Optional<PreKey> get(String number, long deviceId) {
PreKey preKey = retrieveFirst(number, deviceId);
if (preKey != null && !preKey.isLastResort()) {
removeKey(preKey.getId());
}
for (PreKey preKey : preKeys) {
if (!preKey.isLastResort())
removeKey(preKey.getId());
if (preKey != null) return Optional.of(preKey);
else return Optional.absent();
}
@Transaction(TransactionIsolationLevel.SERIALIZABLE)
public Optional<UnstructuredPreKeyList> get(String number) {
List<PreKey> preKeys = retrieveFirst(number);
if (preKeys != null) {
for (PreKey preKey : preKeys) {
if (!preKey.isLastResort()) {
removeKey(preKey.getId());
}
}
}
return new UnstructuredPreKeyList(preKeys);
if (preKeys != null) return Optional.of(new UnstructuredPreKeyList(preKeys));
else return Optional.absent();
}
@BindingAnnotation(PreKeyBinder.PreKeyBinderFactory.class)

View File

@@ -20,7 +20,7 @@ import org.skife.jdbi.v2.sqlobject.Bind;
import org.skife.jdbi.v2.sqlobject.SqlQuery;
import org.skife.jdbi.v2.sqlobject.SqlUpdate;
public interface PendingDeviceRegistrations {
public interface PendingDevices {
@SqlUpdate("WITH upsert AS (UPDATE pending_devices SET verification_code = :verification_code WHERE number = :number RETURNING *) " +
"INSERT INTO pending_devices (number, verification_code) SELECT :number, :verification_code WHERE NOT EXISTS (SELECT * FROM upsert)")

View File

@@ -23,10 +23,10 @@ public class PendingDevicesManager {
private static final String MEMCACHE_PREFIX = "pending_devices";
private final PendingDeviceRegistrations pendingDevices;
private final MemcachedClient memcachedClient;
private final PendingDevices pendingDevices;
private final MemcachedClient memcachedClient;
public PendingDevicesManager(PendingDeviceRegistrations pendingDevices,
public PendingDevicesManager(PendingDevices pendingDevices,
MemcachedClient memcachedClient)
{
this.pendingDevices = pendingDevices;
@@ -42,8 +42,10 @@ public class PendingDevicesManager {
}
public void remove(String number) {
if (memcachedClient != null)
if (memcachedClient != null) {
memcachedClient.delete(MEMCACHE_PREFIX + number);
}
pendingDevices.remove(number);
}

View File

@@ -16,6 +16,7 @@
*/
package org.whispersystems.textsecuregcm.storage;
import org.whispersystems.textsecuregcm.entities.CryptoEncodingException;
import org.whispersystems.textsecuregcm.entities.EncryptedOutgoingMessage;
import java.io.IOException;
@@ -27,7 +28,9 @@ public class StoredMessageManager {
this.storedMessages = storedMessages;
}
public void storeMessage(Device device, EncryptedOutgoingMessage outgoingMessage) throws IOException {
public void storeMessage(Device device, EncryptedOutgoingMessage outgoingMessage)
throws CryptoEncodingException
{
storedMessages.insert(device.getId(), outgoingMessage.serialize());
}