mirror of
https://github.com/oxen-io/session-android.git
synced 2025-12-03 10:32:39 +00:00
Implement delivery receipts.
1) Support a "receipt" push message type. 2) Identify messages by timestamp. 3) Introduce a JobManager to handle the queue for network dependent jobs.
This commit is contained in:
@@ -19,7 +19,7 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile 'com.google.protobuf:protobuf-java:2.4.1'
|
||||
compile 'com.google.protobuf:protobuf-java:2.5.0'
|
||||
compile 'com.madgag:sc-light-jdk15on:1.47.0.2'
|
||||
compile 'com.googlecode.libphonenumber:libphonenumber:6.1'
|
||||
compile 'org.whispersystems:gson:2.2.4'
|
||||
|
||||
@@ -10,6 +10,7 @@ message IncomingPushMessageSignal {
|
||||
KEY_EXCHANGE = 2;
|
||||
PREKEY_BUNDLE = 3;
|
||||
PLAINTEXT = 4;
|
||||
RECEIPT = 5;
|
||||
}
|
||||
optional Type type = 1;
|
||||
optional string source = 2;
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
package org.whispersystems.textsecure.push;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class AuthorizationFailedException extends IOException {
|
||||
public AuthorizationFailedException(String s) {
|
||||
super(s);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
package org.whispersystems.textsecure.push;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class ExpectationFailedException extends IOException {
|
||||
}
|
||||
@@ -138,4 +138,12 @@ public class IncomingPushMessage implements Parcelable {
|
||||
public boolean isPreKeyBundle() {
|
||||
return getType() == IncomingPushMessageSignal.Type.PREKEY_BUNDLE_VALUE;
|
||||
}
|
||||
|
||||
public boolean isReceipt() {
|
||||
return getType() == IncomingPushMessageSignal.Type.RECEIPT_VALUE;
|
||||
}
|
||||
|
||||
public boolean isPlaintext() {
|
||||
return getType() == IncomingPushMessageSignal.Type.PLAINTEXT_VALUE;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
package org.whispersystems.textsecure.push;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class NotFoundException extends IOException {
|
||||
public NotFoundException(String s) {
|
||||
super(s);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.whispersystems.textsecure.push;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
public class OutgoingPushMessageList {
|
||||
@@ -9,9 +8,14 @@ public class OutgoingPushMessageList {
|
||||
|
||||
private String relay;
|
||||
|
||||
private long timestamp;
|
||||
|
||||
private List<OutgoingPushMessage> messages;
|
||||
|
||||
public OutgoingPushMessageList(String destination, String relay, List<OutgoingPushMessage> messages) {
|
||||
public OutgoingPushMessageList(String destination, long timestamp, String relay,
|
||||
List<OutgoingPushMessage> messages)
|
||||
{
|
||||
this.timestamp = timestamp;
|
||||
this.destination = destination;
|
||||
this.relay = relay;
|
||||
this.messages = messages;
|
||||
@@ -28,4 +32,8 @@ public class OutgoingPushMessageList {
|
||||
public String getRelay() {
|
||||
return relay;
|
||||
}
|
||||
|
||||
public long getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,6 +240,10 @@ public final class PushMessageProtos {
|
||||
* <code>PLAINTEXT = 4;</code>
|
||||
*/
|
||||
PLAINTEXT(4, 4),
|
||||
/**
|
||||
* <code>RECEIPT = 5;</code>
|
||||
*/
|
||||
RECEIPT(5, 5),
|
||||
;
|
||||
|
||||
/**
|
||||
@@ -262,6 +266,10 @@ public final class PushMessageProtos {
|
||||
* <code>PLAINTEXT = 4;</code>
|
||||
*/
|
||||
public static final int PLAINTEXT_VALUE = 4;
|
||||
/**
|
||||
* <code>RECEIPT = 5;</code>
|
||||
*/
|
||||
public static final int RECEIPT_VALUE = 5;
|
||||
|
||||
|
||||
public final int getNumber() { return value; }
|
||||
@@ -273,6 +281,7 @@ public final class PushMessageProtos {
|
||||
case 2: return KEY_EXCHANGE;
|
||||
case 3: return PREKEY_BUNDLE;
|
||||
case 4: return PLAINTEXT;
|
||||
case 5: return RECEIPT;
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
@@ -4079,28 +4088,28 @@ public final class PushMessageProtos {
|
||||
static {
|
||||
java.lang.String[] descriptorData = {
|
||||
"\n\037IncomingPushMessageSignal.proto\022\ntexts" +
|
||||
"ecure\"\207\002\n\031IncomingPushMessageSignal\0228\n\004t" +
|
||||
"ecure\"\224\002\n\031IncomingPushMessageSignal\0228\n\004t" +
|
||||
"ype\030\001 \001(\0162*.textsecure.IncomingPushMessa" +
|
||||
"geSignal.Type\022\016\n\006source\030\002 \001(\t\022\024\n\014sourceD" +
|
||||
"evice\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\"W\n\004Type\022\013\n\007UNKNOW" +
|
||||
"\005 \001(\004\022\017\n\007message\030\006 \001(\014\"d\n\004Type\022\013\n\007UNKNOW" +
|
||||
"N\020\000\022\016\n\nCIPHERTEXT\020\001\022\020\n\014KEY_EXCHANGE\020\002\022\021\n" +
|
||||
"\rPREKEY_BUNDLE\020\003\022\r\n\tPLAINTEXT\020\004\"\207\004\n\022Push" +
|
||||
"MessageContent\022\014\n\004body\030\001 \001(\t\022E\n\013attachme" +
|
||||
"nts\030\002 \003(\01320.textsecure.PushMessageConten",
|
||||
"t.AttachmentPointer\022:\n\005group\030\003 \001(\0132+.tex" +
|
||||
"tsecure.PushMessageContent.GroupContext\022" +
|
||||
"\r\n\005flags\030\004 \001(\r\032A\n\021AttachmentPointer\022\n\n\002i" +
|
||||
"d\030\001 \001(\006\022\023\n\013contentType\030\002 \001(\t\022\013\n\003key\030\003 \001(" +
|
||||
"\014\032\363\001\n\014GroupContext\022\n\n\002id\030\001 \001(\014\022>\n\004type\030\002" +
|
||||
" \001(\01620.textsecure.PushMessageContent.Gro" +
|
||||
"upContext.Type\022\014\n\004name\030\003 \001(\t\022\017\n\007members\030" +
|
||||
"\004 \003(\t\022@\n\006avatar\030\005 \001(\01320.textsecure.PushM" +
|
||||
"essageContent.AttachmentPointer\"6\n\004Type\022" +
|
||||
"\013\n\007UNKNOWN\020\000\022\n\n\006UPDATE\020\001\022\013\n\007DELIVER\020\002\022\010\n",
|
||||
"\004QUIT\020\003\"\030\n\005Flags\022\017\n\013END_SESSION\020\001B7\n\"org" +
|
||||
".whispersystems.textsecure.pushB\021PushMes" +
|
||||
"sageProtos"
|
||||
"\rPREKEY_BUNDLE\020\003\022\r\n\tPLAINTEXT\020\004\022\013\n\007RECEI" +
|
||||
"PT\020\005\"\207\004\n\022PushMessageContent\022\014\n\004body\030\001 \001(" +
|
||||
"\t\022E\n\013attachments\030\002 \003(\01320.textsecure.Push",
|
||||
"MessageContent.AttachmentPointer\022:\n\005grou" +
|
||||
"p\030\003 \001(\0132+.textsecure.PushMessageContent." +
|
||||
"GroupContext\022\r\n\005flags\030\004 \001(\r\032A\n\021Attachmen" +
|
||||
"tPointer\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\363\001\n\014GroupContext\022\n\n\002id\030\001 " +
|
||||
"\001(\014\022>\n\004type\030\002 \001(\01620.textsecure.PushMessa" +
|
||||
"geContent.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.tex" +
|
||||
"tsecure.PushMessageContent.AttachmentPoi" +
|
||||
"nter\"6\n\004Type\022\013\n\007UNKNOWN\020\000\022\n\n\006UPDATE\020\001\022\013\n",
|
||||
"\007DELIVER\020\002\022\010\n\004QUIT\020\003\"\030\n\005Flags\022\017\n\013END_SES" +
|
||||
"SION\020\001B7\n\"org.whispersystems.textsecure." +
|
||||
"pushB\021PushMessageProtos"
|
||||
};
|
||||
com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner =
|
||||
new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() {
|
||||
|
||||
@@ -25,9 +25,17 @@ import com.google.thoughtcrimegson.JsonParseException;
|
||||
import org.apache.http.conn.ssl.StrictHostnameVerifier;
|
||||
import org.whispersystems.libaxolotl.IdentityKey;
|
||||
import org.whispersystems.libaxolotl.ecc.ECPublicKey;
|
||||
import org.whispersystems.libaxolotl.state.SignedPreKeyRecord;
|
||||
import org.whispersystems.libaxolotl.state.PreKeyBundle;
|
||||
import org.whispersystems.libaxolotl.state.PreKeyRecord;
|
||||
import org.whispersystems.libaxolotl.state.SignedPreKeyRecord;
|
||||
import org.whispersystems.textsecure.push.exceptions.AuthorizationFailedException;
|
||||
import org.whispersystems.textsecure.push.exceptions.ExpectationFailedException;
|
||||
import org.whispersystems.textsecure.push.exceptions.MismatchedDevicesException;
|
||||
import org.whispersystems.textsecure.push.exceptions.NonSuccessfulResponseCodeException;
|
||||
import org.whispersystems.textsecure.push.exceptions.NotFoundException;
|
||||
import org.whispersystems.textsecure.push.exceptions.PushNetworkException;
|
||||
import org.whispersystems.textsecure.push.exceptions.RateLimitException;
|
||||
import org.whispersystems.textsecure.push.exceptions.StaleDevicesException;
|
||||
import org.whispersystems.textsecure.util.Base64;
|
||||
import org.whispersystems.textsecure.util.BlacklistingTrustManager;
|
||||
import org.whispersystems.textsecure.util.Util;
|
||||
@@ -75,6 +83,7 @@ public class PushServiceSocket {
|
||||
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/%s";
|
||||
private static final String RECEIPT_PATH = "/v1/receipt/%s/%d";
|
||||
private static final String ATTACHMENT_PATH = "/v1/attachments/%s";
|
||||
|
||||
private static final boolean ENFORCE_SSL = true;
|
||||
@@ -109,6 +118,16 @@ public class PushServiceSocket {
|
||||
"PUT", new Gson().toJson(signalingKeyEntity));
|
||||
}
|
||||
|
||||
public void sendReceipt(String destination, long messageId, String relay) throws IOException {
|
||||
String path = String.format(RECEIPT_PATH, destination, messageId);
|
||||
|
||||
if (!Util.isEmpty(relay)) {
|
||||
path += "?relay=" + relay;
|
||||
}
|
||||
|
||||
makeRequest(path, "PUT", null);
|
||||
}
|
||||
|
||||
public void registerGcmId(String gcmRegistrationId) throws IOException {
|
||||
GcmRegistrationId registration = new GcmRegistrationId(gcmRegistrationId);
|
||||
makeRequest(REGISTER_GCM_PATH, "PUT", new Gson().toJson(registration));
|
||||
@@ -380,68 +399,77 @@ public class PushServiceSocket {
|
||||
}
|
||||
|
||||
private String makeRequest(String urlFragment, String method, String body)
|
||||
throws IOException
|
||||
throws NonSuccessfulResponseCodeException, PushNetworkException
|
||||
{
|
||||
HttpURLConnection connection = makeBaseRequest(urlFragment, method, body);
|
||||
String response = Util.readFully(connection.getInputStream());
|
||||
|
||||
connection.disconnect();
|
||||
try {
|
||||
String response = Util.readFully(connection.getInputStream());
|
||||
connection.disconnect();
|
||||
|
||||
return response;
|
||||
return response;
|
||||
} catch (IOException ioe) {
|
||||
throw new PushNetworkException(ioe);
|
||||
}
|
||||
}
|
||||
|
||||
private HttpURLConnection makeBaseRequest(String urlFragment, String method, String body)
|
||||
throws IOException
|
||||
throws NonSuccessfulResponseCodeException, PushNetworkException
|
||||
{
|
||||
HttpURLConnection connection = getConnection(urlFragment, method);
|
||||
try {
|
||||
HttpURLConnection connection = getConnection(urlFragment, method);
|
||||
|
||||
if (body != null) {
|
||||
connection.setDoOutput(true);
|
||||
if (body != null) {
|
||||
connection.setDoOutput(true);
|
||||
}
|
||||
|
||||
connection.connect();
|
||||
|
||||
if (body != null) {
|
||||
Log.w("PushServiceSocket", method + " -- " + body);
|
||||
OutputStream out = connection.getOutputStream();
|
||||
out.write(body.getBytes());
|
||||
out.close();
|
||||
}
|
||||
|
||||
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() == 410) {
|
||||
String response = Util.readFully(connection.getErrorStream());
|
||||
throw new StaleDevicesException(new Gson().fromJson(response, StaleDevices.class));
|
||||
}
|
||||
|
||||
if (connection.getResponseCode() == 417) {
|
||||
throw new ExpectationFailedException();
|
||||
}
|
||||
|
||||
if (connection.getResponseCode() != 200 && connection.getResponseCode() != 204) {
|
||||
throw new NonSuccessfulResponseCodeException("Bad response: " + connection.getResponseCode() +
|
||||
" " + connection.getResponseMessage());
|
||||
}
|
||||
|
||||
return connection;
|
||||
} catch (IOException e) {
|
||||
throw new PushNetworkException(e);
|
||||
}
|
||||
|
||||
connection.connect();
|
||||
|
||||
if (body != null) {
|
||||
Log.w("PushServiceSocket", method + " -- " + body);
|
||||
OutputStream out = connection.getOutputStream();
|
||||
out.write(body.getBytes());
|
||||
out.close();
|
||||
}
|
||||
|
||||
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() == 410) {
|
||||
String response = Util.readFully(connection.getErrorStream());
|
||||
throw new StaleDevicesException(new Gson().fromJson(response, StaleDevices.class));
|
||||
}
|
||||
|
||||
if (connection.getResponseCode() == 417) {
|
||||
throw new ExpectationFailedException();
|
||||
}
|
||||
|
||||
if (connection.getResponseCode() != 200 && connection.getResponseCode() != 204) {
|
||||
throw new IOException("Bad response: " + connection.getResponseCode() + " " + connection.getResponseMessage());
|
||||
}
|
||||
|
||||
return connection;
|
||||
}
|
||||
|
||||
private HttpURLConnection getConnection(String urlFragment, String method) throws IOException {
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
package org.whispersystems.textsecure.push;
|
||||
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class RateLimitException extends IOException {
|
||||
public RateLimitException(String s) {
|
||||
super(s);
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package org.whispersystems.textsecure.push;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class StaleDevicesException extends IOException {
|
||||
|
||||
private final StaleDevices staleDevices;
|
||||
|
||||
public StaleDevicesException(StaleDevices staleDevices) {
|
||||
this.staleDevices = staleDevices;
|
||||
}
|
||||
|
||||
public StaleDevices getStaleDevices() {
|
||||
return staleDevices;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package org.whispersystems.textsecure.push.exceptions;
|
||||
|
||||
public class AuthorizationFailedException extends NonSuccessfulResponseCodeException {
|
||||
public AuthorizationFailedException(String s) {
|
||||
super(s);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
package org.whispersystems.textsecure.push.exceptions;
|
||||
|
||||
public class ExpectationFailedException extends NonSuccessfulResponseCodeException {
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
package org.whispersystems.textsecure.push;
|
||||
package org.whispersystems.textsecure.push.exceptions;
|
||||
|
||||
import java.io.IOException;
|
||||
import org.whispersystems.textsecure.push.MismatchedDevices;
|
||||
|
||||
public class MismatchedDevicesException extends IOException {
|
||||
public class MismatchedDevicesException extends NonSuccessfulResponseCodeException {
|
||||
|
||||
private final MismatchedDevices mismatchedDevices;
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
package org.whispersystems.textsecure.push.exceptions;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class NonSuccessfulResponseCodeException extends IOException {
|
||||
|
||||
public NonSuccessfulResponseCodeException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public NonSuccessfulResponseCodeException(String s) {
|
||||
super(s);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package org.whispersystems.textsecure.push.exceptions;
|
||||
|
||||
import org.whispersystems.textsecure.push.exceptions.NonSuccessfulResponseCodeException;
|
||||
|
||||
public class NotFoundException extends NonSuccessfulResponseCodeException {
|
||||
public NotFoundException(String s) {
|
||||
super(s);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package org.whispersystems.textsecure.push.exceptions;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class PushNetworkException extends IOException {
|
||||
public PushNetworkException(Exception exception) {
|
||||
super(exception);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package org.whispersystems.textsecure.push.exceptions;
|
||||
|
||||
|
||||
import org.whispersystems.textsecure.push.exceptions.NonSuccessfulResponseCodeException;
|
||||
|
||||
public class RateLimitException extends NonSuccessfulResponseCodeException {
|
||||
public RateLimitException(String s) {
|
||||
super(s);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package org.whispersystems.textsecure.push.exceptions;
|
||||
|
||||
import org.whispersystems.textsecure.push.StaleDevices;
|
||||
import org.whispersystems.textsecure.push.exceptions.NonSuccessfulResponseCodeException;
|
||||
|
||||
public class StaleDevicesException extends NonSuccessfulResponseCodeException {
|
||||
|
||||
private final StaleDevices staleDevices;
|
||||
|
||||
public StaleDevicesException(StaleDevices staleDevices) {
|
||||
this.staleDevices = staleDevices;
|
||||
}
|
||||
|
||||
public StaleDevices getStaleDevices() {
|
||||
return staleDevices;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user