New API to support multiple accounts per # (FREEBIE)

This commit is contained in:
Matt Corallo
2014-01-07 13:35:58 -10:00
parent 4cd1082a4a
commit ef1160eda8
35 changed files with 1591 additions and 388 deletions

View File

@@ -27,27 +27,36 @@ public class Account implements Serializable {
private long id;
private String number;
private long deviceId;
private String hashedAuthenticationToken;
private String salt;
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;
private boolean fetchesMessages;
public Account() {}
public Account(long id, String number, String hashedAuthenticationToken, String salt,
public Account(long id, String number, long deviceId, String hashedAuthenticationToken, String salt,
String signalingKey, String gcmRegistrationId, String apnRegistrationId,
boolean supportsSms)
boolean supportsSms, 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;
}
public String getApnRegistrationId() {
@@ -74,6 +83,14 @@ public class Account implements Serializable {
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();
@@ -106,4 +123,12 @@ public class Account implements Serializable {
public void setId(long id) {
this.id = id;
}
public void setFetchesMessages(boolean fetchesMessages) {
this.fetchesMessages = fetchesMessages;
}
public boolean getFetchesMessages() {
return fetchesMessages;
}
}

View File

@@ -29,6 +29,7 @@ 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.tweak.ResultSetMapper;
import org.whispersystems.textsecuregcm.util.NumberData;
import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
@@ -42,50 +43,76 @@ import java.util.List;
public abstract class Accounts {
public static final String ID = "id";
public static final String NUMBER = "number";
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 SUPPORTS_SMS = "supports_sms";
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";
@SqlUpdate("INSERT INTO accounts (" + NUMBER + ", " + AUTH_TOKEN + ", " +
SALT + ", " + SIGNALING_KEY + ", " + GCM_ID + ", " +
APN_ID + ", " + SUPPORTS_SMS + ") " +
"VALUES (:number, :auth_token, :salt, :signaling_key, :gcm_id, :apn_id, :supports_sms)")
@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 createStep(@AccountBinder Account account);
abstract long insertStep(@AccountBinder Account account);
@SqlUpdate("DELETE FROM accounts WHERE number = :number")
abstract void removeStep(@Bind("number") String number);
@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 Account account) {
account.setDeviceId(getHighestDeviceId(account.getNumber()) + 1);
return insertStep(account);
}
@SqlUpdate("DELETE FROM accounts WHERE " + NUMBER + " = :number RETURNING id")
abstract void removeAccountsByNumber(@Bind("number") String number);
@SqlUpdate("UPDATE accounts SET " + AUTH_TOKEN + " = :auth_token, " + SALT + " = :salt, " +
SIGNALING_KEY + " = :signaling_key, " + GCM_ID + " = :gcm_id, " +
APN_ID + " = :apn_id, " + SUPPORTS_SMS + " = :supports_sms " +
"WHERE " + NUMBER + " = :number")
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 Account account);
@Mapper(AccountMapper.class)
@SqlQuery("SELECT * FROM accounts WHERE " + NUMBER + " = :number AND " + DEVICE_ID + " = :device_id")
abstract Account get(@Bind("number") String number, @Bind("device_id") long deviceId);
@SqlQuery("SELECT COUNT(DISTINCT " + NUMBER + ") from accounts")
abstract long getNumberCount();
private static final String NUMBER_DATA_QUERY = "SELECT number, COUNT(" +
"CASE WHEN (" + GCM_ID + " IS NOT NULL OR " + APN_ID + " IS NOT NULL OR " + FETCHES_MESSAGES + " = 1) " +
"THEN 1 ELSE 0 END) AS active, COUNT(" +
"CASE WHEN " + SUPPORTS_SMS + " = 1 THEN 1 ELSE 0 END) AS " + SUPPORTS_SMS + " " +
"FROM accounts";
@Mapper(NumberDataMapper.class)
@SqlQuery(NUMBER_DATA_QUERY + " GROUP BY " + NUMBER + " OFFSET :offset LIMIT :limit")
abstract List<NumberData> getAllNumbers(@Bind("offset") int offset, @Bind("limit") int length);
@Mapper(NumberDataMapper.class)
@SqlQuery(NUMBER_DATA_QUERY + " GROUP BY " + NUMBER)
public abstract Iterator<NumberData> getAllNumbers();
@Mapper(NumberDataMapper.class)
@SqlQuery(NUMBER_DATA_QUERY + " WHERE " + NUMBER + " = :number GROUP BY " + NUMBER)
abstract NumberData getNumberData(@Bind("number") String number);
@Mapper(AccountMapper.class)
@SqlQuery("SELECT * FROM accounts WHERE " + NUMBER + " = :number")
abstract Account get(@Bind("number") String number);
public abstract List<Account> getAllByNumber(@Bind("number") String number);
@SqlQuery("SELECT COUNT(*) from accounts")
abstract long getCount();
@Mapper(AccountMapper.class)
@SqlQuery("SELECT * FROM accounts OFFSET :offset LIMIT :limit")
abstract List<Account> getAll(@Bind("offset") int offset, @Bind("limit") int length);
@Mapper(AccountMapper.class)
@SqlQuery("SELECT * FROM accounts")
abstract Iterator<Account> getAll();
@Transaction(TransactionIsolationLevel.REPEATABLE_READ)
public long create(Account account) {
removeStep(account.getNumber());
return createStep(account);
@Transaction(TransactionIsolationLevel.SERIALIZABLE)
public long insertClearingNumber(Account account) {
removeAccountsByNumber(account.getNumber());
account.setDeviceId(getHighestDeviceId(account.getNumber()) + 1);
return insertStep(account);
}
public static class AccountMapper implements ResultSetMapper<Account> {
@@ -94,11 +121,21 @@ public abstract class Accounts {
public Account map(int i, ResultSet resultSet, StatementContext statementContext)
throws SQLException
{
return new Account(resultSet.getLong(ID), resultSet.getString(NUMBER),
return new Account(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(SUPPORTS_SMS) == 1, resultSet.getInt(FETCHES_MESSAGES) == 1);
}
}
public static class NumberDataMapper implements ResultSetMapper<NumberData> {
@Override
public NumberData map(int i, ResultSet resultSet, StatementContext statementContext)
throws SQLException
{
return new NumberData(resultSet.getString("number"), resultSet.getInt("active") != 0, resultSet.getInt(SUPPORTS_SMS) != 0);
}
}
@@ -117,6 +154,7 @@ public abstract class Accounts {
{
sql.bind(ID, account.getId());
sql.bind(NUMBER, account.getNumber());
sql.bind(DEVICE_ID, account.getDeviceId());
sql.bind(AUTH_TOKEN, account.getAuthenticationCredentials()
.getHashedAuthenticationToken());
sql.bind(SALT, account.getAuthenticationCredentials().getSalt());
@@ -124,6 +162,7 @@ public abstract class Accounts {
sql.bind(GCM_ID, account.getGcmRegistrationId());
sql.bind(APN_ID, account.getApnRegistrationId());
sql.bind(SUPPORTS_SMS, account.getSupportsSms() ? 1 : 0);
sql.bind(FETCHES_MESSAGES, account.getFetchesMessages() ? 1 : 0);
}
};
}

View File

@@ -20,6 +20,7 @@ package org.whispersystems.textsecuregcm.storage;
import com.google.common.base.Optional;
import net.spy.memcached.MemcachedClient;
import org.whispersystems.textsecuregcm.entities.ClientContact;
import org.whispersystems.textsecuregcm.util.NumberData;
import org.whispersystems.textsecuregcm.util.Util;
import java.util.Iterator;
@@ -41,24 +42,36 @@ public class AccountsManager {
}
public long getCount() {
return accounts.getCount();
return accounts.getNumberCount();
}
public List<Account> getAll(int offset, int length) {
return accounts.getAll(offset, length);
public List<NumberData> getAllNumbers(int offset, int length) {
return accounts.getAllNumbers(offset, length);
}
public Iterator<Account> getAll() {
return accounts.getAll();
public Iterator<NumberData> getAllNumbers() {
return accounts.getAllNumbers();
}
public void create(Account account) {
long id = accounts.create(account);
/** Creates a new Account and NumberData, clearing all existing accounts/data on the given number */
public void createResetNumber(Account account) {
long id = accounts.insertClearingNumber(account);
account.setId(id);
if (memcachedClient != null) {
memcachedClient.set(getKey(account.getNumber()), 0, account);
memcachedClient.set(getKey(account.getNumber(), account.getDeviceId()), 0, account);
}
updateDirectory(account);
}
/** Creates a new Account for an existing NumberData (setting the deviceId) */
public void createAccountOnExistingNumber(Account account) {
long id = accounts.insert(account);
account.setId(id);
if (memcachedClient != null) {
memcachedClient.set(getKey(account.getNumber(), account.getDeviceId()), 0, account);
}
updateDirectory(account);
@@ -66,25 +79,25 @@ public class AccountsManager {
public void update(Account account) {
if (memcachedClient != null) {
memcachedClient.set(getKey(account.getNumber()), 0, account);
memcachedClient.set(getKey(account.getNumber(), account.getDeviceId()), 0, account);
}
accounts.update(account);
updateDirectory(account);
}
public Optional<Account> get(String number) {
public Optional<Account> get(String number, long deviceId) {
Account account = null;
if (memcachedClient != null) {
account = (Account)memcachedClient.get(getKey(number));
account = (Account)memcachedClient.get(getKey(number, deviceId));
}
if (account == null) {
account = accounts.get(number);
account = accounts.get(number, deviceId);
if (account != null && memcachedClient != null) {
memcachedClient.set(getKey(number), 0, account);
memcachedClient.set(getKey(number, deviceId), 0, account);
}
}
@@ -92,17 +105,31 @@ public class AccountsManager {
else return Optional.absent();
}
public List<Account> getAllByNumber(String number) {
return accounts.getAllByNumber(number);
}
private void updateDirectory(Account account) {
if (account.getGcmRegistrationId() != null || account.getApnRegistrationId() != null) {
boolean active = account.getFetchesMessages() ||
!Util.isEmpty(account.getApnRegistrationId()) || !Util.isEmpty(account.getGcmRegistrationId());
boolean supportsSms = account.getSupportsSms();
if (!active || !supportsSms) {
NumberData numberData = accounts.getNumberData(account.getNumber());
active = numberData.isActive();
supportsSms = numberData.isSupportsSms();
}
if (active) {
byte[] token = Util.getContactToken(account.getNumber());
ClientContact clientContact = new ClientContact(token, null, account.getSupportsSms());
ClientContact clientContact = new ClientContact(token, null, supportsSms);
directory.add(clientContact);
} else {
directory.remove(account.getNumber());
}
}
private String getKey(String number) {
return Account.class.getSimpleName() + Account.MEMCACHE_VERION + number;
private String getKey(String number, long accountId) {
return Account.class.getSimpleName() + Account.MEMCACHE_VERION + number + accountId;
}
}

View File

@@ -30,6 +30,7 @@ import org.skife.jdbi.v2.sqlobject.Transaction;
import org.skife.jdbi.v2.sqlobject.customizers.Mapper;
import org.skife.jdbi.v2.tweak.ResultSetMapper;
import org.whispersystems.textsecuregcm.entities.PreKey;
import org.whispersystems.textsecuregcm.entities.UnstructuredPreKeyList;
import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
@@ -38,48 +39,60 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.LinkedList;
import java.util.List;
public abstract class Keys {
@SqlUpdate("DELETE FROM keys WHERE number = :number")
abstract void removeKeys(@Bind("number") String number);
@SqlUpdate("DELETE FROM keys WHERE number = :number AND device_id = :device_id")
abstract void removeKeys(@Bind("number") String number, @Bind("device_id") long deviceId);
@SqlUpdate("DELETE FROM keys WHERE id = :id")
abstract void removeKey(@Bind("id") long id);
@SqlBatch("INSERT INTO keys (number, key_id, public_key, identity_key, last_resort) VALUES (:number, :key_id, :public_key, :identity_key, :last_resort)")
@SqlBatch("INSERT INTO keys (number, device_id, key_id, public_key, identity_key, last_resort) VALUES " +
"(:number, :device_id, :key_id, :public_key, :identity_key, :last_resort)")
abstract void append(@PreKeyBinder List<PreKey> preKeys);
@SqlUpdate("INSERT INTO keys (number, key_id, public_key, identity_key, last_resort) VALUES (:number, :key_id, :public_key, :identity_key, :last_resort)")
@SqlUpdate("INSERT INTO keys (number, device_id, key_id, public_key, identity_key, last_resort) VALUES " +
"(:number, :device_id, :key_id, :public_key, :identity_key, :last_resort)")
abstract void append(@PreKeyBinder PreKey preKey);
@SqlQuery("SELECT * FROM keys WHERE number = :number ORDER BY id LIMIT 1 FOR UPDATE")
@SqlQuery("SELECT * FROM keys WHERE number = :number AND device_id = :device_id ORDER BY key_id ASC FOR UPDATE")
@Mapper(PreKeyMapper.class)
abstract PreKey retrieveFirst(@Bind("number") String number);
abstract PreKey retrieveFirst(@Bind("number") String number, @Bind("device_id") long deviceId);
@Transaction(TransactionIsolationLevel.SERIALIZABLE)
public void store(String number, PreKey lastResortKey, List<PreKey> keys) {
public void store(String number, long deviceId, PreKey lastResortKey, List<PreKey> keys) {
for (PreKey key : keys) {
key.setNumber(number);
key.setDeviceId(deviceId);
}
lastResortKey.setNumber(number);
lastResortKey.setDeviceId(deviceId);
lastResortKey.setLastResort(true);
removeKeys(number);
removeKeys(number, deviceId);
append(keys);
append(lastResortKey);
}
@Transaction(TransactionIsolationLevel.SERIALIZABLE)
public PreKey get(String number) {
PreKey preKey = retrieveFirst(number);
if (preKey != null && !preKey.isLastResort()) {
removeKey(preKey.getId());
public UnstructuredPreKeyList get(String number, List<Account> accounts) {
List<PreKey> preKeys = new LinkedList<>();
for (Account account : accounts) {
PreKey preKey = retrieveFirst(number, account.getDeviceId());
if (preKey != null)
preKeys.add(preKey);
}
return preKey;
for (PreKey preKey : preKeys) {
if (!preKey.isLastResort())
removeKey(preKey.getId());
}
return new UnstructuredPreKeyList(preKeys);
}
@BindingAnnotation(PreKeyBinder.PreKeyBinderFactory.class)
@@ -95,6 +108,7 @@ public abstract class Keys {
{
sql.bind("id", preKey.getId());
sql.bind("number", preKey.getNumber());
sql.bind("device_id", preKey.getDeviceId());
sql.bind("key_id", preKey.getKeyId());
sql.bind("public_key", preKey.getPublicKey());
sql.bind("identity_key", preKey.getIdentityKey());
@@ -111,7 +125,7 @@ public abstract class Keys {
public PreKey map(int i, ResultSet resultSet, StatementContext statementContext)
throws SQLException
{
return new PreKey(resultSet.getLong("id"), resultSet.getString("number"),
return new PreKey(resultSet.getLong("id"), resultSet.getString("number"), resultSet.getLong("device_id"),
resultSet.getLong("key_id"), resultSet.getString("public_key"),
resultSet.getString("identity_key"),
resultSet.getInt("last_resort") == 1);

View File

@@ -29,4 +29,6 @@ public interface PendingAccounts {
@SqlQuery("SELECT verification_code FROM pending_accounts WHERE number = :number")
String getCodeForNumber(@Bind("number") String number);
@SqlUpdate("DELETE FROM pending_accounts WHERE number = :number")
void remove(@Bind("number") String number);
}

View File

@@ -41,6 +41,12 @@ public class PendingAccountsManager {
pendingAccounts.insert(number, code);
}
public void remove(String number) {
if (memcachedClient != null)
memcachedClient.delete(MEMCACHE_PREFIX + number);
pendingAccounts.remove(number);
}
public Optional<String> getCodeForNumber(String number) {
String code = null;

View File

@@ -0,0 +1,34 @@
/**
* Copyright (C) 2013 Open WhisperSystems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.whispersystems.textsecuregcm.storage;
import org.skife.jdbi.v2.sqlobject.Bind;
import org.skife.jdbi.v2.sqlobject.SqlQuery;
import org.skife.jdbi.v2.sqlobject.SqlUpdate;
public interface PendingDeviceRegistrations {
@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)")
void insert(@Bind("number") String number, @Bind("verification_code") String verificationCode);
@SqlQuery("SELECT verification_code FROM pending_devices WHERE number = :number")
String getCodeForNumber(@Bind("number") String number);
@SqlUpdate("DELETE FROM pending_devices WHERE number = :number")
void remove(@Bind("number") String number);
}

View File

@@ -0,0 +1,68 @@
/**
* Copyright (C) 2013 Open WhisperSystems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.whispersystems.textsecuregcm.storage;
import com.google.common.base.Optional;
import net.spy.memcached.MemcachedClient;
public class PendingDevicesManager {
private static final String MEMCACHE_PREFIX = "pending_devices";
private final PendingDeviceRegistrations pendingDevices;
private final MemcachedClient memcachedClient;
public PendingDevicesManager(PendingDeviceRegistrations pendingDevices,
MemcachedClient memcachedClient)
{
this.pendingDevices = pendingDevices;
this.memcachedClient = memcachedClient;
}
public void store(String number, String code) {
if (memcachedClient != null) {
memcachedClient.set(MEMCACHE_PREFIX + number, 0, code);
}
pendingDevices.insert(number, code);
}
public void remove(String number) {
if (memcachedClient != null)
memcachedClient.delete(MEMCACHE_PREFIX + number);
pendingDevices.remove(number);
}
public Optional<String> getCodeForNumber(String number) {
String code = null;
if (memcachedClient != null) {
code = (String)memcachedClient.get(MEMCACHE_PREFIX + number);
}
if (code == null) {
code = pendingDevices.getCodeForNumber(number);
if (code != null && memcachedClient != null) {
memcachedClient.set(MEMCACHE_PREFIX + number, 0, code);
}
}
if (code != null) return Optional.of(code);
else return Optional.absent();
}
}

View File

@@ -0,0 +1,30 @@
/**
* Copyright (C) 2013 Open WhisperSystems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.whispersystems.textsecuregcm.storage;
import org.whispersystems.textsecuregcm.entities.EncryptedOutgoingMessage;
public class StoredMessageManager {
StoredMessages storedMessages;
public StoredMessageManager(StoredMessages storedMessages) {
this.storedMessages = storedMessages;
}
public void storeMessage(Account account, EncryptedOutgoingMessage outgoingMessage) {
storedMessages.insert(account.getId(), outgoingMessage);
}
}

View File

@@ -0,0 +1,33 @@
/**
* Copyright (C) 2013 Open WhisperSystems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.whispersystems.textsecuregcm.storage;
import org.skife.jdbi.v2.sqlobject.Bind;
import org.skife.jdbi.v2.sqlobject.SqlQuery;
import org.skife.jdbi.v2.sqlobject.SqlUpdate;
import org.whispersystems.textsecuregcm.entities.EncryptedOutgoingMessage;
import java.util.List;
public interface StoredMessages {
@SqlUpdate("INSERT INTO stored_messages (destination_id, encrypted_message) VALUES :destination_id, :encrypted_message")
void insert(@Bind("destination_id") long destinationAccountId, @Bind("encrypted_message") EncryptedOutgoingMessage encryptedOutgoingMessage);
@SqlQuery("SELECT encrypted_message FROM stored_messages WHERE destination_id = :account_id")
List<EncryptedOutgoingMessage> getMessagesForAccountId(@Bind("account_id") long accountId);
}