mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-23 18:15:22 +00:00
parent
99d3374d35
commit
92593d459b
@ -15,7 +15,6 @@ import org.thoughtcrime.securesms.mms.CompatMmsConnection;
|
||||
import org.thoughtcrime.securesms.mms.IncomingMediaMessage;
|
||||
import org.thoughtcrime.securesms.mms.MmsRadioException;
|
||||
import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
||||
import org.thoughtcrime.securesms.protocol.WirePrefix;
|
||||
import org.thoughtcrime.securesms.service.KeyCachingService;
|
||||
import org.whispersystems.jobqueue.JobParameters;
|
||||
import org.whispersystems.jobqueue.requirements.NetworkRequirement;
|
||||
@ -144,16 +143,9 @@ public class MmsDownloadJob extends MasterSecretJob {
|
||||
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
|
||||
IncomingMediaMessage message = new IncomingMediaMessage(retrieved);
|
||||
|
||||
Pair<Long, Long> messageAndThreadId;
|
||||
|
||||
if (retrieved.getSubject() != null && WirePrefix.isEncryptedMmsSubject(retrieved.getSubject().getString())) {
|
||||
database.markAsLegacyVersion(messageId, threadId);
|
||||
messageAndThreadId = new Pair<>(messageId, threadId);
|
||||
} else {
|
||||
messageAndThreadId = database.insertMessageInbox(new MasterSecretUnion(masterSecret),
|
||||
message, contentLocation, threadId);
|
||||
database.delete(messageId);
|
||||
}
|
||||
Pair<Long, Long> messageAndThreadId = database.insertMessageInbox(new MasterSecretUnion(masterSecret),
|
||||
message, contentLocation, threadId);
|
||||
database.delete(messageId);
|
||||
|
||||
MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second);
|
||||
}
|
||||
|
@ -11,11 +11,9 @@ import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.EncryptingSmsDatabase;
|
||||
import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
||||
import org.thoughtcrime.securesms.protocol.WirePrefix;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientFactory;
|
||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
||||
import org.thoughtcrime.securesms.service.KeyCachingService;
|
||||
import org.thoughtcrime.securesms.sms.IncomingEncryptedMessage;
|
||||
import org.thoughtcrime.securesms.sms.IncomingTextMessage;
|
||||
import org.whispersystems.jobqueue.JobParameters;
|
||||
import org.whispersystems.libaxolotl.util.guava.Optional;
|
||||
@ -108,16 +106,6 @@ public class SmsReceiveJob extends ContextJob {
|
||||
return Optional.absent();
|
||||
}
|
||||
|
||||
IncomingTextMessage message = new IncomingTextMessage(messages);
|
||||
|
||||
if (WirePrefix.isEncryptedMessage(message.getMessageBody()) ||
|
||||
WirePrefix.isKeyExchange(message.getMessageBody()) ||
|
||||
WirePrefix.isPreKeyBundle(message.getMessageBody()) ||
|
||||
WirePrefix.isEndSession(message.getMessageBody()))
|
||||
{
|
||||
return Optional.<IncomingTextMessage>of(new IncomingEncryptedMessage(message, message.getMessageBody()));
|
||||
} else {
|
||||
return Optional.of(message);
|
||||
}
|
||||
return Optional.of(new IncomingTextMessage(messages));
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +0,0 @@
|
||||
package org.thoughtcrime.securesms.protocol;
|
||||
|
||||
public class EndSessionWirePrefix extends WirePrefix {
|
||||
@Override
|
||||
public String calculatePrefix(String message) {
|
||||
return super.calculateEndSessionPrefix(message);
|
||||
}
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2011 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.thoughtcrime.securesms.protocol;
|
||||
|
||||
public class KeyExchangeWirePrefix extends WirePrefix {
|
||||
|
||||
@Override
|
||||
public String calculatePrefix(String message) {
|
||||
return super.calculateKeyExchangePrefix(message);
|
||||
}
|
||||
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
package org.thoughtcrime.securesms.protocol;
|
||||
|
||||
public class PrekeyBundleWirePrefix extends WirePrefix {
|
||||
@Override
|
||||
public String calculatePrefix(String message) {
|
||||
return super.calculatePreKeyBundlePrefix(message);
|
||||
}
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2011 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.thoughtcrime.securesms.protocol;
|
||||
|
||||
public class SecureMessageWirePrefix extends WirePrefix {
|
||||
|
||||
@Override
|
||||
public String calculatePrefix(String message) {
|
||||
return super.calculateEncryptedMesagePrefix(message);
|
||||
}
|
||||
|
||||
}
|
@ -1,128 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2011 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.thoughtcrime.securesms.protocol;
|
||||
|
||||
|
||||
import org.thoughtcrime.securesms.util.Base64;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
/**
|
||||
* Calculates prefixes that identify a message as
|
||||
* being part of an encrypted session. The idea was to
|
||||
* make calculating and identifying these prefixes somewhat
|
||||
* expensive, so that filtering them en-mass would come at a cost.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*/
|
||||
|
||||
public abstract class WirePrefix {
|
||||
|
||||
private static final int HASH_ITERATIONS = 1000;
|
||||
private static final int PREFIX_BYTES = 3;
|
||||
public static final int PREFIX_SIZE = 4;
|
||||
|
||||
public abstract String calculatePrefix(String message);
|
||||
|
||||
public static boolean isKeyExchange(String message) {
|
||||
return verifyPrefix("?TSK", message);
|
||||
}
|
||||
|
||||
public static boolean isEncryptedMessage(String message) {
|
||||
return verifyPrefix("?TSM", message);
|
||||
}
|
||||
|
||||
public static boolean isPreKeyBundle(String message) {
|
||||
return verifyPrefix("?TSP", message);
|
||||
}
|
||||
|
||||
public static boolean isEndSession(String message) {
|
||||
return verifyPrefix("?TSE", message);
|
||||
}
|
||||
|
||||
public static String calculateKeyExchangePrefix(String message) {
|
||||
return calculatePrefix(("?TSK" + message).getBytes(), PREFIX_BYTES);
|
||||
}
|
||||
|
||||
public static String calculateEncryptedMesagePrefix(String message) {
|
||||
return calculatePrefix(("?TSM" + message).getBytes(), PREFIX_BYTES);
|
||||
}
|
||||
|
||||
public static String calculatePreKeyBundlePrefix(String message) {
|
||||
return calculatePrefix(("?TSP" + message).getBytes(), PREFIX_BYTES);
|
||||
}
|
||||
|
||||
public static String calculateEndSessionPrefix(String message) {
|
||||
return calculatePrefix(("?TSE" + message).getBytes(), PREFIX_BYTES);
|
||||
}
|
||||
|
||||
private static boolean verifyPrefix(String prefixType, String message) {
|
||||
if (message.length() <= PREFIX_SIZE)
|
||||
return false;
|
||||
|
||||
String prefix = message.substring(0, PREFIX_SIZE);
|
||||
message = message.substring(PREFIX_SIZE);
|
||||
|
||||
String calculatedPrefix = calculatePrefix((prefixType + message).getBytes(), PREFIX_BYTES);
|
||||
|
||||
assert(calculatedPrefix.length() == PREFIX_SIZE);
|
||||
|
||||
return prefix.equals(calculatedPrefix);
|
||||
}
|
||||
|
||||
private static String calculatePrefix(byte[] message, int byteCount) {
|
||||
try {
|
||||
MessageDigest md = MessageDigest.getInstance("SHA1");
|
||||
byte[] runningDigest = message;
|
||||
|
||||
for (int i=0;i<HASH_ITERATIONS;i++) {
|
||||
runningDigest = md.digest(runningDigest);
|
||||
}
|
||||
|
||||
return Base64.encodeBytes(runningDigest, 0, byteCount);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static String calculateEncryptedMmsSubject() {
|
||||
try {
|
||||
byte[] postfix = new byte[6];
|
||||
SecureRandom.getInstance("SHA1PRNG").nextBytes(postfix);
|
||||
|
||||
String postfixString = Base64.encodeBytes(postfix);
|
||||
String prefix = calculatePrefix(postfixString.getBytes(), 6);
|
||||
|
||||
return prefix + postfixString;
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isEncryptedMmsSubject(String subject) {
|
||||
if (subject.length() < 9)
|
||||
return false;
|
||||
|
||||
String prefix = subject.substring(0, 8);
|
||||
String postfix = subject.substring(8);
|
||||
|
||||
String calculatedPrefix = calculatePrefix(postfix.getBytes(), 6);
|
||||
return calculatedPrefix.equals(prefix);
|
||||
}
|
||||
}
|
@ -25,24 +25,14 @@ import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.ApplicationContext;
|
||||
import org.thoughtcrime.securesms.jobs.MmsReceiveJob;
|
||||
import org.thoughtcrime.securesms.protocol.WirePrefix;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
|
||||
import ws.com.google.android.mms.pdu.GenericPdu;
|
||||
import ws.com.google.android.mms.pdu.NotificationInd;
|
||||
import ws.com.google.android.mms.pdu.PduHeaders;
|
||||
import ws.com.google.android.mms.pdu.PduParser;
|
||||
|
||||
public class MmsListener extends BroadcastReceiver {
|
||||
|
||||
private static final String TAG = MmsListener.class.getSimpleName();
|
||||
|
||||
private boolean isRelevant(Context context, Intent intent) {
|
||||
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.DONUT) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ApplicationMigrationService.isDatabaseImported(context)) {
|
||||
return false;
|
||||
}
|
||||
@ -60,21 +50,7 @@ public class MmsListener extends BroadcastReceiver {
|
||||
return true;
|
||||
}
|
||||
|
||||
byte[] mmsData = intent.getByteArrayExtra("data");
|
||||
PduParser parser = new PduParser(mmsData);
|
||||
GenericPdu pdu = parser.parse();
|
||||
|
||||
if (pdu == null || pdu.getMessageType() != PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND) {
|
||||
Log.w(TAG, "Received Invalid notification PDU");
|
||||
return false;
|
||||
}
|
||||
|
||||
NotificationInd notificationPdu = (NotificationInd)pdu;
|
||||
|
||||
if (notificationPdu.getSubject() == null)
|
||||
return false;
|
||||
|
||||
return WirePrefix.isEncryptedMmsSubject(notificationPdu.getSubject().getString());
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -27,7 +27,6 @@ import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.ApplicationContext;
|
||||
import org.thoughtcrime.securesms.jobs.SmsReceiveJob;
|
||||
import org.thoughtcrime.securesms.protocol.WirePrefix;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
|
||||
@ -121,7 +120,7 @@ public class SmsListener extends BroadcastReceiver {
|
||||
return true;
|
||||
}
|
||||
|
||||
return WirePrefix.isEncryptedMessage(messageBody) || WirePrefix.isKeyExchange(messageBody);
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isChallenge(Context context, Intent intent) {
|
||||
|
@ -1,32 +0,0 @@
|
||||
package org.thoughtcrime.securesms.sms;
|
||||
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
public class MultipartSmsIdentifier {
|
||||
|
||||
private static final MultipartSmsIdentifier instance = new MultipartSmsIdentifier();
|
||||
|
||||
public static MultipartSmsIdentifier getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
private final HashMap<String, Integer> idMap = new HashMap<String, Integer>();
|
||||
|
||||
public synchronized byte getIdForRecipient(String recipient) {
|
||||
Integer currentId;
|
||||
|
||||
if (idMap.containsKey(recipient)) {
|
||||
currentId = idMap.get(recipient);
|
||||
idMap.remove(recipient);
|
||||
} else {
|
||||
currentId = 0;
|
||||
}
|
||||
|
||||
byte id = currentId.byteValue();
|
||||
idMap.put(recipient, (currentId + 1) % 255);
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
}
|
@ -1,222 +0,0 @@
|
||||
package org.thoughtcrime.securesms.sms;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.protocol.EndSessionWirePrefix;
|
||||
import org.thoughtcrime.securesms.protocol.KeyExchangeWirePrefix;
|
||||
import org.thoughtcrime.securesms.protocol.PrekeyBundleWirePrefix;
|
||||
import org.thoughtcrime.securesms.protocol.SecureMessageWirePrefix;
|
||||
import org.thoughtcrime.securesms.protocol.WirePrefix;
|
||||
import org.thoughtcrime.securesms.util.Base64;
|
||||
import org.thoughtcrime.securesms.util.Conversions;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class MultipartSmsTransportMessage {
|
||||
private static final String TAG = MultipartSmsTransportMessage.class.getName();
|
||||
|
||||
private static final int MULTIPART_SUPPORTED_AFTER_VERSION = 1;
|
||||
|
||||
public static final int SINGLE_MESSAGE_MULTIPART_OVERHEAD = 1;
|
||||
public static final int MULTI_MESSAGE_MULTIPART_OVERHEAD = 3;
|
||||
public static final int FIRST_MULTI_MESSAGE_MULTIPART_OVERHEAD = 2;
|
||||
|
||||
public static final int WIRETYPE_SECURE = 1;
|
||||
public static final int WIRETYPE_KEY = 2;
|
||||
public static final int WIRETYPE_PREKEY = 3;
|
||||
public static final int WIRETYPE_END_SESSION = 4;
|
||||
|
||||
private static final int VERSION_OFFSET = 0;
|
||||
private static final int MULTIPART_OFFSET = 1;
|
||||
private static final int IDENTIFIER_OFFSET = 2;
|
||||
|
||||
private final int wireType;
|
||||
private final byte[] decodedMessage;
|
||||
private final IncomingTextMessage message;
|
||||
|
||||
public MultipartSmsTransportMessage(IncomingTextMessage message) throws IOException {
|
||||
this.message = message;
|
||||
this.decodedMessage = Base64.decodeWithoutPadding(message.getMessageBody().substring(WirePrefix.PREFIX_SIZE));
|
||||
|
||||
if (WirePrefix.isEncryptedMessage(message.getMessageBody())) wireType = WIRETYPE_SECURE;
|
||||
else if (WirePrefix.isPreKeyBundle(message.getMessageBody())) wireType = WIRETYPE_PREKEY;
|
||||
else if (WirePrefix.isEndSession(message.getMessageBody())) wireType = WIRETYPE_END_SESSION;
|
||||
else wireType = WIRETYPE_KEY;
|
||||
|
||||
Log.w(TAG, "Decoded message with version: " + getCurrentVersion());
|
||||
}
|
||||
|
||||
public int getWireType() {
|
||||
return wireType;
|
||||
}
|
||||
|
||||
public int getCurrentVersion() {
|
||||
return Conversions.highBitsToInt(decodedMessage[VERSION_OFFSET]);
|
||||
}
|
||||
|
||||
public int getMultipartIndex() {
|
||||
return Conversions.highBitsToInt(decodedMessage[MULTIPART_OFFSET]);
|
||||
}
|
||||
|
||||
public int getMultipartCount() {
|
||||
if (isDeprecatedTransport())
|
||||
return 1;
|
||||
|
||||
return Conversions.lowBitsToInt(decodedMessage[MULTIPART_OFFSET]);
|
||||
}
|
||||
|
||||
public int getIdentifier() {
|
||||
return decodedMessage[IDENTIFIER_OFFSET] & 0xFF;
|
||||
}
|
||||
|
||||
public boolean isDeprecatedTransport() {
|
||||
return getCurrentVersion() < MULTIPART_SUPPORTED_AFTER_VERSION;
|
||||
}
|
||||
|
||||
public boolean isInvalid() {
|
||||
return getMultipartIndex() >= getMultipartCount();
|
||||
}
|
||||
|
||||
public boolean isSinglePart() {
|
||||
return getMultipartCount() == 1;
|
||||
}
|
||||
|
||||
public byte[] getStrippedMessage() {
|
||||
if (isDeprecatedTransport()) return getStrippedMessageForDeprecatedTransport();
|
||||
else if (getMultipartCount() == 1) return getStrippedMessageForSinglePart();
|
||||
else return getStrippedMessageForMultiPart();
|
||||
}
|
||||
|
||||
/*
|
||||
* We're dealing with a message that isn't using the multipart transport.
|
||||
*
|
||||
*/
|
||||
private byte[] getStrippedMessageForDeprecatedTransport() {
|
||||
return decodedMessage;
|
||||
}
|
||||
|
||||
/*
|
||||
* We're dealing with a transport message that is of the format:
|
||||
* Version (1 byte)
|
||||
* Index_And_Count (1 byte)
|
||||
* Message (remainder)
|
||||
*
|
||||
* The version byte was stolen off the message, so we strip Index_And_Count byte out,
|
||||
* put the version byte back on the front of the message, and return.
|
||||
*/
|
||||
private byte[] getStrippedMessageForSinglePart() {
|
||||
byte[] stripped = new byte[decodedMessage.length - 1];
|
||||
System.arraycopy(decodedMessage, 1, stripped, 0, decodedMessage.length - 1);
|
||||
stripped[0] = decodedMessage[VERSION_OFFSET];
|
||||
|
||||
return stripped;
|
||||
}
|
||||
|
||||
/*
|
||||
* We're dealing with a transport message that is of the format:
|
||||
*
|
||||
* Version (1 byte)
|
||||
* Index_And_Count (1 byte)
|
||||
* Identifier (1 byte)
|
||||
* Message (remainder)
|
||||
*
|
||||
* The version byte was stolen off the first byte of the message, but only for the first fragment
|
||||
* of the message. So for the first fragment we strip off everything and put the version byte
|
||||
* back on. For the remaining fragments, we just strip everything.
|
||||
*/
|
||||
|
||||
private byte[] getStrippedMessageForMultiPart() {
|
||||
byte[] strippedMessage = new byte[decodedMessage.length - (getMultipartIndex() == 0 ? 2 : 3)];
|
||||
|
||||
int copyDestinationIndex = 0;
|
||||
int copyDestinationLength = strippedMessage.length;
|
||||
|
||||
if (getMultipartIndex() == 0) {
|
||||
strippedMessage[0] = decodedMessage[0];
|
||||
copyDestinationIndex++;
|
||||
copyDestinationLength--;
|
||||
}
|
||||
|
||||
System.arraycopy(decodedMessage, 3, strippedMessage, copyDestinationIndex, copyDestinationLength);
|
||||
return strippedMessage;
|
||||
|
||||
}
|
||||
|
||||
public String getKey() {
|
||||
return message.getSender() + getIdentifier();
|
||||
}
|
||||
|
||||
public IncomingTextMessage getBaseMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public static ArrayList<String> getEncoded(OutgoingTextMessage message, byte identifier)
|
||||
{
|
||||
try {
|
||||
byte[] decoded = Base64.decodeWithoutPadding(message.getMessageBody());
|
||||
int count = new SmsTransportDetails().getMessageCountForBytes(decoded.length);
|
||||
|
||||
WirePrefix prefix;
|
||||
|
||||
if (message.isKeyExchange()) prefix = new KeyExchangeWirePrefix();
|
||||
else if (message.isPreKeyBundle()) prefix = new PrekeyBundleWirePrefix();
|
||||
else if (message.isEndSession()) prefix = new EndSessionWirePrefix();
|
||||
else prefix = new SecureMessageWirePrefix();
|
||||
|
||||
if (count == 1) return getSingleEncoded(decoded, prefix);
|
||||
else return getMultiEncoded(decoded, prefix, count, identifier);
|
||||
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static ArrayList<String> getSingleEncoded(byte[] decoded, WirePrefix prefix) {
|
||||
ArrayList<String> list = new ArrayList<String>(1);
|
||||
byte[] messageWithMultipartHeader = new byte[decoded.length + 1];
|
||||
System.arraycopy(decoded, 0, messageWithMultipartHeader, 1, decoded.length);
|
||||
|
||||
messageWithMultipartHeader[VERSION_OFFSET] = decoded[VERSION_OFFSET];
|
||||
messageWithMultipartHeader[MULTIPART_OFFSET] = Conversions.intsToByteHighAndLow(0, 1);
|
||||
|
||||
String encodedMessage = Base64.encodeBytesWithoutPadding(messageWithMultipartHeader);
|
||||
|
||||
list.add(prefix.calculatePrefix(encodedMessage) + encodedMessage);
|
||||
|
||||
Log.w(TAG, "Complete fragment size: " + list.get(list.size()-1).length());
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
private static ArrayList<String> getMultiEncoded(byte[] decoded, WirePrefix prefix,
|
||||
int segmentCount, byte id)
|
||||
{
|
||||
ArrayList<String> list = new ArrayList<String>(segmentCount);
|
||||
byte versionByte = decoded[VERSION_OFFSET];
|
||||
int messageOffset = 1;
|
||||
int segmentIndex = 0;
|
||||
|
||||
while (messageOffset < decoded.length-1) {
|
||||
int segmentSize = Math.min(SmsTransportDetails.BASE_MAX_BYTES, decoded.length-messageOffset+3);
|
||||
|
||||
byte[] segment = new byte[segmentSize];
|
||||
segment[VERSION_OFFSET] = versionByte;
|
||||
segment[MULTIPART_OFFSET] = Conversions.intsToByteHighAndLow(segmentIndex++, segmentCount);
|
||||
segment[IDENTIFIER_OFFSET] = id;
|
||||
|
||||
Log.w(TAG, "Fragment: (" + segmentIndex + "/" + segmentCount +") -- ID: " + id);
|
||||
|
||||
System.arraycopy(decoded, messageOffset, segment, 3, segmentSize-3);
|
||||
messageOffset += segmentSize-3;
|
||||
|
||||
String encodedSegment = Base64.encodeBytesWithoutPadding(segment);
|
||||
list.add(prefix.calculatePrefix(encodedSegment) + encodedSegment);
|
||||
|
||||
Log.w(TAG, "Complete fragment size: " + list.get(list.size()-1).length());
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
}
|
@ -1,57 +0,0 @@
|
||||
package org.thoughtcrime.securesms.sms;
|
||||
|
||||
public class MultipartSmsTransportMessageFragments {
|
||||
|
||||
private static final long VALID_TIME = 60 * 60 * 1000; // 1 Hour
|
||||
|
||||
private final byte[][] fragments;
|
||||
private final long initializedTime;
|
||||
|
||||
public MultipartSmsTransportMessageFragments(int count) {
|
||||
this.fragments = new byte[count][];
|
||||
this.initializedTime = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public void add(MultipartSmsTransportMessage fragment) {
|
||||
this.fragments[fragment.getMultipartIndex()] = fragment.getStrippedMessage();
|
||||
}
|
||||
|
||||
public int getSize() {
|
||||
return this.fragments.length;
|
||||
}
|
||||
|
||||
public boolean isExpired() {
|
||||
return (System.currentTimeMillis() - initializedTime) >= VALID_TIME;
|
||||
}
|
||||
|
||||
public boolean isComplete() {
|
||||
for (int i=0;i<fragments.length;i++)
|
||||
if (fragments[i] == null) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public byte[] getJoined() {
|
||||
int totalMessageLength = 0;
|
||||
|
||||
for (int i=0;i<fragments.length;i++) {
|
||||
totalMessageLength += fragments[i].length;
|
||||
}
|
||||
|
||||
byte[] totalMessage = new byte[totalMessageLength];
|
||||
int totalMessageOffset = 0;
|
||||
|
||||
for (int i=0;i<fragments.length;i++) {
|
||||
System.arraycopy(fragments[i], 0, totalMessage, totalMessageOffset, fragments[i].length);
|
||||
totalMessageOffset += fragments[i].length;
|
||||
}
|
||||
|
||||
return totalMessage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("[Size: %d, Initialized: %d, Exipired: %s, Complete: %s]",
|
||||
fragments.length, initializedTime, isExpired()+"", isComplete()+"");
|
||||
}
|
||||
}
|
@ -17,92 +17,9 @@
|
||||
*/
|
||||
package org.thoughtcrime.securesms.sms;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.protocol.WirePrefix;
|
||||
import org.thoughtcrime.securesms.util.Base64;
|
||||
import org.whispersystems.libaxolotl.protocol.CiphertextMessage;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class SmsTransportDetails {
|
||||
|
||||
public static final int SMS_SIZE = 160;
|
||||
public static final int MULTIPART_SMS_SIZE = 153;
|
||||
|
||||
public static final int BASE_MAX_BYTES = Base64.getEncodedBytesForTarget(SMS_SIZE - WirePrefix.PREFIX_SIZE);
|
||||
public static final int SINGLE_MESSAGE_MAX_BYTES = BASE_MAX_BYTES - MultipartSmsTransportMessage.SINGLE_MESSAGE_MULTIPART_OVERHEAD;
|
||||
public static final int MULTI_MESSAGE_MAX_BYTES = BASE_MAX_BYTES - MultipartSmsTransportMessage.MULTI_MESSAGE_MULTIPART_OVERHEAD;
|
||||
public static final int FIRST_MULTI_MESSAGE_MAX_BYTES = BASE_MAX_BYTES - MultipartSmsTransportMessage.FIRST_MULTI_MESSAGE_MULTIPART_OVERHEAD;
|
||||
|
||||
public static final int ENCRYPTED_SINGLE_MESSAGE_BODY_MAX_SIZE = SINGLE_MESSAGE_MAX_BYTES - CiphertextMessage.ENCRYPTED_MESSAGE_OVERHEAD;
|
||||
|
||||
public byte[] getEncodedMessage(byte[] messageWithMac) {
|
||||
String encodedMessage = Base64.encodeBytesWithoutPadding(messageWithMac);
|
||||
Log.w("SmsTransportDetails", "Encoded Message Length: " + encodedMessage.length());
|
||||
return encodedMessage.getBytes();
|
||||
}
|
||||
|
||||
public byte[] getDecodedMessage(byte[] encodedMessageBytes) throws IOException {
|
||||
String encodedMessage = new String(encodedMessageBytes);
|
||||
return Base64.decodeWithoutPadding(encodedMessage);
|
||||
}
|
||||
|
||||
public byte[] getStrippedPaddingMessageBody(byte[] messageWithPadding) {
|
||||
int paddingBeginsIndex = 0;
|
||||
|
||||
for (int i=1;i<messageWithPadding.length;i++) {
|
||||
if (messageWithPadding[i] == (byte)0x00) {
|
||||
paddingBeginsIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (paddingBeginsIndex == 0)
|
||||
return messageWithPadding;
|
||||
|
||||
byte[] message = new byte[paddingBeginsIndex];
|
||||
System.arraycopy(messageWithPadding, 0, message, 0, message.length);
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
public byte[] getPaddedMessageBody(byte[] messageBody) {
|
||||
int paddedBodySize = getMaxBodySizeForBytes(messageBody.length);
|
||||
Log.w("SmsTransportDetails", "Padding message body out to: " + paddedBodySize);
|
||||
|
||||
byte[] paddedBody = new byte[paddedBodySize];
|
||||
System.arraycopy(messageBody, 0, paddedBody, 0, messageBody.length);
|
||||
|
||||
return paddedBody;
|
||||
}
|
||||
|
||||
private int getMaxBodySizeForBytes(int bodyLength) {
|
||||
int encryptedBodyLength = bodyLength + CiphertextMessage.ENCRYPTED_MESSAGE_OVERHEAD;
|
||||
int messageRecordsForBody = getMessageCountForBytes(encryptedBodyLength);
|
||||
|
||||
if (messageRecordsForBody == 1) {
|
||||
return ENCRYPTED_SINGLE_MESSAGE_BODY_MAX_SIZE;
|
||||
} else {
|
||||
return
|
||||
FIRST_MULTI_MESSAGE_MAX_BYTES +
|
||||
(MULTI_MESSAGE_MAX_BYTES * (messageRecordsForBody-1)) -
|
||||
CiphertextMessage.ENCRYPTED_MESSAGE_OVERHEAD;
|
||||
}
|
||||
}
|
||||
|
||||
public int getMessageCountForBytes(int bytes) {
|
||||
if (bytes <= SINGLE_MESSAGE_MAX_BYTES)
|
||||
return 1;
|
||||
|
||||
bytes = Math.max(bytes - FIRST_MULTI_MESSAGE_MAX_BYTES, 0);
|
||||
|
||||
int messageCount = 1 + (bytes / MULTI_MESSAGE_MAX_BYTES);
|
||||
int remainder = bytes % MULTI_MESSAGE_MAX_BYTES;
|
||||
|
||||
if (remainder > 0)
|
||||
messageCount++;
|
||||
|
||||
return messageCount;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user