/**
* 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 .
*/
package org.thoughtcrime.securesms.transport;
import android.app.PendingIntent;
import android.content.Context;
import android.telephony.SmsManager;
import android.util.Log;
import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.sms.MultipartSmsMessageHandler;
import org.thoughtcrime.securesms.sms.OutgoingPrekeyBundleMessage;
import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
import org.thoughtcrime.securesms.sms.SmsTransportDetails;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.crypto.SessionCipher;
import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage;
import org.whispersystems.textsecure.storage.RecipientDevice;
import org.whispersystems.textsecure.util.Hex;
import java.util.ArrayList;
public class SmsTransport extends BaseTransport {
private final Context context;
private final MasterSecret masterSecret;
public SmsTransport(Context context, MasterSecret masterSecret) {
this.context = context.getApplicationContext();
this.masterSecret = masterSecret;
}
public void deliver(SmsMessageRecord message) throws UndeliverableMessageException {
if (message.isSecure() || message.isKeyExchange() || message.isEndSession()) {
deliverSecureMessage(message);
} else {
deliverPlaintextMessage(message);
}
}
private void deliverSecureMessage(SmsMessageRecord message) throws UndeliverableMessageException {
MultipartSmsMessageHandler multipartMessageHandler = new MultipartSmsMessageHandler();
OutgoingTextMessage transportMessage = OutgoingTextMessage.from(message);
if (message.isSecure() || message.isEndSession()) {
transportMessage = getAsymmetricEncrypt(masterSecret, transportMessage);
}
ArrayList messages = multipartMessageHandler.divideMessage(transportMessage);
ArrayList sentIntents = constructSentIntents(message.getId(), message.getType(), messages, message.isSecure());
ArrayList deliveredIntents = constructDeliveredIntents(message.getId(), message.getType(), messages);
Log.w("SmsTransport", "Secure divide into message parts: " + messages.size());
for (int i=0;i messages = SmsManager.getDefault().divideMessage(message.getBody().getBody());
ArrayList sentIntents = constructSentIntents(message.getId(), message.getType(), messages, false);
ArrayList deliveredIntents = constructDeliveredIntents(message.getId(), message.getType(), messages);
String recipient = message.getIndividualRecipient().getNumber();
// XXX moxie@thoughtcrime.org 1/7/11 -- There's apparently a bug where for some unknown recipients
// and messages, this will throw an NPE. I have no idea why, so I'm just catching it and marking
// the message as a failure. That way at least it doesn't repeatedly crash every time you start
// the app.
// d3sre 12/10/13 -- extended the log file to further analyse the problem
try {
SmsManager.getDefault().sendMultipartTextMessage(recipient, null, messages, sentIntents, deliveredIntents);
} catch (NullPointerException npe) {
Log.w("SmsTransport", npe);
Log.w("SmsTransport", "Recipient: " + recipient);
Log.w("SmsTransport", "Message Parts: " + messages.size());
try {
for (int i=0;i constructSentIntents(long messageId, long type,
ArrayList messages, boolean secure)
{
ArrayList sentIntents = new ArrayList(messages.size());
for (String ignored : messages) {
sentIntents.add(PendingIntent.getBroadcast(context, 0,
constructSentIntent(context, messageId, type, secure),
0));
}
return sentIntents;
}
private ArrayList constructDeliveredIntents(long messageId, long type, ArrayList messages) {
if (!TextSecurePreferences.isSmsDeliveryReportsEnabled(context)) {
return null;
}
ArrayList deliveredIntents = new ArrayList(messages.size());
for (String ignored : messages) {
deliveredIntents.add(PendingIntent.getBroadcast(context, 0,
constructDeliveredIntent(context, messageId, type),
0));
}
return deliveredIntents;
}
private OutgoingTextMessage getAsymmetricEncrypt(MasterSecret masterSecret,
OutgoingTextMessage message)
{
Recipient recipient = message.getRecipients().getPrimaryRecipient();
RecipientDevice recipientDevice = new RecipientDevice(recipient.getRecipientId(), RecipientDevice.DEFAULT_DEVICE_ID);
String body = message.getMessageBody();
SmsTransportDetails transportDetails = new SmsTransportDetails();
SessionCipher sessionCipher = SessionCipher.createFor(context, masterSecret, recipientDevice);
byte[] paddedPlaintext = transportDetails.getPaddedMessageBody(body.getBytes());
CiphertextMessage ciphertextMessage = sessionCipher.encrypt(paddedPlaintext);
String encodedCiphertext = new String(transportDetails.getEncodedMessage(ciphertextMessage.serialize()));
if (ciphertextMessage.getType() == CiphertextMessage.PREKEY_WHISPER_TYPE) {
message = new OutgoingPrekeyBundleMessage(message, encodedCiphertext);
} else {
message = message.withBody(encodedCiphertext);
}
return message;
}
}