/** * 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 . */ package org.thoughtcrime.securesms.sms; import android.util.Log; import org.thoughtcrime.securesms.protocol.WirePrefix; import org.thoughtcrime.securesms.util.Base64; import org.thoughtcrime.securesms.util.Conversions; import org.thoughtcrime.securesms.util.Hex; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; public class MultipartMessageHandler { private static final int VERSION_OFFSET = 0; private static final int MULTIPART_OFFSET = 1; private static final int IDENTIFIER_OFFSET = 2; private static final int MULTIPART_SUPPORTED_AFTER_VERSION = 1; private final HashMap partialMessages = new HashMap(); private final HashMap idMap = new HashMap(); private String spliceMessage(String prefix, byte[][] messageParts) { Log.w("MultipartMessageHandler", "Have complete message fragments, splicing..."); int totalMessageLength = 0; for (int i=0;i= multipartCount) return message; if (multipartCount == 1) return processSinglePartMessage(prefix, decodedMessage); else return processMultipartMessage(prefix, multipartIndex, multipartCount, sender, identifier, decodedMessage); } catch (IOException e) { return message; } } private ArrayList buildSingleMessage(byte[] decodedMessage, WirePrefix prefix) { Log.w("MultipartMessageHandler", "Adding transport info to single-part message..."); ArrayList list = new ArrayList(); byte[] messageWithMultipartHeader = new byte[decodedMessage.length + 1]; System.arraycopy(decodedMessage, 0, messageWithMultipartHeader, 1, decodedMessage.length); messageWithMultipartHeader[0] = decodedMessage[0]; messageWithMultipartHeader[1] = Conversions.intsToByteHighAndLow(0, 1); String encodedMessage = Base64.encodeBytesWithoutPadding(messageWithMultipartHeader); list.add(prefix.calculatePrefix(encodedMessage) + encodedMessage); Log.w("MultipartMessageHandler", "Complete fragment size: " + list.get(list.size()-1).length()); return list; } private byte getIdForRecipient(String recipient) { Integer currentId; if (idMap.containsKey(recipient)) { currentId = idMap.get(recipient); idMap.remove(recipient); } else { currentId = Integer.valueOf(0); } byte id = currentId.byteValue(); idMap.put(recipient, Integer.valueOf((currentId.intValue() + 1) % 255)); return id; } private ArrayList buildMultipartMessage(String recipient, byte[] decodedMessage, WirePrefix prefix) { Log.w("MultipartMessageHandler", "Building multipart message..."); ArrayList list = new ArrayList(); byte versionByte = decodedMessage[0]; int messageOffset = 1; int segmentIndex = 0; int segmentCount = SmsTransportDetails.getMessageCountForBytes(decodedMessage.length); byte id = getIdForRecipient(recipient); while (messageOffset < decodedMessage.length-1) { int segmentSize = Math.min(SmsTransportDetails.BASE_MAX_BYTES, decodedMessage.length-messageOffset+3); byte[] segment = new byte[segmentSize]; segment[0] = versionByte; segment[1] = Conversions.intsToByteHighAndLow(segmentIndex++, segmentCount); segment[2] = id; Log.w("MultipartMessageHandler", "Fragment: (" + segmentIndex + "/" + segmentCount +") -- ID: " + id); System.arraycopy(decodedMessage, messageOffset, segment, 3, segmentSize-3); messageOffset += segmentSize-3; String encodedSegment = Base64.encodeBytesWithoutPadding(segment); list.add(prefix.calculatePrefix(encodedSegment) + encodedSegment); Log.w("MultipartMessageHandler", "Complete fragment size: " + list.get(list.size()-1).length()); } return list; } public boolean isManualTransport(String message) { try { byte[] decodedMessage = Base64.decodeWithoutPadding(message); return Conversions.highBitsToInt(decodedMessage[0]) >= MULTIPART_SUPPORTED_AFTER_VERSION; } catch (IOException ioe) { throw new AssertionError(ioe); } } public ArrayList divideMessage(String recipient, String message, WirePrefix prefix) { try { byte[] decodedMessage = Base64.decodeWithoutPadding(message); if (decodedMessage.length <= SmsTransportDetails.SINGLE_MESSAGE_MAX_BYTES) return buildSingleMessage(decodedMessage, prefix); else return buildMultipartMessage(recipient, decodedMessage, prefix); } catch (IOException ioe) { throw new AssertionError(ioe); } } }