Различные сервисы, использующие SMS уведомления или SMS авторизацию, с каждым днем набирают популярность, т.к. это действительно очень удобно с точки зрения пользователя и достаточно безопасно с точки зрения разработчиков. Большинство компаний, предоставляющие сервис SMS рассылок, предлагают свои API для реализации процесса отправки (как правило на основе HTTP), но у всех есть стандартный вариант подключения — протокол SMPP. Мой опыт работы с пользователями SMPP протокола показывает, что они испытываю довольно много сложностей. Причиной этого (по моему мнению) является бездумное использование готовых библиотек и в результате полное непонимание что происходит «под капотом».

В этой статье я опишу один из самых частых вопросов от клиентов — «Как прочитать текст входящего сообщения? Я нашёл on-line декодер, но он показывает абракадабру». Дело в том, что у некоторых сервисов есть не только возможность отправки сообщений, но и получения на них ответных сообщений. Однако алгоритм, описанный мной, применим к любому действию в SMPP протоколе, т.к. по сути является инструкцией к спецификации протокола.

Разбор входящего сообщения без использования какого-либо SMPP ПО


Нам потребуются:

  • SMPP Protocol Specification v3.4
  • Wireshark
  • Блокнот
  • Браузер

Я не буду описывать процесс получения самого cap файла (дампа) SMPP обмена, т.к. уже есть много статей на эту тему (например эта).

Общий порядок обмена пакетами в SMPP выглядит следующим образом:



Как видно в случае входящего сообщения нас интересует пакет deliver_sm. Определение типа пакета происходит по полю command_id, которое в случае deliver_sm имеет значение 0x00000005 (пункт 5.1.2.1 в спецификации).

В итоге фильтр в wireshark выглядит так:

smpp.command_id == 0x00000005

После того как нашли пакет по фильтру копируем его содержимое в HEX формате:



Итоговая строка, которую предстоит расшифровать:

00000048000000050000000000000003000101636F6F6E6F6C616E642E727500010137393132333435363738390000000000000000 0800000424000c043f044004380432043504424

Пункт, описывающий формат пакета deliver_sm, в спецификации, имеет номер 4.6.1 “DELIVER_SM” Syntax. В этом пункте имеется таблица, в которой расписывается каждое поле пакета:



Приступим к разбору пакета

Первое поле пакета — command_length имеет размер 4 октета (столбец size octets) и тип Integer. Это означает что от начала нашей HEX строки необходимо отсчитать 4 х 2 = 8 символов.

Почему умножаем на 2?
Мы скопировали строку в шестнадцатеричном формате который кодирует один октет (байт) двумя символами.

00 00 00 48 000000050000000000000003000101636F6F6...

(HEX) 00000048 -> (DEC) 72 октета или 144 символа — длина всего SMPP пакета.

Следующее поле command_id — аналогично 4 октета и Integer.

00000048 00 00 00 05 0000000000000003000101636F6F6...

Преобразуем в читаемый вид: 00000005 = deliver_sm



Поля command_status и sequence_number так же имею по 4 октета. Где посмотреть их значения, я предполагаю Вы уже догадываетесь.

0000004800000005 00 00 00 00 00 00 00 03 000101636F6F6...

Поле service_type может содержать максимум 6 октетов и имеет тип C-Octet String. В пункте 3.1 SMPP PDU — Type Definitions сказано:

C-Octet String A series of ASCII characters terminated with the NULL character

Другими словами это последовательность ASCII символов с нулевым октетом как знак окончания поля. Т.е. это поле может содержать максимум 6 символов, но может быть и меньше — конец данного поля обозначается HEX 00. В данном конкретном примере мы сразу видим 00 — поле не содержит ни какого значения.

00000048000000050000000000000003 00 0101636F6F6...

Если бы этот столбец был заполнен, то расшифровать его можно по таблице ASCII символов.

Пропустим следующие 2 поля source_addr_ton и source_addr_npi, т.к. как их расшифровать уже ясно и разберем еще одно поле с типом C-Octet String — source_addr.

