Skip to content

Commit b35e4d7

Browse files
author
Jake Hamby
committed
Fix CDMA decoding of multipart UTF-16 SMS messages.
Recent changes to support CMAS over CDMA introduced a bug causing an exception to be thrown when decoding multipart UTF-16 encoded messages. This change fixes the exception by correctly subtracting the header size from the number of bytes to decode. It also adds more robust error handling to try to decode the maximum length possible instead of throwing an exception if the length is still larger than the user data length after subtracting the header. This also fixes a bug in the encoder, which was padding the UTF-16 user data to 16-bit alignment, which is incorrect (should be padded to an 8-bit boundary). The code happened to work because we always generated a UDH that was an even number of bytes (including length) so the padding was a no-op. The decoder works correctly. Bug: 6939151 Change-Id: Iba9e7156bd7df94e972963959a7ce1c78464f7f5
1 parent cf9e9d6 commit b35e4d7

File tree

2 files changed

+91
-30
lines changed

2 files changed

+91
-30
lines changed

telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java

Lines changed: 25 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -595,15 +595,14 @@ private static void encode16bitEms(UserData uData, byte[] udhData)
595595
byte[] payload = encodeUtf16(uData.payloadStr);
596596
int udhBytes = udhData.length + 1; // Add length octet.
597597
int udhCodeUnits = (udhBytes + 1) / 2;
598-
int udhPadding = udhBytes % 2;
599598
int payloadCodeUnits = payload.length / 2;
600599
uData.msgEncoding = UserData.ENCODING_UNICODE_16;
601600
uData.msgEncodingSet = true;
602601
uData.numFields = udhCodeUnits + payloadCodeUnits;
603602
uData.payload = new byte[uData.numFields * 2];
604603
uData.payload[0] = (byte)udhData.length;
605604
System.arraycopy(udhData, 0, uData.payload, 1, udhData.length);
606-
System.arraycopy(payload, 0, uData.payload, udhBytes + udhPadding, payload.length);
605+
System.arraycopy(payload, 0, uData.payload, udhBytes, payload.length);
607606
}
608607

