mirror of
https://github.com/oxen-io/session-android.git
synced 2025-10-27 12:39:04 +00:00
Basic support for encrypted push-based attachments.
1) Move the attachment structures into the encrypted message body.
2) Encrypt attachments with symmetric keys transmitted in the
encryptd attachment pointer structure.
3) Correctly handle asynchronous decryption and categorization of
encrypted push messages.
TODO: Correct notification process and network/interruption
retries.
This commit is contained in:
@@ -0,0 +1,156 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecure.crypto;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import org.whispersystems.textsecure.util.Hex;
|
||||
import org.whispersystems.textsecure.util.Util;
|
||||
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Encrypts push attachments.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*/
|
||||
public class AttachmentCipher {
|
||||
|
||||
static final int CIPHER_KEY_SIZE = 32;
|
||||
static final int MAC_KEY_SIZE = 20;
|
||||
|
||||
private final SecretKeySpec cipherKey;
|
||||
private final SecretKeySpec macKey;
|
||||
private final Cipher cipher;
|
||||
private final Mac mac;
|
||||
|
||||
public AttachmentCipher() {
|
||||
this.cipherKey = initializeRandomCipherKey();
|
||||
this.macKey = initializeRandomMacKey();
|
||||
this.cipher = initializeCipher();
|
||||
this.mac = initializeMac();
|
||||
}
|
||||
|
||||
public AttachmentCipher(byte[] combinedKeyMaterial) {
|
||||
byte[][] parts = Util.split(combinedKeyMaterial, CIPHER_KEY_SIZE, MAC_KEY_SIZE);
|
||||
this.cipherKey = new SecretKeySpec(parts[0], "AES");
|
||||
this.macKey = new SecretKeySpec(parts[1], "HmacSHA1");
|
||||
this.cipher = initializeCipher();
|
||||
this.mac = initializeMac();
|
||||
}
|
||||
|
||||
public byte[] getCombinedKeyMaterial() {
|
||||
return Util.combine(this.cipherKey.getEncoded(), this.macKey.getEncoded());
|
||||
}
|
||||
|
||||
public byte[] encrypt(byte[] plaintext) {
|
||||
try {
|
||||
this.cipher.init(Cipher.ENCRYPT_MODE, this.cipherKey);
|
||||
this.mac.init(this.macKey);
|
||||
|
||||
byte[] ciphertext = this.cipher.doFinal(plaintext);
|
||||
byte[] iv = this.cipher.getIV();
|
||||
byte[] mac = this.mac.doFinal(Util.combine(iv, ciphertext));
|
||||
|
||||
return Util.combine(iv, ciphertext, mac);
|
||||
} catch (IllegalBlockSizeException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (BadPaddingException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] decrypt(byte[] ciphertext)
|
||||
throws InvalidMacException, InvalidMessageException
|
||||
{
|
||||
try {
|
||||
if (ciphertext.length <= cipher.getBlockSize() + mac.getMacLength()) {
|
||||
throw new InvalidMessageException("Message too short!");
|
||||
}
|
||||
|
||||
byte[][] ciphertextParts = Util.split(ciphertext,
|
||||
this.cipher.getBlockSize(),
|
||||
ciphertext.length - this.cipher.getBlockSize() - this.mac.getMacLength(),
|
||||
this.mac.getMacLength());
|
||||
|
||||
this.mac.update(ciphertext, 0, ciphertext.length - mac.getMacLength());
|
||||
byte[] ourMac = this.mac.doFinal();
|
||||
|
||||
if (!Arrays.equals(ourMac, ciphertextParts[2])) {
|
||||
throw new InvalidMacException("Mac doesn't match!");
|
||||
}
|
||||
|
||||
this.cipher.init(Cipher.DECRYPT_MODE, this.cipherKey,
|
||||
new IvParameterSpec(ciphertextParts[0]));
|
||||
|
||||
return cipher.doFinal(ciphertextParts[1]);
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (InvalidAlgorithmParameterException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (IllegalBlockSizeException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (BadPaddingException e) {
|
||||
throw new InvalidMessageException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private Mac initializeMac() {
|
||||
try {
|
||||
Mac mac = Mac.getInstance("HmacSHA1");
|
||||
return mac;
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private Cipher initializeCipher() {
|
||||
try {
|
||||
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
||||
return cipher;
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (NoSuchPaddingException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private SecretKeySpec initializeRandomCipherKey() {
|
||||
byte[] key = new byte[CIPHER_KEY_SIZE];
|
||||
Util.getSecureRandom().nextBytes(key);
|
||||
return new SecretKeySpec(key, "AES");
|
||||
}
|
||||
|
||||
private SecretKeySpec initializeRandomMacKey() {
|
||||
byte[] key = new byte[MAC_KEY_SIZE];
|
||||
Util.getSecureRandom().nextBytes(key);
|
||||
return new SecretKeySpec(key, "HmacSHA1");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecure.crypto;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import org.whispersystems.textsecure.util.Hex;
|
||||
import org.whispersystems.textsecure.util.Util;
|
||||
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import javax.crypto.ShortBufferException;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Class for streaming an encrypted push attachment off disk.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*/
|
||||
|
||||
public class AttachmentCipherInputStream extends FileInputStream {
|
||||
|
||||
private static final int BLOCK_SIZE = 16;
|
||||
|
||||
private Cipher cipher;
|
||||
private boolean done;
|
||||
private long totalDataSize;
|
||||
private long totalRead;
|
||||
|
||||
public AttachmentCipherInputStream(File file, byte[] combinedKeyMaterial)
|
||||
throws IOException, InvalidMessageException
|
||||
{
|
||||
super(file);
|
||||
|
||||
try {
|
||||
byte[][] parts = Util.split(combinedKeyMaterial,
|
||||
AttachmentCipher.CIPHER_KEY_SIZE,
|
||||
AttachmentCipher.MAC_KEY_SIZE);
|
||||
|
||||
Mac mac = Mac.getInstance("HmacSHA1");
|
||||
mac.init(new SecretKeySpec(parts[1], "HmacSHA1"));
|
||||
|
||||
if (file.length() <= BLOCK_SIZE + mac.getMacLength()) {
|
||||
throw new InvalidMessageException("Message shorter than crypto overhead!");
|
||||
}
|
||||
|
||||
verifyMac(file, mac);
|
||||
|
||||
byte[] iv = new byte[BLOCK_SIZE];
|
||||
readFully(iv);
|
||||
|
||||
this.cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
||||
this.cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(parts[0], "AES"), new IvParameterSpec(iv));
|
||||
|
||||
this.done = false;
|
||||
this.totalRead = 0;
|
||||
this.totalDataSize = file.length() - cipher.getBlockSize() - mac.getMacLength();
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (InvalidMacException e) {
|
||||
throw new InvalidMessageException(e);
|
||||
} catch (NoSuchPaddingException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (InvalidAlgorithmParameterException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] buffer) throws IOException {
|
||||
return read(buffer, 0, buffer.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] buffer, int offset, int length) throws IOException {
|
||||
if (totalRead != totalDataSize) return readIncremental(buffer, offset, length);
|
||||
else if (!done) return readFinal(buffer, offset, length);
|
||||
else return -1;
|
||||
}
|
||||
|
||||
private int readFinal(byte[] buffer, int offset, int length) throws IOException {
|
||||
try {
|
||||
int flourish = cipher.doFinal(buffer, offset);
|
||||
|
||||
done = true;
|
||||
return flourish;
|
||||
} catch (IllegalBlockSizeException e) {
|
||||
Log.w("EncryptingPartInputStream", e);
|
||||
throw new IOException("Illegal block size exception!");
|
||||
} catch (ShortBufferException e) {
|
||||
Log.w("EncryptingPartInputStream", e);
|
||||
throw new IOException("Short buffer exception!");
|
||||
} catch (BadPaddingException e) {
|
||||
Log.w("EncryptingPartInputStream", e);
|
||||
throw new IOException("Bad padding exception!");
|
||||
}
|
||||
}
|
||||
|
||||
private int readIncremental(byte[] buffer, int offset, int length) throws IOException {
|
||||
if (length + totalRead > totalDataSize)
|
||||
length = (int)(totalDataSize - totalRead);
|
||||
|
||||
byte[] internalBuffer = new byte[length];
|
||||
int read = super.read(internalBuffer, 0, internalBuffer.length <= cipher.getBlockSize() ? internalBuffer.length : internalBuffer.length - cipher.getBlockSize());
|
||||
totalRead += read;
|
||||
|
||||
try {
|
||||
return cipher.update(internalBuffer, 0, read, buffer, offset);
|
||||
} catch (ShortBufferException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void verifyMac(File file, Mac mac) throws FileNotFoundException, InvalidMacException {
|
||||
try {
|
||||
FileInputStream fin = new FileInputStream(file);
|
||||
int remainingData = (int) file.length() - mac.getMacLength();
|
||||
byte[] buffer = new byte[4096];
|
||||
|
||||
while (remainingData > 0) {
|
||||
int read = fin.read(buffer, 0, Math.min(buffer.length, remainingData));
|
||||
mac.update(buffer, 0, read);
|
||||
remainingData -= read;
|
||||
}
|
||||
|
||||
byte[] ourMac = mac.doFinal();
|
||||
byte[] theirMac = new byte[mac.getMacLength()];
|
||||
Util.readFully(fin, theirMac);
|
||||
|
||||
if (!Arrays.equals(ourMac, theirMac)) {
|
||||
throw new InvalidMacException("MAC doesn't match!");
|
||||
}
|
||||
} catch (IOException e1) {
|
||||
throw new InvalidMacException(e1);
|
||||
}
|
||||
}
|
||||
|
||||
private void readFully(byte[] buffer) throws IOException {
|
||||
int offset = 0;
|
||||
|
||||
for (;;) {
|
||||
int read = super.read(buffer, offset, buffer.length - offset);
|
||||
|
||||
if (read + offset < buffer.length) offset += read;
|
||||
else return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,12 +1,26 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecure.push;
|
||||
|
||||
import org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal;
|
||||
import org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal.AttachmentPointer;
|
||||
import org.whispersystems.textsecure.util.Base64;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import org.whispersystems.textsecure.push.PushMessageProtos.IncomingPushMessageSignal;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -24,12 +38,20 @@ public class IncomingPushMessage implements PushMessage, Parcelable {
|
||||
}
|
||||
};
|
||||
|
||||
private int type;
|
||||
private String source;
|
||||
private List<String> destinations;
|
||||
private byte[] message;
|
||||
private List<PushAttachmentPointer> attachments;
|
||||
private long timestamp;
|
||||
private int type;
|
||||
private String source;
|
||||
private List<String> destinations;
|
||||
private byte[] message;
|
||||
private long timestamp;
|
||||
|
||||
private IncomingPushMessage(IncomingPushMessage message, byte[] body) {
|
||||
this.type = message.type;
|
||||
this.source = message.source;
|
||||
this.destinations = new LinkedList<String>();
|
||||
this.destinations.addAll(message.destinations);
|
||||
this.message = body;
|
||||
this.timestamp = message.timestamp;
|
||||
}
|
||||
|
||||
public IncomingPushMessage(IncomingPushMessageSignal signal) {
|
||||
this.type = signal.getType();
|
||||
@@ -37,25 +59,15 @@ public class IncomingPushMessage implements PushMessage, Parcelable {
|
||||
this.destinations = signal.getDestinationsList();
|
||||
this.message = signal.getMessage().toByteArray();
|
||||
this.timestamp = signal.getTimestamp();
|
||||
this.attachments = new LinkedList<PushAttachmentPointer>();
|
||||
|
||||
List<AttachmentPointer> attachmentPointers = signal.getAttachmentsList();
|
||||
|
||||
for (AttachmentPointer pointer : attachmentPointers) {
|
||||
this.attachments.add(new PushAttachmentPointer(pointer.getContentType(), pointer.getKey()));
|
||||
}
|
||||
}
|
||||
|
||||
public IncomingPushMessage(Parcel in) {
|
||||
this.destinations = new LinkedList<String>();
|
||||
this.attachments = new LinkedList<PushAttachmentPointer>();
|
||||
|
||||
this.type = in.readInt();
|
||||
this.source = in.readString();
|
||||
in.readStringList(destinations);
|
||||
this.message = new byte[in.readInt()];
|
||||
in.readByteArray(this.message);
|
||||
in.readList(attachments, PushAttachmentPointer.class.getClassLoader());
|
||||
this.timestamp = in.readLong();
|
||||
}
|
||||
|
||||
@@ -67,10 +79,6 @@ public class IncomingPushMessage implements PushMessage, Parcelable {
|
||||
return source;
|
||||
}
|
||||
|
||||
public List<PushAttachmentPointer> getAttachments() {
|
||||
return attachments;
|
||||
}
|
||||
|
||||
public byte[] getBody() {
|
||||
return message;
|
||||
}
|
||||
@@ -79,10 +87,6 @@ public class IncomingPushMessage implements PushMessage, Parcelable {
|
||||
return destinations;
|
||||
}
|
||||
|
||||
public boolean hasAttachments() {
|
||||
return getAttachments() != null && !getAttachments().isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
@@ -95,11 +99,22 @@ public class IncomingPushMessage implements PushMessage, Parcelable {
|
||||
dest.writeStringList(destinations);
|
||||
dest.writeInt(message.length);
|
||||
dest.writeByteArray(message);
|
||||
dest.writeList(attachments);
|
||||
dest.writeLong(timestamp);
|
||||
}
|
||||
|
||||
public IncomingPushMessage withBody(byte[] body) {
|
||||
return new IncomingPushMessage(this, body);
|
||||
}
|
||||
|
||||
public int getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public boolean isSecureMessage() {
|
||||
return getType() == PushMessage.TYPE_MESSAGE_CIPHERTEXT;
|
||||
}
|
||||
|
||||
public boolean isPreKeyBundle() {
|
||||
return getType() == PushMessage.TYPE_MESSAGE_PREKEY_BUNDLE;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,34 +1,35 @@
|
||||
/**
|
||||
* Copyright (C) 2013 Open Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecure.push;
|
||||
|
||||
import org.whispersystems.textsecure.util.Base64;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
public class OutgoingPushMessage implements PushMessage {
|
||||
|
||||
private int type;
|
||||
private String destination;
|
||||
private String body;
|
||||
private List<PushAttachmentPointer> attachments;
|
||||
private int type;
|
||||
private String destination;
|
||||
private String body;
|
||||
|
||||
public OutgoingPushMessage(String destination, byte[] body, int type) {
|
||||
this.attachments = new LinkedList<PushAttachmentPointer>();
|
||||
this.destination = destination;
|
||||
this.body = Base64.encodeBytes(body);
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public OutgoingPushMessage(String destination, byte[] body,
|
||||
List<PushAttachmentPointer> attachments,
|
||||
int type)
|
||||
{
|
||||
this.destination = destination;
|
||||
this.body = Base64.encodeBytes(body);
|
||||
this.attachments = attachments;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public String getDestination() {
|
||||
return destination;
|
||||
}
|
||||
@@ -37,10 +38,6 @@ public class OutgoingPushMessage implements PushMessage {
|
||||
return body;
|
||||
}
|
||||
|
||||
public List<PushAttachmentPointer> getAttachments() {
|
||||
return attachments;
|
||||
}
|
||||
|
||||
public int getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
@@ -18,23 +18,33 @@ public class PushAttachmentPointer implements Parcelable {
|
||||
};
|
||||
|
||||
private final String contentType;
|
||||
private final String key;
|
||||
private final long id;
|
||||
private final byte[] key;
|
||||
|
||||
public PushAttachmentPointer(String contentType, String key) {
|
||||
public PushAttachmentPointer(String contentType, long id, byte[] key) {
|
||||
this.contentType = contentType;
|
||||
this.id = id;
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
public PushAttachmentPointer(Parcel in) {
|
||||
this.contentType = in.readString();
|
||||
this.key = in.readString();
|
||||
this.id = in.readLong();
|
||||
|
||||
int keyLength = in.readInt();
|
||||
this.key = new byte[keyLength];
|
||||
in.readByteArray(this.key);
|
||||
}
|
||||
|
||||
public String getContentType() {
|
||||
return contentType;
|
||||
}
|
||||
|
||||
public String getKey() {
|
||||
public long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public byte[] getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
@@ -46,6 +56,8 @@ public class PushAttachmentPointer implements Parcelable {
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeString(contentType);
|
||||
dest.writeString(key);
|
||||
dest.writeLong(id);
|
||||
dest.writeInt(this.key.length);
|
||||
dest.writeByteArray(this.key);
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -84,31 +84,21 @@ public class PushServiceSocket {
|
||||
sendMessage(new OutgoingPushMessageList(message));
|
||||
}
|
||||
|
||||
public void sendMessage(List<String> recipients, List<byte[]> bodies,
|
||||
List<List<PushAttachmentData>> attachmentsList, int type)
|
||||
public void sendMessage(List<String> recipients, List<byte[]> bodies, List<Integer> types)
|
||||
throws IOException
|
||||
{
|
||||
List<OutgoingPushMessage> messages = new LinkedList<OutgoingPushMessage>();
|
||||
|
||||
Iterator<String> recipientsIterator = recipients.iterator();
|
||||
Iterator<byte[]> bodiesIterator = bodies.iterator();
|
||||
Iterator<List<PushAttachmentData>> attachmentsIterator = attachmentsList.iterator();
|
||||
Iterator<String> recipientsIterator = recipients.iterator();
|
||||
Iterator<byte[]> bodiesIterator = bodies.iterator();
|
||||
Iterator<Integer> typesIterator = types.iterator();
|
||||
|
||||
while (recipientsIterator.hasNext()) {
|
||||
String recipient = recipientsIterator.next();
|
||||
byte[] body = bodiesIterator.next();
|
||||
List<PushAttachmentData> attachments = attachmentsIterator.next();
|
||||
String recipient = recipientsIterator.next();
|
||||
byte[] body = bodiesIterator.next();
|
||||
int type = typesIterator.next();
|
||||
|
||||
OutgoingPushMessage message;
|
||||
|
||||
if (!attachments.isEmpty()) {
|
||||
List<PushAttachmentPointer> attachmentIds = sendAttachments(attachments);
|
||||
message = new OutgoingPushMessage(recipient, body, attachmentIds, type);
|
||||
} else {
|
||||
message = new OutgoingPushMessage(recipient, body, type);
|
||||
}
|
||||
|
||||
messages.add(message);
|
||||
messages.add(new OutgoingPushMessage(recipient, body, type));
|
||||
}
|
||||
|
||||
sendMessage(new OutgoingPushMessageList(messages));
|
||||
@@ -149,20 +139,7 @@ public class PushServiceSocket {
|
||||
return PreKeyEntity.fromJson(responseText);
|
||||
}
|
||||
|
||||
private List<PushAttachmentPointer> sendAttachments(List<PushAttachmentData> attachments)
|
||||
throws IOException
|
||||
{
|
||||
List<PushAttachmentPointer> attachmentIds = new LinkedList<PushAttachmentPointer>();
|
||||
|
||||
for (PushAttachmentData attachment : attachments) {
|
||||
attachmentIds.add(new PushAttachmentPointer(attachment.getContentType(),
|
||||
sendAttachment(attachment)));
|
||||
}
|
||||
|
||||
return attachmentIds;
|
||||
}
|
||||
|
||||
private String sendAttachment(PushAttachmentData attachment) throws IOException {
|
||||
public long sendAttachment(PushAttachmentData attachment) throws IOException {
|
||||
Pair<String, String> response = makeRequestForResponseHeader(String.format(ATTACHMENT_PATH, ""),
|
||||
"GET", null, "Content-Location");
|
||||
|
||||
@@ -178,25 +155,18 @@ public class PushServiceSocket {
|
||||
return new Gson().fromJson(response.second, AttachmentKey.class).getId();
|
||||
}
|
||||
|
||||
public List<Pair<File,String>> retrieveAttachments(List<PushAttachmentPointer> attachmentIds)
|
||||
throws IOException
|
||||
{
|
||||
List<Pair<File,String>> attachments = new LinkedList<Pair<File,String>>();
|
||||
public File retrieveAttachment(long attachmentId) throws IOException {
|
||||
Pair<String, String> response = makeRequestForResponseHeader(String.format(ATTACHMENT_PATH, String.valueOf(attachmentId)),
|
||||
"GET", null, "Content-Location");
|
||||
|
||||
for (PushAttachmentPointer attachmentId : attachmentIds) {
|
||||
Pair<String, String> response = makeRequestForResponseHeader(String.format(ATTACHMENT_PATH, attachmentId.getKey()),
|
||||
"GET", null, "Content-Location");
|
||||
Log.w("PushServiceSocket", "Attachment: " + attachmentId + " is at: " + response.first);
|
||||
|
||||
Log.w("PushServiceSocket", "Attachment: " + attachmentId.getKey() + " is at: " + response.first);
|
||||
File attachment = File.createTempFile("attachment", ".tmp", context.getFilesDir());
|
||||
attachment.deleteOnExit();
|
||||
|
||||
File attachment = File.createTempFile("attachment", ".tmp", context.getFilesDir());
|
||||
attachment.deleteOnExit();
|
||||
downloadExternalFile(response.first, attachment);
|
||||
|
||||
downloadExternalFile(response.first, attachment);
|
||||
attachments.add(new Pair<File, String>(attachment, attachmentId.getContentType()));
|
||||
}
|
||||
|
||||
return attachments;
|
||||
return attachment;
|
||||
}
|
||||
|
||||
public Pair<DirectoryDescriptor, File> retrieveDirectory() {
|
||||
@@ -394,13 +364,13 @@ public class PushServiceSocket {
|
||||
}
|
||||
|
||||
private static class AttachmentKey {
|
||||
private String id;
|
||||
private long id;
|
||||
|
||||
public AttachmentKey(String id) {
|
||||
public AttachmentKey(long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
public long getId() {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,6 +45,33 @@ public class Util {
|
||||
|
||||
}
|
||||
|
||||
public static byte[][] split(byte[] input, int firstLength, int secondLength) {
|
||||
byte[][] parts = new byte[2][];
|
||||
|
||||
parts[0] = new byte[firstLength];
|
||||
System.arraycopy(input, 0, parts[0], 0, firstLength);
|
||||
|
||||
parts[1] = new byte[secondLength];
|
||||
System.arraycopy(input, firstLength, parts[1], 0, secondLength);
|
||||
|
||||
return parts;
|
||||
}
|
||||
|
||||
public static byte[][] split(byte[] input, int firstLength, int secondLength, int thirdLength) {
|
||||
byte[][] parts = new byte[3][];
|
||||
|
||||
parts[0] = new byte[firstLength];
|
||||
System.arraycopy(input, 0, parts[0], 0, firstLength);
|
||||
|
||||
parts[1] = new byte[secondLength];
|
||||
System.arraycopy(input, firstLength, parts[1], 0, secondLength);
|
||||
|
||||
parts[2] = new byte[thirdLength];
|
||||
System.arraycopy(input, firstLength + secondLength, parts[2], 0, thirdLength);
|
||||
|
||||
return parts;
|
||||
}
|
||||
|
||||
public static boolean isEmpty(String value) {
|
||||
return value == null || value.trim().length() == 0;
|
||||
}
|
||||
@@ -94,6 +121,18 @@ public class Util {
|
||||
return new String(bout.toByteArray());
|
||||
}
|
||||
|
||||
public static void readFully(InputStream in, byte[] buffer) throws IOException {
|
||||
int offset = 0;
|
||||
|
||||
for (;;) {
|
||||
int read = in.read(buffer, offset, buffer.length - offset);
|
||||
|
||||
if (read + offset < buffer.length) offset += read;
|
||||
else return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static void copy(InputStream in, OutputStream out) throws IOException {
|
||||
byte[] buffer = new byte[4096];
|
||||
int read;
|
||||
|
||||
Reference in New Issue
Block a user