Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit e00dd1e5 authored by Tammo Spalink's avatar Tammo Spalink
Browse files

Make GSM 7-bit encoding properly deal with initial padding.

For CDMA, clean up the GSM encapsulation to properly align
user data payload after the user data header.

Addresses http://buganizer/issue?id=2007011
parent 6307f028
Loading
Loading
Loading
Loading
+18 −45
Original line number Diff line number Diff line
@@ -183,15 +183,9 @@ public class GsmAlphabet {
        }

        int headerBits = (header.length + 1) * 8;
        int headerSeptets = headerBits / 7;
        headerSeptets += (headerBits % 7) > 0 ? 1 : 0;
        int headerSeptets = (headerBits + 6) / 7;

        int sz = data.length();
        int septetCount;
        septetCount = countGsmSeptets(data, true) + headerSeptets;

        byte[] ret = stringToGsm7BitPacked(data, 0, septetCount,
                (headerSeptets*7), true);
        byte[] ret = stringToGsm7BitPacked(data, headerSeptets, true);

        // Paste in the header
        ret[1] = (byte)header.length;
@@ -215,7 +209,7 @@ public class GsmAlphabet {
     */
    public static byte[] stringToGsm7BitPacked(String data)
            throws EncodeException {
        return stringToGsm7BitPacked(data, 0, -1, 0, true);
        return stringToGsm7BitPacked(data, 0, true);
    }

    /**
@@ -228,58 +222,37 @@ public class GsmAlphabet {
     * septets.
     *
     * @param data the text to convert to septets
     * @param dataOffset the character offset in data to start the encoding from
     * @param maxSeptets the maximum number of septets to convert, or -1 for no
     *  enforced maximum.
     * @param startingBitOffset the number of padding bits to put before
     *  the start of the first septet at the begining of the array
     * @param startingSeptetOffset the number of padding septets to put before
     *  the character data at the begining of the array
     * @param throwException If true, throws EncodeException on invalid char.
     *   If false, replaces unencodable char with GSM alphabet space char.
     *
     * @throws EncodeException if String is too large to encode
     */
    public static byte[] stringToGsm7BitPacked(String data, int dataOffset,
            int maxSeptets, int startingBitOffset, boolean throwException)
            throws EncodeException {

        int sz = data.length();
        int septetCount;
        if (maxSeptets == -1) {
            septetCount = countGsmSeptets(data, true);
        } else {
            septetCount = maxSeptets;
        }

        if(septetCount > 0xff) {
            throw new EncodeException("Payload cannot exceed " + Short.MAX_VALUE
                    + " septets");
        }

        // Enough for all the septets and the length 2 byte prefix
        byte[] ret = new byte[1 + (((septetCount * 7) + 7) / 8)];

        int bitOffset = startingBitOffset;
        int septets = startingBitOffset/7;
        for (int i = dataOffset; i < sz && septets < septetCount; i++, bitOffset += 7) {
    public static byte[] stringToGsm7BitPacked(String data, int startingSeptetOffset,
            boolean throwException) throws EncodeException {
        int dataLen = data.length();
        int septetCount = countGsmSeptets(data, throwException) + startingSeptetOffset;
        if (septetCount > 255) {
            throw new EncodeException("Payload cannot exceed 255 septets");
        }
        int byteCount = ((septetCount * 7) + 7) / 8;
        byte[] ret = new byte[byteCount + 1];  // Include space for one byte length prefix.
        for (int i = 0, septets = startingSeptetOffset, bitOffset = startingSeptetOffset * 7;
                 i < dataLen && septets < septetCount;
                 i++, bitOffset += 7) {
            char c = data.charAt(i);

            int v = GsmAlphabet.charToGsm(c, throwException);
            if (v == GSM_EXTENDED_ESCAPE) {
                // Lookup the extended char
                v = GsmAlphabet.charToGsmExtended(c);

                v = GsmAlphabet.charToGsmExtended(c);  // Lookup the extended char.
                packSmsChar(ret, bitOffset, GSM_EXTENDED_ESCAPE);
                bitOffset += 7;
                septets++;
            }

            packSmsChar(ret, bitOffset, v);
            septets++;
        }

        // See check for > 0xff above
        ret[0] = (byte)septets;

        ret[0] = (byte) (septetCount);  // Validated by check above.
        return ret;
    }

+114 −67
Original line number Diff line number Diff line
@@ -455,53 +455,114 @@ public final class BearerData {
        }
    }

    private static int calcUdhSeptetPadding(int userDataHeaderLen) {
        int udhBits = userDataHeaderLen * 8;
        int udhSeptets = (udhBits + 6) / 7;
        int paddingBits = (udhSeptets * 7) - udhBits;
        return paddingBits;
    private static class Gsm7bitCodingResult {
        int septets;
        byte[] data;
    }

    private static byte[] encode7bitGsm(String msg, int paddingBits)
    private static Gsm7bitCodingResult encode7bitGsm(String msg, int septetOffset, boolean force)
        throws CodingException
    {
        try {
            /*
             * TODO(cleanup): It would be nice if GsmAlphabet provided
             * an option to produce just the data without prepending
             * the length.
             * the septet count, as this function is really just a
             * wrapper to strip that off.  Not to mention that the
             * septet count is generally known prior to invocation of
             * the encoder.  Note that it cannot be derived from the
             * resulting array length, since that cannot distinguish
             * if the last contains either 1 or 8 valid bits.
             *
             * TODO(cleanup): The BitwiseXStreams could also be
             * extended with byte-wise reversed endianness read/write
             * routines to allow a corresponding implementation of
             * stringToGsm7BitPacked, and potentially directly support
             * access to the main bitwise stream from encode/decode.
             */
            byte []fullData = GsmAlphabet.stringToGsm7BitPacked(msg, 0, -1, paddingBits, true);
            byte []data = new byte[fullData.length - 1];
            System.arraycopy(fullData, 1, data, 0, fullData.length - 1);
            return data;
            byte[] fullData = GsmAlphabet.stringToGsm7BitPacked(msg, septetOffset, !force);
            Gsm7bitCodingResult result = new Gsm7bitCodingResult();
            result.data = new byte[fullData.length - 1];
            System.arraycopy(fullData, 1, result.data, 0, fullData.length - 1);
            result.septets = fullData[0];
            return result;
        } catch (com.android.internal.telephony.EncodeException ex) {
            throw new CodingException("7bit GSM encode failed: " + ex);
        }
    }

    private static void encode7bitEms(UserData uData, byte[] udhData, boolean force)
        throws CodingException
    {
        int udhBytes = udhData.length + 1;  // Add length octet.
        int udhSeptets = ((udhBytes * 8) + 6) / 7;
        Gsm7bitCodingResult gcr = encode7bitGsm(uData.payloadStr, udhSeptets, force);
        uData.msgEncoding = UserData.ENCODING_GSM_7BIT_ALPHABET;
        uData.numFields = gcr.septets;
        uData.payload = gcr.data;
        uData.payload[0] = (byte)udhData.length;
        System.arraycopy(udhData, 0, uData.payload, 1, udhData.length);
    }

    private static void encode16bitEms(UserData uData, byte[] udhData)
        throws CodingException
    {
        byte[] payload = encodeUtf16(uData.payloadStr);
        int udhBytes = udhData.length + 1;  // Add length octet.
        int udhCodeUnits = (udhBytes + 1) / 2;
        int udhPadding = udhBytes % 2;
        int payloadCodeUnits = payload.length / 2;
        uData.numFields = udhCodeUnits + payloadCodeUnits;
        uData.payload = new byte[uData.numFields * 2];
        uData.payload[0] = (byte)udhData.length;
        System.arraycopy(udhData, 0, uData.payload, 1, udhData.length);
        System.arraycopy(payload, 0, uData.payload, udhBytes + udhPadding, payload.length);
    }

    private static void encodeEmsUserDataPayload(UserData uData)
        throws CodingException
    {
        byte[] headerData = SmsHeader.toByteArray(uData.userDataHeader);
        if (uData.msgEncodingSet) {
            if (uData.msgEncoding == UserData.ENCODING_GSM_7BIT_ALPHABET) {
                encode7bitEms(uData, headerData, true);
            } else if (uData.msgEncoding == UserData.ENCODING_UNICODE_16) {
                encode16bitEms(uData, headerData);
            } else {
                throw new CodingException("unsupported EMS user data encoding (" +
                                          uData.msgEncoding + ")");
            }
        } else {
            try {
                encode7bitEms(uData, headerData, false);
            } catch (CodingException ex) {
                encode16bitEms(uData, headerData);
            }
        }
    }

    private static void encodeUserDataPayload(UserData uData)
        throws CodingException
    {
        // TODO(cleanup): UDH can only occur in EMS mode, meaning
        // encapsulation of GSM encoding, and so the logic here should
        // be refactored to more cleanly reflect this constraint.
        if ((uData.payloadStr == null) && (uData.msgEncoding != UserData.ENCODING_OCTET)) {
            Log.e(LOG_TAG, "user data with null payloadStr");
            uData.payloadStr = "";
        }

        byte[] headerData = null;
        if (uData.userDataHeader != null) headerData = SmsHeader.toByteArray(uData.userDataHeader);
        int headerDataLen = (headerData == null) ? 0 : headerData.length + 1;  // + length octet
        if (uData.userDataHeader != null) {
            encodeEmsUserDataPayload(uData);
            return;
        }

        byte[] payloadData;
        int codeUnitCount;
        if (uData.msgEncodingSet) {
            if (uData.msgEncoding == UserData.ENCODING_OCTET) {
                if (uData.payload == null) {
                    Log.e(LOG_TAG, "user data with octet encoding but null payload");
                    payloadData = new byte[0];
                    codeUnitCount = 0;
                    uData.payload = new byte[0];
                    uData.numFields = 0;
                } else {
                    payloadData = uData.payload;
                    codeUnitCount = uData.payload.length;
                    uData.payload = uData.payload;
                    uData.numFields = uData.payload.length;
                }
            } else {
                if (uData.payloadStr == null) {
@@ -509,65 +570,48 @@ public final class BearerData {
                    uData.payloadStr = "";
                }
                if (uData.msgEncoding == UserData.ENCODING_GSM_7BIT_ALPHABET) {
                    int paddingBits = calcUdhSeptetPadding(headerDataLen);
                    payloadData = encode7bitGsm(uData.payloadStr, paddingBits);
                    codeUnitCount = ((payloadData.length + headerDataLen) * 8) / 7;
                    Gsm7bitCodingResult gcr = encode7bitGsm(uData.payloadStr, 0, true);
                    uData.payload = gcr.data;
                    uData.numFields = gcr.septets;
                } else if (uData.msgEncoding == UserData.ENCODING_7BIT_ASCII) {
                    payloadData = encode7bitAscii(uData.payloadStr, true);
                    codeUnitCount = uData.payloadStr.length();
                    uData.payload = encode7bitAscii(uData.payloadStr, true);
                    uData.numFields = uData.payloadStr.length();
                } else if (uData.msgEncoding == UserData.ENCODING_UNICODE_16) {
                    payloadData = encodeUtf16(uData.payloadStr);
                    codeUnitCount = uData.payloadStr.length();
                    uData.payload = encodeUtf16(uData.payloadStr);
                    uData.numFields = uData.payloadStr.length();
                } else {
                    throw new CodingException("unsupported user data encoding (" +
                                              uData.msgEncoding + ")");
                }
            }
        } else {
            if (uData.payloadStr == null) {
                Log.e(LOG_TAG, "user data with null payloadStr");
                uData.payloadStr = "";
            }
            try {
                if (headerData == null) {
                    payloadData = encode7bitAscii(uData.payloadStr, false);
                    codeUnitCount = uData.payloadStr.length();
                uData.payload = encode7bitAscii(uData.payloadStr, false);
                uData.msgEncoding = UserData.ENCODING_7BIT_ASCII;
                } else {
                    // If there is a header, we are in EMS mode, in
                    // which case we use GSM encodings.
                    int paddingBits = calcUdhSeptetPadding(headerDataLen);
                    payloadData = encode7bitGsm(uData.payloadStr, paddingBits);
                    codeUnitCount = ((payloadData.length + headerDataLen) * 8) / 7;
                    uData.msgEncoding = UserData.ENCODING_GSM_7BIT_ALPHABET;
                }
            } catch (CodingException ex) {
                payloadData = encodeUtf16(uData.payloadStr);
                codeUnitCount = uData.payloadStr.length();
                uData.payload = encodeUtf16(uData.payloadStr);
                uData.msgEncoding = UserData.ENCODING_UNICODE_16;
            }
            uData.numFields = uData.payloadStr.length();
            uData.msgEncodingSet = true;
        }

        int totalLength = payloadData.length + headerDataLen;
        if (totalLength > SmsMessage.MAX_USER_DATA_BYTES) {
            throw new CodingException("encoded user data too large (" + totalLength +
                                      " > " + SmsMessage.MAX_USER_DATA_BYTES + " bytes)");
        }

        uData.numFields = codeUnitCount;
        uData.payload = new byte[totalLength];
        if (headerData != null) {
            uData.payload[0] = (byte)headerData.length;
            System.arraycopy(headerData, 0, uData.payload, 1, headerData.length);
        }
        System.arraycopy(payloadData, 0, uData.payload, headerDataLen, payloadData.length);
    }

    private static void encodeUserData(BearerData bData, BitwiseOutputStream outStream)
        throws BitwiseOutputStream.AccessException, CodingException
    {
        /*
         * TODO(cleanup): Do we really need to set userData.payload as
         * a side effect of encoding?  If not, we could avoid data
         * copies by passing outStream directly.
         */
        encodeUserDataPayload(bData.userData);
        if (bData.userData.payload.length > SmsMessage.MAX_USER_DATA_BYTES) {
            throw new CodingException("encoded user data too large (" +
                                      bData.userData.payload.length +
                                      " > " + SmsMessage.MAX_USER_DATA_BYTES + " bytes)");
        }

        /**
         * XXX/TODO: figure out what the right answer is WRT padding bits
         *
@@ -846,6 +890,9 @@ public final class BearerData {
    private static String decodeUtf16(byte[] data, int offset, int numFields)
        throws CodingException
    {
        // Start reading from the next 16-bit aligned boundry after offset.
        int padding = offset % 2;
        numFields -= (offset + padding) / 2;
        try {
            return new String(data, offset, numFields * 2, "utf-16be");
        } catch (java.io.UnsupportedEncodingException ex) {
@@ -889,11 +936,11 @@ public final class BearerData {
    private static String decode7bitGsm(byte[] data, int offset, int numFields)
        throws CodingException
    {
        int paddingBits = calcUdhSeptetPadding(offset);
        numFields -= (((offset * 8) + paddingBits) / 7);
        // TODO: It seems wrong that only Gsm7 bit encodings would
        // take into account the header in numFields calculations.
        // This should be verified.
        // Start reading from the next 7-bit aligned boundry after offset.
        int offsetBits = offset * 8;
        int offsetSeptets = (offsetBits + 6) / 7;
        numFields -= offsetSeptets;
        int paddingBits = (offsetSeptets * 7) - offsetBits;
        String result = GsmAlphabet.gsm7BitPackedToString(data, offset, numFields, paddingBits);
        if (result == null) {
            throw new CodingException("7bit GSM decoding failed");
+48 −2
Original line number Diff line number Diff line
@@ -18,9 +18,11 @@ package com.android.unit_tests;

import com.android.internal.telephony.GsmAlphabet;
import com.android.internal.telephony.SmsHeader;
import com.android.internal.telephony.cdma.SmsMessage;
import com.android.internal.telephony.cdma.sms.BearerData;
import com.android.internal.telephony.cdma.sms.UserData;
import com.android.internal.telephony.cdma.sms.CdmaSmsAddress;
import com.android.internal.telephony.SmsMessageBase.TextEncodingDetails;
import com.android.internal.util.BitwiseInputStream;
import com.android.internal.util.BitwiseOutputStream;
import com.android.internal.util.HexDump;
@@ -28,12 +30,12 @@ import com.android.internal.util.HexDump;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.SmallTest;

import android.util.Log;

import java.util.Iterator;

import java.lang.Integer;

import android.util.Log;

public class CdmaSmsTest extends AndroidTestCase {
    private final static String LOG_TAG = "CDMA";

@@ -151,6 +153,9 @@ public class CdmaSmsTest extends AndroidTestCase {
        userData.payloadStr = "Test \n standard \r SMS";
        revBearerData = BearerData.decode(BearerData.encode(bearerData));
        assertEquals(userData.payloadStr, revBearerData.userData.payloadStr);
        userData.payloadStr = "";
        revBearerData = BearerData.decode(BearerData.encode(bearerData));
        assertEquals(userData.payloadStr, revBearerData.userData.payloadStr);
    }

    @SmallTest
@@ -172,6 +177,21 @@ public class CdmaSmsTest extends AndroidTestCase {
        assertEquals(userData.msgEncoding, revBearerData.userData.msgEncoding);
        assertEquals(userData.payloadStr.length(), revBearerData.userData.numFields);
        assertEquals(userData.payloadStr, revBearerData.userData.payloadStr);
        userData.payloadStr = "1234567";
        revBearerData = BearerData.decode(BearerData.encode(bearerData));
        assertEquals(userData.payloadStr, revBearerData.userData.payloadStr);
        userData.payloadStr = "";
        revBearerData = BearerData.decode(BearerData.encode(bearerData));
        assertEquals(userData.payloadStr, revBearerData.userData.payloadStr);
        userData.payloadStr = "12345678901234567890123456789012345678901234567890" +
                "12345678901234567890123456789012345678901234567890" +
                "12345678901234567890123456789012345678901234567890" +
                "1234567890";
        revBearerData = BearerData.decode(BearerData.encode(bearerData));
        assertEquals(userData.payloadStr, revBearerData.userData.payloadStr);
        userData.payloadStr = "Test \u007f illegal \u0000 SMS chars";
        revBearerData = BearerData.decode(BearerData.encode(bearerData));
        assertEquals("Test   illegal   SMS chars", revBearerData.userData.payloadStr);
        userData.payloadStr = "More @ testing\nis great^|^~woohoo";
        revBearerData = BearerData.decode(BearerData.encode(bearerData));
        assertEquals(userData.payloadStr, revBearerData.userData.payloadStr);
@@ -220,6 +240,12 @@ public class CdmaSmsTest extends AndroidTestCase {
        assertEquals(userData.msgEncoding, revBearerData.userData.msgEncoding);
        assertEquals(userData.payloadStr.length(), revBearerData.userData.numFields);
        assertEquals(userData.payloadStr, revBearerData.userData.payloadStr);
        userData.payloadStr = "1234567";
        revBearerData = BearerData.decode(BearerData.encode(bearerData));
        assertEquals(userData.payloadStr, revBearerData.userData.payloadStr);
        userData.payloadStr = "";
        revBearerData = BearerData.decode(BearerData.encode(bearerData));
        assertEquals(userData.payloadStr, revBearerData.userData.payloadStr);
    }

    @SmallTest
@@ -784,4 +810,24 @@ public class CdmaSmsTest extends AndroidTestCase {
        assertEquals(bd4.userData.payloadStr, "ABCDEFG");
    }

    @SmallTest
    public void testUserDataHeaderWithEightCharMsg() throws Exception {
        BearerData bearerData = new BearerData();
        bearerData.messageType = BearerData.MESSAGE_TYPE_DELIVER;
        bearerData.messageId = 55;
        SmsHeader.ConcatRef concatRef = new SmsHeader.ConcatRef();
        concatRef.refNumber = 0xEE;
        concatRef.msgCount = 2;
        concatRef.seqNumber = 2;
        concatRef.isEightBits = true;
        SmsHeader smsHeader = new SmsHeader();
        smsHeader.concatRef = concatRef;
        UserData userData = new UserData();
        userData.payloadStr = "01234567";
        userData.userDataHeader = smsHeader;
        bearerData.userData = userData;
        byte[] encodedSms = BearerData.encode(bearerData);
        BearerData revBearerData = BearerData.decode(encodedSms);
        assertEquals(userData.payloadStr, revBearerData.userData.payloadStr);
    }
}