609608
private static void encodeEmsUserDataPayload(UserData uData)
@@ -997,27 +996,37 @@ private static boolean decodeUserData(BearerData bData, BitwiseInputStream inStr
997996
private static String decodeUtf8(byte[] data, int offset, int numFields)
998997
throws CodingException
999998
{
1000-
if (numFields < 0 || (numFields + offset) > data.length) {
1001-
throw new CodingException("UTF-8 decode failed: offset or length out of range");
1002-
}
1003-
try {
1004-
return new String(data, offset, numFields, "UTF-8");
1005-
} catch (java.io.UnsupportedEncodingException ex) {
1006-
throw new CodingException("UTF-8 decode failed: " + ex);
1007-
}
999+
return decodeCharset(data, offset, numFields, 1, "UTF-8");
10081000
}
10091001

10101002
private static String decodeUtf16(byte[] data, int offset, int numFields)
10111003
throws CodingException
10121004
{
1013-
int byteCount = numFields * 2;
1014-
if (byteCount < 0 || (byteCount + offset) > data.length) {
1015-
throw new CodingException("UTF-16 decode failed: offset or length out of range");
1005+
// Subtract header and possible padding byte (at end) from num fields.
1006+
int padding = offset % 2;
1007+
numFields -= (offset + padding) / 2;
1008+
return decodeCharset(data, offset, numFields, 2, "utf-16be");
1009+
}
1010+
1011+
private static String decodeCharset(byte[] data, int offset, int numFields, int width,
1012+
String charset) throws CodingException
1013+
{
1014+
if (numFields < 0 || (numFields * width + offset) > data.length) {
1015+
// Try to decode the max number of characters in payload
1016+
int padding = offset % width;
1017+
int maxNumFields = (data.length - offset - padding) / width;
1018+
if (maxNumFields < 0) {
1019+
throw new CodingException(charset + " decode failed: offset out of range");
1020+
}
1021+
Log.e(LOG_TAG, charset + " decode error: offset = " + offset + " numFields = "
1022+
+ numFields + " data.length = " + data.length + " maxNumFields = "
1023+
+ maxNumFields);
1024+
numFields = maxNumFields;
10161025
}
10171026
try {
1018-
return new String(data, offset, byteCount, "utf-16be");
1027+
return new String(data, offset, numFields * width, charset);
10191028
} catch (java.io.UnsupportedEncodingException ex) {
1020-
throw new CodingException("UTF-16 decode failed: " + ex);
1029+
throw new CodingException(charset + " decode failed: " + ex);
10211030
}
10221031
}
10231032

@@ -1073,14 +1082,7 @@ private static String decode7bitGsm(byte[] data, int offset, int numFields)
10731082
private static String decodeLatin(byte[] data, int offset, int numFields)
10741083
throws CodingException
10751084
{
1076-
if (numFields < 0 || (numFields + offset) > data.length) {
1077-
throw new CodingException("ISO-8859-1 decode failed: offset or length out of range");
1078-
}
1079-
try {
1080-
return new String(data, offset, numFields, "ISO-8859-1");
1081-
} catch (java.io.UnsupportedEncodingException ex) {
1082-
throw new CodingException("ISO-8859-1 decode failed: " + ex);
1083-
}
1085+
return decodeCharset(data, offset, numFields, 1, "ISO-8859-1");
10841086
}
10851087

10861088
private static void decodeUserDataPayload(UserData userData, boolean hasUserDataHeader)

telephony/tests/telephonytests/src/com/android/internal/telephony/cdma/sms/CdmaSmsTest.java

Lines changed: 66 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,21 @@
3535
import android.util.Log;
3636

3737
import java.util.ArrayList;
38+
import java.util.Arrays;
3839

3940
public class CdmaSmsTest extends AndroidTestCase {
4041
private final static String LOG_TAG = "XXX CdmaSmsTest XXX";
4142

43+
// CJK ideographs, Hiragana, Katakana, full width letters, Cyrillic, etc.
44+
private static final String sUnicodeChars = "\u4e00\u4e01\u4e02\u4e03" +
45+
"\u4e04\u4e05\u4e06\u4e07\u4e08\u4e09\u4e0a\u4e0b\u4e0c\u4e0d" +
46+
"\u4e0e\u4e0f\u3041\u3042\u3043\u3044\u3045\u3046\u3047\u3048" +
47+
"\u30a1\u30a2\u30a3\u30a4\u30a5\u30a6\u30a7\u30a8" +
48+
"\uff10\uff11\uff12\uff13\uff14\uff15\uff16\uff17\uff18" +
49+
"\uff70\uff71\uff72\uff73\uff74\uff75\uff76\uff77\uff78" +
50+
"\u0400\u0401\u0402\u0403\u0404\u0405\u0406\u0407\u0408" +
51+
"\u00a2\u00a9\u00ae\u2122";
52+
4253
@SmallTest
4354
public void testCdmaSmsAddrParsing() throws Exception {
4455
CdmaSmsAddress addr = CdmaSmsAddress.parse("6502531000");
@@ -811,23 +822,51 @@ public void testIs91() throws Exception {
811822

812823
@SmallTest
813824
public void testUserDataHeaderWithEightCharMsg() throws Exception {
825+
encodeDecodeAssertEquals("01234567", 2, 2, false);
826+
}
827+
828+
private void encodeDecodeAssertEquals(String payload, int index, int total,
829+
boolean oddLengthHeader) throws Exception {
814830
BearerData bearerData = new BearerData();
815831
bearerData.messageType = BearerData.MESSAGE_TYPE_DELIVER;
816832
bearerData.messageId = 55;
817-
SmsHeader.ConcatRef concatRef = new SmsHeader.ConcatRef();
818-
concatRef.refNumber = 0xEE;
819-
concatRef.msgCount = 2;
820-
concatRef.seqNumber = 2;
821-
concatRef.isEightBits = true;
822833
SmsHeader smsHeader = new SmsHeader();
823-
smsHeader.concatRef = concatRef;
834+
if (oddLengthHeader) {
835+
// Odd length header to verify correct UTF-16 header padding
836+
SmsHeader.MiscElt miscElt = new SmsHeader.MiscElt();
837+
miscElt.id = 0x27; // reserved for future use; ignored on decode
838+
miscElt.data = new byte[]{0x12, 0x34};
839+
smsHeader.miscEltList.add(miscElt);
840+
} else {
841+
// Even length header normally generated for concatenated SMS.
842+
SmsHeader.ConcatRef concatRef = new SmsHeader.ConcatRef();
843+
concatRef.refNumber = 0xEE;
844+
concatRef.msgCount = total;
845+
concatRef.seqNumber = index;
846+
concatRef.isEightBits = true;
847+
smsHeader.concatRef = concatRef;
848+
}
849+
byte[] encodeHeader = SmsHeader.toByteArray(smsHeader);
850+
if (oddLengthHeader) {
851+
assertEquals(4, encodeHeader.length); // 5 bytes with UDH length
852+
} else {
853+
assertEquals(5, encodeHeader.length); // 6 bytes with UDH length
854+
}
824855
UserData userData = new UserData();
825-
userData.payloadStr = "01234567";
856+
userData.payloadStr = payload;
826857
userData.userDataHeader = smsHeader;
827858
bearerData.userData = userData;
828859
byte[] encodedSms = BearerData.encode(bearerData);
829860
BearerData revBearerData = BearerData.decode(encodedSms);
830861
assertEquals(userData.payloadStr, revBearerData.userData.payloadStr);
862+
assertTrue(revBearerData.hasUserDataHeader);
863+
byte[] header = SmsHeader.toByteArray(revBearerData.userData.userDataHeader);
864+
if (oddLengthHeader) {
865+
assertEquals(4, header.length); // 5 bytes with UDH length
866+
} else {
867+
assertEquals(5, header.length); // 6 bytes with UDH length
868+
}
869+
assertTrue(Arrays.equals(encodeHeader, header));
831870
}
832871

833872
@SmallTest
@@ -881,7 +920,27 @@ public void testFragmentText() throws Exception {
881920
if (isCdmaPhone) {
882921
ArrayList<String> fragments = android.telephony.SmsMessage.fragmentText(text2);
883922
assertEquals(3, fragments.size());
923+
924+
for (int i = 0; i < 3; i++) {
925+
encodeDecodeAssertEquals(fragments.get(i), i + 1, 3, false);
926+
encodeDecodeAssertEquals(fragments.get(i), i + 1, 3, true);
927+
}
884928
}
885929

930+
// Test case for multi-part UTF-16 message.
931+
String text3 = sUnicodeChars + sUnicodeChars + sUnicodeChars;
932+
ted = SmsMessage.calculateLength(text3, false);
933+
assertEquals(3, ted.msgCount);
934+
assertEquals(189, ted.codeUnitCount);
935+
assertEquals(3, ted.codeUnitSize);
936+
if (isCdmaPhone) {
937+
ArrayList<String> fragments = android.telephony.SmsMessage.fragmentText(text3);
938+
assertEquals(3, fragments.size());
939+
940+
for (int i = 0; i < 3; i++) {
941+
encodeDecodeAssertEquals(fragments.get(i), i + 1, 3, false);
942+
encodeDecodeAssertEquals(fragments.get(i), i + 1, 3, true);
943+
}
944+
}
886945
}
887946
}

0 commit comments

Comments
 (0)