/**
* 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.crypto.IdentityKeyUtil;
import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.service.SendReceiveService;
import org.thoughtcrime.securesms.service.SmsDeliveryListener;
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.IdentityKeyPair;
import org.whispersystems.textsecure.crypto.KeyUtil;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.crypto.MessageCipher;
import org.whispersystems.textsecure.crypto.ecc.Curve;
import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage;
import org.whispersystems.textsecure.crypto.protocol.PreKeyBundleMessage;
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()) {
deliverSecureMessage(message);
} else {
deliverPlaintextMessage(message);
}
}
private void deliverSecureMessage(SmsMessageRecord message) throws UndeliverableMessageException {
MultipartSmsMessageHandler multipartMessageHandler = new MultipartSmsMessageHandler();
OutgoingTextMessage transportMessage = OutgoingTextMessage.from(message);
if (message.isSecure()) {
transportMessage = getAsymmetricEncrypt(masterSecret, transportMessage);
}
ArrayList messages = multipartMessageHandler.divideMessage(transportMessage);
ArrayList sentIntents = constructSentIntents(message.getId(), message.getType(), messages, true);
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();
String body = message.getMessageBody();
IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret,
Curve.DJB_TYPE);
SmsTransportDetails transportDetails = new SmsTransportDetails();
if (KeyUtil.isNonPrekeySessionFor(context, masterSecret, recipient)) {
Log.w("SmsTransport", "Delivering standard ciphertext...");
MessageCipher messageCipher = new MessageCipher(context, masterSecret, identityKey);
byte[] paddedPlaintext = transportDetails.getPaddedMessageBody(body.getBytes());
CiphertextMessage ciphertextMessage = messageCipher.encrypt(recipient, paddedPlaintext);
String ciphertxt = new String(transportDetails.getEncodedMessage(ciphertextMessage.serialize()));
return message.withBody(ciphertxt);
} else {
Log.w("SmsTransport", "Delivering prekeybundle ciphertext...");
MessageCipher messageCipher = new MessageCipher(context, masterSecret, identityKey);
CiphertextMessage ciphertextMessage = messageCipher.encrypt(recipient, body.getBytes());
PreKeyBundleMessage preKeyBundleMessage = new PreKeyBundleMessage(ciphertextMessage, identityKey.getPublicKey());
byte[] cipherText = preKeyBundleMessage.serialize();
return new OutgoingPrekeyBundleMessage(message, new String(transportDetails.getEncodedMessage(cipherText)));
}
}
}