Source_addr может содержать максимум 21 октет, но в данном случае оно состоит из 13 октетов включая нулевой октет который означает окончание поля:

63 6F 6F 6E 6F 6C 61 6E 64 2E 72 75 00

В поисковике делаем запрос «ascii table» и начинаем расшифровывать:



63 = c
6F = o
6F = o
6E = n
6F = o
6C = l
61 = a
6E = n
64 = d

И так далее до символов 00.

Остальные поля я тоже пропускаю — их много и их определение ни чем не отличается от выше описанного. Перейдём к полю data_coding, без значения данного поля мы не сможем прочитать текст сообщения.

Я разделил все поля в HEX строке примера, а поле data_coding выделил жирным и вот что получилось:
00000047;00000005;00000000;00000003;00;01;01;373932393937333630333000;01;01;373932333235303039353900;00;00;00;00;00;00;00;
08;00;00;0424;000c;043f04400438043204350442

Пункт 5.2.19 спецификации указывает что используется кодировка UCS2 (ISO/IEC-10646). (HEX) 08 -> (BIN) 00001000 -> UCS2 (ISO/IEC-10646):



Ищем таблицу символов по запросу: «ucs2 code chart»

043f04400438043204350442

Выбираем первые 2 октета текста «043f» и ищем их на странице



Продолжаем выделять по 2 октета и искать:
[р] 0440 CYRILLIC SMALL LETTER ER
[и] 0438 CYRILLIC SMALL LETTER I
[в] 0432 CYRILLIC SMALL LETTER VE
[е] 0435 CYRILLIC SMALL LETTER IE
[т] 0442 CYRILLIC SMALL LETTER TE

На этом разбор пакета окончен. Надеюсь что описание получилось понятное и достаточно полное. В целом если у Вас получилось осознать как прочитать сообщение, то отправить его не составит ни какого труда. Поменяется несколько полей.

Читайте спецификации — там почти все уже написано.

Комментарии (7)


  1. Vamp
    30.09.2015 19:55

    Есть ещё склеенные из нескольких частей сообщения и message_payload TLV. В первом случае первые 6-7 байт поля short_message будут содержать UDH, определяющий порядок склейки, а во втором случае сообщение будет находиться в другом месте.

    Ручная работа с символами и их кодами лучше всего организована на unicode-table.com.


    1. ilicho
      01.10.2015 07:47

      Спасибо, за замечание. Я не раскрыл эту особенность.
      Одним из признаков того что в deliver_sm использован message_payload является поле sm_length = 0.


  1. sunnybear
    01.10.2015 00:08

    даешь онлайн-сервис по расшифровке дампа SMPP сообщений!



  1. ivnik
    01.10.2015 18:34
    +1

    А зачем это всё делать, если wireshark итак умеет показывать декодированный smpp pdu по полям?


  1. vp7
    02.10.2015 00:51

    В чём глубинный смысл? Как писали выше — wireshark великолепно и сам всё декодирует (у меня, правда, не получилось уговорить его декодировать русский текст из UCS2, но это частности).

    Единственное разумное объяснение — у автора есть софт, который в логи пишет hex dump отправленных/полученных PDU'шек и есть необходимость декодировать эти SMS'кт.
    Тогда цель исследования ясна — сначала разобраться самому, а потом написать парсер.

    По поводу длинных SMS — вероятность получить от оператора текст в message_payload существует, но она достаточно низка.
    А вот UDH заголовки для длинной SMS'ки вы будете получать практически гарантированно.


    1. ilicho
      02.10.2015 07:33

      Глубинный смысл показать что библиотеки, которые реализуют SMPP, не содержат ни какой магии и в случае проблем вполне можно самостоятельно выяснить что именно пошло не так и исправить. Второе значение — повышение качества запросов к техподдержке компаний, предоставляющих подключение по SMPP, все мы знаем как приятно грамотно указать на чужие ошибки :)