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:
Tammo Spalink
2009-07-28 20:35:36 +08:00
parent 6307f0285e
commit e00dd1e5a3
3 changed files with 180 additions and 114 deletions

View File

@@ -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;
}

View File

@@ -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");

View File

@@ -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);
}
}