Обмен данными и вопросы кодирования
Для восьмого входного символа все семь значащих бит записываются в 7-й байт результата. После каждого перехода к следующему символу во входной строке необходимо сохранить текущий выходной байт в переменную tmpVall, чтобы "освободить" для следующего шага переменную tmpVal2:
if (shift != 7) { ... tmpVal1 = tmpVal2; }
Такое перемещение не надо выполнять только для каждого восьмого входного символа, когда все семь значащих бит сохраняются в предыдущий выходной байт, а текущий оказывается незаполненным (shift = 7).
Как уже было отмечено, кодирование происходит в один проход. В результате выполнения функции получается не бинарный код, реально отправляемый в SMS, а его шестнадцатеричное представление, которое можно передать GSM-модему посредством терминала. По-этому каждый выходной байт представляется в виде пары шестнадцатеричных цифр:
res[ j] = decToHex(tmpVal1 / 16); res[ j + 1] = decToHex(tmpVal1 % 16);
Функция decToHex преобразует десятичную цифру в шестнадцатеричную.
char decToHex(unsigned char val) { if ( (val >= 0) && (val <= 9) ) { return (char)(val + (unsigned char)'0'); } else { if ( (val >= 10) && (val <= 15) ) { return (char)(val + (unsigned char)'A' - 10); } else { return 0; } } }
Деление десятичного числа на 16 дает первый разряд шестнадцатеричного числа, а остаток от деления - второй. Отметим, что в выходную строку записывается не текущий получаемый выходной байт (tmpVal2), а предыдущий - tmpVal1, так как к нему в старшие биты уже добавлены биты следующего входного символа (получаемые как curNum << (8 - shift)). Поэтому после прохождения всей входной строки в цикле while (i < len) {...} в переменной tmpVal2 остается последний формируемый выходной символ, который в конце цикла записывается в tmpVal1. Его приходится дополнительно кодировать в шестнадцатеричное представление после завершения цикла:
res[j] = decToHex(tmpVal1 / 16); res[j + 1] = decToHex(tmpVal1 % 16); res[j + 2] = '\0';
В конец строки согласно правилам представления строки в языке Си дописывается символ '\0 '.
Теперь рассмотрим функцию декодирования сообщения decodeFromPDU:
void decodeFromPDU(char *str, char *res) { int len; int i, j; int shift; unsigned char prevNum, d1, d2, curNum, curRes; j = 0; len = length(str); i = 1; prevNum = 0; while(str[i-1]) { if(str[i]) { d1 = hexToDec(str[i-1]); d2 = hexToDec(str[i]); curNum = d1*16 + d2; shift = ((((i + 1) / 2)-1) % 7); if(((shift) == 0)&&(i>1)) { prevNum = prevNum & 127; res[j++] = prevNum; /* special code for symbol @ */ if (res[(j-1)] == 0) {res[(j-1)] = (unsigned char)'@';} prevNum = (curNum >> (7 - shift)); } curRes = (curNum << shift) + prevNum; curRes = curRes & 127; res[j++] = curRes; /* special code for symbol @ */ if (res[(j-1)] == 0) {res[(j-1)] = (unsigned char)'@';} prevNum = (curNum >> (7 - shift)); } else { break; } i+=2; } if(shift == 6) { prevNum = prevNum & 127; /* special code for symbol @ */ if (res[(j-1)] == 0) {res[(j-1)] = (unsigned char)'@';} res[j++] = prevNum; } res[j] = '\0'; }
Она также последовательно движется по входной строке str, в которой закодированный текст SMS-сообщения представлен в шестнадцатеричном текстовом виде.
Поэтому шаг равен двум (на каждой итерации цикла прохода по строке str выполняется i+=2) и очередные два символа переводятся в один байт кода SMS:
d1 = hexToDec(str[i-1]); d2 = hexToDec(str[i]); curNum = d1*16 + d2;
Функция hexToDec переводит шестнадцатеричную текстовую цифру в десятичное число:
unsigned char hexToDec(char hexDigit) { if ((hexDigit >= '0') && (hexDigit <= '9')) { return (unsigned char)hexDigit - (unsigned char)'0'; } else { if ((hexDigit >= 'A') && (hexDigit <= 'F')) { return (unsigned char)hexDigit - (unsigned char)'A' + 10; } else { return 0; } } }
Далее рассчитывается сдвиг, на который в текущем кодированном байте сдвинут код текущего символа: shift =( (((i + 1) / 2) -1) % 7);
Здесь ((i + 1) / 2) дает номер входного байта (i - это счетчик шестнадцатеричных символов, начинающийся с 0, на каждый входной кодированный байт занято две шестнадцатеричные цифры).
На первом шаге (i=1) сдвиг равен нулю, на втором он равен 1, для 7-го входного кодированного байта (i=13) сдвиг равен 6. Для 8-го входного байта сдвиг снова равен 0, так как в его 7-ми младших байтах полностью содержится 9-й выходной символ. А 8-й выходной символ хранится в 7-ми старших битах 7-го входного байта.
На каждом 7-м входном байте, для которого сдвиг оказывается равен 0, надо сделать двойную обработку - "вынуть" символ из предыдущего байта с учетом одного бита из текущего байта и "вынуть" символ из текущего байта, для чего дополнительно выполняется следующий код:
if(((shift) == 0)&&(i>1)) { prevNum = prevNum & 127; res[j++] = prevNum; /* special code for symbol @ */ if (res[(j-1)] == 0) {res[(j-1)] = (unsigned char)'@';} prevNum = (curNum >> (7 - shift)); }
Следует отметить, что если количество байт кода кратно восьми, то на последнем шаге обрабатывается лишь один младший бит последнего байта кода, а сдвиг равен шести. "Вынимание" последнего закодированного символа (из первых семи бит последнего байта кода) по алгоритму выполняется отдельно, поэтому надо после цикла сформировать последний символ:
if(shift == 6) { prevNum = prevNum & 127; /* special code for symbol @ */ if (res[(j-1)] == 0) {res[(j-1)] = (unsigned char)'@';} res[j++] = prevNum; } res[j] = '\0';
В GSM кодировка символов определяется стандартом GSM 03.38 и несколько отличается от стандартной таблицы ASCII. В частности, символ '@' имеет нулевой код и совпадает с кодом конца строки в языке Си. Из-за этого в приведенном примере символ с кодом '\0' заменяется символом '@':
if (res[ (j-1)] == 0) {res[ (j-1)] = (unsigned char)'@';}
А при работе с кодировкой GSM 03.38 на языке Си необходимо знать длину строки, иначе в случае восьми символов текста, в которых последний символ равен '@', последний значащий байт строки равен нулю, что соответствует символу конца строки, и он не будет корректно обрабатываться стандартными функциями как значащий.
В случае когда необходимо передать символы, отсутствующие в GSM 03.38, используется Unicode, в котором один символ кодируется двумя байтами. В Unicode можно хранить сразу несколько алфавитов, но в этом случае 140 байт вмещают лишь 70 символов текста. Поэтому если в SMS-сообщении содержатся русские буквы (которых нет в таблице GSM 03.38), то оно кодируется в формате Unicode и содержит максимум 70 символов. Если же использовать только английский алфавит, то, как уже было показано, в сообщении можно использовать целых 160 символов!
Вопросы и задачи для самостоятельного решения
- Придумайте и опишите протокол общения двух программ при игре "Морской бой". Предусмотрите упаковку координат (X, Y) на поле боя в один байт 16-битовой команды протокола.
- Реализуйте программу кодирования (шифрования) и декодирования сообщения по алгоритму Цезаря (каждая буква сообщения заменяется на другую букву алфавита, циклически сдвинутую на позицию буквы ключа).