mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-23 18:15:22 +00:00
clean up stickers
This commit is contained in:
parent
19a829d011
commit
88bbc0b677
@ -274,7 +274,6 @@ public class PushGroupSendJob extends PushSendJob implements InjectableType {
|
|||||||
String groupId = address.toGroupString();
|
String groupId = address.toGroupString();
|
||||||
Optional<byte[]> profileKey = getProfileKey(message.getRecipient());
|
Optional<byte[]> profileKey = getProfileKey(message.getRecipient());
|
||||||
Optional<Quote> quote = getQuoteFor(message);
|
Optional<Quote> quote = getQuoteFor(message);
|
||||||
Optional<SignalServiceDataMessage.Sticker> sticker = getStickerFor(message);
|
|
||||||
List<SharedContact> sharedContacts = getSharedContactsFor(message);
|
List<SharedContact> sharedContacts = getSharedContactsFor(message);
|
||||||
List<Preview> previews = getPreviewsFor(message);
|
List<Preview> previews = getPreviewsFor(message);
|
||||||
List<Attachment> attachments = Stream.of(message.getAttachments()).filterNot(Attachment::isSticker).toList();
|
List<Attachment> attachments = Stream.of(message.getAttachments()).filterNot(Attachment::isSticker).toList();
|
||||||
|
@ -212,26 +212,6 @@ public abstract class PushSendJob extends SendJob {
|
|||||||
return Optional.of(new SignalServiceDataMessage.Quote(quoteId, new SignalServiceAddress(quoteAuthor.serialize()), quoteBody, quoteAttachments));
|
return Optional.of(new SignalServiceDataMessage.Quote(quoteId, new SignalServiceAddress(quoteAuthor.serialize()), quoteBody, quoteAttachments));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Optional<SignalServiceDataMessage.Sticker> getStickerFor(OutgoingMediaMessage message) {
|
|
||||||
Attachment stickerAttachment = Stream.of(message.getAttachments()).filter(Attachment::isSticker).findFirst().orElse(null);
|
|
||||||
|
|
||||||
if (stickerAttachment == null) {
|
|
||||||
return Optional.absent();
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
byte[] packId = Hex.fromStringCondensed(stickerAttachment.getSticker().getPackId());
|
|
||||||
byte[] packKey = Hex.fromStringCondensed(stickerAttachment.getSticker().getPackKey());
|
|
||||||
int stickerId = stickerAttachment.getSticker().getStickerId();
|
|
||||||
SignalServiceAttachment attachment = getAttachmentPointerFor(stickerAttachment);
|
|
||||||
|
|
||||||
return Optional.of(new SignalServiceDataMessage.Sticker(packId, packKey, stickerId, attachment));
|
|
||||||
} catch (IOException e) {
|
|
||||||
Log.w(TAG, "Failed to decode sticker id/key", e);
|
|
||||||
return Optional.absent();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
List<SharedContact> getSharedContactsFor(OutgoingMediaMessage mediaMessage) {
|
List<SharedContact> getSharedContactsFor(OutgoingMediaMessage mediaMessage) {
|
||||||
List<SharedContact> sharedContacts = new LinkedList<>();
|
List<SharedContact> sharedContacts = new LinkedList<>();
|
||||||
|
|
||||||
@ -259,9 +239,5 @@ public abstract class PushSendJob extends SendJob {
|
|||||||
}).toList();
|
}).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void rotateSenderCertificateIfNecessary() throws IOException {
|
|
||||||
// Loki - We don't need verification on sender certificates
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract void onPushSend() throws Exception;
|
protected abstract void onPushSend() throws Exception;
|
||||||
}
|
}
|
||||||
|
@ -1,13 +0,0 @@
|
|||||||
package org.session.libsignal.libsignal.util;
|
|
||||||
|
|
||||||
import org.session.libsignal.libsignal.IdentityKey;
|
|
||||||
|
|
||||||
import java.util.Comparator;
|
|
||||||
|
|
||||||
public class IdentityKeyComparator extends ByteArrayComparator implements Comparator<IdentityKey> {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int compare(IdentityKey first, IdentityKey second) {
|
|
||||||
return compare(first.getPublicKey().serialize(), second.getPublicKey().serialize());
|
|
||||||
}
|
|
||||||
}
|
|
@ -46,10 +46,6 @@ public class SendMessageResult {
|
|||||||
return networkFailure;
|
return networkFailure;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isUnregisteredFailure() {
|
|
||||||
return unregisteredFailure;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IdentityFailure getIdentityFailure() {
|
public IdentityFailure getIdentityFailure() {
|
||||||
return identityFailure;
|
return identityFailure;
|
||||||
}
|
}
|
||||||
|
@ -25,10 +25,6 @@ public class SignalServiceAttachmentStream extends SignalServiceAttachment {
|
|||||||
private final int height;
|
private final int height;
|
||||||
private final Optional<String> caption;
|
private final Optional<String> caption;
|
||||||
|
|
||||||
public SignalServiceAttachmentStream(InputStream inputStream, String contentType, long length, Optional<String> fileName, boolean voiceNote, ProgressListener listener) {
|
|
||||||
this(inputStream, contentType, length, fileName, voiceNote, Optional.<byte[]>absent(), 0, 0, Optional.<String>absent(), listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
public SignalServiceAttachmentStream(InputStream inputStream, String contentType, long length, Optional<String> fileName, boolean voiceNote, Optional<byte[]> preview, int width, int height, Optional<String> caption, ProgressListener listener) {
|
public SignalServiceAttachmentStream(InputStream inputStream, String contentType, long length, Optional<String> fileName, boolean voiceNote, Optional<byte[]> preview, int width, int height, Optional<String> caption, ProgressListener listener) {
|
||||||
super(contentType);
|
super(contentType);
|
||||||
this.inputStream = inputStream;
|
this.inputStream = inputStream;
|
||||||
|
@ -276,11 +276,6 @@ public class SignalServiceDataMessage {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Builder withAttachment(SignalServiceAttachment attachment) {
|
|
||||||
this.attachments.add(attachment);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder withAttachments(List<SignalServiceAttachment> attachments) {
|
public Builder withAttachments(List<SignalServiceAttachment> attachments) {
|
||||||
this.attachments.addAll(attachments);
|
this.attachments.addAll(attachments);
|
||||||
return this;
|
return this;
|
||||||
@ -296,10 +291,6 @@ public class SignalServiceDataMessage {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Builder asExpirationUpdate() {
|
|
||||||
return asExpirationUpdate(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder asExpirationUpdate(boolean expirationUpdate) {
|
public Builder asExpirationUpdate(boolean expirationUpdate) {
|
||||||
this.expirationUpdate = expirationUpdate;
|
this.expirationUpdate = expirationUpdate;
|
||||||
return this;
|
return this;
|
||||||
@ -320,11 +311,6 @@ public class SignalServiceDataMessage {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Builder withSharedContact(SharedContact contact) {
|
|
||||||
this.sharedContacts.add(contact);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder withSharedContacts(List<SharedContact> contacts) {
|
public Builder withSharedContacts(List<SharedContact> contacts) {
|
||||||
this.sharedContacts.addAll(contacts);
|
this.sharedContacts.addAll(contacts);
|
||||||
return this;
|
return this;
|
||||||
@ -420,34 +406,4 @@ public class SignalServiceDataMessage {
|
|||||||
return image;
|
return image;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Sticker {
|
|
||||||
private final byte[] packId;
|
|
||||||
private final byte[] packKey;
|
|
||||||
private final int stickerId;
|
|
||||||
private final SignalServiceAttachment attachment;
|
|
||||||
|
|
||||||
public Sticker(byte[] packId, byte[] packKey, int stickerId, SignalServiceAttachment attachment) {
|
|
||||||
this.packId = packId;
|
|
||||||
this.packKey = packKey;
|
|
||||||
this.stickerId = stickerId;
|
|
||||||
this.attachment = attachment;
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] getPackId() {
|
|
||||||
return packId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] getPackKey() {
|
|
||||||
return packKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getStickerId() {
|
|
||||||
return stickerId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public SignalServiceAttachment getAttachment() {
|
|
||||||
return attachment;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -39,63 +39,8 @@ import javax.crypto.spec.SecretKeySpec;
|
|||||||
*/
|
*/
|
||||||
public class SignalServiceEnvelope {
|
public class SignalServiceEnvelope {
|
||||||
|
|
||||||
private static final String TAG = SignalServiceEnvelope.class.getSimpleName();
|
|
||||||
|
|
||||||
private static final int SUPPORTED_VERSION = 1;
|
|
||||||
private static final int CIPHER_KEY_SIZE = 32;
|
|
||||||
private static final int MAC_KEY_SIZE = 20;
|
|
||||||
private static final int MAC_SIZE = 10;
|
|
||||||
|
|
||||||
private static final int VERSION_OFFSET = 0;
|
|
||||||
private static final int VERSION_LENGTH = 1;
|
|
||||||
private static final int IV_OFFSET = VERSION_OFFSET + VERSION_LENGTH;
|
|
||||||
private static final int IV_LENGTH = 16;
|
|
||||||
private static final int CIPHERTEXT_OFFSET = IV_OFFSET + IV_LENGTH;
|
|
||||||
|
|
||||||
private final Envelope envelope;
|
private final Envelope envelope;
|
||||||
|
|
||||||
/**
|
|
||||||
* Construct an envelope from a serialized, Base64 encoded SignalServiceEnvelope, encrypted
|
|
||||||
* with a signaling key.
|
|
||||||
*
|
|
||||||
* @param message The serialized SignalServiceEnvelope, base64 encoded and encrypted.
|
|
||||||
* @param signalingKey The signaling key.
|
|
||||||
* @throws IOException
|
|
||||||
* @throws InvalidVersionException
|
|
||||||
*/
|
|
||||||
public SignalServiceEnvelope(String message, String signalingKey, boolean isSignalingKeyEncrypted)
|
|
||||||
throws IOException, InvalidVersionException
|
|
||||||
{
|
|
||||||
this(Base64.decode(message), signalingKey, isSignalingKeyEncrypted);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Construct an envelope from a serialized SignalServiceEnvelope, encrypted with a signaling key.
|
|
||||||
*
|
|
||||||
* @param input The serialized and (optionally) encrypted SignalServiceEnvelope.
|
|
||||||
* @param signalingKey The signaling key.
|
|
||||||
* @throws InvalidVersionException
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
public SignalServiceEnvelope(byte[] input, String signalingKey, boolean isSignalingKeyEncrypted)
|
|
||||||
throws InvalidVersionException, IOException
|
|
||||||
{
|
|
||||||
if (!isSignalingKeyEncrypted) {
|
|
||||||
this.envelope = Envelope.parseFrom(input);
|
|
||||||
} else {
|
|
||||||
if (input.length < VERSION_LENGTH || input[VERSION_OFFSET] != SUPPORTED_VERSION) {
|
|
||||||
throw new InvalidVersionException("Unsupported version!");
|
|
||||||
}
|
|
||||||
|
|
||||||
SecretKeySpec cipherKey = getCipherKey(signalingKey);
|
|
||||||
SecretKeySpec macKey = getMacKey(signalingKey);
|
|
||||||
|
|
||||||
verifyMac(input, macKey);
|
|
||||||
|
|
||||||
this.envelope = Envelope.parseFrom(getPlaintext(input, cipherKey));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public SignalServiceEnvelope(Envelope proto) {
|
public SignalServiceEnvelope(Envelope proto) {
|
||||||
Envelope.Builder builder = Envelope.newBuilder();
|
Envelope.Builder builder = Envelope.newBuilder();
|
||||||
builder.setType(Envelope.Type.valueOf(proto.getType().getNumber()));
|
builder.setType(Envelope.Type.valueOf(proto.getType().getNumber()));
|
||||||
@ -126,17 +71,6 @@ public class SignalServiceEnvelope {
|
|||||||
this.envelope = builder.build();
|
this.envelope = builder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
public SignalServiceEnvelope(int type, long timestamp, byte[] content, long serverTimestamp) {
|
|
||||||
Envelope.Builder builder = Envelope.newBuilder()
|
|
||||||
.setType(Envelope.Type.valueOf(type))
|
|
||||||
.setTimestamp(timestamp)
|
|
||||||
.setServerTimestamp(serverTimestamp);
|
|
||||||
|
|
||||||
if (content != null) builder.setContent(ByteString.copyFrom(content));
|
|
||||||
|
|
||||||
this.envelope = builder.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean hasSource() {
|
public boolean hasSource() {
|
||||||
return envelope.hasSource() && envelope.getSource().length() > 0;
|
return envelope.hasSource() && envelope.getSource().length() > 0;
|
||||||
}
|
}
|
||||||
@ -205,80 +139,4 @@ public class SignalServiceEnvelope {
|
|||||||
public boolean isClosedGroupCiphertext() {
|
public boolean isClosedGroupCiphertext() {
|
||||||
return envelope.getType().getNumber() == Envelope.Type.CLOSED_GROUP_CIPHERTEXT_VALUE;
|
return envelope.getType().getNumber() == Envelope.Type.CLOSED_GROUP_CIPHERTEXT_VALUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] getPlaintext(byte[] ciphertext, SecretKeySpec cipherKey) throws IOException {
|
|
||||||
try {
|
|
||||||
byte[] ivBytes = new byte[IV_LENGTH];
|
|
||||||
System.arraycopy(ciphertext, IV_OFFSET, ivBytes, 0, ivBytes.length);
|
|
||||||
IvParameterSpec iv = new IvParameterSpec(ivBytes);
|
|
||||||
|
|
||||||
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
|
||||||
cipher.init(Cipher.DECRYPT_MODE, cipherKey, iv);
|
|
||||||
|
|
||||||
return cipher.doFinal(ciphertext, CIPHERTEXT_OFFSET,
|
|
||||||
ciphertext.length - VERSION_LENGTH - IV_LENGTH - MAC_SIZE);
|
|
||||||
} catch (NoSuchAlgorithmException e) {
|
|
||||||
throw new AssertionError(e);
|
|
||||||
} catch (NoSuchPaddingException e) {
|
|
||||||
throw new AssertionError(e);
|
|
||||||
} catch (InvalidKeyException e) {
|
|
||||||
throw new AssertionError(e);
|
|
||||||
} catch (InvalidAlgorithmParameterException e) {
|
|
||||||
throw new AssertionError(e);
|
|
||||||
} catch (IllegalBlockSizeException e) {
|
|
||||||
throw new AssertionError(e);
|
|
||||||
} catch (BadPaddingException e) {
|
|
||||||
Log.w(TAG, e);
|
|
||||||
throw new IOException("Bad padding?");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void verifyMac(byte[] ciphertext, SecretKeySpec macKey) throws IOException {
|
|
||||||
try {
|
|
||||||
Mac mac = Mac.getInstance("HmacSHA256");
|
|
||||||
mac.init(macKey);
|
|
||||||
|
|
||||||
if (ciphertext.length < MAC_SIZE + 1)
|
|
||||||
throw new IOException("Invalid MAC!");
|
|
||||||
|
|
||||||
mac.update(ciphertext, 0, ciphertext.length - MAC_SIZE);
|
|
||||||
|
|
||||||
byte[] ourMacFull = mac.doFinal();
|
|
||||||
byte[] ourMacBytes = new byte[MAC_SIZE];
|
|
||||||
System.arraycopy(ourMacFull, 0, ourMacBytes, 0, ourMacBytes.length);
|
|
||||||
|
|
||||||
byte[] theirMacBytes = new byte[MAC_SIZE];
|
|
||||||
System.arraycopy(ciphertext, ciphertext.length-MAC_SIZE, theirMacBytes, 0, theirMacBytes.length);
|
|
||||||
|
|
||||||
Log.w(TAG, "Our MAC: " + Hex.toString(ourMacBytes));
|
|
||||||
Log.w(TAG, "Thr MAC: " + Hex.toString(theirMacBytes));
|
|
||||||
|
|
||||||
if (!Arrays.equals(ourMacBytes, theirMacBytes)) {
|
|
||||||
throw new IOException("Invalid MAC compare!");
|
|
||||||
}
|
|
||||||
} catch (NoSuchAlgorithmException e) {
|
|
||||||
throw new AssertionError(e);
|
|
||||||
} catch (InvalidKeyException e) {
|
|
||||||
throw new AssertionError(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private SecretKeySpec getCipherKey(String signalingKey) throws IOException {
|
|
||||||
byte[] signalingKeyBytes = Base64.decode(signalingKey);
|
|
||||||
byte[] cipherKey = new byte[CIPHER_KEY_SIZE];
|
|
||||||
System.arraycopy(signalingKeyBytes, 0, cipherKey, 0, cipherKey.length);
|
|
||||||
|
|
||||||
return new SecretKeySpec(cipherKey, "AES");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private SecretKeySpec getMacKey(String signalingKey) throws IOException {
|
|
||||||
byte[] signalingKeyBytes = Base64.decode(signalingKey);
|
|
||||||
byte[] macKey = new byte[MAC_KEY_SIZE];
|
|
||||||
System.arraycopy(signalingKeyBytes, CIPHER_KEY_SIZE, macKey, 0, macKey.length);
|
|
||||||
|
|
||||||
return new SecretKeySpec(macKey, "HmacSHA256");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -105,10 +105,6 @@ public class SignalServiceGroup {
|
|||||||
return admins;
|
return admins;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Builder newUpdateBuilder() {
|
|
||||||
return new Builder(Type.UPDATE);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Builder newBuilder(Type type) {
|
public static Builder newBuilder(Type type) {
|
||||||
return new Builder(type);
|
return new Builder(type);
|
||||||
}
|
}
|
||||||
|
@ -1,56 +0,0 @@
|
|||||||
package org.session.libsignal.service.api.messages;
|
|
||||||
|
|
||||||
import org.session.libsignal.libsignal.util.guava.Optional;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class SignalServiceStickerManifest {
|
|
||||||
|
|
||||||
private final Optional<String> title;
|
|
||||||
private final Optional<String> author;
|
|
||||||
private final Optional<StickerInfo> cover;
|
|
||||||
private final List<StickerInfo> stickers;
|
|
||||||
|
|
||||||
public SignalServiceStickerManifest(String title, String author, StickerInfo cover, List<StickerInfo> stickers) {
|
|
||||||
this.title = Optional.of(title);
|
|
||||||
this.author = Optional.of(author);
|
|
||||||
this.cover = Optional.of(cover);
|
|
||||||
this.stickers = (stickers == null) ? Collections.<StickerInfo>emptyList() : new ArrayList<StickerInfo>(stickers);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Optional<String> getTitle() {
|
|
||||||
return title;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Optional<String> getAuthor() {
|
|
||||||
return author;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Optional<StickerInfo> getCover() {
|
|
||||||
return cover;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<StickerInfo> getStickers() {
|
|
||||||
return stickers;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final class StickerInfo {
|
|
||||||
private final int id;
|
|
||||||
private final String emoji;
|
|
||||||
|
|
||||||
public StickerInfo(int id, String emoji) {
|
|
||||||
this.id = id;
|
|
||||||
this.emoji = emoji;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getId() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getEmoji() {
|
|
||||||
return emoji;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
@ -1,340 +0,0 @@
|
|||||||
package org.session.libsignal.service.internal.websocket;
|
|
||||||
|
|
||||||
import com.google.protobuf.InvalidProtocolBufferException;
|
|
||||||
|
|
||||||
import org.session.libsignal.utilities.logging.Log;
|
|
||||||
import org.session.libsignal.libsignal.util.Pair;
|
|
||||||
import org.session.libsignal.libsignal.util.guava.Optional;
|
|
||||||
import org.session.libsignal.service.api.push.TrustStore;
|
|
||||||
import org.session.libsignal.service.api.util.CredentialsProvider;
|
|
||||||
import org.session.libsignal.service.api.util.SleepTimer;
|
|
||||||
import org.session.libsignal.service.api.util.Tls12SocketFactory;
|
|
||||||
import org.session.libsignal.service.api.websocket.ConnectivityListener;
|
|
||||||
import org.session.libsignal.service.internal.util.BlacklistingTrustManager;
|
|
||||||
import org.session.libsignal.service.internal.util.Util;
|
|
||||||
import org.session.libsignal.utilities.concurrent.SettableFuture;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.security.KeyManagementException;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.Future;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.concurrent.TimeoutException;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
|
|
||||||
import javax.net.ssl.SSLContext;
|
|
||||||
import javax.net.ssl.SSLSocketFactory;
|
|
||||||
import javax.net.ssl.TrustManager;
|
|
||||||
import javax.net.ssl.X509TrustManager;
|
|
||||||
|
|
||||||
import okhttp3.ConnectionSpec;
|
|
||||||
import okhttp3.OkHttpClient;
|
|
||||||
import okhttp3.Request;
|
|
||||||
import okhttp3.Response;
|
|
||||||
import okhttp3.WebSocket;
|
|
||||||
import okhttp3.WebSocketListener;
|
|
||||||
import okio.ByteString;
|
|
||||||
|
|
||||||
import static org.session.libsignal.service.internal.websocket.WebSocketProtos.WebSocketMessage;
|
|
||||||
import static org.session.libsignal.service.internal.websocket.WebSocketProtos.WebSocketRequestMessage;
|
|
||||||
import static org.session.libsignal.service.internal.websocket.WebSocketProtos.WebSocketResponseMessage;
|
|
||||||
|
|
||||||
public class WebSocketConnection extends WebSocketListener {
|
|
||||||
|
|
||||||
private static final String TAG = WebSocketConnection.class.getSimpleName();
|
|
||||||
private static final int KEEPALIVE_TIMEOUT_SECONDS = 55;
|
|
||||||
|
|
||||||
private final LinkedList<WebSocketRequestMessage> incomingRequests = new LinkedList<WebSocketRequestMessage>();
|
|
||||||
private final Map<Long, SettableFuture<Pair<Integer, String>>> outgoingRequests = new HashMap<Long, SettableFuture<Pair<Integer, String>>>();
|
|
||||||
|
|
||||||
private final String wsUri;
|
|
||||||
private final TrustStore trustStore;
|
|
||||||
private final Optional<CredentialsProvider> credentialsProvider;
|
|
||||||
private final String userAgent;
|
|
||||||
private final ConnectivityListener listener;
|
|
||||||
private final SleepTimer sleepTimer;
|
|
||||||
|
|
||||||
private WebSocket client;
|
|
||||||
private KeepAliveSender keepAliveSender;
|
|
||||||
private int attempts;
|
|
||||||
private boolean connected;
|
|
||||||
|
|
||||||
public WebSocketConnection(String httpUri,
|
|
||||||
TrustStore trustStore,
|
|
||||||
Optional<CredentialsProvider> credentialsProvider,
|
|
||||||
String userAgent,
|
|
||||||
ConnectivityListener listener,
|
|
||||||
SleepTimer timer)
|
|
||||||
{
|
|
||||||
this.trustStore = trustStore;
|
|
||||||
this.credentialsProvider = credentialsProvider;
|
|
||||||
this.userAgent = userAgent;
|
|
||||||
this.listener = listener;
|
|
||||||
this.sleepTimer = timer;
|
|
||||||
this.attempts = 0;
|
|
||||||
this.connected = false;
|
|
||||||
|
|
||||||
String uri = httpUri.replace("https://", "wss://").replace("http://", "ws://");
|
|
||||||
|
|
||||||
if (credentialsProvider.isPresent()) this.wsUri = uri + "/v1/websocket/?login=%s&password=%s";
|
|
||||||
else this.wsUri = uri + "/v1/websocket/";
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized void connect() {
|
|
||||||
Log.w(TAG, "WSC connect()...");
|
|
||||||
|
|
||||||
if (client == null) {
|
|
||||||
String filledUri;
|
|
||||||
|
|
||||||
if (credentialsProvider.isPresent()) {
|
|
||||||
filledUri = String.format(wsUri, credentialsProvider.get().getUser(), credentialsProvider.get().getPassword());
|
|
||||||
} else {
|
|
||||||
filledUri = wsUri;
|
|
||||||
}
|
|
||||||
|
|
||||||
Pair<SSLSocketFactory, X509TrustManager> socketFactory = createTlsSocketFactory(trustStore);
|
|
||||||
|
|
||||||
OkHttpClient okHttpClient = new OkHttpClient.Builder()
|
|
||||||
.sslSocketFactory(new Tls12SocketFactory(socketFactory.first()), socketFactory.second())
|
|
||||||
.connectionSpecs(Util.immutableList(ConnectionSpec.RESTRICTED_TLS))
|
|
||||||
.readTimeout(KEEPALIVE_TIMEOUT_SECONDS + 10, TimeUnit.SECONDS)
|
|
||||||
.connectTimeout(KEEPALIVE_TIMEOUT_SECONDS + 10, TimeUnit.SECONDS)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
Request.Builder requestBuilder = new Request.Builder().url(filledUri);
|
|
||||||
|
|
||||||
if (userAgent != null) {
|
|
||||||
requestBuilder.addHeader("X-Signal-Agent", userAgent);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (listener != null) {
|
|
||||||
listener.onConnecting();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.connected = false;
|
|
||||||
this.client = okHttpClient.newWebSocket(requestBuilder.build(), this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized void disconnect() {
|
|
||||||
Log.w(TAG, "WSC disconnect()...");
|
|
||||||
|
|
||||||
if (client != null) {
|
|
||||||
client.close(1000, "OK");
|
|
||||||
client = null;
|
|
||||||
connected = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (keepAliveSender != null) {
|
|
||||||
keepAliveSender.shutdown();
|
|
||||||
keepAliveSender = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized WebSocketRequestMessage readRequest(long timeoutMillis)
|
|
||||||
throws TimeoutException, IOException
|
|
||||||
{
|
|
||||||
if (client == null) {
|
|
||||||
throw new IOException("Connection closed!");
|
|
||||||
}
|
|
||||||
|
|
||||||
long startTime = System.currentTimeMillis();
|
|
||||||
|
|
||||||
while (client != null && incomingRequests.isEmpty() && elapsedTime(startTime) < timeoutMillis) {
|
|
||||||
Util.wait(this, Math.max(1, timeoutMillis - elapsedTime(startTime)));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (incomingRequests.isEmpty() && client == null) throw new IOException("Connection closed!");
|
|
||||||
else if (incomingRequests.isEmpty()) throw new TimeoutException("Timeout exceeded");
|
|
||||||
else return incomingRequests.removeFirst();
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized Future<Pair<Integer, String>> sendRequest(WebSocketRequestMessage request) throws IOException {
|
|
||||||
if (client == null || !connected) throw new IOException("No connection!");
|
|
||||||
|
|
||||||
WebSocketMessage message = WebSocketMessage.newBuilder()
|
|
||||||
.setType(WebSocketMessage.Type.REQUEST)
|
|
||||||
.setRequest(request)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
SettableFuture<Pair<Integer, String>> future = new SettableFuture<Pair<Integer, String>>();
|
|
||||||
outgoingRequests.put(request.getId(), future);
|
|
||||||
|
|
||||||
if (!client.send(ByteString.of(message.toByteArray()))) {
|
|
||||||
throw new IOException("Write failed!");
|
|
||||||
}
|
|
||||||
|
|
||||||
return future;
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized void sendResponse(WebSocketResponseMessage response) throws IOException {
|
|
||||||
if (client == null) {
|
|
||||||
throw new IOException("Connection closed!");
|
|
||||||
}
|
|
||||||
|
|
||||||
WebSocketMessage message = WebSocketMessage.newBuilder()
|
|
||||||
.setType(WebSocketMessage.Type.RESPONSE)
|
|
||||||
.setResponse(response)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
if (!client.send(ByteString.of(message.toByteArray()))) {
|
|
||||||
throw new IOException("Write failed!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private synchronized void sendKeepAlive() throws IOException {
|
|
||||||
if (keepAliveSender != null && client != null) {
|
|
||||||
byte[] message = WebSocketMessage.newBuilder()
|
|
||||||
.setType(WebSocketMessage.Type.REQUEST)
|
|
||||||
.setRequest(WebSocketRequestMessage.newBuilder()
|
|
||||||
.setId(System.currentTimeMillis())
|
|
||||||
.setPath("/v1/keepalive")
|
|
||||||
.setVerb("GET")
|
|
||||||
.build()).build()
|
|
||||||
.toByteArray();
|
|
||||||
|
|
||||||
if (!client.send(ByteString.of(message))) {
|
|
||||||
throw new IOException("Write failed!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized void onOpen(WebSocket webSocket, Response response) {
|
|
||||||
if (client != null && keepAliveSender == null) {
|
|
||||||
Log.w(TAG, "onConnected()");
|
|
||||||
attempts = 0;
|
|
||||||
connected = true;
|
|
||||||
keepAliveSender = new KeepAliveSender();
|
|
||||||
keepAliveSender.start();
|
|
||||||
|
|
||||||
if (listener != null) listener.onConnected();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized void onMessage(WebSocket webSocket, ByteString payload) {
|
|
||||||
Log.w(TAG, "WSC onMessage()");
|
|
||||||
try {
|
|
||||||
WebSocketMessage message = WebSocketMessage.parseFrom(payload.toByteArray());
|
|
||||||
|
|
||||||
Log.w(TAG, "Message Type: " + message.getType().getNumber());
|
|
||||||
|
|
||||||
if (message.getType().getNumber() == WebSocketMessage.Type.REQUEST_VALUE) {
|
|
||||||
incomingRequests.add(message.getRequest());
|
|
||||||
} else if (message.getType().getNumber() == WebSocketMessage.Type.RESPONSE_VALUE) {
|
|
||||||
SettableFuture<Pair<Integer, String>> listener = outgoingRequests.get(message.getResponse().getId());
|
|
||||||
if (listener != null) listener.set(new Pair<Integer, String>(message.getResponse().getStatus(),
|
|
||||||
new String(message.getResponse().getBody().toByteArray())));
|
|
||||||
}
|
|
||||||
|
|
||||||
notifyAll();
|
|
||||||
} catch (InvalidProtocolBufferException e) {
|
|
||||||
Log.w(TAG, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized void onClosed(WebSocket webSocket, int code, String reason) {
|
|
||||||
Log.w(TAG, "onClose()...");
|
|
||||||
this.connected = false;
|
|
||||||
|
|
||||||
Iterator<Map.Entry<Long, SettableFuture<Pair<Integer, String>>>> iterator = outgoingRequests.entrySet().iterator();
|
|
||||||
|
|
||||||
while (iterator.hasNext()) {
|
|
||||||
Map.Entry<Long, SettableFuture<Pair<Integer, String>>> entry = iterator.next();
|
|
||||||
entry.getValue().setException(new IOException("Closed: " + code + ", " + reason));
|
|
||||||
iterator.remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (keepAliveSender != null) {
|
|
||||||
keepAliveSender.shutdown();
|
|
||||||
keepAliveSender = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (listener != null) {
|
|
||||||
listener.onDisconnected();
|
|
||||||
}
|
|
||||||
|
|
||||||
Util.wait(this, Math.min(++attempts * 200, TimeUnit.SECONDS.toMillis(15)));
|
|
||||||
|
|
||||||
if (client != null) {
|
|
||||||
client.close(1000, "OK");
|
|
||||||
client = null;
|
|
||||||
connected = false;
|
|
||||||
connect();
|
|
||||||
}
|
|
||||||
|
|
||||||
notifyAll();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized void onFailure(WebSocket webSocket, Throwable t, Response response) {
|
|
||||||
Log.w(TAG, "onFailure()");
|
|
||||||
Log.w(TAG, t);
|
|
||||||
|
|
||||||
if (response != null && (response.code() == 401 || response.code() == 403)) {
|
|
||||||
if (listener != null) listener.onAuthenticationFailure();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (client != null) {
|
|
||||||
onClosed(webSocket, 1000, "OK");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onMessage(WebSocket webSocket, String text) {
|
|
||||||
Log.w(TAG, "onMessage(text)! " + text);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized void onClosing(WebSocket webSocket, int code, String reason) {
|
|
||||||
Log.w(TAG, "onClosing()!...");
|
|
||||||
webSocket.close(1000, "OK");
|
|
||||||
}
|
|
||||||
|
|
||||||
private long elapsedTime(long startTime) {
|
|
||||||
return System.currentTimeMillis() - startTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Pair<SSLSocketFactory, X509TrustManager> createTlsSocketFactory(TrustStore trustStore) {
|
|
||||||
try {
|
|
||||||
SSLContext context = SSLContext.getInstance("TLS");
|
|
||||||
TrustManager[] trustManagers = BlacklistingTrustManager.createFor(trustStore);
|
|
||||||
context.init(null, trustManagers, null);
|
|
||||||
|
|
||||||
return new Pair<SSLSocketFactory, X509TrustManager>(context.getSocketFactory(), (X509TrustManager)trustManagers[0]);
|
|
||||||
} catch (NoSuchAlgorithmException e) {
|
|
||||||
throw new AssertionError(e);
|
|
||||||
} catch (KeyManagementException e) {
|
|
||||||
throw new AssertionError(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class KeepAliveSender extends Thread {
|
|
||||||
|
|
||||||
private AtomicBoolean stop = new AtomicBoolean(false);
|
|
||||||
|
|
||||||
public void run() {
|
|
||||||
while (!stop.get()) {
|
|
||||||
try {
|
|
||||||
sleepTimer.sleep(TimeUnit.SECONDS.toMillis(KEEPALIVE_TIMEOUT_SECONDS));
|
|
||||||
|
|
||||||
Log.w(TAG, "Sending keep alive...");
|
|
||||||
sendKeepAlive();
|
|
||||||
} catch (Throwable e) {
|
|
||||||
Log.w(TAG, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void shutdown() {
|
|
||||||
stop.set(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,9 +0,0 @@
|
|||||||
package org.session.libsignal.service.internal.websocket;
|
|
||||||
|
|
||||||
public interface WebSocketEventListener {
|
|
||||||
|
|
||||||
public void onMessage(byte[] payload);
|
|
||||||
public void onClose();
|
|
||||||
public void onConnected();
|
|
||||||
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user