Skip to content

Instantly share code, notes, and snippets.

@wjx
Last active August 29, 2015 14:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save wjx/c85c7a2cc45fecc5ec29 to your computer and use it in GitHub Desktop.
Save wjx/c85c7a2cc45fecc5ec29 to your computer and use it in GitHub Desktop.
Android sms parse
frameworks/base/telephony/java/com/android/internal/telephony/GsmAlphabet.java
static {
int numTables = sLanguageTables.length;
sCharsToGsmTables = new SparseIntArray[numTables];
for (int i = 0; i < numTables; i++) {
String table = sLanguageTables[i];
int tableLen = table.length();
if (tableLen != 0 && tableLen != 128) {
Rlog.e(TAG, "Error: language tables index " + i +
" length " + tableLen + " (expected 128 or 0)");
}
SparseIntArray charToGsmTable = new SparseIntArray(tableLen);
sCharsToGsmTables[i] = charToGsmTable;
for (int j = 0; j < tableLen; j++) {
char c = table.charAt(j);
charToGsmTable.put(c, j);
}
}
}
/**
* Converts a String into a byte array containing
* the 7-bit packed GSM Alphabet representation of the string.
*
* Byte 0 in the returned byte array is the count of septets used
* The returned byte array is the minimum size required to store
* the packed septets. The returned array cannot contain more than 255
* septets.
*
* @param data the text to convert to septets
* @param startingSeptetOffset the number of padding septets to put before
* the character data at the beginning of the array
* @param throwException If true, throws EncodeException on invalid char.
* If false, replaces unencodable char with GSM alphabet space char.
* @param languageTable the 7 bit language table, or 0 for the default GSM alphabet
* @param languageShiftTable the 7 bit single shift language table, or 0 for the default
* GSM extension table
* @return the encoded message
*
* @throws EncodeException if String is too large to encode
*/
public static byte[] stringToGsm7BitPacked(String data, int startingSeptetOffset,
boolean throwException, int languageTable, int languageShiftTable)
throws EncodeException {
int dataLen = data.length();
int septetCount = countGsmSeptetsUsingTables(data, !throwException,
languageTable, languageShiftTable);
int byteCount = ((septetCount * 7) + 7) / 8;
byte[] ret = new byte[byteCount + 1]; // Include space for one byte length prefix.
SparseIntArray charToLanguageTable = sCharsToGsmTables[languageTable];
SparseIntArray charToShiftTable = sCharsToShiftTables[languageShiftTable];
for (int i = 0, septets = startingSeptetOffset, bitOffset = startingSeptetOffset * 7;
i < dataLen && septets < septetCount;
i++, bitOffset += 7) {
char c = data.charAt(i);
int v = charToLanguageTable.get(c, -1);
if (v == -1) {
v = charToShiftTable.get(c, -1); // Lookup the extended char.
if (v == -1) {
if (throwException) {
throw new EncodeException("stringToGsm7BitPacked(): unencodable char");
} else {
v = charToLanguageTable.get(' ', ' '); // should return ASCII space
}
} else {
packSmsChar(ret, bitOffset, GSM_EXTENDED_ESCAPE);
bitOffset += 7;
septets++;
}
}
packSmsChar(ret, bitOffset, v);
septets++;
}
ret[0] = (byte) (septetCount); // Validated by check above.
return ret;
}
/**
* Pack a 7-bit char into its appropriate place in a byte array
*
* @param packedChars the destination byte array
* @param bitOffset the bit offset that the septet should be packed at
* (septet index * 7)
* @param value the 7-bit character to store
*/
private static void
packSmsChar(byte[] packedChars, int bitOffset, int value) {
int byteOffset = bitOffset / 8;
int shift = bitOffset % 8;
packedChars[++byteOffset] |= value << shift;
if (shift > 1) {
packedChars[++byteOffset] = (byte)(value >> (8 - shift));
}
}
/*----------------------------------------------------------------------*/
/**
* Convert a GSM alphabet 7 bit packed string (SMS string) into a
* {@link java.lang.String}.
*
* See TS 23.038 6.1.2.1 for SMS Character Packing
*
* @param pdu the raw data from the pdu
* @param offset the byte offset of
* @param lengthSeptets string length in septets, not bytes
* @param numPaddingBits the number of padding bits before the start of the
* string in the first byte
* @param languageTable the 7 bit language table, or 0 for the default GSM alphabet
* @param shiftTable the 7 bit single shift language table, or 0 for the default
* GSM extension table
* @return String representation or null on decoding exception
*/
public static String gsm7BitPackedToString(byte[] pdu, int offset,
int lengthSeptets, int numPaddingBits, int languageTable, int shiftTable) {
StringBuilder ret = new StringBuilder(lengthSeptets);
try {
boolean prevCharWasEscape = false;
String languageTableToChar = sLanguageTables[languageTable];
String shiftTableToChar = sLanguageShiftTables[shiftTable];
for (int i = 0 ; i < lengthSeptets ; i++) {
int bitOffset = (7 * i) + numPaddingBits;
int byteOffset = bitOffset / 8;
int shift = bitOffset % 8;
int gsmVal;
gsmVal = (0x7f & (pdu[offset + byteOffset] >> shift));
// if it crosses a byte boundary
if (shift > 1) {
// set msb bits to 0
gsmVal &= 0x7f >> (shift - 1);
gsmVal |= 0x7f & (pdu[offset + byteOffset + 1] << (8 - shift));
}
if (prevCharWasEscape) {
if (gsmVal == GSM_EXTENDED_ESCAPE) {
ret.append(' '); // display ' ' for reserved double escape sequence
} else {
char c = shiftTableToChar.charAt(gsmVal);
if (c == ' ') {
ret.append(languageTableToChar.charAt(gsmVal));
} else {
ret.append(c);
}
}
prevCharWasEscape = false;
} else if (gsmVal == GSM_EXTENDED_ESCAPE) {
prevCharWasEscape = true;
} else {
ret.append(languageTableToChar.charAt(gsmVal));
}
}
} catch (RuntimeException ex) {
Rlog.e(TAG, "Error GSM 7 bit packed: ", ex);
return null;
}
return ret.toString();
}
/**
* GSM default 7 bit alphabet plus national language locking shift character tables.
* Comment lines above strings indicate the lower four bits of the table position.
*/
private static final String[] sLanguageTables = {
/* 3GPP TS 23.038 V9.1.1 section 6.2.1 - GSM 7 bit Default Alphabet
01.....23.....4.....5.....6.....7.....8.....9.....A.B.....C.....D.E.....F.....0.....1 */
"@\u00a3$\u00a5\u00e8\u00e9\u00f9\u00ec\u00f2\u00c7\n\u00d8\u00f8\r\u00c5\u00e5\u0394_"
// 2.....3.....4.....5.....6.....7.....8.....9.....A.....B.....C.....D.....E.....
+ "\u03a6\u0393\u039b\u03a9\u03a0\u03a8\u03a3\u0398\u039e\uffff\u00c6\u00e6\u00df"
// F.....012.34.....56789ABCDEF0123456789ABCDEF0.....123456789ABCDEF0123456789A
+ "\u00c9 !\"#\u00a4%&'()*+,-./0123456789:;<=>?\u00a1ABCDEFGHIJKLMNOPQRSTUVWXYZ"
// B.....C.....D.....E.....F.....0.....123456789ABCDEF0123456789AB.....C.....D.....
+ "\u00c4\u00d6\u00d1\u00dc\u00a7\u00bfabcdefghijklmnopqrstuvwxyz\u00e4\u00f6\u00f1"
// E.....F.....
+ "\u00fc\u00e0",
};
frameworks/opt/telephony/src/java/com/android/internal/telephony/gsm/SmsMessage.java
/**
* TS 27.005 3.4.1 lines[0] and lines[1] are the two lines read from the
* +CMT unsolicited response (PDU mode, of course)
* +CMT: [&lt;alpha>],<length><CR><LF><pdu>
*
* Only public for debugging
*
* {@hide}
*/
public static SmsMessage newFromCMT(String[] lines) {
try {
SmsMessage msg = new SmsMessage();
msg.parsePdu(IccUtils.hexStringToBytes(lines[1]));
return msg;
} catch (RuntimeException ex) {
Rlog.e(LOG_TAG, "SMS PDU parsing failed: ", ex);
return null;
}
}
/**
* TS 27.005 3.1, &lt;pdu&gt; definition "In the case of SMS: 3GPP TS 24.011 [6]
* SC address followed by 3GPP TS 23.040 [3] TPDU in hexadecimal format:
* ME/TA converts each octet of TP data unit into two IRA character long
* hex number (e.g. octet with integer value 42 is presented to TE as two
* characters 2A (IRA 50 and 65))" ...in the case of cell broadcast,
* something else...
*/
private void parsePdu(byte[] pdu) {
mPdu = pdu;
// Rlog.d(LOG_TAG, "raw sms message:");
// Rlog.d(LOG_TAG, s);
PduParser p = new PduParser(pdu);
mScAddress = p.getSCAddress();
if (mScAddress != null) {
if (VDBG) Rlog.d(LOG_TAG, "SMS SC address: " + mScAddress);
}
// TODO(mkf) support reply path, user data header indicator
// TP-Message-Type-Indicator
// 9.2.3
int firstByte = p.getByte();
mMti = firstByte & 0x3;
switch (mMti) {
// TP-Message-Type-Indicator
// 9.2.3
case 0:
case 3: //GSM 03.40 9.2.3.1: MTI == 3 is Reserved.
//This should be processed in the same way as MTI == 0 (Deliver)
parseSmsDeliver(p, firstByte);
break;
case 1:
parseSmsSubmit(p, firstByte);
break;
case 2:
parseSmsStatusReport(p, firstByte);
break;
default:
// TODO(mkf) the rest of these
throw new RuntimeException("Unsupported message type");
}
}
private void parseSmsDeliver(PduParser p, int firstByte) {
mReplyPathPresent = (firstByte & 0x80) == 0x80;
mOriginatingAddress = p.getAddress();
if (mOriginatingAddress != null) {
if (VDBG) Rlog.v(LOG_TAG, "SMS originating address: "
+ mOriginatingAddress.address);
}
// TP-Protocol-Identifier (TP-PID)
// TS 23.040 9.2.3.9
mProtocolIdentifier = p.getByte();
// TP-Data-Coding-Scheme
// see TS 23.038
mDataCodingScheme = p.getByte();
if (VDBG) {
Rlog.v(LOG_TAG, "SMS TP-PID:" + mProtocolIdentifier
+ " data coding scheme: " + mDataCodingScheme);
}
mScTimeMillis = p.getSCTimestampMillis();
if (VDBG) Rlog.d(LOG_TAG, "SMS SC timestamp: " + mScTimeMillis);
boolean hasUserDataHeader = (firstByte & 0x40) == 0x40;
parseUserData(p, hasUserDataHeader);
}
private static class PduParser {
byte mPdu[];
int mCur;
SmsHeader mUserDataHeader;
byte[] mUserData;
int mUserDataSeptetPadding;
PduParser(byte[] pdu) {
mPdu = pdu;
mCur = 0;
mUserDataSeptetPadding = 0;
}
/**
* returns non-sign-extended byte value
*/
int getByte() {
return mPdu[mCur++] & 0xff;
}
/**
* Interprets the user data payload as packed GSM 7bit characters, and
* decodes them into a String.
*
* @param septetCount the number of septets in the user data payload
* @return a String with the decoded characters
*/
String getUserDataGSM7Bit(int septetCount, int languageTable,
int languageShiftTable) {
String ret;
ret = GsmAlphabet.gsm7BitPackedToString(mPdu, mCur, septetCount,
mUserDataSeptetPadding, languageTable, languageShiftTable);
mCur += (septetCount * 7) / 8;
return ret;
}
/**
* Interprets the user data payload as UCS2 characters, and
* decodes them into a String.
*
* @param byteCount the number of bytes in the user data payload
* @return a String with the decoded characters
*/
String getUserDataUCS2(int byteCount) {
String ret;
try {
ret = new String(mPdu, mCur, byteCount, "utf-16");
} catch (UnsupportedEncodingException ex) {
ret = "";
Rlog.e(LOG_TAG, "implausible UnsupportedEncodingException", ex);
}
mCur += byteCount;
return ret;
}
boolean moreDataPresent() {
return (mPdu.length > mCur);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment