mirror of
https://github.com/signalapp/Signal-Server
synced 2026-04-26 05:18:06 +01:00
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:
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)")
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user