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
This commit is contained in:
@@ -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;
|
||||
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");
|
||||
}
|
||||
|
||||
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) {
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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.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;
|
||||
}
|
||||
uData.payload = encode7bitAscii(uData.payloadStr, false);
|
||||
uData.msgEncoding = UserData.ENCODING_7BIT_ASCII;
|
||||
} 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");
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user