Программирование, мои исходники, PHP скрипты

  • Главная
    • Контакты
  • Загрузки
    • Мои разработки
    • Другой софт
    • PHP скрипты
  • Документы
    • Статьи
    • DELPHI
    • PHP
    • C++
    • Другое
  • Сервисы
    • Видео с yotube
    • ФОТО
    • МУЗЫКА




  • ВСЕ ЗАПИСИ

    • Определение своего IP адреса с помощью приложения на Delphi

    • Отправка post запросов с помощью Delphi

    • Скачиваем субтитры с youtube

    • Новый метод защиты на youtube от скачивания видео по прямым ссылкам

    • Получение информации о балансе на банковском счёте бесплатно, по СМС

    • Хостинг с PHP на домашнем компьютере с динамическим IP

    • Загрузка файлов с докачкой, с использованием компонента Indy 9-10

    • File Thingie - файловый менеджер для сайта

    • Отправка почты на Delphi через внешний smtp с использованием SSL-авторизации

    • POP3 клиент на Delphi (SSL, Indy 9)

    • Email с изображениями, встроенными в тело письма (Delphi)

    • SOCKS5 прокси на PHP через TOR

    • Скрипт листинга директорий

    • Отправка СМС через GSM модем

    • Получение СМС сообщений на компьютере и расшифровка PDU

     свернуть

    Статьи,заметки

    Получение СМС сообщений на компьютере и расшифровка PDU

    В интернете полно примеров и документации, по вопросу как раскодировать СМС из PDU в читаемый текст, здесь я постараюсь популярно объяснить, как это реализовано в моей программе. Ссылки на исходники для Delphi XE 5 есть в конце статьи, также имеется рабочий код для Delphi 7. Архив с кучей инфы по AT командам и спецификациями PDU можно загрузить по ссылке . Там много полезного, главное правильно переварить эти сведения и не запутаться ещё большесмайл, лично мне в процессе написания кода больше помог поиск по различным форумам и блогам.

    У себя на сайте выложил несколько полезных программ для конвертирования PDU: encode_pdu, PDUDecoder, и PDU Spy, а вот мои собственные утилиты для перекодировки:

    PDU Decoder - извлекает из строки PDU номер СЦ, номер телефона отправителя, время отправки и текст сообщения
    PDU2TEXT Decoder- конвертирует строку символов из 7bit default alphabet или UCS2 в текст.
    Архивы также содержат исходники с комментариями.

    Вот пример принятого СМС сообщения, в виде шестнадцатеричной последовательности (PDU), данные которые выдает в COM порт USB модем:

    07919730071111F1040B919781274894F7000861502012116121100414043E0441044204300432043A0430

    Чтобы раскодировать его нужно правильно интерпретировать значения полей, которые для наглядности подкрашены различными цветами.

    07 обозначает длину номера сервисного центра СМС , здесь номер это 9730071111F1  07- десятичное значение, применяем такой алгоритм: отнимаем единицу, получаем 6, шесть пар символов, это и будет номер.
    Либо так: семь умножить на два = 14, минус 2, получим 12, считываем 12 одиночных символов, получаем номер СЦ.

    91 следующее поле формат номера, 91 обозначает - международный, в большинстве случаев, можно сказать всегда, значение равно 91.

    9730071111F1 В этом поле находится номер СЦ. Чтобы его получить, следует разбить это поле на пары по 2 символа и в каждой паре поменять символы местами: 79 03 70 11 11 1F
    Если номер содержит нечётное количество цифр, в конце всегда будет символ F, удаляем его. Впереди дописываем плюс, и вот он, номер СЦ СМС: +79037011111.
    Следует иметь ввиду, что номер СЦ может и вовсе отсутствовать, я нашёл у себя в телефоне такое СМС: 00000C9144977389050000004160910213250023D9775D0EBABEE56C728B5AC6D341D0A413E4AEB7C56539283D07D56C39580D - здесь первые 2 поля (4 символа) имеют нулевые значения.

    04 Поле TP-MTI & Co, играет важную роль, его значение зависит от различных условий, например, включен ли на передающей стороне запрос отчета о доставке, является ли это сообщение multipart, то есть одной из частей длинного сообщения более 140 символов, разбитого на несколько частей, и др.На практике следует учитывать, что от значения этого поля зависит, есть ли в теле СМС заголовок перед пользовательскими данными, или он отсутствует, это нужно знать, чтобы определить, где в PDU находится непосредственно сам текст сообщения. В моей программе задействован следующий алгоритм: если значение поля равно 00, 04, или 24, значит заголовок отсутствует, если же значения равны 40, 44, или 64, то определяем длину заголовка, откусываем его от пользовательских данных и получаем текст сообщения.
    Про заголовки будет рассказано ниже.

    0B Шестнадцатеричное значение, переводим в десятичное, получаем 11. Это количество цифр в номере отправителя, если номер - цифровой, в данном примере номер содержится в этом поле 9781274894F7
    Но так как, согласно правилам стандарта PDU, если количество символов номера - нечетное, то в конец добавляется символ F, значит, чтобы получить правильное значение, считываем 11+1, то есть 12 символов.
    Номер также может иметь и алфавитно-цифровой формат, напр., Beeline или Tele2, тогда считываем именно указанное количесто символов в поле номера отправителя (в этом случае единицу не добавляем, даже если число нечетное). После конвертирования из 7- битной кодировки считанного значения получим имя отправителя. Вот пример сообщения от отправителя InternetSMS
    07919730071111F10014D04937BD2C7797E9D3E61400086140618064750014042D0442043E002004110418041B04100419041D
    В желтом поле - значение 14 в HEX (десятичное значение 20), значит 20 символов в красном поле содержат имя/номер отправителя, можно ввести его в прогу PDU2TEXT Decoder и увидеть результат.

    91 это поле обозначает тип номера, 91 - международный формат, 81 - нестандартный цифровой номер (как напр., в СМС-ках приходящих с коротких номеров), D0 - алфавитно-цифровое имя отправителя.

    9781274894F7 поле, где содержится номер, с которого отправлено СМС. В данном случае, это номер в международном формате, считываем его значение, определив сколько в нем цифр, из данных прописанных в этом поле - 0B, и обрабатываем по такому же алгоритму, как и номер СЦ: разбиваем поле на пары, и меняем местами символы в каждой паре, если последний символ будет F, - удаляем его. Добавляем плюс к номеру. Для данного примера наш номер это: +79187284497
    Для нестандартных номеров (тип 81) алгоритм точно такой же: вычисляем длину, меняем местами символы в парах, удаляем F, если он есть, только плюс впереди не ставим. Ниже пример сообщения от номера 679
    07919740430900F340038176F90008516091206173004A0500034E020204410430043904420435002000540065006C00650032002C002004400430043704340435043B002000AB041104350437043E043F04300441043D043E04410442044C00BB
    желтое поле - количество цифр (3), красное - тип номера (81), синее - сам номер (679)
    Случай, когда тип номера D0 (алфавитно-цифровой) был уже разобран ранее.

    00 для входящих СМС это поле всегда имеет нулевое значение, поэтому рассматривать его не буду.

    08 тип кодировки СМС. Возможные значения: 08, 18 - кодировка UCS2(юникод); 00, 10 - GSM 7-bit default alphabet (латиница и спецсимволы).

    61502012116121 время отправки (время, когда сообщение поступило в СЦ). Это поле всегда состоит из семи пар цифр. Алгоритм расшифровки: разбиваем на пары, в каждой паре меняем цифры местами,получим вот это:
    16 05 02 21 11 16 12 , что означает: дата: 2016 г. 5-й месяц(май) 2-е число, время: 21 час 11 мин. 16 сек. часовой пояс: 12(UTC+3)

    10 двухсимвольное поле, значение в шестнадцатеричном формате. Указывает длину USER DATA (пользовательские данные), в которых содержится текст сообщения. Подробно описывать это поле не буду, желающие могут почитать спецификации (ссылка есть в начале статьи). Для упрощения алгоритма в программе я считываю просто все оставшиеся после этого поля символы до конца строки, чтобы получить текст СМС, но заранее следует убедиться, что перед текстом отстутствует заголовок с дополнительными данными, иначе вместо текста получится нечитаемая мешанина из непонятных знаковсмайл
    Так как значение этого поля 04 равно 04, значит в данном примере заголовка нет и можно оставшиеся символы воспринимать как текст, иначе пришлось бы сначала вычислить, где заканчивается заголовок, и отделить его от текста.

    0414043E0441044204300432043A0430 это текст СМС. которое используется в примере. Так как это поле 08 означает, что сообщение использует кодировку UCS2, будем использовать соответствующую функцию

    Вот пример кода для юникодных версий Delphi:

    показать
     

    //----Decode PDU string----Функция для расшифровки юникода---------
    Function PDU2TEXT(ss:string):string;
    var
    i,n:Integer;
    t,s,letter:string;
    begin
    t:=ss;
    s:='';
    for i:=1 to Length(t)div 4 do
    begin
    letter:=Copy(t,1,4);
    n:=StrToInt('$'+letter);
    s:=s+Chr(n);
    Delete(t,1,4);
    end;
    Result:=s;
    end;
    //----Decode PDU string end-------------

    скрыть

    А это функция, работающая в Delphi 7:

    показать
     

    //----Decode PDU string----Функция для расшифровки юникода---------
    function UCSToAnsi(AStr:AnsiString):AnsiString;
    function Convert(ACnvStr:AnsiString):AnsiChar;
    var
    j:integer;
    begin
    j:= StrToIntDef('$'+ACnvStr,0);
    case j of
    1040..1103:j:= j - 848;
    1105:j:= 184;
    end;
    Result:= Chr(j);
    end;
    var
    c,i:integer;
    begin
    Result:= '';
    c:= Length(AStr) div 4;
    for i:= 0 to c - 1 do
    Result:= Result + Convert(Copy(AStr,i*4+1,4));
    end;
    //----Decode PDU string end-------------

    скрыть

    В результате получили текст СМС:  Доставка

    Теперь покажу алгоритм, как в поле USER DATA убрать дополнительный заголовок и найти текст сообщения.

    07919730071111F1440B919781532319F200086150803152732130050003F902020424043E0440043C043004420435042D0442043E04120442043E04400430044F0427043004410442044C

    Здесь видим, что поле 44 TP-MTI & Co имеет значение 44, что однозначно говорит о наличии заголовка в USER DATA. Поле 30 - длина пользовательских данных, пропускаем эти два символа, следующее поле, 05, которое я отметил розовым цветом, имеет значение 05. Оно указывает длину заголовка. Смотрим какую кодировку имеет СМС (это важно!), в этом примере у нас - 08, - кодировка юникод. Длина заголовка 05, и если кодировка СМС в юникоде, то пропускаем следующие пять пар символов, вот эти, 0003F90202, оставшиеся символы до конца строки, это и есть текст СМС
    0424043E0440043C043004420435042D0442043E04120442043E04400430044F0427043004410442044C вот этот текст:ФорматеЭтоВтораяЧасть

    Следующий пример

    07919730071111F1440B919750814364F30000615061819575215E050003FD0202F2A03B3A4C07A5E920F6FBBD9E83D8E97519247CBFE9A069794C7FCB41E7371D440EB7C3E73219347FB7CBE8F79D9506B5C3EEF0B94C06D1DF2079794E7FCBCB20EB9B5D6F9741E872985C9603

    Опять же видим, что в пользовательских данных имеется заголовок, поле 44 TP-MTI & Co имеет значение 44. Пропускаем два символа 5E, длина заголовка 05 равна 05. Смотрим, что кодировка сообщения здесь 7bit default alphabet, так как 00 , - это поле содержит нулевые значения. И в этом случае, (ВНИМАНИЕ!), если длина заголовка равна 05, а кодировка 7bit, то пропускаем следующие 6 (шесть!) пар символов (5+1), вот эти 0003FD0202F2. Оставшаяся часть строки A03B3A4C07A5E920F6FBBD9E83D8E97519247CBFE9A069794C7FCB41E7371D440EB7C3E73219347FB7CBE8F79D9506B5C3EEF0B94C06D1DF2079794E7FCBCB20EB9B5D6F9741E872985C9603 - это текст СМС: what it looks like Boot Sector got damaged somehow,i managed to restore Volume header

    Расшифровка 7-битной кодировки, - это целая песня, просто приведу здесь код модуля, с функцией Decode7bit(), которую я написал

    показать
     

    unit Decode7bitsA;
    interface
    uses
    SysUtils,StrUtils;
    function Decode7bit(pval:string):string;
    implementation
    const
    in_hex:array [1..128] of String=('00','01','02','03','04','05','06','07','08','09','0A','0B','0C','0D','0E','0F','10','11','12','13','14','15','16','17','18','19','1A','1B','1C','1D','1E','1F','20','21','22','23','24','25','26','27','28','29','2A','2B','2C','2D','2E','2F','30','31','32','33','34','35','36','37','38','39','3A','3B','3C','3D','3E','3F','40','41','42','43','44','45','46','47','48','49','4A','4B','4C','4D','4E','4F','50','51','52','53','54','55','56','57','58','59','5A','5B','5C','5D','5E','5F','60','61','62','63','64','65','66','67','68','69','6A','6B','6C','6D','6E','6F','70','71','72','73','74','75','76','77','78','79','7A','7B','7C','7D','7E','7F');
    in_letters:array [1..128] of String=('64','163','36','165','232','233','249','236','242','199','10','216','248','13','197','229','916','95','934','915','923','937','928','936','931','920','926','27','198','230','223','201','32','33','34','35','164','37','38','39','40','41','42','43','44','45','46','47','48','49','50','51','52','53','54','55','56','57','58','59','60','61','62','63','161','65','66','67','68','69','70','71','72','73','74','75','76','77','78','79','80','81','82','83','84','85','86','87','88','89','90','196','214','209','220','167','191','97','98','99','100','101','102','103','104','105','106','107','108','109','110','111','112','113','114','115','116','117','118','119','120','121','122','228','246','241','252','224');
    //--
    function HEX2BIN(HEX:string):string;
    var
    BIN:string;
    I:INTEGER;
    Error:BOOLEAN;
    begin
    Error:= False;
    BIN:= '';
    for I:= 1 to Length(HEX) do
    case UpCase(HEX[I]) of
    '0':BIN:= BIN + '0000';
    '1':BIN:= BIN + '0001';
    '2':BIN:= BIN + '0010';
    '3':BIN:= BIN + '0011';
    '4':BIN:= BIN + '0100';
    '5':BIN:= BIN + '0101';
    '6':BIN:= BIN + '0110';
    '7':BIN:= BIN + '0111';
    '8':BIN:= BIN + '1000';
    '9':BIN:= BIN + '1001';
    'A':BIN:= BIN + '1010';
    'B':BIN:= BIN + '1011';
    'C':BIN:= BIN + '1100';
    'D':BIN:= BIN + '1101';
    'E':BIN:= BIN + '1110';
    'F':BIN:= BIN + '1111';
    else
    Error:= True;
    end;
    if Error then
    HEX2BIN:= '0'
    else
    HEX2BIN:= BIN;
    end;
    //--
    function BIN2HEX(BIN:string):string;
    function SetHex(St:string; var Error:BOOLEAN):CHAR;
    var
    Ch:CHAR;
    begin
    if St = '0000' then
    Ch:= '0'
    else if St = '0001' then
    Ch:= '1'
    else if St = '0010' then
    Ch:= '2'
    else if St = '0011' then
    Ch:= '3'
    else if St = '0100' then
    Ch:= '4'
    else if St = '0101' then
    Ch:= '5'
    else if St = '0110' then
    Ch:= '6'
    else if St = '0111' then
    Ch:= '7'
    else if St = '1000' then
    Ch:= '8'
    else if St = '1001' then
    Ch:= '9'
    else if St = '1010' then
    Ch:= 'A'
    else if St = '1011' then
    Ch:= 'B'
    else if St = '1100' then
    Ch:= 'C'
    else if St = '1101' then
    Ch:= 'D'
    else if St = '1110' then
    Ch:= 'E'
    else if St = '1111' then
    Ch:= 'F'
    else
    Error:= True;
    SetHex:= Ch;
    end;
    var
    HEX:string;
    I:INTEGER;
    Temp:string[4];
    Error:BOOLEAN;
    begin
    Error:= False;
    if BIN = '0' then
    HEX:= '0'
    else
    begin
    Temp:= '';
    HEX:= '';
    if Length(BIN) mod 4 <> 0 then
    repeat
    BIN:= '0' + BIN;
    until Length(BIN) mod 4 = 0;
    for I:= 1 to Length(BIN) do
    begin
    Temp:= Temp + BIN[I];
    if Length(Temp) = 4 then
    begin
    HEX:= HEX + SetHex(Temp,Error);
    Temp:= '';
    end;
    end;
    end;
    if Error then
    BIN2HEX:= '0'
    else
    BIN2HEX:= HEX;
    end;
    //--
    function Decode7bit(pval:string):string;
    var
    txt,s,s1,s2,s3:string;
    t,t1,symbol:string;
    d,d1,d2:string;
    i,n,n1:Integer;
    nc:Char;
    Label
    a;
    begin
    txt:=pval;
    s2:='';
    for i:=1 to Length(txt)div 2 do
    begin
    s1:=Copy(txt,1,2);
    s:=HEX2BIN(s1);
    Insert(s,s2,1);
    Delete(txt,1,2);
    end;
    d2:='';
    s3:=ReverseString(s2);
    for i:=1 to Length(s3)div 7 do
    begin
    d1:=Copy(s3,1,7);
    d1:=ReverseString(d1);
    d:=BIN2HEX(d1);
    Insert(d,d2,1);
    Delete(s3,1,7);
    end;
    t:='';
    for i:= 1 to length(d2) div 2 do
    begin
    t1:=Copy(d2,1,2);
    for n:=1 to 128 do
    begin
    if in_hex[n]= t1 then
    begin
    symbol:= in_letters[n];
    n1:=StrToInt(symbol);
    nc:=Chr(n1);
    if (n1=64) and (i=1) then goto a;
    Insert(nc,t,1);
    a:
    break;
    end;
    end;
    Delete(d2,1,2);
    end;
    t:=StringReplace(t,Chr(27)+Chr(10),Chr(10),[rfReplaceAll]);//Form feed
    t:=StringReplace(t,Chr(27)+Chr(923),Chr(94),[rfReplaceAll]);//Circumflex
    t:=StringReplace(t,Chr(27)+Chr(60),Chr(91),[rfReplaceAll]);//Left square bracket
    t:=StringReplace(t,Chr(27)+Chr(62),Chr(93),[rfReplaceAll]);//Right square bracket
    t:=StringReplace(t,Chr(27)+Chr(40),Chr(123),[rfReplaceAll]);//Left curly bracket
    t:=StringReplace(t,Chr(27)+Chr(41),Chr(125),[rfReplaceAll]);//Right curly bracket
    t:=StringReplace(t,Chr(27)+Chr(47),Chr(92),[rfReplaceAll]);//Backslash
    t:=StringReplace(t,Chr(27)+Chr(61),Chr(126),[rfReplaceAll]);//Tilde
    t:=StringReplace(t,Chr(27)+Chr(161),Chr(124),[rfReplaceAll]);//Vertical bar
    t:=StringReplace(t,Chr(27)+Chr(101),Chr(8364),[rfReplaceAll]);//Euro sign
    Result:=t;
    end;
    end.

    скрыть

    На вход функции передаём строку символов в кодировке 7bit, на выходе получаем текст СМС в читаемом виде. Функция корректно обрабатывает текст, по возможности даже удаляет терминальный ноль в конце строки, к примеру в других PDU конвертерах имя отправителя, допустим, Beeline, отображается как Beeline@, или Beelinex, (не фильтруется символ конца строки), в моей функции же предусмотрена обработка таких ситуаций.

    Программа испытывалась в работе с модемом ZTE-MF180, в теории она совместима и с другими моделями. Даю ссылки на исходники для тех, кто захочет, добавить в прогу дополнительный фукционал, например, возможность выполнять на компьютере какие-либо действия по командам через СМС. Если будут вопросы и предложения, пишем в комментарии.

    скачать рабочий исходник для RAD Studio
    скачать рабочий исходник для Delphi 7

    Добавлено: май 2016

    ©Veterock

© 2013-2023 Design by VETEROCK