Better UX handling on identity key mismatches.

1) Migrate from GSON to Jackson everywhere.

2) Add support for storing identity key conflicts on message rows.

3) Add limited support for surfacing identity key conflicts in UI.
This commit is contained in:
Moxie Marlinspike
2015-01-15 13:35:35 -08:00
parent 4397b55ceb
commit 00d7b5c284
76 changed files with 2395 additions and 721 deletions

View File

@@ -35,6 +35,8 @@ import org.whispersystems.textsecure.api.messages.TextSecureGroup;
import org.whispersystems.textsecure.api.messages.TextSecureMessage;
import org.whispersystems.textsecure.api.push.PushAddress;
import org.whispersystems.textsecure.api.push.TrustStore;
import org.whispersystems.textsecure.api.push.exceptions.NetworkFailureException;
import org.whispersystems.textsecure.api.push.exceptions.PushNetworkException;
import org.whispersystems.textsecure.internal.push.MismatchedDevices;
import org.whispersystems.textsecure.internal.push.OutgoingPushMessage;
import org.whispersystems.textsecure.internal.push.OutgoingPushMessageList;
@@ -176,6 +178,7 @@ public class TextSecureMessageSender {
{
List<UntrustedIdentityException> untrustedIdentities = new LinkedList<>();
List<UnregisteredUserException> unregisteredUsers = new LinkedList<>();
List<NetworkFailureException> networkExceptions = new LinkedList<>();
for (PushAddress recipient : recipients) {
try {
@@ -186,11 +189,14 @@ public class TextSecureMessageSender {
} catch (UnregisteredUserException e) {
Log.w(TAG, e);
unregisteredUsers.add(e);
} catch (PushNetworkException e) {
Log.w(TAG, e);
networkExceptions.add(new NetworkFailureException(recipient.getNumber(), e));
}
}
if (!untrustedIdentities.isEmpty() || !unregisteredUsers.isEmpty()) {
throw new EncapsulatedExceptions(untrustedIdentities, unregisteredUsers);
if (!untrustedIdentities.isEmpty() || !unregisteredUsers.isEmpty() || !networkExceptions.isEmpty()) {
throw new EncapsulatedExceptions(untrustedIdentities, unregisteredUsers, networkExceptions);
}
}

View File

@@ -16,24 +16,28 @@
*/
package org.whispersystems.textsecure.api.push;
import com.google.thoughtcrimegson.GsonBuilder;
import com.google.thoughtcrimegson.JsonDeserializationContext;
import com.google.thoughtcrimegson.JsonDeserializer;
import com.google.thoughtcrimegson.JsonElement;
import com.google.thoughtcrimegson.JsonParseException;
import com.google.thoughtcrimegson.JsonPrimitive;
import com.google.thoughtcrimegson.JsonSerializationContext;
import com.google.thoughtcrimegson.JsonSerializer;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import org.whispersystems.libaxolotl.ecc.ECPublicKey;
import org.whispersystems.textsecure.internal.util.Base64;
import org.whispersystems.textsecure.internal.push.PreKeyEntity;
import org.whispersystems.textsecure.internal.util.Base64;
import java.io.IOException;
import java.lang.reflect.Type;
public class SignedPreKeyEntity extends PreKeyEntity {
@JsonProperty
@JsonSerialize(using = ByteArraySerializer.class)
@JsonDeserialize(using = ByteArrayDeserializer.class)
private byte[] signature;
public SignedPreKeyEntity() {}
@@ -47,42 +51,18 @@ public class SignedPreKeyEntity extends PreKeyEntity {
return signature;
}
public static String toJson(SignedPreKeyEntity entity) {
GsonBuilder builder = new GsonBuilder();
return forBuilder(builder).create().toJson(entity);
}
public static SignedPreKeyEntity fromJson(String serialized) {
GsonBuilder builder = new GsonBuilder();
return forBuilder(builder).create().fromJson(serialized, SignedPreKeyEntity.class);
}
public static GsonBuilder forBuilder(GsonBuilder builder) {
return PreKeyEntity.forBuilder(builder)
.registerTypeAdapter(byte[].class, new ByteArrayJsonAdapter());
}
private static class ByteArrayJsonAdapter
implements JsonSerializer<byte[]>, JsonDeserializer<byte[]>
{
private static class ByteArraySerializer extends JsonSerializer<byte[]> {
@Override
public JsonElement serialize(byte[] signature, Type type,
JsonSerializationContext jsonSerializationContext)
{
return new JsonPrimitive(Base64.encodeBytesWithoutPadding(signature));
public void serialize(byte[] value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeString(Base64.encodeBytesWithoutPadding(value));
}
}
private static class ByteArrayDeserializer extends JsonDeserializer<byte[]> {
@Override
public byte[] deserialize(JsonElement jsonElement, Type type,
JsonDeserializationContext jsonDeserializationContext)
throws JsonParseException
{
try {
return Base64.decodeWithoutPadding(jsonElement.getAsJsonPrimitive().getAsString());
} catch (IOException e) {
throw new JsonParseException(e);
}
public byte[] deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
return Base64.decodeWithoutPadding(p.getValueAsString());
}
}
}

View File

@@ -24,12 +24,15 @@ public class EncapsulatedExceptions extends Throwable {
private final List<UntrustedIdentityException> untrustedIdentityExceptions;
private final List<UnregisteredUserException> unregisteredUserExceptions;
private final List<NetworkFailureException> networkExceptions;
public EncapsulatedExceptions(List<UntrustedIdentityException> untrustedIdentities,
List<UnregisteredUserException> unregisteredUsers)
List<UnregisteredUserException> unregisteredUsers,
List<NetworkFailureException> networkExceptions)
{
this.untrustedIdentityExceptions = untrustedIdentities;
this.unregisteredUserExceptions = unregisteredUsers;
this.networkExceptions = networkExceptions;
}
public List<UntrustedIdentityException> getUntrustedIdentityExceptions() {
@@ -39,4 +42,8 @@ public class EncapsulatedExceptions extends Throwable {
public List<UnregisteredUserException> getUnregisteredUserExceptions() {
return unregisteredUserExceptions;
}
public List<NetworkFailureException> getNetworkExceptions() {
return networkExceptions;
}
}

View File

@@ -0,0 +1,15 @@
package org.whispersystems.textsecure.api.push.exceptions;
public class NetworkFailureException extends Exception {
private final String e164number;
public NetworkFailureException(String e164number, Exception nested) {
super(nested);
this.e164number = e164number;
}
public String getE164number() {
return e164number;
}
}

View File

@@ -19,6 +19,7 @@ package org.whispersystems.textsecure.api.push.exceptions;
import java.io.IOException;
public class PushNetworkException extends IOException {
public PushNetworkException(Exception exception) {
super(exception);
}
@@ -26,4 +27,5 @@ public class PushNetworkException extends IOException {
public PushNetworkException(String s) {
super(s);
}
}

View File

@@ -16,10 +16,17 @@
*/
package org.whispersystems.textsecure.internal.push;
import com.fasterxml.jackson.annotation.JsonProperty;
public class AccountAttributes {
@JsonProperty
private String signalingKey;
@JsonProperty
private boolean supportsSms;
@JsonProperty
private int registrationId;
public AccountAttributes(String signalingKey, boolean supportsSms, int registrationId) {

View File

@@ -1,7 +1,10 @@
package org.whispersystems.textsecure.internal.push;
import com.fasterxml.jackson.annotation.JsonProperty;
public class DeviceCode {
@JsonProperty
private String verificationCode;
public String getVerificationCode() {

View File

@@ -16,11 +16,15 @@
*/
package org.whispersystems.textsecure.internal.push;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.List;
public class MismatchedDevices {
@JsonProperty
private List<Integer> missingDevices;
@JsonProperty
private List<Integer> extraDevices;
public List<Integer> getMissingDevices() {

View File

@@ -17,14 +17,20 @@
package org.whispersystems.textsecure.internal.push;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.whispersystems.textsecure.api.push.PushAddress;
import org.whispersystems.textsecure.internal.util.Base64;
public class OutgoingPushMessage {
@JsonProperty
private int type;
@JsonProperty
private int destinationDeviceId;
@JsonProperty
private int destinationRegistrationId;
@JsonProperty
private String body;
public OutgoingPushMessage(PushAddress address, int deviceId, PushBody body) {

View File

@@ -16,16 +16,22 @@
*/
package org.whispersystems.textsecure.internal.push;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.List;
public class OutgoingPushMessageList {
@JsonProperty
private String destination;
@JsonProperty
private String relay;
@JsonProperty
private long timestamp;
@JsonProperty
private List<OutgoingPushMessage> messages;
public OutgoingPushMessageList(String destination, long timestamp, String relay,

View File

@@ -16,14 +16,15 @@
*/
package org.whispersystems.textsecure.internal.push;
import com.google.thoughtcrimegson.GsonBuilder;
import com.google.thoughtcrimegson.JsonDeserializationContext;
import com.google.thoughtcrimegson.JsonDeserializer;
import com.google.thoughtcrimegson.JsonElement;
import com.google.thoughtcrimegson.JsonParseException;
import com.google.thoughtcrimegson.JsonPrimitive;
import com.google.thoughtcrimegson.JsonSerializationContext;
import com.google.thoughtcrimegson.JsonSerializer;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import org.whispersystems.libaxolotl.InvalidKeyException;
import org.whispersystems.libaxolotl.ecc.Curve;
@@ -31,11 +32,15 @@ import org.whispersystems.libaxolotl.ecc.ECPublicKey;
import org.whispersystems.textsecure.internal.util.Base64;
import java.io.IOException;
import java.lang.reflect.Type;
public class PreKeyEntity {
private int keyId;
@JsonProperty
private int keyId;
@JsonProperty
@JsonSerialize(using = ECPublicKeySerializer.class)
@JsonDeserialize(using = ECPublicKeyDeserializer.class)
private ECPublicKey publicKey;
public PreKeyEntity() {}
@@ -53,32 +58,21 @@ public class PreKeyEntity {
return publicKey;
}
public static GsonBuilder forBuilder(GsonBuilder builder) {
return builder.registerTypeAdapter(ECPublicKey.class, new ECPublicKeyJsonAdapter());
private static class ECPublicKeySerializer extends JsonSerializer<ECPublicKey> {
@Override
public void serialize(ECPublicKey value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeString(Base64.encodeBytesWithoutPadding(value.serialize()));
}
}
private static class ECPublicKeyJsonAdapter
implements JsonSerializer<ECPublicKey>, JsonDeserializer<ECPublicKey>
{
private static class ECPublicKeyDeserializer extends JsonDeserializer<ECPublicKey> {
@Override
public JsonElement serialize(ECPublicKey preKeyPublic, Type type,
JsonSerializationContext jsonSerializationContext)
{
return new JsonPrimitive(Base64.encodeBytesWithoutPadding(preKeyPublic.serialize()));
}
@Override
public ECPublicKey deserialize(JsonElement jsonElement, Type type,
JsonDeserializationContext jsonDeserializationContext)
throws JsonParseException
{
public ECPublicKey deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
try {
return Curve.decodePoint(Base64.decodeWithoutPadding(jsonElement.getAsJsonPrimitive().getAsString()), 0);
} catch (InvalidKeyException | IOException e) {
throw new JsonParseException(e);
return Curve.decodePoint(Base64.decodeWithoutPadding(p.getValueAsString()), 0);
} catch (InvalidKeyException e) {
throw new IOException(e);
}
}
}
}

View File

@@ -16,26 +16,32 @@
*/
package org.whispersystems.textsecure.internal.push;
import com.google.thoughtcrimegson.GsonBuilder;
import com.google.thoughtcrimegson.JsonDeserializationContext;
import com.google.thoughtcrimegson.JsonDeserializer;
import com.google.thoughtcrimegson.JsonElement;
import com.google.thoughtcrimegson.JsonParseException;
import com.google.thoughtcrimegson.JsonPrimitive;
import com.google.thoughtcrimegson.JsonSerializationContext;
import com.google.thoughtcrimegson.JsonSerializer;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import org.whispersystems.libaxolotl.IdentityKey;
import org.whispersystems.libaxolotl.InvalidKeyException;
import org.whispersystems.textsecure.internal.util.Base64;
import org.whispersystems.textsecure.internal.util.JsonUtil;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.List;
public class PreKeyResponse {
private IdentityKey identityKey;
@JsonProperty
@JsonSerialize(using = JsonUtil.IdentityKeySerializer.class)
@JsonDeserialize(using = JsonUtil.IdentityKeyDeserializer.class)
private IdentityKey identityKey;
@JsonProperty
private List<PreKeyResponseItem> devices;
public IdentityKey getIdentityKey() {
@@ -46,36 +52,5 @@ public class PreKeyResponse {
return devices;
}
public static PreKeyResponse fromJson(String serialized) {
GsonBuilder builder = new GsonBuilder();
return PreKeyResponseItem.forBuilder(builder)
.registerTypeAdapter(IdentityKey.class, new IdentityKeyJsonAdapter())
.create().fromJson(serialized, PreKeyResponse.class);
}
public static class IdentityKeyJsonAdapter
implements JsonSerializer<IdentityKey>, JsonDeserializer<IdentityKey>
{
@Override
public JsonElement serialize(IdentityKey identityKey, Type type,
JsonSerializationContext jsonSerializationContext)
{
return new JsonPrimitive(Base64.encodeBytesWithoutPadding(identityKey.serialize()));
}
@Override
public IdentityKey deserialize(JsonElement jsonElement, Type type,
JsonDeserializationContext jsonDeserializationContext)
throws JsonParseException
{
try {
return new IdentityKey(Base64.decodeWithoutPadding(jsonElement.getAsJsonPrimitive().getAsString()), 0);
} catch (InvalidKeyException | IOException e) {
throw new JsonParseException(e);
}
}
}
}

View File

@@ -16,15 +16,22 @@
*/
package org.whispersystems.textsecure.internal.push;
import com.google.thoughtcrimegson.GsonBuilder;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.whispersystems.textsecure.api.push.SignedPreKeyEntity;
public class PreKeyResponseItem {
@JsonProperty
private int deviceId;
@JsonProperty
private int registrationId;
@JsonProperty
private SignedPreKeyEntity signedPreKey;
@JsonProperty
private PreKeyEntity preKey;
public int getDeviceId() {
@@ -43,7 +50,4 @@ public class PreKeyResponseItem {
return preKey;
}
public static GsonBuilder forBuilder(GsonBuilder builder) {
return SignedPreKeyEntity.forBuilder(builder);
}
}

View File

@@ -1,17 +1,29 @@
package org.whispersystems.textsecure.internal.push;
import com.google.thoughtcrimegson.GsonBuilder;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import org.whispersystems.libaxolotl.IdentityKey;
import org.whispersystems.textsecure.api.push.SignedPreKeyEntity;
import org.whispersystems.textsecure.internal.util.JsonUtil;
import java.util.List;
public class PreKeyState {
@JsonProperty
@JsonSerialize(using = JsonUtil.IdentityKeySerializer.class)
@JsonDeserialize(using = JsonUtil.IdentityKeyDeserializer.class)
private IdentityKey identityKey;
@JsonProperty
private List<PreKeyEntity> preKeys;
@JsonProperty
private PreKeyEntity lastResortKey;
@JsonProperty
private SignedPreKeyEntity signedPreKey;
@@ -24,10 +36,4 @@ public class PreKeyState {
this.identityKey = identityKey;
}
public static String toJson(PreKeyState state) {
GsonBuilder builder = new GsonBuilder();
return SignedPreKeyEntity.forBuilder(builder)
.registerTypeAdapter(IdentityKey.class, new PreKeyResponse.IdentityKeyJsonAdapter())
.create().toJson(state);
}
}

View File

@@ -16,8 +16,11 @@
*/
package org.whispersystems.textsecure.internal.push;
import com.fasterxml.jackson.annotation.JsonProperty;
public class PreKeyStatus {
@JsonProperty
private int count;
public PreKeyStatus() {}

View File

@@ -18,8 +18,7 @@ package org.whispersystems.textsecure.internal.push;
import android.util.Log;
import com.google.thoughtcrimegson.Gson;
import com.google.thoughtcrimegson.JsonParseException;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.apache.http.conn.ssl.StrictHostnameVerifier;
import org.whispersystems.libaxolotl.IdentityKey;
@@ -44,6 +43,7 @@ import org.whispersystems.textsecure.internal.push.exceptions.MismatchedDevicesE
import org.whispersystems.textsecure.internal.push.exceptions.StaleDevicesException;
import org.whispersystems.textsecure.internal.util.Base64;
import org.whispersystems.textsecure.internal.util.BlacklistingTrustManager;
import org.whispersystems.textsecure.internal.util.JsonUtil;
import org.whispersystems.textsecure.internal.util.Util;
import java.io.File;
@@ -115,17 +115,17 @@ public class PushServiceSocket {
{
AccountAttributes signalingKeyEntity = new AccountAttributes(signalingKey, supportsSms, registrationId);
makeRequest(String.format(VERIFY_ACCOUNT_PATH, verificationCode),
"PUT", new Gson().toJson(signalingKeyEntity));
"PUT", JsonUtil.toJson(signalingKeyEntity));
}
public String getNewDeviceVerificationCode() throws IOException {
String responseText = makeRequest(PROVISIONING_CODE_PATH, "GET", null);
return new Gson().fromJson(responseText, DeviceCode.class).getVerificationCode();
return JsonUtil.fromJson(responseText, DeviceCode.class).getVerificationCode();
}
public void sendProvisioningMessage(String destination, byte[] body) throws IOException {
makeRequest(String.format(PROVISIONING_MESSAGE_PATH, destination), "PUT",
new Gson().toJson(new ProvisioningMessage(Base64.encodeBytes(body))));
JsonUtil.toJson(new ProvisioningMessage(Base64.encodeBytes(body))));
}
public void sendReceipt(String destination, long messageId, String relay) throws IOException {
@@ -140,7 +140,7 @@ public class PushServiceSocket {
public void registerGcmId(String gcmRegistrationId) throws IOException {
GcmRegistrationId registration = new GcmRegistrationId(gcmRegistrationId, true);
makeRequest(REGISTER_GCM_PATH, "PUT", new Gson().toJson(registration));
makeRequest(REGISTER_GCM_PATH, "PUT", JsonUtil.toJson(registration));
}
public void unregisterGcmId() throws IOException {
@@ -151,11 +151,10 @@ public class PushServiceSocket {
throws IOException
{
try {
String responseText = makeRequest(String.format(MESSAGE_PATH, bundle.getDestination()), "PUT", new Gson().toJson(bundle));
String responseText = makeRequest(String.format(MESSAGE_PATH, bundle.getDestination()), "PUT", JsonUtil.toJson(bundle));
if (responseText == null) return new SendMessageResponse(false);
else return new Gson().fromJson(responseText, SendMessageResponse.class);
else return JsonUtil.fromJson(responseText, SendMessageResponse.class);
} catch (NotFoundException nfe) {
throw new UnregisteredUserException(bundle.getDestination(), nfe);
}
@@ -184,13 +183,13 @@ public class PushServiceSocket {
signedPreKey.getSignature());
makeRequest(String.format(PREKEY_PATH, ""), "PUT",
PreKeyState.toJson(new PreKeyState(entities, lastResortEntity,
signedPreKeyEntity, identityKey)));
JsonUtil.toJson(new PreKeyState(entities, lastResortEntity,
signedPreKeyEntity, identityKey)));
}
public int getAvailablePreKeys() throws IOException {
String responseText = makeRequest(PREKEY_METADATA_PATH, "GET", null);
PreKeyStatus preKeyStatus = new Gson().fromJson(responseText, PreKeyStatus.class);
PreKeyStatus preKeyStatus = JsonUtil.fromJson(responseText, PreKeyStatus.class);
return preKeyStatus.getCount();
}
@@ -209,7 +208,7 @@ public class PushServiceSocket {
}
String responseText = makeRequest(path, "GET", null);
PreKeyResponse response = PreKeyResponse.fromJson(responseText);
PreKeyResponse response = JsonUtil.fromJson(responseText, PreKeyResponse.class);
List<PreKeyBundle> bundles = new LinkedList<>();
for (PreKeyResponseItem device : response.getDevices()) {
@@ -236,7 +235,7 @@ public class PushServiceSocket {
}
return bundles;
} catch (JsonParseException e) {
} catch (JsonUtil.JsonParseException e) {
throw new IOException(e);
} catch (NotFoundException nfe) {
throw new UnregisteredUserException(destination.getNumber(), nfe);
@@ -253,7 +252,7 @@ public class PushServiceSocket {
}
String responseText = makeRequest(path, "GET", null);
PreKeyResponse response = PreKeyResponse.fromJson(responseText);
PreKeyResponse response = JsonUtil.fromJson(responseText, PreKeyResponse.class);
if (response.getDevices() == null || response.getDevices().size() < 1)
throw new IOException("Empty prekey list");
@@ -278,7 +277,7 @@ public class PushServiceSocket {
return new PreKeyBundle(device.getRegistrationId(), device.getDeviceId(), preKeyId, preKey,
signedPreKeyId, signedPreKey, signedPreKeySignature, response.getIdentityKey());
} catch (JsonParseException e) {
} catch (JsonUtil.JsonParseException e) {
throw new IOException(e);
} catch (NotFoundException nfe) {
throw new UnregisteredUserException(destination.getNumber(), nfe);
@@ -288,7 +287,7 @@ public class PushServiceSocket {
public SignedPreKeyEntity getCurrentSignedPreKey() throws IOException {
try {
String responseText = makeRequest(SIGNED_PREKEY_PATH, "GET", null);
return SignedPreKeyEntity.fromJson(responseText);
return JsonUtil.fromJson(responseText, SignedPreKeyEntity.class);
} catch (NotFoundException e) {
Log.w("PushServiceSocket", e);
return null;
@@ -299,12 +298,12 @@ public class PushServiceSocket {
SignedPreKeyEntity signedPreKeyEntity = new SignedPreKeyEntity(signedPreKey.getId(),
signedPreKey.getKeyPair().getPublicKey(),
signedPreKey.getSignature());
makeRequest(SIGNED_PREKEY_PATH, "PUT", SignedPreKeyEntity.toJson(signedPreKeyEntity));
makeRequest(SIGNED_PREKEY_PATH, "PUT", JsonUtil.toJson(signedPreKeyEntity));
}
public long sendAttachment(PushAttachmentData attachment) throws IOException {
String response = makeRequest(String.format(ATTACHMENT_PATH, ""), "GET", null);
AttachmentDescriptor attachmentKey = new Gson().fromJson(response, AttachmentDescriptor.class);
AttachmentDescriptor attachmentKey = JsonUtil.fromJson(response, AttachmentDescriptor.class);
if (attachmentKey == null || attachmentKey.getLocation() == null) {
throw new IOException("Server failed to allocate an attachment key!");
@@ -326,7 +325,7 @@ public class PushServiceSocket {
}
String response = makeRequest(path, "GET", null);
AttachmentDescriptor descriptor = new Gson().fromJson(response, AttachmentDescriptor.class);
AttachmentDescriptor descriptor = JsonUtil.fromJson(response, AttachmentDescriptor.class);
Log.w("PushServiceSocket", "Attachment: " + attachmentId + " is at: " + descriptor.getLocation());
@@ -337,8 +336,8 @@ public class PushServiceSocket {
throws NonSuccessfulResponseCodeException, PushNetworkException
{
ContactTokenList contactTokenList = new ContactTokenList(new LinkedList<>(contactTokens));
String response = makeRequest(DIRECTORY_TOKENS_PATH, "PUT", new Gson().toJson(contactTokenList));
ContactTokenDetailsList activeTokens = new Gson().fromJson(response, ContactTokenDetailsList.class);
String response = makeRequest(DIRECTORY_TOKENS_PATH, "PUT", JsonUtil.toJson(contactTokenList));
ContactTokenDetailsList activeTokens = JsonUtil.fromJson(response, ContactTokenDetailsList.class);
return activeTokens.getContacts();
}
@@ -346,7 +345,7 @@ public class PushServiceSocket {
public ContactTokenDetails getContactTokenDetails(String contactToken) throws IOException {
try {
String response = makeRequest(String.format(DIRECTORY_VERIFY_PATH, contactToken), "GET", null);
return new Gson().fromJson(response, ContactTokenDetails.class);
return JsonUtil.fromJson(response, ContactTokenDetails.class);
} catch (NotFoundException nfe) {
return null;
}
@@ -463,14 +462,14 @@ public class PushServiceSocket {
} catch (IOException e) {
throw new PushNetworkException(e);
}
throw new MismatchedDevicesException(new Gson().fromJson(response, MismatchedDevices.class));
throw new MismatchedDevicesException(JsonUtil.fromJson(response, MismatchedDevices.class));
case 410:
try {
response = Util.readFully(connection.getErrorStream());
} catch (IOException e) {
throw new PushNetworkException(e);
}
throw new StaleDevicesException(new Gson().fromJson(response, StaleDevices.class));
throw new StaleDevicesException(JsonUtil.fromJson(response, StaleDevices.class));
case 417:
throw new ExpectationFailedException();
}
@@ -524,9 +523,7 @@ public class PushServiceSocket {
return connection;
} catch (IOException e) {
throw new PushNetworkException(e);
} catch (NoSuchAlgorithmException e) {
throw new AssertionError(e);
} catch (KeyManagementException e) {
} catch (NoSuchAlgorithmException | KeyManagementException e) {
throw new AssertionError(e);
}
}
@@ -540,7 +537,11 @@ public class PushServiceSocket {
}
private static class GcmRegistrationId {
@JsonProperty
private String gcmRegistrationId;
@JsonProperty
private boolean webSocketChannel;
public GcmRegistrationId() {}
@@ -552,7 +553,10 @@ public class PushServiceSocket {
}
private static class AttachmentDescriptor {
@JsonProperty
private long id;
@JsonProperty
private String location;
public long getId() {

View File

@@ -16,10 +16,13 @@
*/
package org.whispersystems.textsecure.internal.push;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.List;
public class StaleDevices {
@JsonProperty
private List<Integer> staleDevices;
public List<Integer> getStaleDevices() {

View File

@@ -0,0 +1,75 @@
package org.whispersystems.textsecure.internal.util;
import android.util.Log;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializerProvider;
import org.whispersystems.libaxolotl.IdentityKey;
import org.whispersystems.libaxolotl.InvalidKeyException;
import java.io.IOException;
public class JsonUtil {
private static final String TAG = JsonUtil.class.getSimpleName();
private static final ObjectMapper objectMapper = new ObjectMapper();
static {
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}
public static String toJson(Object object) {
try {
return objectMapper.writeValueAsString(object);
} catch (JsonProcessingException e) {
Log.w(TAG, e);
return "";
}
}
public static <T> T fromJson(String json, Class<T> clazz) {
try {
return objectMapper.readValue(json, clazz);
} catch (IOException e) {
Log.w(TAG, e);
throw new JsonParseException(e);
}
}
public static class JsonParseException extends RuntimeException {
public JsonParseException(Exception e) {
super(e);
}
}
public static class IdentityKeySerializer extends JsonSerializer<IdentityKey> {
@Override
public void serialize(IdentityKey value, JsonGenerator gen, SerializerProvider serializers)
throws IOException
{
gen.writeString(Base64.encodeBytesWithoutPadding(value.serialize()));
}
}
public static class IdentityKeyDeserializer extends JsonDeserializer<IdentityKey> {
@Override
public IdentityKey deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
try {
return new IdentityKey(Base64.decodeWithoutPadding(p.getValueAsString()), 0);
} catch (InvalidKeyException e) {
throw new IOException(e);
}
}
}
}