mirror of
https://github.com/oxen-io/session-android.git
synced 2025-08-22 09:17:31 +00:00
Support for multi-device.
1) In addition to the Recipient interface, there is now RecipientDevice. A Recipient can have multiple corresponding RecipientDevices. All addressing is done to a Recipient, but crypto sessions and transport delivery are done to RecipientDevice. 2) The Push transport handles the discovery and session setup of additional Recipient devices. 3) Some internal rejiggering of Groups.
This commit is contained in:
@@ -3,6 +3,6 @@
|
||||
package="org.whispersystems.textsecure"
|
||||
android:versionCode="1"
|
||||
android:versionName="0.1">
|
||||
<uses-sdk android:minSdkVersion="8" android:targetSdkVersion="16"/>
|
||||
<uses-sdk android:minSdkVersion="9" android:targetSdkVersion="16"/>
|
||||
<application />
|
||||
</manifest>
|
||||
|
@@ -4,7 +4,7 @@ buildscript {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:0.6.1'
|
||||
classpath 'com.android.tools.build:gradle:0.7.+'
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -6,6 +6,7 @@ option java_outer_classname = "PushMessageProtos";
|
||||
message IncomingPushMessageSignal {
|
||||
optional uint32 type = 1;
|
||||
optional string source = 2;
|
||||
optional uint32 sourceDevice = 7;
|
||||
optional string relay = 3;
|
||||
// repeated string destinations = 4; // No longer supported
|
||||
optional uint64 timestamp = 5;
|
||||
|
@@ -20,7 +20,7 @@ package org.whispersystems.textsecure.crypto;
|
||||
import android.content.Context;
|
||||
|
||||
import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage;
|
||||
import org.whispersystems.textsecure.storage.CanonicalRecipientAddress;
|
||||
import org.whispersystems.textsecure.storage.RecipientDevice;
|
||||
import org.whispersystems.textsecure.storage.SessionRecordV1;
|
||||
import org.whispersystems.textsecure.storage.SessionRecordV2;
|
||||
|
||||
@@ -31,13 +31,14 @@ public abstract class SessionCipher {
|
||||
public abstract CiphertextMessage encrypt(byte[] paddedMessage);
|
||||
public abstract byte[] decrypt(byte[] decodedMessage) throws InvalidMessageException;
|
||||
|
||||
public static SessionCipher createFor(Context context, MasterSecret masterSecret,
|
||||
CanonicalRecipientAddress recipient)
|
||||
public static SessionCipher createFor(Context context,
|
||||
MasterSecret masterSecret,
|
||||
RecipientDevice recipient)
|
||||
{
|
||||
if (SessionRecordV2.hasSession(context, masterSecret, recipient)) {
|
||||
return new SessionCipherV2(context, masterSecret, recipient);
|
||||
} else if (SessionRecordV1.hasSession(context, recipient)) {
|
||||
return new SessionCipherV1(context, masterSecret, recipient);
|
||||
} else if (SessionRecordV1.hasSession(context, recipient.getRecipientId())) {
|
||||
return new SessionCipherV1(context, masterSecret, recipient.getRecipient());
|
||||
} else {
|
||||
throw new AssertionError("Attempt to initialize cipher for non-existing session.");
|
||||
}
|
||||
|
@@ -9,7 +9,8 @@ import org.whispersystems.textsecure.crypto.kdf.DerivedSecrets;
|
||||
import org.whispersystems.textsecure.crypto.kdf.NKDF;
|
||||
import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage;
|
||||
import org.whispersystems.textsecure.crypto.protocol.WhisperMessageV1;
|
||||
import org.whispersystems.textsecure.storage.CanonicalRecipientAddress;
|
||||
import org.whispersystems.textsecure.storage.CanonicalRecipient;
|
||||
import org.whispersystems.textsecure.storage.RecipientDevice;
|
||||
import org.whispersystems.textsecure.storage.InvalidKeyIdException;
|
||||
import org.whispersystems.textsecure.storage.LocalKeyRecord;
|
||||
import org.whispersystems.textsecure.storage.RemoteKeyRecord;
|
||||
@@ -29,16 +30,17 @@ import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
public class SessionCipherV1 extends SessionCipher {
|
||||
|
||||
private final Context context;
|
||||
private final MasterSecret masterSecret;
|
||||
private final CanonicalRecipientAddress recipient;
|
||||
private final Context context;
|
||||
private final MasterSecret masterSecret;
|
||||
private final CanonicalRecipient recipient;
|
||||
|
||||
public SessionCipherV1(Context context, MasterSecret masterSecret,
|
||||
CanonicalRecipientAddress recipient)
|
||||
public SessionCipherV1(Context context,
|
||||
MasterSecret masterSecret,
|
||||
CanonicalRecipient recipient)
|
||||
{
|
||||
this.context = context;
|
||||
this.masterSecret = masterSecret;
|
||||
this.recipient = recipient;
|
||||
this.context = context;
|
||||
this.masterSecret = masterSecret;
|
||||
this.recipient = recipient;
|
||||
}
|
||||
|
||||
public CiphertextMessage encrypt(byte[] paddedMessageBody) {
|
||||
@@ -219,7 +221,7 @@ public class SessionCipherV1 extends SessionCipher {
|
||||
}
|
||||
|
||||
private KeyRecords getKeyRecords(Context context, MasterSecret masterSecret,
|
||||
CanonicalRecipientAddress recipient)
|
||||
CanonicalRecipient recipient)
|
||||
{
|
||||
LocalKeyRecord localKeyRecord = new LocalKeyRecord(context, masterSecret, recipient);
|
||||
RemoteKeyRecord remoteKeyRecord = new RemoteKeyRecord(context, recipient);
|
||||
|
@@ -1,7 +1,6 @@
|
||||
package org.whispersystems.textsecure.crypto;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
|
||||
import org.whispersystems.textsecure.crypto.ecc.Curve;
|
||||
@@ -13,7 +12,7 @@ import org.whispersystems.textsecure.crypto.protocol.WhisperMessageV2;
|
||||
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.CanonicalRecipientAddress;
|
||||
import org.whispersystems.textsecure.storage.RecipientDevice;
|
||||
import org.whispersystems.textsecure.storage.SessionRecordV2;
|
||||
import org.whispersystems.textsecure.util.Conversions;
|
||||
|
||||
@@ -29,13 +28,13 @@ import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
public class SessionCipherV2 extends SessionCipher {
|
||||
|
||||
private final Context context;
|
||||
private final MasterSecret masterSecret;
|
||||
private final CanonicalRecipientAddress recipient;
|
||||
private final Context context;
|
||||
private final MasterSecret masterSecret;
|
||||
private final RecipientDevice recipient;
|
||||
|
||||
public SessionCipherV2(Context context,
|
||||
MasterSecret masterSecret,
|
||||
CanonicalRecipientAddress recipient)
|
||||
RecipientDevice recipient)
|
||||
{
|
||||
this.context = context;
|
||||
this.masterSecret = masterSecret;
|
||||
|
@@ -37,6 +37,7 @@ public class IncomingPushMessage implements PushMessage, Parcelable {
|
||||
|
||||
private int type;
|
||||
private String source;
|
||||
private int sourceDevice;
|
||||
private byte[] message;
|
||||
private long timestamp;
|
||||
private String relay;
|
||||
@@ -44,6 +45,7 @@ public class IncomingPushMessage implements PushMessage, Parcelable {
|
||||
private IncomingPushMessage(IncomingPushMessage message, byte[] body) {
|
||||
this.type = message.type;
|
||||
this.source = message.source;
|
||||
this.sourceDevice = message.sourceDevice;
|
||||
this.timestamp = message.timestamp;
|
||||
this.relay = message.relay;
|
||||
this.message = body;
|
||||
@@ -52,14 +54,16 @@ public class IncomingPushMessage implements PushMessage, Parcelable {
|
||||
public IncomingPushMessage(IncomingPushMessageSignal signal) {
|
||||
this.type = signal.getType();
|
||||
this.source = signal.getSource();
|
||||
this.sourceDevice = signal.getSourceDevice();
|
||||
this.message = signal.getMessage().toByteArray();
|
||||
this.timestamp = signal.getTimestamp();
|
||||
this.relay = signal.getRelay();
|
||||
}
|
||||
|
||||
public IncomingPushMessage(Parcel in) {
|
||||
this.type = in.readInt();
|
||||
this.source = in.readString();
|
||||
this.type = in.readInt();
|
||||
this.source = in.readString();
|
||||
this.sourceDevice = in.readInt();
|
||||
|
||||
if (in.readInt() == 1) {
|
||||
this.relay = in.readString();
|
||||
@@ -70,11 +74,12 @@ public class IncomingPushMessage implements PushMessage, Parcelable {
|
||||
this.timestamp = in.readLong();
|
||||
}
|
||||
|
||||
public IncomingPushMessage(int type, String source,
|
||||
public IncomingPushMessage(int type, String source, int sourceDevice,
|
||||
byte[] body, long timestamp)
|
||||
{
|
||||
this.type = type;
|
||||
this.source = source;
|
||||
this.sourceDevice = sourceDevice;
|
||||
this.message = body;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
@@ -91,6 +96,10 @@ public class IncomingPushMessage implements PushMessage, Parcelable {
|
||||
return source;
|
||||
}
|
||||
|
||||
public int getSourceDevice() {
|
||||
return sourceDevice;
|
||||
}
|
||||
|
||||
public byte[] getBody() {
|
||||
return message;
|
||||
}
|
||||
@@ -104,6 +113,7 @@ public class IncomingPushMessage implements PushMessage, Parcelable {
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeInt(type);
|
||||
dest.writeString(source);
|
||||
dest.writeInt(sourceDevice);
|
||||
dest.writeInt(relay == null ? 0 : 1);
|
||||
if (relay != null) {
|
||||
dest.writeString(relay);
|
||||
|
@@ -0,0 +1,17 @@
|
||||
package org.whispersystems.textsecure.push;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class MismatchedDevices {
|
||||
private List<Integer> missingDevices;
|
||||
|
||||
private List<Integer> extraDevices;
|
||||
|
||||
public List<Integer> getMissingDevices() {
|
||||
return missingDevices;
|
||||
}
|
||||
|
||||
public List<Integer> getExtraDevices() {
|
||||
return extraDevices;
|
||||
}
|
||||
}
|
@@ -0,0 +1,16 @@
|
||||
package org.whispersystems.textsecure.push;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class MismatchedDevicesException extends IOException {
|
||||
|
||||
private final MismatchedDevices mismatchedDevices;
|
||||
|
||||
public MismatchedDevicesException(MismatchedDevices mismatchedDevices) {
|
||||
this.mismatchedDevices = mismatchedDevices;
|
||||
}
|
||||
|
||||
public MismatchedDevices getMismatchedDevices() {
|
||||
return mismatchedDevices;
|
||||
}
|
||||
}
|
@@ -21,23 +21,17 @@ import org.whispersystems.textsecure.util.Base64;
|
||||
public class OutgoingPushMessage implements PushMessage {
|
||||
|
||||
private int type;
|
||||
private String destination;
|
||||
private int destinationDeviceId;
|
||||
private String body;
|
||||
private String relay;
|
||||
|
||||
public OutgoingPushMessage(String destination, byte[] body, int type) {
|
||||
this(null, destination, body, type);
|
||||
public OutgoingPushMessage(PushAddress address, PushBody body) {
|
||||
this.type = body.getType();
|
||||
this.destinationDeviceId = address.getDeviceId();
|
||||
this.body = Base64.encodeBytes(body.getBody());
|
||||
}
|
||||
|
||||
public OutgoingPushMessage(String relay, String destination, byte[] body, int type) {
|
||||
this.relay = relay;
|
||||
this.destination = destination;
|
||||
this.body = Base64.encodeBytes(body);
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public String getDestination() {
|
||||
return destination;
|
||||
public int getDestinationDeviceId() {
|
||||
return destinationDeviceId;
|
||||
}
|
||||
|
||||
public String getBody() {
|
||||
@@ -47,8 +41,4 @@ public class OutgoingPushMessage implements PushMessage {
|
||||
public int getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public String getRelay() {
|
||||
return relay;
|
||||
}
|
||||
}
|
||||
|
@@ -5,18 +5,27 @@ import java.util.List;
|
||||
|
||||
public class OutgoingPushMessageList {
|
||||
|
||||
private String destination;
|
||||
|
||||
private String relay;
|
||||
|
||||
private List<OutgoingPushMessage> messages;
|
||||
|
||||
public OutgoingPushMessageList(OutgoingPushMessage message) {
|
||||
this.messages = new LinkedList<OutgoingPushMessage>();
|
||||
this.messages.add(message);
|
||||
public OutgoingPushMessageList(String destination, String relay, List<OutgoingPushMessage> messages) {
|
||||
this.destination = destination;
|
||||
this.relay = relay;
|
||||
this.messages = messages;
|
||||
}
|
||||
|
||||
public OutgoingPushMessageList(List<OutgoingPushMessage> messages) {
|
||||
this.messages = messages;
|
||||
public String getDestination() {
|
||||
return destination;
|
||||
}
|
||||
|
||||
public List<OutgoingPushMessage> getMessages() {
|
||||
return messages;
|
||||
}
|
||||
|
||||
public String getRelay() {
|
||||
return relay;
|
||||
}
|
||||
}
|
||||
|
@@ -8,6 +8,7 @@ import com.google.thoughtcrimegson.JsonParseException;
|
||||
import com.google.thoughtcrimegson.JsonPrimitive;
|
||||
import com.google.thoughtcrimegson.JsonSerializationContext;
|
||||
import com.google.thoughtcrimegson.JsonSerializer;
|
||||
import com.google.thoughtcrimegson.annotations.Expose;
|
||||
|
||||
import org.whispersystems.textsecure.crypto.IdentityKey;
|
||||
import org.whispersystems.textsecure.crypto.InvalidKeyException;
|
||||
@@ -20,6 +21,9 @@ import java.lang.reflect.Type;
|
||||
|
||||
public class PreKeyEntity {
|
||||
|
||||
@Expose(serialize = false)
|
||||
private int deviceId;
|
||||
|
||||
private int keyId;
|
||||
private ECPublicKey publicKey;
|
||||
private IdentityKey identityKey;
|
||||
@@ -30,6 +34,10 @@ public class PreKeyEntity {
|
||||
this.identityKey = identityKey;
|
||||
}
|
||||
|
||||
public int getDeviceId() {
|
||||
return deviceId;
|
||||
}
|
||||
|
||||
public int getKeyId() {
|
||||
return keyId;
|
||||
}
|
||||
|
@@ -0,0 +1,32 @@
|
||||
package org.whispersystems.textsecure.push;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import org.whispersystems.textsecure.directory.Directory;
|
||||
import org.whispersystems.textsecure.storage.RecipientDevice;
|
||||
|
||||
public class PushAddress extends RecipientDevice {
|
||||
|
||||
private final String e164number;
|
||||
private final String relay;
|
||||
|
||||
private PushAddress(long recipientId, String e164number, int deviceId, String relay) {
|
||||
super(recipientId, deviceId);
|
||||
this.e164number = e164number;
|
||||
this.relay = relay;
|
||||
}
|
||||
|
||||
public String getNumber() {
|
||||
return e164number;
|
||||
}
|
||||
|
||||
public String getRelay() {
|
||||
return relay;
|
||||
}
|
||||
|
||||
public static PushAddress create(Context context, long recipientId, String e164number, int deviceId) {
|
||||
String relay = Directory.getInstance(context).getRelay(e164number);
|
||||
return new PushAddress(recipientId, e164number, deviceId, relay);
|
||||
}
|
||||
|
||||
}
|
@@ -1,37 +0,0 @@
|
||||
package org.whispersystems.textsecure.push;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import org.whispersystems.textsecure.directory.Directory;
|
||||
import org.whispersystems.textsecure.util.InvalidNumberException;
|
||||
import org.whispersystems.textsecure.util.PhoneNumberFormatter;
|
||||
|
||||
public class PushDestination {
|
||||
|
||||
private final String e164number;
|
||||
private final String relay;
|
||||
|
||||
private PushDestination(String e164number, String relay) {
|
||||
this.e164number = e164number;
|
||||
this.relay = relay;
|
||||
}
|
||||
|
||||
public String getNumber() {
|
||||
return e164number;
|
||||
}
|
||||
|
||||
public String getRelay() {
|
||||
return relay;
|
||||
}
|
||||
|
||||
public static PushDestination create(Context context,
|
||||
String localNumber,
|
||||
String destinationNumber)
|
||||
throws InvalidNumberException
|
||||
{
|
||||
String e164destination = PhoneNumberFormatter.formatNumber(destinationNumber, localNumber);
|
||||
String relay = Directory.getInstance(context).getRelay(e164destination);
|
||||
|
||||
return new PushDestination(e164destination, relay);
|
||||
}
|
||||
}
|
@@ -19,6 +19,10 @@ public final class PushMessageProtos {
|
||||
boolean hasSource();
|
||||
String getSource();
|
||||
|
||||
// optional uint32 sourceDevice = 7;
|
||||
boolean hasSourceDevice();
|
||||
int getSourceDevice();
|
||||
|
||||
// optional string relay = 3;
|
||||
boolean hasRelay();
|
||||
String getRelay();
|
||||
@@ -102,11 +106,21 @@ public final class PushMessageProtos {
|
||||
}
|
||||
}
|
||||
|
||||
// optional uint32 sourceDevice = 7;
|
||||
public static final int SOURCEDEVICE_FIELD_NUMBER = 7;
|
||||
private int sourceDevice_;
|
||||
public boolean hasSourceDevice() {
|
||||
return ((bitField0_ & 0x00000004) == 0x00000004);
|
||||
}
|
||||
public int getSourceDevice() {
|
||||
return sourceDevice_;
|
||||
}
|
||||
|
||||
// optional string relay = 3;
|
||||
public static final int RELAY_FIELD_NUMBER = 3;
|
||||
private java.lang.Object relay_;
|
||||
public boolean hasRelay() {
|
||||
return ((bitField0_ & 0x00000004) == 0x00000004);
|
||||
return ((bitField0_ & 0x00000008) == 0x00000008);
|
||||
}
|
||||
public String getRelay() {
|
||||
java.lang.Object ref = relay_;
|
||||
@@ -138,7 +152,7 @@ public final class PushMessageProtos {
|
||||
public static final int TIMESTAMP_FIELD_NUMBER = 5;
|
||||
private long timestamp_;
|
||||
public boolean hasTimestamp() {
|
||||
return ((bitField0_ & 0x00000008) == 0x00000008);
|
||||
return ((bitField0_ & 0x00000010) == 0x00000010);
|
||||
}
|
||||
public long getTimestamp() {
|
||||
return timestamp_;
|
||||
@@ -148,7 +162,7 @@ public final class PushMessageProtos {
|
||||
public static final int MESSAGE_FIELD_NUMBER = 6;
|
||||
private com.google.protobuf.ByteString message_;
|
||||
public boolean hasMessage() {
|
||||
return ((bitField0_ & 0x00000010) == 0x00000010);
|
||||
return ((bitField0_ & 0x00000020) == 0x00000020);
|
||||
}
|
||||
public com.google.protobuf.ByteString getMessage() {
|
||||
return message_;
|
||||
@@ -157,6 +171,7 @@ public final class PushMessageProtos {
|
||||
private void initFields() {
|
||||
type_ = 0;
|
||||
source_ = "";
|
||||
sourceDevice_ = 0;
|
||||
relay_ = "";
|
||||
timestamp_ = 0L;
|
||||
message_ = com.google.protobuf.ByteString.EMPTY;
|
||||
@@ -179,15 +194,18 @@ public final class PushMessageProtos {
|
||||
if (((bitField0_ & 0x00000002) == 0x00000002)) {
|
||||
output.writeBytes(2, getSourceBytes());
|
||||
}
|
||||
if (((bitField0_ & 0x00000004) == 0x00000004)) {
|
||||
if (((bitField0_ & 0x00000008) == 0x00000008)) {
|
||||
output.writeBytes(3, getRelayBytes());
|
||||
}
|
||||
if (((bitField0_ & 0x00000008) == 0x00000008)) {
|
||||
if (((bitField0_ & 0x00000010) == 0x00000010)) {
|
||||
output.writeUInt64(5, timestamp_);
|
||||
}
|
||||
if (((bitField0_ & 0x00000010) == 0x00000010)) {
|
||||
if (((bitField0_ & 0x00000020) == 0x00000020)) {
|
||||
output.writeBytes(6, message_);
|
||||
}
|
||||
if (((bitField0_ & 0x00000004) == 0x00000004)) {
|
||||
output.writeUInt32(7, sourceDevice_);
|
||||
}
|
||||
getUnknownFields().writeTo(output);
|
||||
}
|
||||
|
||||
@@ -205,18 +223,22 @@ public final class PushMessageProtos {
|
||||
size += com.google.protobuf.CodedOutputStream
|
||||
.computeBytesSize(2, getSourceBytes());
|
||||
}
|
||||
if (((bitField0_ & 0x00000004) == 0x00000004)) {
|
||||
if (((bitField0_ & 0x00000008) == 0x00000008)) {
|
||||
size += com.google.protobuf.CodedOutputStream
|
||||
.computeBytesSize(3, getRelayBytes());
|
||||
}
|
||||
if (((bitField0_ & 0x00000008) == 0x00000008)) {
|
||||
if (((bitField0_ & 0x00000010) == 0x00000010)) {
|
||||
size += com.google.protobuf.CodedOutputStream
|
||||
.computeUInt64Size(5, timestamp_);
|
||||
}
|
||||
if (((bitField0_ & 0x00000010) == 0x00000010)) {
|
||||
if (((bitField0_ & 0x00000020) == 0x00000020)) {
|
||||
size += com.google.protobuf.CodedOutputStream
|
||||
.computeBytesSize(6, message_);
|
||||
}
|
||||
if (((bitField0_ & 0x00000004) == 0x00000004)) {
|
||||
size += com.google.protobuf.CodedOutputStream
|
||||
.computeUInt32Size(7, sourceDevice_);
|
||||
}
|
||||
size += getUnknownFields().getSerializedSize();
|
||||
memoizedSerializedSize = size;
|
||||
return size;
|
||||
@@ -345,12 +367,14 @@ public final class PushMessageProtos {
|
||||
bitField0_ = (bitField0_ & ~0x00000001);
|
||||
source_ = "";
|
||||
bitField0_ = (bitField0_ & ~0x00000002);
|
||||
relay_ = "";
|
||||
sourceDevice_ = 0;
|
||||
bitField0_ = (bitField0_ & ~0x00000004);
|
||||
timestamp_ = 0L;
|
||||
relay_ = "";
|
||||
bitField0_ = (bitField0_ & ~0x00000008);
|
||||
message_ = com.google.protobuf.ByteString.EMPTY;
|
||||
timestamp_ = 0L;
|
||||
bitField0_ = (bitField0_ & ~0x00000010);
|
||||
message_ = com.google.protobuf.ByteString.EMPTY;
|
||||
bitField0_ = (bitField0_ & ~0x00000020);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -400,14 +424,18 @@ public final class PushMessageProtos {
|
||||
if (((from_bitField0_ & 0x00000004) == 0x00000004)) {
|
||||
to_bitField0_ |= 0x00000004;
|
||||
}
|
||||
result.relay_ = relay_;
|
||||
result.sourceDevice_ = sourceDevice_;
|
||||
if (((from_bitField0_ & 0x00000008) == 0x00000008)) {
|
||||
to_bitField0_ |= 0x00000008;
|
||||
}
|
||||
result.timestamp_ = timestamp_;
|
||||
result.relay_ = relay_;
|
||||
if (((from_bitField0_ & 0x00000010) == 0x00000010)) {
|
||||
to_bitField0_ |= 0x00000010;
|
||||
}
|
||||
result.timestamp_ = timestamp_;
|
||||
if (((from_bitField0_ & 0x00000020) == 0x00000020)) {
|
||||
to_bitField0_ |= 0x00000020;
|
||||
}
|
||||
result.message_ = message_;
|
||||
result.bitField0_ = to_bitField0_;
|
||||
onBuilt();
|
||||
@@ -431,6 +459,9 @@ public final class PushMessageProtos {
|
||||
if (other.hasSource()) {
|
||||
setSource(other.getSource());
|
||||
}
|
||||
if (other.hasSourceDevice()) {
|
||||
setSourceDevice(other.getSourceDevice());
|
||||
}
|
||||
if (other.hasRelay()) {
|
||||
setRelay(other.getRelay());
|
||||
}
|
||||
@@ -482,20 +513,25 @@ public final class PushMessageProtos {
|
||||
break;
|
||||
}
|
||||
case 26: {
|
||||
bitField0_ |= 0x00000004;
|
||||
bitField0_ |= 0x00000008;
|
||||
relay_ = input.readBytes();
|
||||
break;
|
||||
}
|
||||
case 40: {
|
||||
bitField0_ |= 0x00000008;
|
||||
bitField0_ |= 0x00000010;
|
||||
timestamp_ = input.readUInt64();
|
||||
break;
|
||||
}
|
||||
case 50: {
|
||||
bitField0_ |= 0x00000010;
|
||||
bitField0_ |= 0x00000020;
|
||||
message_ = input.readBytes();
|
||||
break;
|
||||
}
|
||||
case 56: {
|
||||
bitField0_ |= 0x00000004;
|
||||
sourceDevice_ = input.readUInt32();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -559,10 +595,31 @@ public final class PushMessageProtos {
|
||||
onChanged();
|
||||
}
|
||||
|
||||
// optional uint32 sourceDevice = 7;
|
||||
private int sourceDevice_ ;
|
||||
public boolean hasSourceDevice() {
|
||||
return ((bitField0_ & 0x00000004) == 0x00000004);
|
||||
}
|
||||
public int getSourceDevice() {
|
||||
return sourceDevice_;
|
||||
}
|
||||
public Builder setSourceDevice(int value) {
|
||||
bitField0_ |= 0x00000004;
|
||||
sourceDevice_ = value;
|
||||
onChanged();
|
||||
return this;
|
||||
}
|
||||
public Builder clearSourceDevice() {
|
||||
bitField0_ = (bitField0_ & ~0x00000004);
|
||||
sourceDevice_ = 0;
|
||||
onChanged();
|
||||
return this;
|
||||
}
|
||||
|
||||
// optional string relay = 3;
|
||||
private java.lang.Object relay_ = "";
|
||||
public boolean hasRelay() {
|
||||
return ((bitField0_ & 0x00000004) == 0x00000004);
|
||||
return ((bitField0_ & 0x00000008) == 0x00000008);
|
||||
}
|
||||
public String getRelay() {
|
||||
java.lang.Object ref = relay_;
|
||||
@@ -578,19 +635,19 @@ public final class PushMessageProtos {
|
||||
if (value == null) {
|
||||
throw new NullPointerException();
|
||||
}
|
||||
bitField0_ |= 0x00000004;
|
||||
bitField0_ |= 0x00000008;
|
||||
relay_ = value;
|
||||
onChanged();
|
||||
return this;
|
||||
}
|
||||
public Builder clearRelay() {
|
||||
bitField0_ = (bitField0_ & ~0x00000004);
|
||||
bitField0_ = (bitField0_ & ~0x00000008);
|
||||
relay_ = getDefaultInstance().getRelay();
|
||||
onChanged();
|
||||
return this;
|
||||
}
|
||||
void setRelay(com.google.protobuf.ByteString value) {
|
||||
bitField0_ |= 0x00000004;
|
||||
bitField0_ |= 0x00000008;
|
||||
relay_ = value;
|
||||
onChanged();
|
||||
}
|
||||
@@ -598,19 +655,19 @@ public final class PushMessageProtos {
|
||||
// optional uint64 timestamp = 5;
|
||||
private long timestamp_ ;
|
||||
public boolean hasTimestamp() {
|
||||
return ((bitField0_ & 0x00000008) == 0x00000008);
|
||||
return ((bitField0_ & 0x00000010) == 0x00000010);
|
||||
}
|
||||
public long getTimestamp() {
|
||||
return timestamp_;
|
||||
}
|
||||
public Builder setTimestamp(long value) {
|
||||
bitField0_ |= 0x00000008;
|
||||
bitField0_ |= 0x00000010;
|
||||
timestamp_ = value;
|
||||
onChanged();
|
||||
return this;
|
||||
}
|
||||
public Builder clearTimestamp() {
|
||||
bitField0_ = (bitField0_ & ~0x00000008);
|
||||
bitField0_ = (bitField0_ & ~0x00000010);
|
||||
timestamp_ = 0L;
|
||||
onChanged();
|
||||
return this;
|
||||
@@ -619,7 +676,7 @@ public final class PushMessageProtos {
|
||||
// optional bytes message = 6;
|
||||
private com.google.protobuf.ByteString message_ = com.google.protobuf.ByteString.EMPTY;
|
||||
public boolean hasMessage() {
|
||||
return ((bitField0_ & 0x00000010) == 0x00000010);
|
||||
return ((bitField0_ & 0x00000020) == 0x00000020);
|
||||
}
|
||||
public com.google.protobuf.ByteString getMessage() {
|
||||
return message_;
|
||||
@@ -628,13 +685,13 @@ public final class PushMessageProtos {
|
||||
if (value == null) {
|
||||
throw new NullPointerException();
|
||||
}
|
||||
bitField0_ |= 0x00000010;
|
||||
bitField0_ |= 0x00000020;
|
||||
message_ = value;
|
||||
onChanged();
|
||||
return this;
|
||||
}
|
||||
public Builder clearMessage() {
|
||||
bitField0_ = (bitField0_ & ~0x00000010);
|
||||
bitField0_ = (bitField0_ & ~0x00000020);
|
||||
message_ = getDefaultInstance().getMessage();
|
||||
onChanged();
|
||||
return this;
|
||||
@@ -2800,23 +2857,24 @@ public final class PushMessageProtos {
|
||||
static {
|
||||
java.lang.String[] descriptorData = {
|
||||
"\n\037IncomingPushMessageSignal.proto\022\ntexts" +
|
||||
"ecure\"l\n\031IncomingPushMessageSignal\022\014\n\004ty" +
|
||||
"pe\030\001 \001(\r\022\016\n\006source\030\002 \001(\t\022\r\n\005relay\030\003 \001(\t\022" +
|
||||
"\021\n\ttimestamp\030\005 \001(\004\022\017\n\007message\030\006 \001(\014\"\363\003\n\022" +
|
||||
"PushMessageContent\022\014\n\004body\030\001 \001(\t\022E\n\013atta" +
|
||||
"chments\030\002 \003(\01320.textsecure.PushMessageCo" +
|
||||
"ntent.AttachmentPointer\022:\n\005group\030\003 \001(\0132+" +
|
||||
".textsecure.PushMessageContent.GroupCont" +
|
||||
"ext\032A\n\021AttachmentPointer\022\n\n\002id\030\001 \001(\006\022\023\n\013" +
|
||||
"contentType\030\002 \001(\t\022\013\n\003key\030\003 \001(\014\032\210\002\n\014Group",
|
||||
"Context\022\n\n\002id\030\001 \001(\014\022>\n\004type\030\002 \001(\01620.text" +
|
||||
"secure.PushMessageContent.GroupContext.T" +
|
||||
"ype\022\014\n\004name\030\003 \001(\t\022\017\n\007members\030\004 \003(\t\022@\n\006av" +
|
||||
"atar\030\005 \001(\01320.textsecure.PushMessageConte" +
|
||||
"nt.AttachmentPointer\"K\n\004Type\022\013\n\007UNKNOWN\020" +
|
||||
"\000\022\n\n\006CREATE\020\001\022\n\n\006MODIFY\020\002\022\013\n\007DELIVER\020\003\022\007" +
|
||||
"\n\003ADD\020\004\022\010\n\004QUIT\020\005B7\n\"org.whispersystems." +
|
||||
"textsecure.pushB\021PushMessageProtos"
|
||||
"ecure\"\202\001\n\031IncomingPushMessageSignal\022\014\n\004t" +
|
||||
"ype\030\001 \001(\r\022\016\n\006source\030\002 \001(\t\022\024\n\014sourceDevic" +
|
||||
"e\030\007 \001(\r\022\r\n\005relay\030\003 \001(\t\022\021\n\ttimestamp\030\005 \001(" +
|
||||
"\004\022\017\n\007message\030\006 \001(\014\"\363\003\n\022PushMessageConten" +
|
||||
"t\022\014\n\004body\030\001 \001(\t\022E\n\013attachments\030\002 \003(\01320.t" +
|
||||
"extsecure.PushMessageContent.AttachmentP" +
|
||||
"ointer\022:\n\005group\030\003 \001(\0132+.textsecure.PushM" +
|
||||
"essageContent.GroupContext\032A\n\021Attachment" +
|
||||
"Pointer\022\n\n\002id\030\001 \001(\006\022\023\n\013contentType\030\002 \001(\t",
|
||||
"\022\013\n\003key\030\003 \001(\014\032\210\002\n\014GroupContext\022\n\n\002id\030\001 \001" +
|
||||
"(\014\022>\n\004type\030\002 \001(\01620.textsecure.PushMessag" +
|
||||
"eContent.GroupContext.Type\022\014\n\004name\030\003 \001(\t" +
|
||||
"\022\017\n\007members\030\004 \003(\t\022@\n\006avatar\030\005 \001(\01320.text" +
|
||||
"secure.PushMessageContent.AttachmentPoin" +
|
||||
"ter\"K\n\004Type\022\013\n\007UNKNOWN\020\000\022\n\n\006CREATE\020\001\022\n\n\006" +
|
||||
"MODIFY\020\002\022\013\n\007DELIVER\020\003\022\007\n\003ADD\020\004\022\010\n\004QUIT\020\005" +
|
||||
"B7\n\"org.whispersystems.textsecure.pushB\021" +
|
||||
"PushMessageProtos"
|
||||
};
|
||||
com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner =
|
||||
new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() {
|
||||
@@ -2828,7 +2886,7 @@ public final class PushMessageProtos {
|
||||
internal_static_textsecure_IncomingPushMessageSignal_fieldAccessorTable = new
|
||||
com.google.protobuf.GeneratedMessage.FieldAccessorTable(
|
||||
internal_static_textsecure_IncomingPushMessageSignal_descriptor,
|
||||
new java.lang.String[] { "Type", "Source", "Relay", "Timestamp", "Message", },
|
||||
new java.lang.String[] { "Type", "Source", "SourceDevice", "Relay", "Timestamp", "Message", },
|
||||
org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.class,
|
||||
org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.Builder.class);
|
||||
internal_static_textsecure_PushMessageContent_descriptor =
|
||||
|
@@ -5,6 +5,7 @@ import android.util.Log;
|
||||
|
||||
import com.google.thoughtcrimegson.Gson;
|
||||
import com.google.thoughtcrimegson.JsonParseException;
|
||||
import com.google.thoughtcrimegson.JsonSyntaxException;
|
||||
|
||||
import org.apache.http.conn.ssl.StrictHostnameVerifier;
|
||||
import org.whispersystems.textsecure.crypto.IdentityKey;
|
||||
@@ -42,13 +43,14 @@ public class PushServiceSocket {
|
||||
private static final String VERIFY_ACCOUNT_PATH = "/v1/accounts/code/%s";
|
||||
private static final String REGISTER_GCM_PATH = "/v1/accounts/gcm/";
|
||||
private static final String PREKEY_PATH = "/v1/keys/%s";
|
||||
private static final String PREKEY_DEVICE_PATH = "/v1/keys/%s/%s";
|
||||
|
||||
private static final String DIRECTORY_TOKENS_PATH = "/v1/directory/tokens";
|
||||
private static final String DIRECTORY_VERIFY_PATH = "/v1/directory/%s";
|
||||
private static final String MESSAGE_PATH = "/v1/messages/";
|
||||
private static final String MESSAGE_PATH = "/v1/messages/%s";
|
||||
private static final String ATTACHMENT_PATH = "/v1/attachments/%s";
|
||||
|
||||
private static final boolean ENFORCE_SSL = true;
|
||||
private static final boolean ENFORCE_SSL = false;
|
||||
|
||||
private final Context context;
|
||||
private final String serviceUrl;
|
||||
@@ -86,44 +88,14 @@ public class PushServiceSocket {
|
||||
makeRequest(REGISTER_GCM_PATH, "DELETE", null);
|
||||
}
|
||||
|
||||
public void sendMessage(PushDestination recipient, PushBody pushBody)
|
||||
public void sendMessage(OutgoingPushMessageList bundle)
|
||||
throws IOException
|
||||
{
|
||||
OutgoingPushMessage message = new OutgoingPushMessage(recipient.getRelay(),
|
||||
recipient.getNumber(),
|
||||
pushBody.getBody(),
|
||||
pushBody.getType());
|
||||
|
||||
sendMessage(new OutgoingPushMessageList(message));
|
||||
}
|
||||
|
||||
public void sendMessage(List<PushDestination> recipients, List<PushBody> bodies)
|
||||
throws IOException
|
||||
{
|
||||
List<OutgoingPushMessage> messages = new LinkedList<OutgoingPushMessage>();
|
||||
|
||||
Iterator<PushDestination> recipientsIterator = recipients.iterator();
|
||||
Iterator<PushBody> bodiesIterator = bodies.iterator();
|
||||
|
||||
while (recipientsIterator.hasNext()) {
|
||||
PushDestination recipient = recipientsIterator.next();
|
||||
PushBody body = bodiesIterator.next();
|
||||
|
||||
messages.add(new OutgoingPushMessage(recipient.getRelay(), recipient.getNumber(),
|
||||
body.getBody(), body.getType()));
|
||||
try {
|
||||
makeRequest(String.format(MESSAGE_PATH, bundle.getDestination()), "PUT", new Gson().toJson(bundle));
|
||||
} catch (NotFoundException nfe) {
|
||||
throw new UnregisteredUserException(nfe);
|
||||
}
|
||||
|
||||
sendMessage(new OutgoingPushMessageList(messages));
|
||||
}
|
||||
|
||||
private void sendMessage(OutgoingPushMessageList messages)
|
||||
throws IOException
|
||||
{
|
||||
String responseText = makeRequest(MESSAGE_PATH, "POST", new Gson().toJson(messages));
|
||||
PushMessageResponse response = new Gson().fromJson(responseText, PushMessageResponse.class);
|
||||
|
||||
if (response.getFailure().size() != 0)
|
||||
throw new UnregisteredUserException(response.getFailure());
|
||||
}
|
||||
|
||||
public void registerPreKeys(IdentityKey identityKey,
|
||||
@@ -150,20 +122,46 @@ public class PushServiceSocket {
|
||||
PreKeyList.toJson(new PreKeyList(lastResortEntity, entities)));
|
||||
}
|
||||
|
||||
public PreKeyEntity getPreKey(PushDestination destination) throws IOException {
|
||||
public List<PreKeyEntity> getPreKeys(PushAddress destination) throws IOException {
|
||||
try {
|
||||
String path = String.format(PREKEY_PATH, destination.getNumber());
|
||||
String deviceId = String.valueOf(destination.getDeviceId());
|
||||
|
||||
if (deviceId.equals("1"))
|
||||
deviceId = "*";
|
||||
|
||||
String path = String.format(PREKEY_DEVICE_PATH, destination.getNumber(), deviceId);
|
||||
|
||||
if (!Util.isEmpty(destination.getRelay())) {
|
||||
path = path + "?relay=" + destination.getRelay();
|
||||
}
|
||||
|
||||
String responseText = makeRequest(path, "GET", null);
|
||||
Log.w("PushServiceSocket", "Got prekey: " + responseText);
|
||||
return PreKeyEntity.fromJson(responseText);
|
||||
PreKeyList response = PreKeyList.fromJson(responseText);
|
||||
|
||||
return response.getKeys();
|
||||
} catch (JsonParseException e) {
|
||||
Log.w("PushServiceSocket", e);
|
||||
throw new IOException("Bad prekey");
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public PreKeyEntity getPreKey(PushAddress destination) throws IOException {
|
||||
try {
|
||||
String path = String.format(PREKEY_DEVICE_PATH, destination.getNumber(),
|
||||
String.valueOf(destination.getDeviceId()));
|
||||
|
||||
if (!Util.isEmpty(destination.getRelay())) {
|
||||
path = path + "?relay=" + destination.getRelay();
|
||||
}
|
||||
|
||||
String responseText = makeRequest(path, "GET", null);
|
||||
PreKeyList response = PreKeyList.fromJson(responseText);
|
||||
|
||||
if (response.getKeys() == null || response.getKeys().size() < 1)
|
||||
throw new IOException("Empty prekey list");
|
||||
|
||||
return response.getKeys().get(0);
|
||||
} catch (JsonParseException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -307,17 +305,25 @@ public class PushServiceSocket {
|
||||
}
|
||||
|
||||
if (connection.getResponseCode() == 413) {
|
||||
connection.disconnect();
|
||||
throw new RateLimitException("Rate limit exceeded: " + connection.getResponseCode());
|
||||
}
|
||||
|
||||
if (connection.getResponseCode() == 401 || connection.getResponseCode() == 403) {
|
||||
connection.disconnect();
|
||||
throw new AuthorizationFailedException("Authorization failed!");
|
||||
}
|
||||
|
||||
if (connection.getResponseCode() == 404) {
|
||||
connection.disconnect();
|
||||
throw new NotFoundException("Not found");
|
||||
}
|
||||
|
||||
if (connection.getResponseCode() == 409) {
|
||||
String response = Util.readFully(connection.getErrorStream());
|
||||
throw new MismatchedDevicesException(new Gson().fromJson(response, MismatchedDevices.class));
|
||||
}
|
||||
|
||||
if (connection.getResponseCode() != 200 && connection.getResponseCode() != 204) {
|
||||
throw new IOException("Bad response: " + connection.getResponseCode() + " " + connection.getResponseMessage());
|
||||
}
|
||||
|
@@ -5,15 +5,8 @@ import java.util.List;
|
||||
|
||||
public class UnregisteredUserException extends IOException {
|
||||
|
||||
private final List<String> addresses;
|
||||
|
||||
public UnregisteredUserException(List<String> addresses) {
|
||||
super();
|
||||
this.addresses = addresses;
|
||||
}
|
||||
|
||||
public List<String> getAddresses() {
|
||||
return addresses;
|
||||
public UnregisteredUserException(Exception exception) {
|
||||
super(exception);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -0,0 +1,6 @@
|
||||
package org.whispersystems.textsecure.storage;
|
||||
|
||||
public interface CanonicalRecipient {
|
||||
// public String getNumber();
|
||||
public long getRecipientId();
|
||||
}
|
@@ -1,7 +0,0 @@
|
||||
package org.whispersystems.textsecure.storage;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
public interface CanonicalRecipientAddress {
|
||||
public long getCanonicalAddress(Context context);
|
||||
}
|
@@ -43,24 +43,24 @@ public class LocalKeyRecord extends Record {
|
||||
private final MasterCipher masterCipher;
|
||||
private final MasterSecret masterSecret;
|
||||
|
||||
public LocalKeyRecord(Context context, MasterSecret masterSecret, CanonicalRecipientAddress recipient) {
|
||||
super(context, SESSIONS_DIRECTORY, getFileNameForRecipient(context, recipient));
|
||||
public LocalKeyRecord(Context context, MasterSecret masterSecret, CanonicalRecipient recipient) {
|
||||
super(context, SESSIONS_DIRECTORY, getFileNameForRecipient(recipient));
|
||||
this.masterSecret = masterSecret;
|
||||
this.masterCipher = new MasterCipher(masterSecret);
|
||||
loadData();
|
||||
}
|
||||
|
||||
public static boolean hasRecord(Context context, CanonicalRecipientAddress recipient) {
|
||||
Log.w("LocalKeyRecord", "Checking: " + getFileNameForRecipient(context, recipient));
|
||||
return Record.hasRecord(context, SESSIONS_DIRECTORY, getFileNameForRecipient(context, recipient));
|
||||
public static boolean hasRecord(Context context, CanonicalRecipient recipient) {
|
||||
Log.w("LocalKeyRecord", "Checking: " + getFileNameForRecipient(recipient));
|
||||
return Record.hasRecord(context, SESSIONS_DIRECTORY, getFileNameForRecipient(recipient));
|
||||
}
|
||||
|
||||
public static void delete(Context context, CanonicalRecipientAddress recipient) {
|
||||
Record.delete(context, SESSIONS_DIRECTORY, getFileNameForRecipient(context, recipient));
|
||||
public static void delete(Context context, CanonicalRecipient recipient) {
|
||||
Record.delete(context, SESSIONS_DIRECTORY, getFileNameForRecipient(recipient));
|
||||
}
|
||||
|
||||
private static String getFileNameForRecipient(Context context, CanonicalRecipientAddress recipient) {
|
||||
return recipient.getCanonicalAddress(context) + "-local";
|
||||
private static String getFileNameForRecipient(CanonicalRecipient recipient) {
|
||||
return recipient.getRecipientId() + "-local";
|
||||
}
|
||||
|
||||
public void advanceKeyIfNecessary(int keyId) {
|
||||
|
@@ -0,0 +1,31 @@
|
||||
package org.whispersystems.textsecure.storage;
|
||||
|
||||
public class RecipientDevice {
|
||||
|
||||
public static final int DEFAULT_DEVICE_ID = 1;
|
||||
|
||||
private final long recipientId;
|
||||
private final int deviceId;
|
||||
|
||||
public RecipientDevice(long recipientId, int deviceId) {
|
||||
this.recipientId = recipientId;
|
||||
this.deviceId = deviceId;
|
||||
}
|
||||
|
||||
public long getRecipientId() {
|
||||
return recipientId;
|
||||
}
|
||||
|
||||
public int getDeviceId() {
|
||||
return deviceId;
|
||||
}
|
||||
|
||||
public CanonicalRecipient getRecipient() {
|
||||
return new CanonicalRecipient() {
|
||||
@Override
|
||||
public long getRecipientId() {
|
||||
return recipientId;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
@@ -69,13 +69,19 @@ public abstract class Record {
|
||||
}
|
||||
|
||||
private static File getAddressFile(Context context, String directory, String address) {
|
||||
File parent = getParentDirectory(context, directory);
|
||||
|
||||
return new File(parent, address);
|
||||
}
|
||||
|
||||
protected static File getParentDirectory(Context context, String directory) {
|
||||
File parent = new File(context.getFilesDir(), directory);
|
||||
|
||||
if (!parent.exists()) {
|
||||
parent.mkdirs();
|
||||
}
|
||||
|
||||
return new File(parent, address);
|
||||
return parent;
|
||||
}
|
||||
|
||||
protected byte[] readBlob(FileInputStream in) throws IOException {
|
||||
|
@@ -43,22 +43,22 @@ public class RemoteKeyRecord extends Record {
|
||||
private PublicKey remoteKeyCurrent;
|
||||
private PublicKey remoteKeyLast;
|
||||
|
||||
public RemoteKeyRecord(Context context, CanonicalRecipientAddress recipient) {
|
||||
super(context, SESSIONS_DIRECTORY, getFileNameForRecipient(context, recipient));
|
||||
public RemoteKeyRecord(Context context, CanonicalRecipient recipient) {
|
||||
super(context, SESSIONS_DIRECTORY, getFileNameForRecipient(recipient));
|
||||
loadData();
|
||||
}
|
||||
|
||||
public static void delete(Context context, CanonicalRecipientAddress recipient) {
|
||||
delete(context, SESSIONS_DIRECTORY, getFileNameForRecipient(context, recipient));
|
||||
public static void delete(Context context, CanonicalRecipient recipient) {
|
||||
delete(context, SESSIONS_DIRECTORY, getFileNameForRecipient(recipient));
|
||||
}
|
||||
|
||||
public static boolean hasRecord(Context context, CanonicalRecipientAddress recipient) {
|
||||
Log.w("LocalKeyRecord", "Checking: " + getFileNameForRecipient(context, recipient));
|
||||
return hasRecord(context, SESSIONS_DIRECTORY, getFileNameForRecipient(context, recipient));
|
||||
public static boolean hasRecord(Context context, CanonicalRecipient recipient) {
|
||||
Log.w("LocalKeyRecord", "Checking: " + getFileNameForRecipient(recipient));
|
||||
return hasRecord(context, SESSIONS_DIRECTORY, getFileNameForRecipient(recipient));
|
||||
}
|
||||
|
||||
private static String getFileNameForRecipient(Context context, CanonicalRecipientAddress recipient) {
|
||||
return recipient.getCanonicalAddress(context) + "-remote";
|
||||
private static String getFileNameForRecipient(CanonicalRecipient recipient) {
|
||||
return recipient.getRecipientId() + "-remote";
|
||||
}
|
||||
|
||||
public void updateCurrentRemoteKey(PublicKey remoteKey) {
|
||||
|
@@ -14,21 +14,21 @@ import org.whispersystems.textsecure.crypto.MasterSecret;
|
||||
|
||||
public class Session {
|
||||
|
||||
public static void clearV1SessionFor(Context context, CanonicalRecipientAddress recipient) {
|
||||
public static void clearV1SessionFor(Context context, CanonicalRecipient recipient) {
|
||||
//XXX Obviously we should probably do something more thorough here eventually.
|
||||
LocalKeyRecord.delete(context, recipient);
|
||||
RemoteKeyRecord.delete(context, recipient);
|
||||
SessionRecordV1.delete(context, recipient);
|
||||
}
|
||||
|
||||
public static void abortSessionFor(Context context, CanonicalRecipientAddress recipient) {
|
||||
public static void abortSessionFor(Context context, CanonicalRecipient recipient) {
|
||||
Log.w("Session", "Aborting session, deleting keys...");
|
||||
clearV1SessionFor(context, recipient);
|
||||
SessionRecordV2.delete(context, recipient);
|
||||
SessionRecordV2.deleteAll(context, recipient);
|
||||
}
|
||||
|
||||
public static boolean hasSession(Context context, MasterSecret masterSecret,
|
||||
CanonicalRecipientAddress recipient)
|
||||
CanonicalRecipient recipient)
|
||||
{
|
||||
Log.w("Session", "Checking session...");
|
||||
return hasV1Session(context, recipient) || hasV2Session(context, masterSecret, recipient);
|
||||
@@ -36,42 +36,40 @@ public class Session {
|
||||
|
||||
public static boolean hasRemoteIdentityKey(Context context,
|
||||
MasterSecret masterSecret,
|
||||
CanonicalRecipientAddress recipient)
|
||||
CanonicalRecipient recipient)
|
||||
{
|
||||
return (hasV2Session(context, masterSecret, recipient) ||
|
||||
(hasV1Session(context, recipient) &&
|
||||
new SessionRecordV1(context, masterSecret, recipient).getIdentityKey() != null));
|
||||
return (hasV2Session(context, masterSecret, recipient) || (hasV1Session(context, recipient) &&
|
||||
new SessionRecordV1(context, masterSecret, recipient).getIdentityKey() != null));
|
||||
}
|
||||
|
||||
private static boolean hasV2Session(Context context, MasterSecret masterSecret,
|
||||
CanonicalRecipientAddress recipient)
|
||||
CanonicalRecipient recipient)
|
||||
{
|
||||
return SessionRecordV2.hasSession(context, masterSecret, recipient);
|
||||
return SessionRecordV2.hasSession(context, masterSecret, recipient.getRecipientId(),
|
||||
RecipientDevice.DEFAULT_DEVICE_ID);
|
||||
}
|
||||
|
||||
private static boolean hasV1Session(Context context, CanonicalRecipientAddress recipient) {
|
||||
private static boolean hasV1Session(Context context, CanonicalRecipient recipient) {
|
||||
return SessionRecordV1.hasSession(context, recipient) &&
|
||||
RemoteKeyRecord.hasRecord(context, recipient) &&
|
||||
LocalKeyRecord.hasRecord(context, recipient);
|
||||
}
|
||||
|
||||
public static IdentityKey getRemoteIdentityKey(Context context, MasterSecret masterSecret,
|
||||
CanonicalRecipientAddress recipient)
|
||||
CanonicalRecipient recipient)
|
||||
{
|
||||
if (SessionRecordV2.hasSession(context, masterSecret, recipient)) {
|
||||
return new SessionRecordV2(context, masterSecret, recipient).getRemoteIdentityKey();
|
||||
} else if (SessionRecordV1.hasSession(context, recipient)) {
|
||||
return new SessionRecordV1(context, masterSecret, recipient).getIdentityKey();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
return getRemoteIdentityKey(context, masterSecret, recipient.getRecipientId());
|
||||
}
|
||||
|
||||
public static IdentityKey getRemoteIdentityKey(Context context, MasterSecret masterSecret,
|
||||
public static IdentityKey getRemoteIdentityKey(Context context,
|
||||
MasterSecret masterSecret,
|
||||
long recipientId)
|
||||
{
|
||||
if (SessionRecordV2.hasSession(context, masterSecret, recipientId)) {
|
||||
return new SessionRecordV2(context, masterSecret, recipientId).getRemoteIdentityKey();
|
||||
if (SessionRecordV2.hasSession(context, masterSecret, recipientId,
|
||||
RecipientDevice.DEFAULT_DEVICE_ID))
|
||||
{
|
||||
return new SessionRecordV2(context, masterSecret, recipientId,
|
||||
RecipientDevice.DEFAULT_DEVICE_ID).getRemoteIdentityKey();
|
||||
} else if (SessionRecordV1.hasSession(context, recipientId)) {
|
||||
return new SessionRecordV1(context, masterSecret, recipientId).getIdentityKey();
|
||||
} else {
|
||||
@@ -80,10 +78,14 @@ public class Session {
|
||||
}
|
||||
|
||||
public static int getSessionVersion(Context context, MasterSecret masterSecret,
|
||||
CanonicalRecipientAddress recipient)
|
||||
CanonicalRecipient recipient)
|
||||
{
|
||||
if (SessionRecordV2.hasSession(context, masterSecret, recipient)) {
|
||||
return new SessionRecordV2(context, masterSecret, recipient).getSessionVersion();
|
||||
if (SessionRecordV2.hasSession(context, masterSecret,
|
||||
recipient.getRecipientId(),
|
||||
RecipientDevice.DEFAULT_DEVICE_ID))
|
||||
{
|
||||
return new SessionRecordV2(context, masterSecret, recipient.getRecipientId(),
|
||||
RecipientDevice.DEFAULT_DEVICE_ID).getSessionVersion();
|
||||
} else if (SessionRecordV1.hasSession(context, recipient)) {
|
||||
return new SessionRecordV1(context, masterSecret, recipient).getSessionVersion();
|
||||
}
|
||||
|
@@ -37,8 +37,8 @@ public class SessionRecordV1 extends Record {
|
||||
|
||||
private final MasterSecret masterSecret;
|
||||
|
||||
public SessionRecordV1(Context context, MasterSecret masterSecret, CanonicalRecipientAddress recipient) {
|
||||
this(context, masterSecret, getRecipientId(context, recipient));
|
||||
public SessionRecordV1(Context context, MasterSecret masterSecret, CanonicalRecipient recipient) {
|
||||
this(context, masterSecret, recipient.getRecipientId());
|
||||
}
|
||||
|
||||
public SessionRecordV1(Context context, MasterSecret masterSecret, long recipientId) {
|
||||
@@ -48,12 +48,12 @@ public class SessionRecordV1 extends Record {
|
||||
loadData();
|
||||
}
|
||||
|
||||
public static void delete(Context context, CanonicalRecipientAddress recipient) {
|
||||
delete(context, SESSIONS_DIRECTORY, getRecipientId(context, recipient) + "");
|
||||
public static void delete(Context context, CanonicalRecipient recipient) {
|
||||
delete(context, SESSIONS_DIRECTORY, recipient.getRecipientId() + "");
|
||||
}
|
||||
|
||||
public static boolean hasSession(Context context, CanonicalRecipientAddress recipient) {
|
||||
return hasSession(context, getRecipientId(context, recipient));
|
||||
public static boolean hasSession(Context context, CanonicalRecipient recipient) {
|
||||
return hasSession(context, recipient.getRecipientId());
|
||||
}
|
||||
|
||||
public static boolean hasSession(Context context, long recipientId) {
|
||||
@@ -61,10 +61,6 @@ public class SessionRecordV1 extends Record {
|
||||
return hasRecord(context, SESSIONS_DIRECTORY, recipientId+"");
|
||||
}
|
||||
|
||||
private static long getRecipientId(Context context, CanonicalRecipientAddress recipient) {
|
||||
return recipient.getCanonicalAddress(context);
|
||||
}
|
||||
|
||||
public void setSessionKey(SessionKey sessionKeyRecord) {
|
||||
this.sessionKeyRecord = sessionKeyRecord;
|
||||
}
|
||||
|
@@ -39,12 +39,14 @@ import org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.Chai
|
||||
import org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingKeyExchange;
|
||||
import org.whispersystems.textsecure.storage.StorageProtos.SessionStructure.PendingPreKey;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
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;
|
||||
@@ -64,33 +66,70 @@ public class SessionRecordV2 extends Record {
|
||||
private StorageProtos.SessionStructure sessionStructure =
|
||||
StorageProtos.SessionStructure.newBuilder().build();
|
||||
|
||||
public SessionRecordV2(Context context, MasterSecret masterSecret, CanonicalRecipientAddress recipient) {
|
||||
this(context, masterSecret, getRecipientId(context, recipient));
|
||||
public SessionRecordV2(Context context, MasterSecret masterSecret, RecipientDevice peer) {
|
||||
this(context, masterSecret, peer.getRecipientId(), peer.getDeviceId());
|
||||
}
|
||||
|
||||
public SessionRecordV2(Context context, MasterSecret masterSecret, long recipientId) {
|
||||
super(context, SESSIONS_DIRECTORY_V2, recipientId+"");
|
||||
public SessionRecordV2(Context context, MasterSecret masterSecret, long recipientId, int deviceId) {
|
||||
super(context, SESSIONS_DIRECTORY_V2, getRecordName(recipientId, deviceId));
|
||||
this.masterSecret = masterSecret;
|
||||
loadData();
|
||||
}
|
||||
|
||||
public static void delete(Context context, CanonicalRecipientAddress recipient) {
|
||||
delete(context, SESSIONS_DIRECTORY_V2, getRecipientId(context, recipient) + "");
|
||||
private static String getRecordName(long recipientId, int deviceId) {
|
||||
return recipientId + (deviceId == RecipientDevice.DEFAULT_DEVICE_ID ? "" : "." + deviceId);
|
||||
}
|
||||
|
||||
public static List<Integer> getSessionSubDevices(Context context, CanonicalRecipient recipient) {
|
||||
List<Integer> results = new LinkedList<Integer>();
|
||||
File parent = getParentDirectory(context, SESSIONS_DIRECTORY_V2);
|
||||
String[] children = parent.list();
|
||||
|
||||
if (children == null) return results;
|
||||
|
||||
for (String child : children) {
|
||||
try {
|
||||
String[] parts = child.split("[.]", 2);
|
||||
long sessionRecipientId = Long.parseLong(parts[0]);
|
||||
|
||||
if (sessionRecipientId == recipient.getRecipientId() && parts.length > 1) {
|
||||
results.add(Integer.parseInt(parts[1]));
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
Log.w("SessionRecordV2", e);
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
public static void deleteAll(Context context, CanonicalRecipient recipient) {
|
||||
List<Integer> devices = getSessionSubDevices(context, recipient);
|
||||
|
||||
delete(context, SESSIONS_DIRECTORY_V2, getRecordName(recipient.getRecipientId(),
|
||||
RecipientDevice.DEFAULT_DEVICE_ID));
|
||||
|
||||
for (int device : devices) {
|
||||
delete(context, SESSIONS_DIRECTORY_V2, getRecordName(recipient.getRecipientId(), device));
|
||||
}
|
||||
}
|
||||
|
||||
public static void delete(Context context, RecipientDevice recipientDevice) {
|
||||
delete(context, SESSIONS_DIRECTORY_V2, getRecordName(recipientDevice.getRecipientId(),
|
||||
recipientDevice.getDeviceId()));
|
||||
}
|
||||
|
||||
public static boolean hasSession(Context context, MasterSecret masterSecret,
|
||||
CanonicalRecipientAddress recipient)
|
||||
RecipientDevice recipient)
|
||||
{
|
||||
return hasSession(context, masterSecret, getRecipientId(context, recipient));
|
||||
return hasSession(context, masterSecret, recipient.getRecipientId(), recipient.getDeviceId());
|
||||
}
|
||||
|
||||
public static boolean hasSession(Context context, MasterSecret masterSecret, long recipientId) {
|
||||
return hasRecord(context, SESSIONS_DIRECTORY_V2, recipientId+"") &&
|
||||
new SessionRecordV2(context, masterSecret, recipientId).hasSenderChain();
|
||||
}
|
||||
|
||||
private static long getRecipientId(Context context, CanonicalRecipientAddress recipient) {
|
||||
return recipient.getCanonicalAddress(context);
|
||||
public static boolean hasSession(Context context, MasterSecret masterSecret,
|
||||
long recipientId, int deviceId)
|
||||
{
|
||||
return hasRecord(context, SESSIONS_DIRECTORY_V2, getRecordName(recipientId, deviceId)) &&
|
||||
new SessionRecordV2(context, masterSecret, recipientId, deviceId).hasSenderChain();
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
|
@@ -16,6 +16,8 @@
|
||||
*/
|
||||
package org.whispersystems.textsecure.util;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Utility for generating hex dumps.
|
||||
*/
|
||||
@@ -43,6 +45,36 @@ public class Hex {
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
public static String toStringCondensed(byte[] bytes) {
|
||||
StringBuffer buf = new StringBuffer();
|
||||
for (int i=0;i<bytes.length;i++) {
|
||||
appendHexChar(buf, bytes[i]);
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
public static byte[] fromStringCondensed(String encoded) throws IOException {
|
||||
final char[] data = encoded.toCharArray();
|
||||
final int len = data.length;
|
||||
|
||||
if ((len & 0x01) != 0) {
|
||||
throw new IOException("Odd number of characters.");
|
||||
}
|
||||
|
||||
final byte[] out = new byte[len >> 1];
|
||||
|
||||
// two characters form the hex value.
|
||||
for (int i = 0, j = 0; j < len; i++) {
|
||||
int f = Character.digit(data[j], 16) << 4;
|
||||
j++;
|
||||
f = f | Character.digit(data[j], 16);
|
||||
j++;
|
||||
out[i] = (byte) (f & 0xFF);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
public static String dump(byte[] bytes) {
|
||||
return dump(bytes, 0, bytes.length);
|
||||
}
|
||||
|
Reference in New Issue
Block a user