Handle simultaneous initiate protocol case.

1) Modify SessionRecord to store a list of "previous" sessions
   in addition to the current active session.  Previous sessions
   can be used for receiving messages, but not for sending
   messages.

2) When a possible "simultaneous initiate" is detected, push the
   current session onto the "previous session" stack instead of
   clearing it and starting over.

3) Additionally, mark the new session created on a received
   possible "simultaneous initiate" as stale for sending.  The
   next outgoing message would trigger a full prekey refresh.

4) Work to do: outgoing messages on the SMS transport should
   probably not use the existing session if it's marked stale
   for sending.  These messages need to fail and notify the user,
   similar to how we'll handle SMS fallback to push users before
   a prekey session is created.
pull/1/head
Moxie Marlinspike 11 years ago
parent edc20883eb
commit 926d3c929f

@ -54,6 +54,13 @@ message SessionStructure {
optional uint32 remoteRegistrationId = 10;
optional uint32 localRegistrationId = 11;
optional bool needsRefresh = 12;
}
message RecordStructure {
optional SessionStructure currentSession = 1;
repeated SessionStructure previousSessions = 2;
}
message PreKeyRecordStructure {

@ -1,6 +1,7 @@
package org.whispersystems.textsecure.crypto;
import android.content.Context;
import android.util.Log;
import android.util.Pair;
import org.whispersystems.textsecure.crypto.ecc.Curve;
@ -14,10 +15,12 @@ import org.whispersystems.textsecure.crypto.ratchet.MessageKeys;
import org.whispersystems.textsecure.crypto.ratchet.RootKey;
import org.whispersystems.textsecure.storage.RecipientDevice;
import org.whispersystems.textsecure.storage.SessionRecordV2;
import org.whispersystems.textsecure.storage.SessionState;
import org.whispersystems.textsecure.util.Conversions;
import java.security.InvalidAlgorithmParameterException;
import java.security.NoSuchAlgorithmException;
import java.util.List;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
@ -45,27 +48,28 @@ public class SessionCipherV2 extends SessionCipher {
public CiphertextMessage encrypt(byte[] paddedMessage) {
synchronized (SESSION_LOCK) {
SessionRecordV2 sessionRecord = getSessionRecord();
ChainKey chainKey = sessionRecord.getSenderChainKey();
SessionState sessionState = sessionRecord.getSessionState();
ChainKey chainKey = sessionState.getSenderChainKey();
MessageKeys messageKeys = chainKey.getMessageKeys();
ECPublicKey senderEphemeral = sessionRecord.getSenderEphemeral();
int previousCounter = sessionRecord.getPreviousCounter();
ECPublicKey senderEphemeral = sessionState.getSenderEphemeral();
int previousCounter = sessionState.getPreviousCounter();
byte[] ciphertextBody = getCiphertext(messageKeys, paddedMessage);
CiphertextMessage ciphertextMessage = new WhisperMessageV2(messageKeys.getMacKey(),
senderEphemeral, chainKey.getIndex(),
previousCounter, ciphertextBody);
if (sessionRecord.hasPendingPreKey()) {
Pair<Integer, ECPublicKey> pendingPreKey = sessionRecord.getPendingPreKey();
int localRegistrationId = sessionRecord.getLocalRegistrationId();
if (sessionState.hasPendingPreKey()) {
Pair<Integer, ECPublicKey> pendingPreKey = sessionState.getPendingPreKey();
int localRegistrationId = sessionState.getLocalRegistrationId();
ciphertextMessage = new PreKeyWhisperMessage(localRegistrationId, pendingPreKey.first,
pendingPreKey.second,
sessionRecord.getLocalIdentityKey(),
sessionState.getLocalIdentityKey(),
(WhisperMessageV2) ciphertextMessage);
}
sessionRecord.setSenderChainKey(chainKey.getNextChainKey());
sessionState.setSenderChainKey(chainKey.getNextChainKey());
sessionRecord.save();
return ciphertextMessage;
@ -75,50 +79,83 @@ public class SessionCipherV2 extends SessionCipher {
@Override
public byte[] decrypt(byte[] decodedMessage) throws InvalidMessageException {
synchronized (SESSION_LOCK) {
SessionRecordV2 sessionRecord = getSessionRecord();
WhisperMessageV2 ciphertextMessage = new WhisperMessageV2(decodedMessage);
ECPublicKey theirEphemeral = ciphertextMessage.getSenderEphemeral();
int counter = ciphertextMessage.getCounter();
ChainKey chainKey = getOrCreateChainKey(sessionRecord, theirEphemeral);
MessageKeys messageKeys = getOrCreateMessageKeys(sessionRecord, theirEphemeral,
chainKey, counter);
SessionRecordV2 sessionRecord = getSessionRecord();
SessionState sessionState = sessionRecord.getSessionState();
List<SessionState> previousStates = sessionRecord.getPreviousSessions();
ciphertextMessage.verifyMac(messageKeys.getMacKey());
try {
byte[] plaintext = decrypt(sessionState, decodedMessage);
sessionRecord.save();
byte[] plaintext = getPlaintext(messageKeys, ciphertextMessage.getBody());
return plaintext;
} catch (InvalidMessageException e) {
Log.w("SessionCipherV2", e);
}
sessionRecord.clearPendingPreKey();
sessionRecord.save();
for (SessionState previousState : previousStates) {
try {
byte[] plaintext = decrypt(previousState, decodedMessage);
sessionRecord.save();
return plaintext;
} catch (InvalidMessageException e) {
Log.w("SessionCipherV2", e);
}
}
return plaintext;
throw new InvalidMessageException("No valid sessions.");
}
}
public byte[] decrypt(SessionState sessionState, byte[] decodedMessage)
throws InvalidMessageException
{
if (!sessionState.hasSenderChain()) {
throw new InvalidMessageException("Uninitialized session!");
}
WhisperMessageV2 ciphertextMessage = new WhisperMessageV2(decodedMessage);
ECPublicKey theirEphemeral = ciphertextMessage.getSenderEphemeral();
int counter = ciphertextMessage.getCounter();
ChainKey chainKey = getOrCreateChainKey(sessionState, theirEphemeral);
MessageKeys messageKeys = getOrCreateMessageKeys(sessionState, theirEphemeral,
chainKey, counter);
ciphertextMessage.verifyMac(messageKeys.getMacKey());
byte[] plaintext = getPlaintext(messageKeys, ciphertextMessage.getBody());
sessionState.clearPendingPreKey();
return plaintext;
}
@Override
public int getRemoteRegistrationId() {
synchronized (SESSION_LOCK) {
SessionRecordV2 sessionRecord = getSessionRecord();
return sessionRecord.getRemoteRegistrationId();
return sessionRecord.getSessionState().getRemoteRegistrationId();
}
}
private ChainKey getOrCreateChainKey(SessionRecordV2 sessionRecord, ECPublicKey theirEphemeral)
private ChainKey getOrCreateChainKey(SessionState sessionState, ECPublicKey theirEphemeral)
throws InvalidMessageException
{
try {
if (sessionRecord.hasReceiverChain(theirEphemeral)) {
return sessionRecord.getReceiverChainKey(theirEphemeral);
if (sessionState.hasReceiverChain(theirEphemeral)) {
return sessionState.getReceiverChainKey(theirEphemeral);
} else {
RootKey rootKey = sessionRecord.getRootKey();
ECKeyPair ourEphemeral = sessionRecord.getSenderEphemeralPair();
RootKey rootKey = sessionState.getRootKey();
ECKeyPair ourEphemeral = sessionState.getSenderEphemeralPair();
Pair<RootKey, ChainKey> receiverChain = rootKey.createChain(theirEphemeral, ourEphemeral);
ECKeyPair ourNewEphemeral = Curve.generateKeyPairForType(Curve.DJB_TYPE);
Pair<RootKey, ChainKey> senderChain = receiverChain.first.createChain(theirEphemeral, ourNewEphemeral);
sessionRecord.setRootKey(senderChain.first);
sessionRecord.addReceiverChain(theirEphemeral, receiverChain.second);
sessionRecord.setPreviousCounter(sessionRecord.getSenderChainKey().getIndex()-1);
sessionRecord.setSenderChain(ourNewEphemeral, senderChain.second);
sessionState.setRootKey(senderChain.first);
sessionState.addReceiverChain(theirEphemeral, receiverChain.second);
sessionState.setPreviousCounter(sessionState.getSenderChainKey().getIndex()-1);
sessionState.setSenderChain(ourNewEphemeral, senderChain.second);
return receiverChain.second;
}
@ -127,14 +164,14 @@ public class SessionCipherV2 extends SessionCipher {
}
}
private MessageKeys getOrCreateMessageKeys(SessionRecordV2 sessionRecord,
private MessageKeys getOrCreateMessageKeys(SessionState sessionState,
ECPublicKey theirEphemeral,
ChainKey chainKey, int counter)
throws InvalidMessageException
{
if (chainKey.getIndex() > counter) {
if (sessionRecord.hasMessageKeys(theirEphemeral, counter)) {
return sessionRecord.removeMessageKeys(theirEphemeral, counter);
if (sessionState.hasMessageKeys(theirEphemeral, counter)) {
return sessionState.removeMessageKeys(theirEphemeral, counter);
} else {
throw new InvalidMessageException("Received message with old counter!");
}
@ -146,11 +183,11 @@ public class SessionCipherV2 extends SessionCipher {
while (chainKey.getIndex() < counter) {
MessageKeys messageKeys = chainKey.getMessageKeys();
sessionRecord.setMessageKeys(theirEphemeral, messageKeys);
sessionState.setMessageKeys(theirEphemeral, messageKeys);
chainKey = chainKey.getNextChainKey();
}
sessionRecord.setReceiverChainKey(theirEphemeral, chainKey.getNextChainKey());
sessionState.setReceiverChainKey(theirEphemeral, chainKey.getNextChainKey());
return chainKey.getMessageKeys();
}

@ -10,14 +10,14 @@ import org.whispersystems.textsecure.crypto.ecc.ECKeyPair;
import org.whispersystems.textsecure.crypto.ecc.ECPublicKey;
import org.whispersystems.textsecure.crypto.kdf.DerivedSecrets;
import org.whispersystems.textsecure.crypto.kdf.HKDF;
import org.whispersystems.textsecure.storage.SessionRecordV2;
import org.whispersystems.textsecure.storage.SessionState;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
public class RatchetingSession {
public static void initializeSession(SessionRecordV2 sessionRecord,
public static void initializeSession(SessionState sessionState,
ECKeyPair ourBaseKey,
ECPublicKey theirBaseKey,
ECKeyPair ourEphemeralKey,
@ -27,48 +27,48 @@ public class RatchetingSession {
throws InvalidKeyException
{
if (isAlice(ourBaseKey.getPublicKey(), theirBaseKey, ourEphemeralKey.getPublicKey(), theirEphemeralKey)) {
initializeSessionAsAlice(sessionRecord, ourBaseKey, theirBaseKey, theirEphemeralKey,
initializeSessionAsAlice(sessionState, ourBaseKey, theirBaseKey, theirEphemeralKey,
ourIdentityKey, theirIdentityKey);
} else {
initializeSessionAsBob(sessionRecord, ourBaseKey, theirBaseKey,
initializeSessionAsBob(sessionState, ourBaseKey, theirBaseKey,
ourEphemeralKey, ourIdentityKey, theirIdentityKey);
}
}
private static void initializeSessionAsAlice(SessionRecordV2 sessionRecord,
private static void initializeSessionAsAlice(SessionState sessionState,
ECKeyPair ourBaseKey, ECPublicKey theirBaseKey,
ECPublicKey theirEphemeralKey,
IdentityKeyPair ourIdentityKey,
IdentityKey theirIdentityKey)
throws InvalidKeyException
{
sessionRecord.setRemoteIdentityKey(theirIdentityKey);
sessionRecord.setLocalIdentityKey(ourIdentityKey.getPublicKey());
sessionState.setRemoteIdentityKey(theirIdentityKey);
sessionState.setLocalIdentityKey(ourIdentityKey.getPublicKey());
ECKeyPair sendingKey = Curve.generateKeyPairForType(ourIdentityKey.getPublicKey().getPublicKey().getType());
Pair<RootKey, ChainKey> receivingChain = calculate3DHE(true, ourBaseKey, theirBaseKey, ourIdentityKey, theirIdentityKey);
Pair<RootKey, ChainKey> sendingChain = receivingChain.first.createChain(theirEphemeralKey, sendingKey);
sessionRecord.addReceiverChain(theirEphemeralKey, receivingChain.second);
sessionRecord.setSenderChain(sendingKey, sendingChain.second);
sessionRecord.setRootKey(sendingChain.first);
sessionState.addReceiverChain(theirEphemeralKey, receivingChain.second);
sessionState.setSenderChain(sendingKey, sendingChain.second);
sessionState.setRootKey(sendingChain.first);
}
private static void initializeSessionAsBob(SessionRecordV2 sessionRecord,
private static void initializeSessionAsBob(SessionState sessionState,
ECKeyPair ourBaseKey, ECPublicKey theirBaseKey,
ECKeyPair ourEphemeralKey,
IdentityKeyPair ourIdentityKey,
IdentityKey theirIdentityKey)
throws InvalidKeyException
{
sessionRecord.setRemoteIdentityKey(theirIdentityKey);
sessionRecord.setLocalIdentityKey(ourIdentityKey.getPublicKey());
sessionState.setRemoteIdentityKey(theirIdentityKey);
sessionState.setLocalIdentityKey(ourIdentityKey.getPublicKey());
Pair<RootKey, ChainKey> sendingChain = calculate3DHE(false, ourBaseKey, theirBaseKey,
ourIdentityKey, theirIdentityKey);
sessionRecord.setSenderChain(ourEphemeralKey, sendingChain.second);
sessionRecord.setRootKey(sendingChain.first);
sessionState.setSenderChain(ourEphemeralKey, sendingChain.second);
sessionState.setRootKey(sendingChain.first);
}
private static Pair<RootKey, ChainKey> calculate3DHE(boolean isAlice,

@ -49,7 +49,6 @@ public class Session {
return SessionRecordV2.hasSession(context, masterSecret, recipient.getRecipientId(),
RecipientDevice.DEFAULT_DEVICE_ID);
}
private static boolean hasV1Session(Context context, CanonicalRecipient recipient) {
return SessionRecordV1.hasSession(context, recipient) &&
RemoteKeyRecord.hasRecord(context, recipient) &&
@ -70,7 +69,8 @@ public class Session {
RecipientDevice.DEFAULT_DEVICE_ID))
{
return new SessionRecordV2(context, masterSecret, recipientId,
RecipientDevice.DEFAULT_DEVICE_ID).getRemoteIdentityKey();
RecipientDevice.DEFAULT_DEVICE_ID).getSessionState()
.getRemoteIdentityKey();
} else if (SessionRecordV1.hasSession(context, recipientId)) {
return new SessionRecordV1(context, masterSecret, recipientId).getIdentityKey();
} else {

@ -18,26 +18,10 @@ package org.whispersystems.textsecure.storage;
import android.content.Context;
import android.util.Log;
import android.util.Pair;
import com.google.protobuf.ByteString;
import org.whispersystems.textsecure.crypto.IdentityKey;
import org.whispersystems.textsecure.crypto.IdentityKeyPair;
import org.whispersystems.textsecure.crypto.InvalidKeyException;
import org.whispersystems.textsecure.crypto.InvalidMessageException;
import org.whispersystems.textsecure.crypto.MasterCipher;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.crypto.ecc.Curve;
import org.whispersystems.textsecure.crypto.ecc.ECKeyPair;
import org.whispersystems.textsecure.crypto.ecc.ECPrivateKey;
import org.whispersystems.textsecure.crypto.ecc.ECPublicKey;
import org.whispersystems.textsecure.crypto.ratchet.ChainKey;
import org.whispersystems.textsecure.crypto.ratchet.MessageKeys;
import org.whispersystems.textsecure.crypto.ratchet.RootKey;
import org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain;
import org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingKeyExchange;
import org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingPreKey;
import java.io.File;
import java.io.FileInputStream;
@ -45,11 +29,11 @@ import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import javax.crypto.spec.SecretKeySpec;
import static org.whispersystems.textsecure.storage.StorageProtos.RecordStructure;
import static org.whispersystems.textsecure.storage.StorageProtos.SessionStructure;
/**
* A disk record representing a current session.
@ -60,11 +44,15 @@ import javax.crypto.spec.SecretKeySpec;
public class SessionRecordV2 extends Record {
private static final Object FILE_LOCK = new Object();
private static final int CURRENT_VERSION = 1;
private static final int SINGLE_STATE_VERSION = 1;
private static final int ARCHIVE_STATES_VERSION = 2;
private static final int CURRENT_VERSION = 2;
private final MasterSecret masterSecret;
private StorageProtos.SessionStructure sessionStructure =
StorageProtos.SessionStructure.newBuilder().build();
private SessionState sessionState = new SessionState(SessionStructure.newBuilder().build());
private List<SessionState> previousStates = new LinkedList<SessionState>();
public SessionRecordV2(Context context, MasterSecret masterSecret, RecipientDevice peer) {
this(context, masterSecret, peer.getRecipientId(), peer.getDeviceId());
@ -80,6 +68,15 @@ public class SessionRecordV2 extends Record {
return recipientId + (deviceId == RecipientDevice.DEFAULT_DEVICE_ID ? "" : "." + deviceId);
}
public SessionState getSessionState() {
return sessionState;
}
public List<SessionState> getPreviousSessions() {
return previousStates;
}
public static List<Integer> getSessionSubDevices(Context context, CanonicalRecipient recipient) {
List<Integer> results = new LinkedList<Integer>();
File parent = getParentDirectory(context, SESSIONS_DIRECTORY_V2);
@ -129,404 +126,49 @@ public class SessionRecordV2 extends Record {
long recipientId, int deviceId)
{
return hasRecord(context, SESSIONS_DIRECTORY_V2, getRecordName(recipientId, deviceId)) &&
new SessionRecordV2(context, masterSecret, recipientId, deviceId).hasSenderChain();
}
public void clear() {
this.sessionStructure = StorageProtos.SessionStructure.newBuilder().build();
}
public void setSessionVersion(int version) {
this.sessionStructure = this.sessionStructure.toBuilder()
.setSessionVersion(version)
.build();
}
public int getSessionVersion() {
return this.sessionStructure.getSessionVersion();
}
public void setRemoteIdentityKey(IdentityKey identityKey) {
this.sessionStructure = this.sessionStructure.toBuilder()
.setRemoteIdentityPublic(ByteString.copyFrom(identityKey.serialize()))
.build();
}
public void setLocalIdentityKey(IdentityKey identityKey) {
this.sessionStructure = this.sessionStructure.toBuilder()
.setLocalIdentityPublic(ByteString.copyFrom(identityKey.serialize()))
.build();
}
public IdentityKey getRemoteIdentityKey() {
try {
if (!this.sessionStructure.hasRemoteIdentityPublic()) {
return null;
}
return new IdentityKey(this.sessionStructure.getRemoteIdentityPublic().toByteArray(), 0);
} catch (InvalidKeyException e) {
Log.w("SessionRecordV2", e);
return null;
}
}
public IdentityKey getLocalIdentityKey() {
try {
return new IdentityKey(this.sessionStructure.getLocalIdentityPublic().toByteArray(), 0);
} catch (InvalidKeyException e) {
throw new AssertionError(e);
}
}
public int getPreviousCounter() {
return sessionStructure.getPreviousCounter();
}
public void setPreviousCounter(int previousCounter) {
this.sessionStructure = this.sessionStructure.toBuilder()
.setPreviousCounter(previousCounter)
.build();
}
public RootKey getRootKey() {
return new RootKey(this.sessionStructure.getRootKey().toByteArray());
}
public void setRootKey(RootKey rootKey) {
this.sessionStructure = this.sessionStructure.toBuilder()
.setRootKey(ByteString.copyFrom(rootKey.getKeyBytes()))
.build();
}
public ECPublicKey getSenderEphemeral() {
try {
return Curve.decodePoint(sessionStructure.getSenderChain().getSenderEphemeral().toByteArray(), 0);
} catch (InvalidKeyException e) {
throw new AssertionError(e);
}
new SessionRecordV2(context, masterSecret, recipientId, deviceId).sessionState.hasSenderChain();
}
public ECKeyPair getSenderEphemeralPair() {
ECPublicKey publicKey = getSenderEphemeral();
ECPrivateKey privateKey = Curve.decodePrivatePoint(publicKey.getType(),
sessionStructure.getSenderChain()
.getSenderEphemeralPrivate()
.toByteArray());
return new ECKeyPair(publicKey, privateKey);
public static boolean needsRefresh(Context context, MasterSecret masterSecret,
RecipientDevice recipient)
{
return new SessionRecordV2(context, masterSecret,
recipient.getRecipientId(),
recipient.getDeviceId()).getSessionState()
.getNeedsRefresh();
}
public boolean hasReceiverChain(ECPublicKey senderEphemeral) {
return getReceiverChain(senderEphemeral) != null;
public void clear() {
this.sessionState = new SessionState(SessionStructure.newBuilder().build());
this.previousStates = new LinkedList<SessionState>();
}
public boolean hasSenderChain() {
return sessionStructure.hasSenderChain();
public void archiveCurrentState() {
this.previousStates.add(sessionState);
this.sessionState = new SessionState(SessionStructure.newBuilder().build());
}
private Pair<Chain,Integer> getReceiverChain(ECPublicKey senderEphemeral) {
List<Chain> receiverChains = sessionStructure.getReceiverChainsList();
int index = 0;
for (Chain receiverChain : receiverChains) {
public void save() {
synchronized (FILE_LOCK) {
try {
ECPublicKey chainSenderEphemeral = Curve.decodePoint(receiverChain.getSenderEphemeral().toByteArray(), 0);
List<SessionStructure> previousStructures = new LinkedList<SessionStructure>();
if (chainSenderEphemeral.equals(senderEphemeral)) {
return new Pair<Chain,Integer>(receiverChain,index);
for (SessionState previousState : previousStates) {
previousStructures.add(previousState.getStructure());
}
} catch (InvalidKeyException e) {
Log.w("SessionRecordV2", e);
}
index++;
}
return null;
}
public ChainKey getReceiverChainKey(ECPublicKey senderEphemeral) {
Pair<Chain,Integer> receiverChainAndIndex = getReceiverChain(senderEphemeral);
Chain receiverChain = receiverChainAndIndex.first;
if (receiverChain == null) {
return null;
} else {
return new ChainKey(receiverChain.getChainKey().getKey().toByteArray(),
receiverChain.getChainKey().getIndex());
}
}
public void addReceiverChain(ECPublicKey senderEphemeral, ChainKey chainKey) {
Chain.ChainKey chainKeyStructure = Chain.ChainKey.newBuilder()
.setKey(ByteString.copyFrom(chainKey.getKey()))
.setIndex(chainKey.getIndex())
.build();
RecordStructure record = RecordStructure.newBuilder()
.setCurrentSession(sessionState.getStructure())
.addAllPreviousSessions(previousStructures)
.build();
Chain chain = Chain.newBuilder()
.setChainKey(chainKeyStructure)
.setSenderEphemeral(ByteString.copyFrom(senderEphemeral.serialize()))
.build();
this.sessionStructure = this.sessionStructure.toBuilder().addReceiverChains(chain).build();
if (this.sessionStructure.getReceiverChainsList().size() > 5) {
this.sessionStructure = this.sessionStructure.toBuilder()
.removeReceiverChains(0)
.build();
}
}
public void setSenderChain(ECKeyPair senderEphemeralPair, ChainKey chainKey) {
Chain.ChainKey chainKeyStructure = Chain.ChainKey.newBuilder()
.setKey(ByteString.copyFrom(chainKey.getKey()))
.setIndex(chainKey.getIndex())
.build();
Chain senderChain = Chain.newBuilder()
.setSenderEphemeral(ByteString.copyFrom(senderEphemeralPair.getPublicKey().serialize()))
.setSenderEphemeralPrivate(ByteString.copyFrom(senderEphemeralPair.getPrivateKey().serialize()))
.setChainKey(chainKeyStructure)
.build();
this.sessionStructure = this.sessionStructure.toBuilder().setSenderChain(senderChain).build();
}
public ChainKey getSenderChainKey() {
Chain.ChainKey chainKeyStructure = sessionStructure.getSenderChain().getChainKey();
return new ChainKey(chainKeyStructure.getKey().toByteArray(), chainKeyStructure.getIndex());
}
public void setSenderChainKey(ChainKey nextChainKey) {
Chain.ChainKey chainKey = Chain.ChainKey.newBuilder()
.setKey(ByteString.copyFrom(nextChainKey.getKey()))
.setIndex(nextChainKey.getIndex())
.build();
Chain chain = sessionStructure.getSenderChain().toBuilder()
.setChainKey(chainKey).build();
this.sessionStructure = this.sessionStructure.toBuilder().setSenderChain(chain).build();
}
public boolean hasMessageKeys(ECPublicKey senderEphemeral, int counter) {
Pair<Chain,Integer> chainAndIndex = getReceiverChain(senderEphemeral);
Chain chain = chainAndIndex.first;
if (chain == null) {
return false;
}
List<Chain.MessageKey> messageKeyList = chain.getMessageKeysList();
for (Chain.MessageKey messageKey : messageKeyList) {
if (messageKey.getIndex() == counter) {
return true;
}
}
return false;
}
public MessageKeys removeMessageKeys(ECPublicKey senderEphemeral, int counter) {
Pair<Chain,Integer> chainAndIndex = getReceiverChain(senderEphemeral);
Chain chain = chainAndIndex.first;
if (chain == null) {
return null;
}
List<Chain.MessageKey> messageKeyList = new LinkedList<Chain.MessageKey>(chain.getMessageKeysList());
Iterator<Chain.MessageKey> messageKeyIterator = messageKeyList.iterator();
MessageKeys result = null;
while (messageKeyIterator.hasNext()) {
Chain.MessageKey messageKey = messageKeyIterator.next();
if (messageKey.getIndex() == counter) {
result = new MessageKeys(new SecretKeySpec(messageKey.getCipherKey().toByteArray(), "AES"),
new SecretKeySpec(messageKey.getMacKey().toByteArray(), "HmacSHA256"),
messageKey.getIndex());
messageKeyIterator.remove();
break;
}
}
Chain updatedChain = chain.toBuilder().clearMessageKeys()
.addAllMessageKeys(messageKeyList)
.build();
this.sessionStructure = this.sessionStructure.toBuilder()
.setReceiverChains(chainAndIndex.second, updatedChain)
.build();
return result;
}
public void setMessageKeys(ECPublicKey senderEphemeral, MessageKeys messageKeys) {
Pair<Chain,Integer> chainAndIndex = getReceiverChain(senderEphemeral);
Chain chain = chainAndIndex.first;
Chain.MessageKey messageKeyStructure = Chain.MessageKey.newBuilder()
.setCipherKey(ByteString.copyFrom(messageKeys.getCipherKey().getEncoded()))
.setMacKey(ByteString.copyFrom(messageKeys.getMacKey().getEncoded()))
.setIndex(messageKeys.getCounter())
.build();
Chain updatedChain = chain.toBuilder()
.addMessageKeys(messageKeyStructure)
.build();
this.sessionStructure = this.sessionStructure.toBuilder()
.setReceiverChains(chainAndIndex.second, updatedChain)
.build();
}
public void setReceiverChainKey(ECPublicKey senderEphemeral, ChainKey chainKey) {
Pair<Chain,Integer> chainAndIndex = getReceiverChain(senderEphemeral);
Chain chain = chainAndIndex.first;
Chain.ChainKey chainKeyStructure = Chain.ChainKey.newBuilder()
.setKey(ByteString.copyFrom(chainKey.getKey()))
.setIndex(chainKey.getIndex())
.build();
Chain updatedChain = chain.toBuilder().setChainKey(chainKeyStructure).build();
this.sessionStructure = this.sessionStructure.toBuilder()
.setReceiverChains(chainAndIndex.second, updatedChain)
.build();
}
public void setPendingKeyExchange(int sequence,
ECKeyPair ourBaseKey,
ECKeyPair ourEphemeralKey,
IdentityKeyPair ourIdentityKey)
{
PendingKeyExchange structure =
PendingKeyExchange.newBuilder()
.setSequence(sequence)
.setLocalBaseKey(ByteString.copyFrom(ourBaseKey.getPublicKey().serialize()))
.setLocalBaseKeyPrivate(ByteString.copyFrom(ourBaseKey.getPrivateKey().serialize()))
.setLocalEphemeralKey(ByteString.copyFrom(ourEphemeralKey.getPublicKey().serialize()))
.setLocalEphemeralKeyPrivate(ByteString.copyFrom(ourEphemeralKey.getPrivateKey().serialize()))
.setLocalIdentityKey(ByteString.copyFrom(ourIdentityKey.getPublicKey().serialize()))
.setLocalIdentityKeyPrivate(ByteString.copyFrom(ourIdentityKey.getPrivateKey().serialize()))
.build();
this.sessionStructure = this.sessionStructure.toBuilder()
.setPendingKeyExchange(structure)
.build();
}
public int getPendingKeyExchangeSequence() {
return sessionStructure.getPendingKeyExchange().getSequence();
}
public ECKeyPair getPendingKeyExchangeBaseKey() throws InvalidKeyException {
ECPublicKey publicKey = Curve.decodePoint(sessionStructure.getPendingKeyExchange()
.getLocalBaseKey().toByteArray(), 0);
ECPrivateKey privateKey = Curve.decodePrivatePoint(publicKey.getType(),
sessionStructure.getPendingKeyExchange()
.getLocalBaseKeyPrivate()
.toByteArray());
return new ECKeyPair(publicKey, privateKey);
}
public ECKeyPair getPendingKeyExchangeEphemeralKey() throws InvalidKeyException {
ECPublicKey publicKey = Curve.decodePoint(sessionStructure.getPendingKeyExchange()
.getLocalEphemeralKey().toByteArray(), 0);
ECPrivateKey privateKey = Curve.decodePrivatePoint(publicKey.getType(),
sessionStructure.getPendingKeyExchange()
.getLocalEphemeralKeyPrivate()
.toByteArray());
return new ECKeyPair(publicKey, privateKey);
}
public IdentityKeyPair getPendingKeyExchangeIdentityKey() throws InvalidKeyException {
IdentityKey publicKey = new IdentityKey(sessionStructure.getPendingKeyExchange()
.getLocalIdentityKey().toByteArray(), 0);
ECPrivateKey privateKey = Curve.decodePrivatePoint(publicKey.getPublicKey().getType(),
sessionStructure.getPendingKeyExchange()
.getLocalIdentityKeyPrivate()
.toByteArray());
return new IdentityKeyPair(publicKey, privateKey);
}
public boolean hasPendingKeyExchange() {
return sessionStructure.hasPendingKeyExchange();
}
public void setPendingPreKey(int preKeyId, ECPublicKey baseKey) {
PendingPreKey pending = PendingPreKey.newBuilder()
.setPreKeyId(preKeyId)
.setBaseKey(ByteString.copyFrom(baseKey.serialize()))
.build();
this.sessionStructure = this.sessionStructure.toBuilder()
.setPendingPreKey(pending)
.build();
}
public boolean hasPendingPreKey() {
return this.sessionStructure.hasPendingPreKey();
}
public Pair<Integer, ECPublicKey> getPendingPreKey() {
try {
return new Pair<Integer, ECPublicKey>(sessionStructure.getPendingPreKey().getPreKeyId(),
Curve.decodePoint(sessionStructure.getPendingPreKey()
.getBaseKey()
.toByteArray(), 0));
} catch (InvalidKeyException e) {
throw new AssertionError(e);
}
}
public void clearPendingPreKey() {
this.sessionStructure = this.sessionStructure.toBuilder()
.clearPendingPreKey()
.build();
}
public void setRemoteRegistrationId(int registrationId) {
this.sessionStructure = this.sessionStructure.toBuilder()
.setRemoteRegistrationId(registrationId)
.build();
}
public int getRemoteRegistrationId() {
return this.sessionStructure.getRemoteRegistrationId();
}
public void setLocalRegistrationId(int registrationId) {
this.sessionStructure = this.sessionStructure.toBuilder()
.setLocalRegistrationId(registrationId)
.build();
}
public int getLocalRegistrationId() {
return this.sessionStructure.getLocalRegistrationId();
}
public void save() {
synchronized (FILE_LOCK) {
try {
RandomAccessFile file = openRandomAccessFile();
FileChannel out = file.getChannel();
out.position(0);
MasterCipher cipher = new MasterCipher(masterSecret);
writeInteger(CURRENT_VERSION, out);
writeBlob(cipher.encryptBytes(sessionStructure.toByteArray()), out);
writeBlob(cipher.encryptBytes(record.toByteArray()), out);
out.truncate(out.position());
file.close();
@ -549,11 +191,26 @@ public class SessionRecordV2 extends Record {
MasterCipher cipher = new MasterCipher(masterSecret);
byte[] encryptedBlob = readBlob(in);
this.sessionStructure = StorageProtos.SessionStructure
.parseFrom(cipher.decryptBytes(encryptedBlob));
if (versionMarker == SINGLE_STATE_VERSION) {
byte[] plaintextBytes = cipher.decryptBytes(encryptedBlob);
SessionStructure sessionStructure = SessionStructure.parseFrom(plaintextBytes);
this.sessionState = new SessionState(sessionStructure);
} else if (versionMarker == ARCHIVE_STATES_VERSION) {
byte[] plaintextBytes = cipher.decryptBytes(encryptedBlob);
RecordStructure recordStructure = RecordStructure.parseFrom(plaintextBytes);
this.sessionState = new SessionState(recordStructure.getCurrentSession());
this.previousStates = new LinkedList<SessionState>();
for (SessionStructure sessionStructure : recordStructure.getPreviousSessionsList()) {
this.previousStates.add(new SessionState(sessionStructure));
}
} else {
throw new AssertionError("Unknown version: " + versionMarker);
}
in.close();
} catch (FileNotFoundException e) {
Log.w("SessionRecordV2", "No session information found.");
// XXX

@ -0,0 +1,436 @@
package org.whispersystems.textsecure.storage;
import android.util.Log;
import android.util.Pair;
import com.google.protobuf.ByteString;
import org.whispersystems.textsecure.crypto.IdentityKey;
import org.whispersystems.textsecure.crypto.IdentityKeyPair;
import org.whispersystems.textsecure.crypto.InvalidKeyException;
import org.whispersystems.textsecure.crypto.ecc.Curve;
import org.whispersystems.textsecure.crypto.ecc.ECKeyPair;
import org.whispersystems.textsecure.crypto.ecc.ECPrivateKey;
import org.whispersystems.textsecure.crypto.ecc.ECPublicKey;
import org.whispersystems.textsecure.crypto.ratchet.ChainKey;
import org.whispersystems.textsecure.crypto.ratchet.MessageKeys;
import org.whispersystems.textsecure.crypto.ratchet.RootKey;
import org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chain;
import org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingKeyExchange;
import org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingPreKey;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import javax.crypto.spec.SecretKeySpec;
import static org.whispersystems.textsecure.storage.StorageProtos.SessionStructure;
public class SessionState {
private SessionStructure sessionStructure;
public SessionState(SessionStructure sessionStructure) {
this.sessionStructure = sessionStructure;
}
public SessionStructure getStructure() {
return sessionStructure;
}
public void setNeedsRefresh(boolean needsRefresh) {
this.sessionStructure = this.sessionStructure.toBuilder()
.setNeedsRefresh(needsRefresh)
.build();
}
public boolean getNeedsRefresh() {
return this.sessionStructure.getNeedsRefresh();
}
public void setSessionVersion(int version) {
this.sessionStructure = this.sessionStructure.toBuilder()
.setSessionVersion(version)
.build();
}
public int getSessionVersion() {
return this.sessionStructure.getSessionVersion();
}
public void setRemoteIdentityKey(IdentityKey identityKey) {
this.sessionStructure = this.sessionStructure.toBuilder()
.setRemoteIdentityPublic(ByteString.copyFrom(identityKey.serialize()))
.build();
}
public void setLocalIdentityKey(IdentityKey identityKey) {
this.sessionStructure = this.sessionStructure.toBuilder()
.setLocalIdentityPublic(ByteString.copyFrom(identityKey.serialize()))
.build();
}
public IdentityKey getRemoteIdentityKey() {
try {
if (!this.sessionStructure.hasRemoteIdentityPublic()) {
return null;
}
return new IdentityKey(this.sessionStructure.getRemoteIdentityPublic().toByteArray(), 0);
} catch (InvalidKeyException e) {
Log.w("SessionRecordV2", e);
return null;
}
}
public IdentityKey getLocalIdentityKey() {
try {
return new IdentityKey(this.sessionStructure.getLocalIdentityPublic().toByteArray(), 0);
} catch (InvalidKeyException e) {
throw new AssertionError(e);
}
}
public int getPreviousCounter() {
return sessionStructure.getPreviousCounter();
}
public void setPreviousCounter(int previousCounter) {
this.sessionStructure = this.sessionStructure.toBuilder()
.setPreviousCounter(previousCounter)
.build();
}
public RootKey getRootKey() {
return new RootKey(this.sessionStructure.getRootKey().toByteArray());
}
public void setRootKey(RootKey rootKey) {
this.sessionStructure = this.sessionStructure.toBuilder()
.setRootKey(ByteString.copyFrom(rootKey.getKeyBytes()))
.build();
}
public ECPublicKey getSenderEphemeral() {
try {
return Curve.decodePoint(sessionStructure.getSenderChain().getSenderEphemeral().toByteArray(), 0);
} catch (InvalidKeyException e) {
throw new AssertionError(e);
}
}
public ECKeyPair getSenderEphemeralPair() {
ECPublicKey publicKey = getSenderEphemeral();
ECPrivateKey privateKey = Curve.decodePrivatePoint(publicKey.getType(),
sessionStructure.getSenderChain()
.getSenderEphemeralPrivate()
.toByteArray());
return new ECKeyPair(publicKey, privateKey);
}
public boolean hasReceiverChain(ECPublicKey senderEphemeral) {
return getReceiverChain(senderEphemeral) != null;
}
public boolean hasSenderChain() {
return sessionStructure.hasSenderChain();
}
private Pair<Chain,Integer> getReceiverChain(ECPublicKey senderEphemeral) {
List<Chain> receiverChains = sessionStructure.getReceiverChainsList();
int index = 0;
for (Chain receiverChain : receiverChains) {
try {
ECPublicKey chainSenderEphemeral = Curve.decodePoint(receiverChain.getSenderEphemeral().toByteArray(), 0);
if (chainSenderEphemeral.equals(senderEphemeral)) {
return new Pair<Chain,Integer>(receiverChain,index);
}
} catch (InvalidKeyException e) {
Log.w("SessionRecordV2", e);
}
index++;
}
return null;
}
public ChainKey getReceiverChainKey(ECPublicKey senderEphemeral) {
Pair<Chain,Integer> receiverChainAndIndex = getReceiverChain(senderEphemeral);
Chain receiverChain = receiverChainAndIndex.first;
if (receiverChain == null) {
return null;
} else {
return new ChainKey(receiverChain.getChainKey().getKey().toByteArray(),
receiverChain.getChainKey().getIndex());
}
}
public void addReceiverChain(ECPublicKey senderEphemeral, ChainKey chainKey) {
Chain.ChainKey chainKeyStructure = Chain.ChainKey.newBuilder()
.setKey(ByteString.copyFrom(chainKey.getKey()))
.setIndex(chainKey.getIndex())
.build();
Chain chain = Chain.newBuilder()
.setChainKey(chainKeyStructure)
.setSenderEphemeral(ByteString.copyFrom(senderEphemeral.serialize()))
.build();
this.sessionStructure = this.sessionStructure.toBuilder().addReceiverChains(chain).build();
if (this.sessionStructure.getReceiverChainsList().size() > 5) {
this.sessionStructure = this.sessionStructure.toBuilder()
.removeReceiverChains(0)
.build();
}
}
public void setSenderChain(ECKeyPair senderEphemeralPair, ChainKey chainKey) {
Chain.ChainKey chainKeyStructure = Chain.ChainKey.newBuilder()
.setKey(ByteString.copyFrom(chainKey.getKey()))
.setIndex(chainKey.getIndex())
.build();
Chain senderChain = Chain.newBuilder()
.setSenderEphemeral(ByteString.copyFrom(senderEphemeralPair.getPublicKey().serialize()))
.setSenderEphemeralPrivate(ByteString.copyFrom(senderEphemeralPair.getPrivateKey().serialize()))
.setChainKey(chainKeyStructure)
.build();
this.sessionStructure = this.sessionStructure.toBuilder().setSenderChain(senderChain).build();
}
public ChainKey getSenderChainKey() {
Chain.ChainKey chainKeyStructure = sessionStructure.getSenderChain().getChainKey();
return new ChainKey(chainKeyStructure.getKey().toByteArray(), chainKeyStructure.getIndex());
}
public void setSenderChainKey(ChainKey nextChainKey) {
Chain.ChainKey chainKey = Chain.ChainKey.newBuilder()
.setKey(ByteString.copyFrom(nextChainKey.getKey()))
.setIndex(nextChainKey.getIndex())
.build();
Chain chain = sessionStructure.getSenderChain().toBuilder()
.setChainKey(chainKey).build();
this.sessionStructure = this.sessionStructure.toBuilder().setSenderChain(chain).build();
}
public boolean hasMessageKeys(ECPublicKey senderEphemeral, int counter) {
Pair<Chain,Integer> chainAndIndex = getReceiverChain(senderEphemeral);
Chain chain = chainAndIndex.first;
if (chain == null) {
return false;
}
List<Chain.MessageKey> messageKeyList = chain.getMessageKeysList();
for (Chain.MessageKey messageKey : messageKeyList) {
if (messageKey.getIndex() == counter) {
return true;
}
}
return false;
}
public MessageKeys removeMessageKeys(ECPublicKey senderEphemeral, int counter) {
Pair<Chain,Integer> chainAndIndex = getReceiverChain(senderEphemeral);
Chain chain = chainAndIndex.first;
if (chain == null) {
return null;
}
List<Chain.MessageKey> messageKeyList = new LinkedList<Chain.MessageKey>(chain.getMessageKeysList());
Iterator<Chain.MessageKey> messageKeyIterator = messageKeyList.iterator();
MessageKeys result = null;
while (messageKeyIterator.hasNext()) {
Chain.MessageKey messageKey = messageKeyIterator.next();
if (messageKey.getIndex() == counter) {
result = new MessageKeys(new SecretKeySpec(messageKey.getCipherKey().toByteArray(), "AES"),
new SecretKeySpec(messageKey.getMacKey().toByteArray(), "HmacSHA256"),
messageKey.getIndex());
messageKeyIterator.remove();
break;
}
}
Chain updatedChain = chain.toBuilder().clearMessageKeys()
.addAllMessageKeys(messageKeyList)
.build();
this.sessionStructure = this.sessionStructure.toBuilder()
.setReceiverChains(chainAndIndex.second, updatedChain)
.build();
return result;
}
public void setMessageKeys(ECPublicKey senderEphemeral, MessageKeys messageKeys) {
Pair<Chain,Integer> chainAndIndex = getReceiverChain(senderEphemeral);
Chain chain = chainAndIndex.first;
Chain.MessageKey messageKeyStructure = Chain.MessageKey.newBuilder()
.setCipherKey(ByteString.copyFrom(messageKeys.getCipherKey().getEncoded()))
.setMacKey(ByteString.copyFrom(messageKeys.getMacKey().getEncoded()))
.setIndex(messageKeys.getCounter())
.build();
Chain updatedChain = chain.toBuilder()
.addMessageKeys(messageKeyStructure)
.build();
this.sessionStructure = this.sessionStructure.toBuilder()
.setReceiverChains(chainAndIndex.second, updatedChain)
.build();
}
public void setReceiverChainKey(ECPublicKey senderEphemeral, ChainKey chainKey) {
Pair<Chain,Integer> chainAndIndex = getReceiverChain(senderEphemeral);
Chain chain = chainAndIndex.first;
Chain.ChainKey chainKeyStructure = Chain.ChainKey.newBuilder()
.setKey(ByteString.copyFrom(chainKey.getKey()))
.setIndex(chainKey.getIndex())
.build();
Chain updatedChain = chain.toBuilder().setChainKey(chainKeyStructure).build();
this.sessionStructure = this.sessionStructure.toBuilder()
.setReceiverChains(chainAndIndex.second, updatedChain)
.build();
}
public void setPendingKeyExchange(int sequence,
ECKeyPair ourBaseKey,
ECKeyPair ourEphemeralKey,
IdentityKeyPair ourIdentityKey)
{
PendingKeyExchange structure =
PendingKeyExchange.newBuilder()
.setSequence(sequence)
.setLocalBaseKey(ByteString.copyFrom(ourBaseKey.getPublicKey().serialize()))
.setLocalBaseKeyPrivate(ByteString.copyFrom(ourBaseKey.getPrivateKey().serialize()))
.setLocalEphemeralKey(ByteString.copyFrom(ourEphemeralKey.getPublicKey().serialize()))
.setLocalEphemeralKeyPrivate(ByteString.copyFrom(ourEphemeralKey.getPrivateKey().serialize()))
.setLocalIdentityKey(ByteString.copyFrom(ourIdentityKey.getPublicKey().serialize()))
.setLocalIdentityKeyPrivate(ByteString.copyFrom(ourIdentityKey.getPrivateKey().serialize()))
.build();
this.sessionStructure = this.sessionStructure.toBuilder()
.setPendingKeyExchange(structure)
.build();
}
public int getPendingKeyExchangeSequence() {
return sessionStructure.getPendingKeyExchange().getSequence();
}
public ECKeyPair getPendingKeyExchangeBaseKey() throws InvalidKeyException {
ECPublicKey publicKey = Curve.decodePoint(sessionStructure.getPendingKeyExchange()
.getLocalBaseKey().toByteArray(), 0);
ECPrivateKey privateKey = Curve.decodePrivatePoint(publicKey.getType(),
sessionStructure.getPendingKeyExchange()
.getLocalBaseKeyPrivate()
.toByteArray());
return new ECKeyPair(publicKey, privateKey);
}
public ECKeyPair getPendingKeyExchangeEphemeralKey() throws InvalidKeyException {
ECPublicKey publicKey = Curve.decodePoint(sessionStructure.getPendingKeyExchange()
.getLocalEphemeralKey().toByteArray(), 0);
ECPrivateKey privateKey = Curve.decodePrivatePoint(publicKey.getType(),
sessionStructure.getPendingKeyExchange()
.getLocalEphemeralKeyPrivate()
.toByteArray());
return new ECKeyPair(publicKey, privateKey);
}
public IdentityKeyPair getPendingKeyExchangeIdentityKey() throws InvalidKeyException {
IdentityKey publicKey = new IdentityKey(sessionStructure.getPendingKeyExchange()
.getLocalIdentityKey().toByteArray(), 0);
ECPrivateKey privateKey = Curve.decodePrivatePoint(publicKey.getPublicKey().getType(),
sessionStructure.getPendingKeyExchange()
.getLocalIdentityKeyPrivate()
.toByteArray());
return new IdentityKeyPair(publicKey, privateKey);
}
public boolean hasPendingKeyExchange() {
return sessionStructure.hasPendingKeyExchange();
}
public void setPendingPreKey(int preKeyId, ECPublicKey baseKey) {
PendingPreKey pending = PendingPreKey.newBuilder()
.setPreKeyId(preKeyId)
.setBaseKey(ByteString.copyFrom(baseKey.serialize()))
.build();
this.sessionStructure = this.sessionStructure.toBuilder()
.setPendingPreKey(pending)
.build();
}
public boolean hasPendingPreKey() {
return this.sessionStructure.hasPendingPreKey();
}
public Pair<Integer, ECPublicKey> getPendingPreKey() {
try {
return new Pair<Integer, ECPublicKey>(sessionStructure.getPendingPreKey().getPreKeyId(),
Curve.decodePoint(sessionStructure.getPendingPreKey()
.getBaseKey()
.toByteArray(), 0));
} catch (InvalidKeyException e) {
throw new AssertionError(e);
}
}
public void clearPendingPreKey() {
this.sessionStructure = this.sessionStructure.toBuilder()
.clearPendingPreKey()
.build();
}
public void setRemoteRegistrationId(int registrationId) {
this.sessionStructure = this.sessionStructure.toBuilder()
.setRemoteRegistrationId(registrationId)
.build();
}
public int getRemoteRegistrationId() {
return this.sessionStructure.getRemoteRegistrationId();
}
public void setLocalRegistrationId(int registrationId) {
this.sessionStructure = this.sessionStructure.toBuilder()
.setLocalRegistrationId(registrationId)
.build();
}
public int getLocalRegistrationId() {
return this.sessionStructure.getLocalRegistrationId();
}
public byte[] serialize() {
return sessionStructure.toByteArray();
}
}

@ -63,6 +63,10 @@ public final class StorageProtos {
// optional uint32 localRegistrationId = 11;
boolean hasLocalRegistrationId();
int getLocalRegistrationId();
// optional bool needsRefresh = 12;
boolean hasNeedsRefresh();
boolean getNeedsRefresh();
}
public static final class SessionStructure extends
com.google.protobuf.GeneratedMessage
@ -2992,6 +2996,16 @@ public final class StorageProtos {
return localRegistrationId_;
}
// optional bool needsRefresh = 12;
public static final int NEEDSREFRESH_FIELD_NUMBER = 12;
private boolean needsRefresh_;
public boolean hasNeedsRefresh() {
return ((bitField0_ & 0x00000400) == 0x00000400);
}
public boolean getNeedsRefresh() {
return needsRefresh_;
}
private void initFields() {
sessionVersion_ = 0;
localIdentityPublic_ = com.google.protobuf.ByteString.EMPTY;
@ -3004,6 +3018,7 @@ public final class StorageProtos {
pendingPreKey_ = org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingPreKey.getDefaultInstance();
remoteRegistrationId_ = 0;
localRegistrationId_ = 0;
needsRefresh_ = false;
}
private byte memoizedIsInitialized = -1;
public final boolean isInitialized() {
@ -3050,6 +3065,9 @@ public final class StorageProtos {
if (((bitField0_ & 0x00000200) == 0x00000200)) {
output.writeUInt32(11, localRegistrationId_);
}
if (((bitField0_ & 0x00000400) == 0x00000400)) {
output.writeBool(12, needsRefresh_);
}
getUnknownFields().writeTo(output);
}
@ -3103,6 +3121,10 @@ public final class StorageProtos {
size += com.google.protobuf.CodedOutputStream
.computeUInt32Size(11, localRegistrationId_);
}
if (((bitField0_ & 0x00000400) == 0x00000400)) {
size += com.google.protobuf.CodedOutputStream
.computeBoolSize(12, needsRefresh_);
}
size += getUnknownFields().getSerializedSize();
memoizedSerializedSize = size;
return size;
@ -3269,6 +3291,8 @@ public final class StorageProtos {
bitField0_ = (bitField0_ & ~0x00000200);
localRegistrationId_ = 0;
bitField0_ = (bitField0_ & ~0x00000400);
needsRefresh_ = false;
bitField0_ = (bitField0_ & ~0x00000800);
return this;
}
@ -3368,6 +3392,10 @@ public final class StorageProtos {
to_bitField0_ |= 0x00000200;
}
result.localRegistrationId_ = localRegistrationId_;
if (((from_bitField0_ & 0x00000800) == 0x00000800)) {
to_bitField0_ |= 0x00000400;
}
result.needsRefresh_ = needsRefresh_;
result.bitField0_ = to_bitField0_;
onBuilt();
return result;
@ -3440,6 +3468,9 @@ public final class StorageProtos {
if (other.hasLocalRegistrationId()) {
setLocalRegistrationId(other.getLocalRegistrationId());
}
if (other.hasNeedsRefresh()) {
setNeedsRefresh(other.getNeedsRefresh());
}
this.mergeUnknownFields(other.getUnknownFields());
return this;
}
@ -3539,6 +3570,11 @@ public final class StorageProtos {
localRegistrationId_ = input.readUInt32();
break;
}
case 96: {
bitField0_ |= 0x00000800;
needsRefresh_ = input.readBool();
break;
}
}
}
}
@ -4157,6 +4193,27 @@ public final class StorageProtos {
return this;
}
// optional bool needsRefresh = 12;
private boolean needsRefresh_ ;
public boolean hasNeedsRefresh() {
return ((bitField0_ & 0x00000800) == 0x00000800);
}
public boolean getNeedsRefresh() {
return needsRefresh_;
}
public Builder setNeedsRefresh(boolean value) {
bitField0_ |= 0x00000800;
needsRefresh_ = value;
onChanged();
return this;
}
public Builder clearNeedsRefresh() {
bitField0_ = (bitField0_ & ~0x00000800);
needsRefresh_ = false;
onChanged();
return this;
}
// @@protoc_insertion_point(builder_scope:textsecure.SessionStructure)
}
@ -4168,6 +4225,703 @@ public final class StorageProtos {
// @@protoc_insertion_point(class_scope:textsecure.SessionStructure)
}
public interface RecordStructureOrBuilder
extends com.google.protobuf.MessageOrBuilder {
// optional .textsecure.SessionStructure currentSession = 1;
boolean hasCurrentSession();
org.whispersystems.textsecure.storage.StorageProtos.SessionStructure getCurrentSession();
org.whispersystems.textsecure.storage.StorageProtos.SessionStructureOrBuilder getCurrentSessionOrBuilder();
// repeated .textsecure.SessionStructure previousSessions = 2;
java.util.List<org.whispersystems.textsecure.storage.StorageProtos.SessionStructure>
getPreviousSessionsList();
org.whispersystems.textsecure.storage.StorageProtos.SessionStructure getPreviousSessions(int index);
int getPreviousSessionsCount();
java.util.List<? extends org.whispersystems.textsecure.storage.StorageProtos.SessionStructureOrBuilder>
getPreviousSessionsOrBuilderList();
org.whispersystems.textsecure.storage.StorageProtos.SessionStructureOrBuilder getPreviousSessionsOrBuilder(
int index);
}
public static final class RecordStructure extends
com.google.protobuf.GeneratedMessage
implements RecordStructureOrBuilder {
// Use RecordStructure.newBuilder() to construct.
private RecordStructure(Builder builder) {
super(builder);
}
private RecordStructure(boolean noInit) {}
private static final RecordStructure defaultInstance;
public static RecordStructure getDefaultInstance() {
return defaultInstance;
}
public RecordStructure getDefaultInstanceForType() {
return defaultInstance;
}
public static final com.google.protobuf.Descriptors.Descriptor
getDescriptor() {
return org.whispersystems.textsecure.storage.StorageProtos.internal_static_textsecure_RecordStructure_descriptor;
}
protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
internalGetFieldAccessorTable() {
return org.whispersystems.textsecure.storage.StorageProtos.internal_static_textsecure_RecordStructure_fieldAccessorTable;
}
private int bitField0_;
// optional .textsecure.SessionStructure currentSession = 1;
public static final int CURRENTSESSION_FIELD_NUMBER = 1;
private org.whispersystems.textsecure.storage.StorageProtos.SessionStructure currentSession_;
public boolean hasCurrentSession() {
return ((bitField0_ & 0x00000001) == 0x00000001);
}
public org.whispersystems.textsecure.storage.StorageProtos.SessionStructure getCurrentSession() {
return currentSession_;
}
public org.whispersystems.textsecure.storage.StorageProtos.SessionStructureOrBuilder getCurrentSessionOrBuilder() {
return currentSession_;
}
// repeated .textsecure.SessionStructure previousSessions = 2;
public static final int PREVIOUSSESSIONS_FIELD_NUMBER = 2;
private java.util.List<org.whispersystems.textsecure.storage.StorageProtos.SessionStructure> previousSessions_;
public java.util.List<org.whispersystems.textsecure.storage.StorageProtos.SessionStructure> getPreviousSessionsList() {
return previousSessions_;
}
public java.util.List<? extends org.whispersystems.textsecure.storage.StorageProtos.SessionStructureOrBuilder>
getPreviousSessionsOrBuilderList() {
return previousSessions_;
}
public int getPreviousSessionsCount() {
return previousSessions_.size();
}
public org.whispersystems.textsecure.storage.StorageProtos.SessionStructure getPreviousSessions(int index) {
return previousSessions_.get(index);
}
public org.whispersystems.textsecure.storage.StorageProtos.SessionStructureOrBuilder getPreviousSessionsOrBuilder(
int index) {
return previousSessions_.get(index);
}
private void initFields() {
currentSession_ = org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.getDefaultInstance();
previousSessions_ = java.util.Collections.emptyList();
}
private byte memoizedIsInitialized = -1;
public final boolean isInitialized() {
byte isInitialized = memoizedIsInitialized;
if (isInitialized != -1) return isInitialized == 1;
memoizedIsInitialized = 1;
return true;
}
public void writeTo(com.google.protobuf.CodedOutputStream output)
throws java.io.IOException {
getSerializedSize();
if (((bitField0_ & 0x00000001) == 0x00000001)) {
output.writeMessage(1, currentSession_);
}
for (int i = 0; i < previousSessions_.size(); i++) {
output.writeMessage(2, previousSessions_.get(i));
}
getUnknownFields().writeTo(output);
}
private int memoizedSerializedSize = -1;
public int getSerializedSize() {
int size = memoizedSerializedSize;
if (size != -1) return size;
size = 0;
if (((bitField0_ & 0x00000001) == 0x00000001)) {
size += com.google.protobuf.CodedOutputStream
.computeMessageSize(1, currentSession_);
}
for (int i = 0; i < previousSessions_.size(); i++) {
size += com.google.protobuf.CodedOutputStream
.computeMessageSize(2, previousSessions_.get(i));
}
size += getUnknownFields().getSerializedSize();
memoizedSerializedSize = size;
return size;
}
private static final long serialVersionUID = 0L;
@java.lang.Override
protected java.lang.Object writeReplace()
throws java.io.ObjectStreamException {
return super.writeReplace();
}
public static org.whispersystems.textsecure.storage.StorageProtos.RecordStructure parseFrom(
com.google.protobuf.ByteString data)
throws com.google.protobuf.InvalidProtocolBufferException {
return newBuilder().mergeFrom(data).buildParsed();
}
public static org.whispersystems.textsecure.storage.StorageProtos.RecordStructure parseFrom(
com.google.protobuf.ByteString data,
com.google.protobuf.ExtensionRegistryLite extensionRegistry)
throws com.google.protobuf.InvalidProtocolBufferException {
return newBuilder().mergeFrom(data, extensionRegistry)
.buildParsed();
}
public static org.whispersystems.textsecure.storage.StorageProtos.RecordStructure parseFrom(byte[] data)
throws com.google.protobuf.InvalidProtocolBufferException {
return newBuilder().mergeFrom(data).buildParsed();
}
public static org.whispersystems.textsecure.storage.StorageProtos.RecordStructure parseFrom(
byte[] data,
com.google.protobuf.ExtensionRegistryLite extensionRegistry)
throws com.google.protobuf.InvalidProtocolBufferException {
return newBuilder().mergeFrom(data, extensionRegistry)
.buildParsed();
}
public static org.whispersystems.textsecure.storage.StorageProtos.RecordStructure parseFrom(java.io.InputStream input)
throws java.io.IOException {
return newBuilder().mergeFrom(input).buildParsed();
}
public static org.whispersystems.textsecure.storage.StorageProtos.RecordStructure parseFrom(
java.io.InputStream input,
com.google.protobuf.ExtensionRegistryLite extensionRegistry)
throws java.io.IOException {
return newBuilder().mergeFrom(input, extensionRegistry)
.buildParsed();
}
public static org.whispersystems.textsecure.storage.StorageProtos.RecordStructure parseDelimitedFrom(java.io.InputStream input)
throws java.io.IOException {
Builder builder = newBuilder();
if (builder.mergeDelimitedFrom(input)) {
return builder.buildParsed();
} else {
return null;
}
}
public static org.whispersystems.textsecure.storage.StorageProtos.RecordStructure parseDelimitedFrom(
java.io.InputStream input,
com.google.protobuf.ExtensionRegistryLite extensionRegistry)
throws java.io.IOException {
Builder builder = newBuilder();
if (builder.mergeDelimitedFrom(input, extensionRegistry)) {
return builder.buildParsed();
} else {
return null;
}
}
public static org.whispersystems.textsecure.storage.StorageProtos.RecordStructure parseFrom(
com.google.protobuf.CodedInputStream input)
throws java.io.IOException {
return newBuilder().mergeFrom(input).buildParsed();
}
public static org.whispersystems.textsecure.storage.StorageProtos.RecordStructure parseFrom(
com.google.protobuf.CodedInputStream input,
com.google.protobuf.ExtensionRegistryLite extensionRegistry)
throws java.io.IOException {
return newBuilder().mergeFrom(input, extensionRegistry)
.buildParsed();
}
public static Builder newBuilder() { return Builder.create(); }
public Builder newBuilderForType() { return newBuilder(); }
public static Builder newBuilder(org.whispersystems.textsecure.storage.StorageProtos.RecordStructure prototype) {
return newBuilder().mergeFrom(prototype);
}
public Builder toBuilder() { return newBuilder(this); }
@java.lang.Override
protected Builder newBuilderForType(
com.google.protobuf.GeneratedMessage.BuilderParent parent) {
Builder builder = new Builder(parent);
return builder;
}
public static final class Builder extends
com.google.protobuf.GeneratedMessage.Builder<Builder>
implements org.whispersystems.textsecure.storage.StorageProtos.RecordStructureOrBuilder {
public static final com.google.protobuf.Descriptors.Descriptor
getDescriptor() {
return org.whispersystems.textsecure.storage.StorageProtos.internal_static_textsecure_RecordStructure_descriptor;
}
protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
internalGetFieldAccessorTable() {
return org.whispersystems.textsecure.storage.StorageProtos.internal_static_textsecure_RecordStructure_fieldAccessorTable;
}
// Construct using org.whispersystems.textsecure.storage.StorageProtos.RecordStructure.newBuilder()
private Builder() {
maybeForceBuilderInitialization();
}
private Builder(BuilderParent parent) {
super(parent);
maybeForceBuilderInitialization();
}
private void maybeForceBuilderInitialization() {
if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) {
getCurrentSessionFieldBuilder();
getPreviousSessionsFieldBuilder();
}
}
private static Builder create() {
return new Builder();
}
public Builder clear() {
super.clear();
if (currentSessionBuilder_ == null) {
currentSession_ = org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.getDefaultInstance();
} else {
currentSessionBuilder_.clear();
}
bitField0_ = (bitField0_ & ~0x00000001);
if (previousSessionsBuilder_ == null) {
previousSessions_ = java.util.Collections.emptyList();
bitField0_ = (bitField0_ & ~0x00000002);
} else {
previousSessionsBuilder_.clear();
}
return this;
}
public Builder clone() {
return create().mergeFrom(buildPartial());
}
public com.google.protobuf.Descriptors.Descriptor
getDescriptorForType() {
return org.whispersystems.textsecure.storage.StorageProtos.RecordStructure.getDescriptor();
}
public org.whispersystems.textsecure.storage.StorageProtos.RecordStructure getDefaultInstanceForType() {
return org.whispersystems.textsecure.storage.StorageProtos.RecordStructure.getDefaultInstance();
}
public org.whispersystems.textsecure.storage.StorageProtos.RecordStructure build() {
org.whispersystems.textsecure.storage.StorageProtos.RecordStructure result = buildPartial();
if (!result.isInitialized()) {
throw newUninitializedMessageException(result);
}
return result;
}
private org.whispersystems.textsecure.storage.StorageProtos.RecordStructure buildParsed()
throws com.google.protobuf.InvalidProtocolBufferException {
org.whispersystems.textsecure.storage.StorageProtos.RecordStructure result = buildPartial();
if (!result.isInitialized()) {
throw newUninitializedMessageException(
result).asInvalidProtocolBufferException();
}
return result;
}
public org.whispersystems.textsecure.storage.StorageProtos.RecordStructure buildPartial() {
org.whispersystems.textsecure.storage.StorageProtos.RecordStructure result = new org.whispersystems.textsecure.storage.StorageProtos.RecordStructure(this);
int from_bitField0_ = bitField0_;
int to_bitField0_ = 0;
if (((from_bitField0_ & 0x00000001) == 0x00000001)) {
to_bitField0_ |= 0x00000001;
}
if (currentSessionBuilder_ == null) {
result.currentSession_ = currentSession_;
} else {
result.currentSession_ = currentSessionBuilder_.build();
}
if (previousSessionsBuilder_ == null) {
if (((bitField0_ & 0x00000002) == 0x00000002)) {
previousSessions_ = java.util.Collections.unmodifiableList(previousSessions_);
bitField0_ = (bitField0_ & ~0x00000002);
}
result.previousSessions_ = previousSessions_;
} else {
result.previousSessions_ = previousSessionsBuilder_.build();
}
result.bitField0_ = to_bitField0_;
onBuilt();
return result;
}
public Builder mergeFrom(com.google.protobuf.Message other) {
if (other instanceof org.whispersystems.textsecure.storage.StorageProtos.RecordStructure) {
return mergeFrom((org.whispersystems.textsecure.storage.StorageProtos.RecordStructure)other);
} else {
super.mergeFrom(other);
return this;
}
}
public Builder mergeFrom(org.whispersystems.textsecure.storage.StorageProtos.RecordStructure other) {
if (other == org.whispersystems.textsecure.storage.StorageProtos.RecordStructure.getDefaultInstance()) return this;
if (other.hasCurrentSession()) {
mergeCurrentSession(other.getCurrentSession());
}
if (previousSessionsBuilder_ == null) {
if (!other.previousSessions_.isEmpty()) {
if (previousSessions_.isEmpty()) {
previousSessions_ = other.previousSessions_;
bitField0_ = (bitField0_ & ~0x00000002);
} else {
ensurePreviousSessionsIsMutable();
previousSessions_.addAll(other.previousSessions_);
}
onChanged();
}
} else {
if (!other.previousSessions_.isEmpty()) {
if (previousSessionsBuilder_.isEmpty()) {
previousSessionsBuilder_.dispose();
previousSessionsBuilder_ = null;
previousSessions_ = other.previousSessions_;
bitField0_ = (bitField0_ & ~0x00000002);
previousSessionsBuilder_ =
com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders ?
getPreviousSessionsFieldBuilder() : null;
} else {
previousSessionsBuilder_.addAllMessages(other.previousSessions_);
}
}
}
this.mergeUnknownFields(other.getUnknownFields());
return this;
}
public final boolean isInitialized() {
return true;
}
public Builder mergeFrom(
com.google.protobuf.CodedInputStream input,
com.google.protobuf.ExtensionRegistryLite extensionRegistry)
throws java.io.IOException {
com.google.protobuf.UnknownFieldSet.Builder unknownFields =
com.google.protobuf.UnknownFieldSet.newBuilder(
this.getUnknownFields());
while (true) {
int tag = input.readTag();
switch (tag) {
case 0:
this.setUnknownFields(unknownFields.build());
onChanged();
return this;
default: {
if (!parseUnknownField(input, unknownFields,
extensionRegistry, tag)) {
this.setUnknownFields(unknownFields.build());
onChanged();
return this;
}
break;
}
case 10: {
org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Builder subBuilder = org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.newBuilder();
if (hasCurrentSession()) {
subBuilder.mergeFrom(getCurrentSession());
}
input.readMessage(subBuilder, extensionRegistry);
setCurrentSession(subBuilder.buildPartial());
break;
}
case 18: {
org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Builder subBuilder = org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.newBuilder();
input.readMessage(subBuilder, extensionRegistry);
addPreviousSessions(subBuilder.buildPartial());
break;
}
}
}
}
private int bitField0_;
// optional .textsecure.SessionStructure currentSession = 1;
private org.whispersystems.textsecure.storage.StorageProtos.SessionStructure currentSession_ = org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.getDefaultInstance();
private com.google.protobuf.SingleFieldBuilder<
org.whispersystems.textsecure.storage.StorageProtos.SessionStructure, org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Builder, org.whispersystems.textsecure.storage.StorageProtos.SessionStructureOrBuilder> currentSessionBuilder_;
public boolean hasCurrentSession() {
return ((bitField0_ & 0x00000001) == 0x00000001);
}
public org.whispersystems.textsecure.storage.StorageProtos.SessionStructure getCurrentSession() {
if (currentSessionBuilder_ == null) {
return currentSession_;
} else {
return currentSessionBuilder_.getMessage();
}
}
public Builder setCurrentSession(org.whispersystems.textsecure.storage.StorageProtos.SessionStructure value) {
if (currentSessionBuilder_ == null) {
if (value == null) {
throw new NullPointerException();
}
currentSession_ = value;
onChanged();
} else {
currentSessionBuilder_.setMessage(value);
}
bitField0_ |= 0x00000001;
return this;
}
public Builder setCurrentSession(
org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Builder builderForValue) {
if (currentSessionBuilder_ == null) {
currentSession_ = builderForValue.build();
onChanged();
} else {
currentSessionBuilder_.setMessage(builderForValue.build());
}
bitField0_ |= 0x00000001;
return this;
}
public Builder mergeCurrentSession(org.whispersystems.textsecure.storage.StorageProtos.SessionStructure value) {
if (currentSessionBuilder_ == null) {
if (((bitField0_ & 0x00000001) == 0x00000001) &&
currentSession_ != org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.getDefaultInstance()) {
currentSession_ =
org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.newBuilder(currentSession_).mergeFrom(value).buildPartial();
} else {
currentSession_ = value;
}
onChanged();
} else {
currentSessionBuilder_.mergeFrom(value);
}
bitField0_ |= 0x00000001;
return this;
}
public Builder clearCurrentSession() {
if (currentSessionBuilder_ == null) {
currentSession_ = org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.getDefaultInstance();
onChanged();
} else {
currentSessionBuilder_.clear();
}
bitField0_ = (bitField0_ & ~0x00000001);
return this;
}
public org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Builder getCurrentSessionBuilder() {
bitField0_ |= 0x00000001;
onChanged();
return getCurrentSessionFieldBuilder().getBuilder();
}
public org.whispersystems.textsecure.storage.StorageProtos.SessionStructureOrBuilder getCurrentSessionOrBuilder() {
if (currentSessionBuilder_ != null) {
return currentSessionBuilder_.getMessageOrBuilder();
} else {
return currentSession_;
}
}
private com.google.protobuf.SingleFieldBuilder<
org.whispersystems.textsecure.storage.StorageProtos.SessionStructure, org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Builder, org.whispersystems.textsecure.storage.StorageProtos.SessionStructureOrBuilder>
getCurrentSessionFieldBuilder() {
if (currentSessionBuilder_ == null) {
currentSessionBuilder_ = new com.google.protobuf.SingleFieldBuilder<
org.whispersystems.textsecure.storage.StorageProtos.SessionStructure, org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Builder, org.whispersystems.textsecure.storage.StorageProtos.SessionStructureOrBuilder>(
currentSession_,
getParentForChildren(),
isClean());
currentSession_ = null;
}
return currentSessionBuilder_;
}
// repeated .textsecure.SessionStructure previousSessions = 2;
private java.util.List<org.whispersystems.textsecure.storage.StorageProtos.SessionStructure> previousSessions_ =
java.util.Collections.emptyList();
private void ensurePreviousSessionsIsMutable() {
if (!((bitField0_ & 0x00000002) == 0x00000002)) {
previousSessions_ = new java.util.ArrayList<org.whispersystems.textsecure.storage.StorageProtos.SessionStructure>(previousSessions_);
bitField0_ |= 0x00000002;
}
}
private com.google.protobuf.RepeatedFieldBuilder<
org.whispersystems.textsecure.storage.StorageProtos.SessionStructure, org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Builder, org.whispersystems.textsecure.storage.StorageProtos.SessionStructureOrBuilder> previousSessionsBuilder_;
public java.util.List<org.whispersystems.textsecure.storage.StorageProtos.SessionStructure> getPreviousSessionsList() {
if (previousSessionsBuilder_ == null) {
return java.util.Collections.unmodifiableList(previousSessions_);
} else {
return previousSessionsBuilder_.getMessageList();
}
}
public int getPreviousSessionsCount() {
if (previousSessionsBuilder_ == null) {
return previousSessions_.size();
} else {
return previousSessionsBuilder_.getCount();
}
}
public org.whispersystems.textsecure.storage.StorageProtos.SessionStructure getPreviousSessions(int index) {
if (previousSessionsBuilder_ == null) {
return previousSessions_.get(index);
} else {
return previousSessionsBuilder_.getMessage(index);
}
}
public Builder setPreviousSessions(
int index, org.whispersystems.textsecure.storage.StorageProtos.SessionStructure value) {
if (previousSessionsBuilder_ == null) {
if (value == null) {
throw new NullPointerException();
}
ensurePreviousSessionsIsMutable();
previousSessions_.set(index, value);
onChanged();
} else {
previousSessionsBuilder_.setMessage(index, value);
}
return this;
}
public Builder setPreviousSessions(
int index, org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Builder builderForValue) {
if (previousSessionsBuilder_ == null) {
ensurePreviousSessionsIsMutable();
previousSessions_.set(index, builderForValue.build());
onChanged();
} else {
previousSessionsBuilder_.setMessage(index, builderForValue.build());
}
return this;
}
public Builder addPreviousSessions(org.whispersystems.textsecure.storage.StorageProtos.SessionStructure value) {
if (previousSessionsBuilder_ == null) {
if (value == null) {
throw new NullPointerException();
}
ensurePreviousSessionsIsMutable();
previousSessions_.add(value);
onChanged();
} else {
previousSessionsBuilder_.addMessage(value);
}
return this;
}
public Builder addPreviousSessions(
int index, org.whispersystems.textsecure.storage.StorageProtos.SessionStructure value) {
if (previousSessionsBuilder_ == null) {
if (value == null) {
throw new NullPointerException();
}
ensurePreviousSessionsIsMutable();
previousSessions_.add(index, value);
onChanged();
} else {
previousSessionsBuilder_.addMessage(index, value);
}
return this;
}
public Builder addPreviousSessions(
org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Builder builderForValue) {
if (previousSessionsBuilder_ == null) {
ensurePreviousSessionsIsMutable();
previousSessions_.add(builderForValue.build());
onChanged();
} else {
previousSessionsBuilder_.addMessage(builderForValue.build());
}
return this;
}
public Builder addPreviousSessions(
int index, org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Builder builderForValue) {
if (previousSessionsBuilder_ == null) {
ensurePreviousSessionsIsMutable();
previousSessions_.add(index, builderForValue.build());
onChanged();
} else {
previousSessionsBuilder_.addMessage(index, builderForValue.build());
}
return this;
}
public Builder addAllPreviousSessions(
java.lang.Iterable<? extends org.whispersystems.textsecure.storage.StorageProtos.SessionStructure> values) {
if (previousSessionsBuilder_ == null) {
ensurePreviousSessionsIsMutable();
super.addAll(values, previousSessions_);
onChanged();
} else {
previousSessionsBuilder_.addAllMessages(values);
}
return this;
}
public Builder clearPreviousSessions() {
if (previousSessionsBuilder_ == null) {
previousSessions_ = java.util.Collections.emptyList();
bitField0_ = (bitField0_ & ~0x00000002);
onChanged();
} else {
previousSessionsBuilder_.clear();
}
return this;
}
public Builder removePreviousSessions(int index) {
if (previousSessionsBuilder_ == null) {
ensurePreviousSessionsIsMutable();
previousSessions_.remove(index);
onChanged();
} else {
previousSessionsBuilder_.remove(index);
}
return this;
}
public org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Builder getPreviousSessionsBuilder(
int index) {
return getPreviousSessionsFieldBuilder().getBuilder(index);
}
public org.whispersystems.textsecure.storage.StorageProtos.SessionStructureOrBuilder getPreviousSessionsOrBuilder(
int index) {
if (previousSessionsBuilder_ == null) {
return previousSessions_.get(index); } else {
return previousSessionsBuilder_.getMessageOrBuilder(index);
}
}
public java.util.List<? extends org.whispersystems.textsecure.storage.StorageProtos.SessionStructureOrBuilder>
getPreviousSessionsOrBuilderList() {
if (previousSessionsBuilder_ != null) {
return previousSessionsBuilder_.getMessageOrBuilderList();
} else {
return java.util.Collections.unmodifiableList(previousSessions_);
}
}
public org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Builder addPreviousSessionsBuilder() {
return getPreviousSessionsFieldBuilder().addBuilder(
org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.getDefaultInstance());
}
public org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Builder addPreviousSessionsBuilder(
int index) {
return getPreviousSessionsFieldBuilder().addBuilder(
index, org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.getDefaultInstance());
}
public java.util.List<org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Builder>
getPreviousSessionsBuilderList() {
return getPreviousSessionsFieldBuilder().getBuilderList();
}
private com.google.protobuf.RepeatedFieldBuilder<
org.whispersystems.textsecure.storage.StorageProtos.SessionStructure, org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Builder, org.whispersystems.textsecure.storage.StorageProtos.SessionStructureOrBuilder>
getPreviousSessionsFieldBuilder() {
if (previousSessionsBuilder_ == null) {
previousSessionsBuilder_ = new com.google.protobuf.RepeatedFieldBuilder<
org.whispersystems.textsecure.storage.StorageProtos.SessionStructure, org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Builder, org.whispersystems.textsecure.storage.StorageProtos.SessionStructureOrBuilder>(
previousSessions_,
((bitField0_ & 0x00000002) == 0x00000002),
getParentForChildren(),
isClean());
previousSessions_ = null;
}
return previousSessionsBuilder_;
}
// @@protoc_insertion_point(builder_scope:textsecure.RecordStructure)
}
static {
defaultInstance = new RecordStructure(true);
defaultInstance.initFields();
}
// @@protoc_insertion_point(class_scope:textsecure.RecordStructure)
}
public interface PreKeyRecordStructureOrBuilder
extends com.google.protobuf.MessageOrBuilder {
@ -4656,6 +5410,11 @@ public final class StorageProtos {
private static
com.google.protobuf.GeneratedMessage.FieldAccessorTable
internal_static_textsecure_SessionStructure_PendingPreKey_fieldAccessorTable;
private static com.google.protobuf.Descriptors.Descriptor
internal_static_textsecure_RecordStructure_descriptor;
private static
com.google.protobuf.GeneratedMessage.FieldAccessorTable
internal_static_textsecure_RecordStructure_fieldAccessorTable;
private static com.google.protobuf.Descriptors.Descriptor
internal_static_textsecure_PreKeyRecordStructure_descriptor;
private static
@ -4671,7 +5430,7 @@ public final class StorageProtos {
static {
java.lang.String[] descriptorData = {
"\n\032LocalStorageProtocol.proto\022\ntextsecure" +
"\"\205\010\n\020SessionStructure\022\026\n\016sessionVersion\030" +
"\"\233\010\n\020SessionStructure\022\026\n\016sessionVersion\030" +
"\001 \001(\r\022\033\n\023localIdentityPublic\030\002 \001(\014\022\034\n\024re" +
"moteIdentityPublic\030\003 \001(\014\022\017\n\007rootKey\030\004 \001(" +
"\014\022\027\n\017previousCounter\030\005 \001(\r\0227\n\013senderChai" +
@ -4682,25 +5441,28 @@ public final class StorageProtos {
"e.PendingKeyExchange\022A\n\rpendingPreKey\030\t ",
"\001(\0132*.textsecure.SessionStructure.Pendin" +
"gPreKey\022\034\n\024remoteRegistrationId\030\n \001(\r\022\033\n" +
"\023localRegistrationId\030\013 \001(\r\032\253\002\n\005Chain\022\027\n\017" +
"senderEphemeral\030\001 \001(\014\022\036\n\026senderEphemeral" +
"Private\030\002 \001(\014\022=\n\010chainKey\030\003 \001(\0132+.textse" +
"cure.SessionStructure.Chain.ChainKey\022B\n\013" +
"messageKeys\030\004 \003(\0132-.textsecure.SessionSt" +
"ructure.Chain.MessageKey\032&\n\010ChainKey\022\r\n\005" +
"index\030\001 \001(\r\022\013\n\003key\030\002 \001(\014\032>\n\nMessageKey\022\r" +
"\n\005index\030\001 \001(\r\022\021\n\tcipherKey\030\002 \001(\014\022\016\n\006macK",
"ey\030\003 \001(\014\032\321\001\n\022PendingKeyExchange\022\020\n\010seque" +
"nce\030\001 \001(\r\022\024\n\014localBaseKey\030\002 \001(\014\022\033\n\023local" +
"BaseKeyPrivate\030\003 \001(\014\022\031\n\021localEphemeralKe" +
"y\030\004 \001(\014\022 \n\030localEphemeralKeyPrivate\030\005 \001(" +
"\014\022\030\n\020localIdentityKey\030\007 \001(\014\022\037\n\027localIden" +
"tityKeyPrivate\030\010 \001(\014\0322\n\rPendingPreKey\022\020\n" +
"\010preKeyId\030\001 \001(\r\022\017\n\007baseKey\030\002 \001(\014\"J\n\025PreK" +
"eyRecordStructure\022\n\n\002id\030\001 \001(\r\022\021\n\tpublicK" +
"ey\030\002 \001(\014\022\022\n\nprivateKey\030\003 \001(\014B6\n%org.whis" +
"persystems.textsecure.storageB\rStoragePr",
"otos"
"\023localRegistrationId\030\013 \001(\r\022\024\n\014needsRefre" +
"sh\030\014 \001(\010\032\253\002\n\005Chain\022\027\n\017senderEphemeral\030\001 " +
"\001(\014\022\036\n\026senderEphemeralPrivate\030\002 \001(\014\022=\n\010c" +
"hainKey\030\003 \001(\0132+.textsecure.SessionStruct" +
"ure.Chain.ChainKey\022B\n\013messageKeys\030\004 \003(\0132" +
"-.textsecure.SessionStructure.Chain.Mess" +
"ageKey\032&\n\010ChainKey\022\r\n\005index\030\001 \001(\r\022\013\n\003key" +
"\030\002 \001(\014\032>\n\nMessageKey\022\r\n\005index\030\001 \001(\r\022\021\n\tc",
"ipherKey\030\002 \001(\014\022\016\n\006macKey\030\003 \001(\014\032\321\001\n\022Pendi" +
"ngKeyExchange\022\020\n\010sequence\030\001 \001(\r\022\024\n\014local" +
"BaseKey\030\002 \001(\014\022\033\n\023localBaseKeyPrivate\030\003 \001" +
"(\014\022\031\n\021localEphemeralKey\030\004 \001(\014\022 \n\030localEp" +
"hemeralKeyPrivate\030\005 \001(\014\022\030\n\020localIdentity" +
"Key\030\007 \001(\014\022\037\n\027localIdentityKeyPrivate\030\010 \001" +
"(\014\0322\n\rPendingPreKey\022\020\n\010preKeyId\030\001 \001(\r\022\017\n" +
"\007baseKey\030\002 \001(\014\"\177\n\017RecordStructure\0224\n\016cur" +
"rentSession\030\001 \001(\0132\034.textsecure.SessionSt" +
"ructure\0226\n\020previousSessions\030\002 \003(\0132\034.text",
"secure.SessionStructure\"J\n\025PreKeyRecordS" +
"tructure\022\n\n\002id\030\001 \001(\r\022\021\n\tpublicKey\030\002 \001(\014\022" +
"\022\n\nprivateKey\030\003 \001(\014B6\n%org.whispersystem" +
"s.textsecure.storageB\rStorageProtos"
};
com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner =
new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() {
@ -4712,7 +5474,7 @@ public final class StorageProtos {
internal_static_textsecure_SessionStructure_fieldAccessorTable = new
com.google.protobuf.GeneratedMessage.FieldAccessorTable(
internal_static_textsecure_SessionStructure_descriptor,
new java.lang.String[] { "SessionVersion", "LocalIdentityPublic", "RemoteIdentityPublic", "RootKey", "PreviousCounter", "SenderChain", "ReceiverChains", "PendingKeyExchange", "PendingPreKey", "RemoteRegistrationId", "LocalRegistrationId", },
new java.lang.String[] { "SessionVersion", "LocalIdentityPublic", "RemoteIdentityPublic", "RootKey", "PreviousCounter", "SenderChain", "ReceiverChains", "PendingKeyExchange", "PendingPreKey", "RemoteRegistrationId", "LocalRegistrationId", "NeedsRefresh", },
org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.class,
org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Builder.class);
internal_static_textsecure_SessionStructure_Chain_descriptor =
@ -4755,8 +5517,16 @@ public final class StorageProtos {
new java.lang.String[] { "PreKeyId", "BaseKey", },
org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingPreKey.class,
org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingPreKey.Builder.class);
internal_static_textsecure_PreKeyRecordStructure_descriptor =
internal_static_textsecure_RecordStructure_descriptor =
getDescriptor().getMessageTypes().get(1);
internal_static_textsecure_RecordStructure_fieldAccessorTable = new
com.google.protobuf.GeneratedMessage.FieldAccessorTable(
internal_static_textsecure_RecordStructure_descriptor,
new java.lang.String[] { "CurrentSession", "PreviousSessions", },
org.whispersystems.textsecure.storage.StorageProtos.RecordStructure.class,
org.whispersystems.textsecure.storage.StorageProtos.RecordStructure.Builder.class);
internal_static_textsecure_PreKeyRecordStructure_descriptor =
getDescriptor().getMessageTypes().get(2);
internal_static_textsecure_PreKeyRecordStructure_fieldAccessorTable = new
com.google.protobuf.GeneratedMessage.FieldAccessorTable(
internal_static_textsecure_PreKeyRecordStructure_descriptor,

@ -420,12 +420,12 @@ public class DecryptingQueue {
}
}
private void handleKeyExchangeProcessing(String plaintxtBody) {
private void handleKeyExchangeProcessing(String plaintextBody) {
if (TextSecurePreferences.isAutoRespondKeyExchangeEnabled(context)) {
try {
Recipient recipient = RecipientFactory.getRecipientsFromString(context, originator, false).getPrimaryRecipient();
RecipientDevice recipientDevice = new RecipientDevice(recipient.getRecipientId(), deviceId);
KeyExchangeMessage message = KeyExchangeMessage.createFor(plaintxtBody);
KeyExchangeMessage message = KeyExchangeMessage.createFor(plaintextBody);
KeyExchangeProcessor processor = KeyExchangeProcessor.createFor(context, masterSecret, recipientDevice, message);
if (processor.isStale(message)) {

@ -75,7 +75,7 @@ public class KeyExchangeInitiator {
RecipientDevice recipientDevice = new RecipientDevice(recipient.getRecipientId(), RecipientDevice.DEFAULT_DEVICE_ID);
SessionRecordV2 sessionRecordV2 = new SessionRecordV2(context, masterSecret, recipientDevice);
sessionRecordV2.setPendingKeyExchange(sequence, baseKey, ephemeralKey, identityKey);
sessionRecordV2.getSessionState().setPendingKeyExchange(sequence, baseKey, ephemeralKey, identityKey);
sessionRecordV2.save();
MessageSender.send(context, masterSecret, textMessage, -1);
@ -87,6 +87,7 @@ public class KeyExchangeInitiator {
RecipientDevice recipientDevice = new RecipientDevice(recipient.getRecipientId(), RecipientDevice.DEFAULT_DEVICE_ID);
return
new SessionRecordV2(context, masterSecret, recipientDevice)
.getSessionState()
.hasPendingKeyExchange();
}

@ -72,8 +72,8 @@ public class KeyExchangeProcessorV2 extends KeyExchangeProcessor {
KeyExchangeMessageV2 message = (KeyExchangeMessageV2)m;
return
message.isResponse() &&
(!sessionRecord.hasPendingKeyExchange() ||
sessionRecord.getPendingKeyExchangeSequence() != message.getSequence()) &&
(!sessionRecord.getSessionState().hasPendingKeyExchange() ||
sessionRecord.getSessionState().getPendingKeyExchangeSequence() != message.getSequence()) &&
!message.isResponseForSimultaneousInitiate();
}
@ -95,18 +95,26 @@ public class KeyExchangeProcessorV2 extends KeyExchangeProcessor {
if (!PreKeyRecord.hasRecord(context, preKeyId))
throw new InvalidKeyIdException("No such prekey: " + preKeyId);
PreKeyRecord preKeyRecord = new PreKeyRecord(context, masterSecret, preKeyId);
ECKeyPair ourBaseKey = preKeyRecord.getKeyPair();
ECKeyPair ourEphemeralKey = ourBaseKey;
IdentityKeyPair ourIdentityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret, ourBaseKey.getPublicKey().getType());
PreKeyRecord preKeyRecord = new PreKeyRecord(context, masterSecret, preKeyId);
ECKeyPair ourBaseKey = preKeyRecord.getKeyPair();
ECKeyPair ourEphemeralKey = ourBaseKey;
IdentityKeyPair ourIdentityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret, ourBaseKey.getPublicKey().getType());
boolean simultaneousInitiate = sessionRecord.getSessionState().hasPendingPreKey();
sessionRecord.clear();
if (!simultaneousInitiate) sessionRecord.clear();
else sessionRecord.archiveCurrentState();
RatchetingSession.initializeSession(sessionRecord.getSessionState(),
ourBaseKey, theirBaseKey,
ourEphemeralKey, theirEphemeralKey,
ourIdentityKey, theirIdentityKey);
RatchetingSession.initializeSession(sessionRecord, ourBaseKey, theirBaseKey, ourEphemeralKey,
theirEphemeralKey, ourIdentityKey, theirIdentityKey);
Session.clearV1SessionFor(context, recipientDevice.getRecipient());
sessionRecord.setLocalRegistrationId(TextSecurePreferences.getLocalRegistrationId(context));
sessionRecord.setRemoteRegistrationId(message.getRegistrationId());
sessionRecord.getSessionState().setLocalRegistrationId(TextSecurePreferences.getLocalRegistrationId(context));
sessionRecord.getSessionState().setRemoteRegistrationId(message.getRegistrationId());
if (simultaneousInitiate) sessionRecord.getSessionState().setNeedsRefresh(true);
sessionRecord.save();
if (preKeyId != Medium.MAX_VALUE) {
@ -129,14 +137,17 @@ public class KeyExchangeProcessorV2 extends KeyExchangeProcessor {
ourBaseKey.getPublicKey()
.getType());
sessionRecord.clear();
if (sessionRecord.getSessionState().getNeedsRefresh()) sessionRecord.archiveCurrentState();
else sessionRecord.clear();
RatchetingSession.initializeSession(sessionRecord, ourBaseKey, theirBaseKey, ourEphemeralKey,
RatchetingSession.initializeSession(sessionRecord.getSessionState(),
ourBaseKey, theirBaseKey, ourEphemeralKey,
theirEphemeralKey, ourIdentityKey, theirIdentityKey);
sessionRecord.setPendingPreKey(message.getKeyId(), ourBaseKey.getPublicKey());
sessionRecord.setLocalRegistrationId(TextSecurePreferences.getLocalRegistrationId(context));
sessionRecord.setRemoteRegistrationId(message.getRegistrationId());
sessionRecord.getSessionState().setPendingPreKey(message.getKeyId(), ourBaseKey.getPublicKey());
sessionRecord.getSessionState().setLocalRegistrationId(TextSecurePreferences.getLocalRegistrationId(context));
sessionRecord.getSessionState().setRemoteRegistrationId(message.getRegistrationId());
sessionRecord.save();
DatabaseFactory.getIdentityDatabase(context)
@ -168,23 +179,23 @@ public class KeyExchangeProcessorV2 extends KeyExchangeProcessor {
Log.w("KeyExchangeProcessorV2", "KeyExchange is an initiate.");
if (!sessionRecord.hasPendingKeyExchange()) {
if (!sessionRecord.getSessionState().hasPendingKeyExchange()) {
Log.w("KeyExchangeProcessorV2", "We don't have a pending initiate...");
ourBaseKey = Curve.generateKeyPairForType(message.getBaseKey().getType());
ourEphemeralKey = Curve.generateKeyPairForType(message.getBaseKey().getType());
ourIdentityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret, message.getBaseKey().getType());
sessionRecord.setPendingKeyExchange(message.getSequence(), ourBaseKey, ourEphemeralKey,
ourIdentityKey);
sessionRecord.getSessionState().setPendingKeyExchange(message.getSequence(), ourBaseKey,
ourEphemeralKey, ourIdentityKey);
} else {
Log.w("KeyExchangeProcessorV2", "We alredy have a pending initiate, responding as simultaneous initiate...");
ourBaseKey = sessionRecord.getPendingKeyExchangeBaseKey();
ourEphemeralKey = sessionRecord.getPendingKeyExchangeEphemeralKey();
ourIdentityKey = sessionRecord.getPendingKeyExchangeIdentityKey();
ourBaseKey = sessionRecord.getSessionState().getPendingKeyExchangeBaseKey();
ourEphemeralKey = sessionRecord.getSessionState().getPendingKeyExchangeEphemeralKey();
ourIdentityKey = sessionRecord.getSessionState().getPendingKeyExchangeIdentityKey();
flags |= KeyExchangeMessageV2.SIMULTAENOUS_INITIATE_FLAG;
sessionRecord.setPendingKeyExchange(message.getSequence(), ourBaseKey, ourEphemeralKey,
ourIdentityKey);
sessionRecord.getSessionState().setPendingKeyExchange(message.getSequence(), ourBaseKey,
ourEphemeralKey, ourIdentityKey);
}
KeyExchangeMessageV2 ourMessage = new KeyExchangeMessageV2(message.getSequence(),
@ -197,23 +208,24 @@ public class KeyExchangeProcessorV2 extends KeyExchangeProcessor {
MessageSender.send(context, masterSecret, textMessage, threadId);
}
if (message.getSequence() != sessionRecord.getPendingKeyExchangeSequence()) {
if (message.getSequence() != sessionRecord.getSessionState().getPendingKeyExchangeSequence()) {
Log.w("KeyExchangeProcessorV2", "No matching sequence for response. " +
"Is simultaneous initiate response: " + message.isResponseForSimultaneousInitiate());
return;
}
ECKeyPair ourBaseKey = sessionRecord.getPendingKeyExchangeBaseKey();
ECKeyPair ourEphemeralKey = sessionRecord.getPendingKeyExchangeEphemeralKey();
IdentityKeyPair ourIdentityKey = sessionRecord.getPendingKeyExchangeIdentityKey();
ECKeyPair ourBaseKey = sessionRecord.getSessionState().getPendingKeyExchangeBaseKey();
ECKeyPair ourEphemeralKey = sessionRecord.getSessionState().getPendingKeyExchangeEphemeralKey();
IdentityKeyPair ourIdentityKey = sessionRecord.getSessionState().getPendingKeyExchangeIdentityKey();
sessionRecord.clear();
RatchetingSession.initializeSession(sessionRecord, ourBaseKey, message.getBaseKey(),
RatchetingSession.initializeSession(sessionRecord.getSessionState(),
ourBaseKey, message.getBaseKey(),
ourEphemeralKey, message.getEphemeralKey(),
ourIdentityKey, message.getIdentityKey());
sessionRecord.setSessionVersion(message.getVersion());
sessionRecord.getSessionState().setSessionVersion(message.getVersion());
Session.clearV1SessionFor(context, recipientDevice.getRecipient());
sessionRecord.save();

@ -327,7 +327,9 @@ public class PushTransport extends BaseTransport {
PushAddress pushAddress, byte[] plaintext)
throws IOException, UntrustedIdentityException
{
if (!SessionRecordV2.hasSession(context, masterSecret, pushAddress)) {
if (!SessionRecordV2.hasSession(context, masterSecret, pushAddress) ||
SessionRecordV2.needsRefresh(context, masterSecret, pushAddress))
{
try {
List<PreKeyEntity> preKeys = socket.getPreKeys(pushAddress);

@ -33,7 +33,6 @@ import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.crypto.SessionCipher;
import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage;
import org.whispersystems.textsecure.storage.RecipientDevice;
import org.whispersystems.textsecure.util.Hex;
import java.util.ArrayList;

Loading…
Cancel
Save