Недавно занимался задачей декодирования входящих СМС, т.к. готовых алгоритмов не нашел, пришлось курить спецификацию GSM 03.38 и заголовочник «RIL.h».
Начну с того, что СМС может кодироваться 3мя методами:
— Метод кодирования «По умолчанию» (Default alphabet) — использует 7 бит на символ, длина СМС считается в символах.
— Метод кодирования «UCS2» — использует 16 бит на символ, длина СМС считается в октетах.
— Метод кодирования «8-ми битные данные, определенные пользователем» (8 bit data is user defined) — использует 8 бит на символ, длина СМС считается в октетах.
Для того, что бы декодировать данные, надо сначала знать как они кодируются :).
Метод «По умолчанию»
Для кодирования (упаковки) методом «По умолчанию», используется следующий алгоритм:
Если символ «q» представить в виде последовательности 7-ми бит:
cpp |copy code |?
1 b7 b6 b5 b4 b3 b2 b12 qa qb qc qd qe qf qg
то упаковка 1го 7ми битного символа в октет заключается в дополнении до октета путем вставки бита «0» слева.
Пример упаковки:
— 1го символа в 1 октет:
cpp |copy code |?
1 7 6 5 4 3 2 1 02 0 1a 1b 1c 1d 1e 1f 1g
— 2х символов в 2 октета:
cpp |copy code |?
1 7 6 5 4 3 2 1 02 2g 1a 1b 1c 1d 1e 1f 1g3 0 0 2a 2b 2c 2d 2e 2f4
— 3х символов в 3 октета:
cpp |copy code |?
1 7 6 5 4 3 2 1 02 2g 1a 1b 1c 1d 1e 1f 1g3 3f 3g 2a 2b 2c 2d 2e 2f4 0 0 0 3a 3b 3c 3d 3e
и т.д.
Визуально можно отобразить так:
Допустим сообщение содержит 10 символов и выглядит следующим образом: «1234567890»
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 0 |
---|---|---|---|---|---|---|---|---|---|
31h | 32h | 33h | 34h | 35h | 36h | 37h | 38h | 39h | 30h |
0110001 | 0110010 | 0110011 | 0110100 | 0110101 | 0110110 | 0110111 | 0111000 | 0111001 | 0110000 |
0110001 | 0110010 | 0110011 | 0110100 | 0110101 | 0110110 | 0110111 | 0111000 | 0111001 | 0110000 |
Первый септет «1» превращается в октет путем добавления правого бита второго септета. Этот бит вставляется слева: 0 + 0110001 = 00110001 (31h). Правый бит второго символа выбрасывается, после чего второму символу (септету), что бы стать октетом, нужно добавить 2 бита (красные), взятые из 3го символа. Этот процесс повторяется до конца строки символов.
00110001 | 11011001 | 10001100 | 01010110 | 10110011 | 11011101 | 01110000 | 00111001 | 00011000 | |
31 | D9 | 8C | 56 | B3 | DD | 70 | 39 | 18 |
Получилось 9 октетов: 31 D9 8C 56 B3 DD 70 39 18
Как же теперь распаковать эти октеты для того, что бы получить в каждом байте по символу?
Надо всего лишь сделать обратное преобразование 🙂
Я набросал алгоритм (критика приветствуется :)), который преобразует входящую последовательность в набор 8ми байтовых символов или в стандарт ASCII:
cpp |copy code |?
01 void ConvertTo(BYTE* Msg, DWORD cchMsgLength, TCHAR* _Dest, DWORD DestSize, DWORD DataCoding)02 {03 //переменная, в которой мы храним число, переносимое в другой октет при сжатии04 //например во второй таблице в первой колонке выделенный "0"05 BYTE t = 0;06 //переменная, указывающая на сколько надо сдвинуть текущий октет, что бы получить "чистое значение "t"07 //инициализируем в 7, т.к. в первом байте нам надо взять 1 бит, а значит байт надо сдвинуть на 7 бит вправо08 int R = 7;09 //переменная, хранящая предыдущее значение "t"10 BYTE p = 0;11 //переменная служит для очистки "не своих" бит в октете, а так же для получения "чистого значения"12 //инициализируется в 0, т.к. первый октет у нас имеет актуальное значение если выбросить старший бит.13 int L = 0;14 //индекс, по которому мы вставляем распакованные байты в приемник.15 int index = 0;16 17 //создаем массив байт для приема раскодированных данных18 BYTE* Drft = new BYTE[DestSize];19 //в цикле перебираем все октеты входящей последовательности20 for (DWORD i = 0; i < cchMsgLength; i++)21 {22 //как Вы заметили из выше опубликованной таблицы, при упаковке теряется 1 октет, а сдвиги повторяются каждые 7 раз23 //по этому как только R достигнет нуля, мы по новой инициализируем переменные R и L24 //и в приемный массив кладем сформировавшийся за это время "лишний" септет (превращенный в байт)25 if (R == 0)26 {27 R = 7;28 L = 0;29 Drft[index] = p;30 index++;31 p = 0;32 }33 //сдвигаем i-й байт сообщения вправо на R бит, что бы вычленить число, относящееся к следующему септету34 t = Msg[i]>>R;35 //2 строки ниже "чистят" i-й байт от выше вычленного числа и помещают в приемный буфер36 Drft[index] = (Msg[i]<<(L+1));37 Drft[index] = (Drft[index]>>L);38 //приемный буфер совмещается с ранее вычлененным числом39 Drft[index] |= p;40 p = t;41 R--;42 L++;43 index++;44 }45 delete[] Drft;46 }47
Таким нехитрым алгоритмом мы преобразовали сжатую последовательность в последовательность байт или обычный массив элементов char.
Метод «UCS2»
Алгоритм прост — думаем что входящий массив — это массив слов, меняем местами байты в слове и получаем Юникод:
cpp |copy code |?
1 {2 WORD* Drft = (WORD*)Msg;3 WORD s = 0;4 for (DWORD i = 0; i < cchMsgLength; i++)5 {6 s = (Drft[i]>>8) | (Drft[i]<<8);7 _Dest[i] = s;8 }
Удачи!
4 comments on “Алгоритм декодирования СМС (SMS 7-bit to 8-bit decoder )”
Alex
23 октября 2013 at 3:02Приветствую!
Большое спасибо за статью! Очень помогла!
Но в строке 37 есть ошибка:
Drft[index] = (Drft[index]>>L);
Тут L надо заменить на 1, так как обратно сдвигаем всегда на 1:
Drft[index] = (Drft[index]>>1);
Lebets_VI
23 октября 2013 at 11:11Здравствуйте, Alex.
Вы правы. Действительно в представленном коде есть ошибка.
В рабочем коде вместо строк 36 и 37 написано вот так:
Alex
13 декабря 2013 at 6:56Не сочтите меня занудой, но нашел еще одну ошибку:
после 30 строки необходимо дописать p = 0, иначе после каждого цикла буква будет неверной.
Т.е. получится так:
if (R == 0)
{
R = 7;
L = 0;
Drft[index] = p;
index++;
p = 0;
}
А по поводу того, что код дебильный — я не согласен! 🙂 Потому как у меня лично после этих сдвигов голова начинает болеть 😉 И с нуля я бы долго вкуривал как правильно реализовать данный алгоритм, но Ваша статья меня просто спасла! Удивительно, но в интернете мало чего толкового нашел по данному вопросу.
Так что ещё раз СПАСИБО автору огромное за данный труд! 🙂
Lebets_VI
13 декабря 2013 at 8:51> после 30 строки необходимо дописать p = 0
Согласен с Вами.