Разборка SMS и отправка на e-mail
Добавлено: 30 май 2020, 10:56
Второй релиз моего скрипта по пересылке входящих sms на почту в первозданном виде.
Начало было положено здесь.
Решил создать отдельную тему, т.к. скрипт перешёл границу простой пересылки и превратился в полноценный парсинг тела sms(PDU).
PDU – формат в котором путешествует сообщение по сети. Был написан в прошлом веке и пытался экономить каждый бит сообщения, что не очень ему и удалось, все старания были сведены на нет при выходе на международный рынок и использованию многобайтовой кодировки UCS2. А вот проблемы с разложением появились у всех программистов. Все последующие стандарты унаследовали его архитектуру(EMS, MMS, CBM).
Что умеет скрипт на данный момент:
- Раскладывает на аргументы.
- Поддерживаются кодировки alphabet default и UCS2
- Поддерживаются форматы номера – Unknown, International number, National number, Alphanumeric . Без форматирования.
- Умеет объединять сообщения с кодом объединения 0x00 и 0x08
- Дожидается блоки сообщения, если они пришли не все или переполнена сим карта.
Что не умеет:
- Не обрабатывает сжатие
- Работает, только с кодовой схемой по умолчанию
- Обрабатывает только входящие sms
Большинство модемов для обработки и хранения sms могут использовать, только память sim карты. Современные карты хранят 5 sms. Поведение модемов при заполнении памяти может различаться, и иногда настраиваться. Желательно своевременно очищать память во избежание потери блоков, объединённых смс (запускать скрипт по расписанию). Из-за ограничения размера скриптов в OS, код пришлось разбить на несколько функций.
Для начала заполните профиль в tool e-mail вашего роутера, что бы последний мог свободно отправлять письма.
Создаём скрипты
содержимое PDUtoEMAIL
содержимое extractSmsModem
содержимое functionMTI
содержимое functionPDU
Имена дочерних скриптов не меняем, т.к. они вызываются по потребности из главного.
Скрипт с функцией извлечения “extractSmsModem” подлежит редактированию под конкретный модем. Изучайте список AT команд для своего конкретного модема. Текущий представленный образец работает с LTE и PPP интерфейсами, поддерживаемыми AT команды по умолчанию.
В главном скрипте в начале содержится список переменных для настройки поведения скрипта.
Теперь для тех кто любит извращения – дело в том, что в PDU могут присутствовать разные флаги, задающие поведение sms. Например, флаг class, который может указать – не сохранять sms в память, а отобразить на дисплее. В случае отсутствия дисплея, стандартом оговорено - принимающая сторона сама решает поведение такого sms. Тоже самое происходит и с CBM(cell broadcast message), если модем способен их обрабатывать. Для тех кто не знает, это особый вид PDU, для многоадресной рассылки, например – сеть оператора может передаёт информацию о наступающей грозе в конкретной местности (пора выдирать роутер из розетки). Так вот по факту, модемы просто вываливают подобные PDU в терминал и забывают о них, а ROS просто их игнорирует.
Вариантов не много – постоянно мониторить. Для тех модемов, у которых есть свободный порт с терминалом, надо обрабатывать каждое сообщение от модема и в случаи нахождения соответствие нужного идентификатора, обрабатывать сообщения. Второй способ более универсальный и подходит для всех модемов у которых есть терминал (LTE и т.д.) – надо включить логирование обмена ROS с модемом (/system loging -> lte, ppp, и т.д.) и дальше парсить уже этот лог на предмет принятых PDU.
P.S. В скрипте встроена поддержка, если вы её не отключите, то я наберу примеры для дополнения не поддерживаемых функции и реализую их. Отправка сработает, только, если главный цикл получит sms и не сможет разобрать его. (не все концепции оговоренные в 3GPP у меня поддерживаются из-за отсутствия образцов) . Если есть проблеммы с извлечением из модема, то ни кому ни чего не отправляется.
За время отладки, только от одного пользователя пришли отладочные конверты. Они и были добавлены в скрипт. По сему в данный момент считаю, что проблем ни у кого с парсингом не возникает.
P.S по глюкам смотрим здесь
Начало было положено здесь.
Решил создать отдельную тему, т.к. скрипт перешёл границу простой пересылки и превратился в полноценный парсинг тела sms(PDU).
PDU – формат в котором путешествует сообщение по сети. Был написан в прошлом веке и пытался экономить каждый бит сообщения, что не очень ему и удалось, все старания были сведены на нет при выходе на международный рынок и использованию многобайтовой кодировки UCS2. А вот проблемы с разложением появились у всех программистов. Все последующие стандарты унаследовали его архитектуру(EMS, MMS, CBM).
Что умеет скрипт на данный момент:
- Раскладывает на аргументы.
- Поддерживаются кодировки alphabet default и UCS2
- Поддерживаются форматы номера – Unknown, International number, National number, Alphanumeric . Без форматирования.
- Умеет объединять сообщения с кодом объединения 0x00 и 0x08
- Дожидается блоки сообщения, если они пришли не все или переполнена сим карта.
Что не умеет:
- Не обрабатывает сжатие
- Работает, только с кодовой схемой по умолчанию
- Обрабатывает только входящие sms
Большинство модемов для обработки и хранения sms могут использовать, только память sim карты. Современные карты хранят 5 sms. Поведение модемов при заполнении памяти может различаться, и иногда настраиваться. Желательно своевременно очищать память во избежание потери блоков, объединённых смс (запускать скрипт по расписанию). Из-за ограничения размера скриптов в OS, код пришлось разбить на несколько функций.
Для начала заполните профиль в tool e-mail вашего роутера, что бы последний мог свободно отправлять письма.
Создаём скрипты
Код: Выделить всё
/system script add name="PDUtoEMAIL"
/system script add name="extractSmsModem"
/system script add name="functionPDU"
/system script add name="functionMTI"
Код: Выделить всё
# author pepelxl 2020.04
# Адрес для отправки сообщений. Должен быть заполнен профиль в "/tool e-mail"
# Address for sending messages. The profile in "/ tool e-mail" must be filled
:local emailAdr "example@mail.ru"
# Поля заголовков в теле сообщения, можно указать на национальных языках в кодах UTF-8. Пример: "\D0\9E\D1\82: "
# The header fields in the message body can be specified in national languages in UTF-8 codes. Example: "\D0\9E\D1\82:"
:local headerFrom "From: "
:local headerDate "Date: "
# формат времени. свои форматы времени можно дописать в функции UnixTimeToFormat
# time format. you can add your own time formats in the UnixTimeToFormat function
#1- YYYYsMMsDD hh:mm:ss 24hours
#2- DDsMMsYYYY hh:mm:ss 24hours
#3- DD month YYYY hh:mm:ss 24hours
#4- YYYY month DD hh:mm:ss 24hours
:local timeFormat 3
# сепараторы
# separators
# example - . / " "
:local dateSeparator "."
# gmt true - time zone added; false - time zone is included in the main format !!! - time +0 offset(non standart)
# если часовой пояс в sms и принимающем роутере отличается, то будет присутствовать оба времени
# if the time zone in sms and the receiving router is different, then both times will be present
:local gmt false
# формат цифр false - не дополняются; true - дополняются нулём
# digit format false - not complemented; true - padded with zero
:local numFill true
# раскоментируйте следующую строку для указания месяцев в национальной кодировке
# uncomment the following line to indicate the months in national encoding
:local monthsStr {"\D1\8F\D0\BD\D0\B2\D0\B0\D1\80\D1\8F";"\D1\84\D0\B5\D0\B2\D1\80\D0\B0\D0\BB\D1\8F";"\D0\BC\D0\B0\D1\80\D1\82\D0\B0";"\D0\B0\D0\BF\D1\80\D0\B5\D0\BB\D1\8F";"\D0\BC\D0\B0\D1\8F";"\D0\B8\D1\8E\D0\BD\D1\8F";"\D0\B8\D1\8E\D0\BB\D1\8F";"\D0\B0\D0\B2\D0\B3\D1\83\D1\81\D1\82\D0\B0";"\D1\81\D0\B5\D0\BD\D1\82\D1\8F\D0\B1\D1\80\D1\8F";"\D0\BE\D0\BA\D1\82\D1\8F\D0\B1\D1\80\D1\8F";"\D0\BD\D0\BE\D1\8F\D0\B1\D1\80\D1\8F";"\D0\B4\D0\B5\D0\BA\D0\B0\D0\B1\D1\80\D1\8F"}
# Если сообщение принято не полностью, то ждать блоки сообщения нужное количество циклов(учитываются, только удачные попытки приёма sms от модема)
# If the message is not completely accepted, then wait for the message blocks the desired number of cycles (only successful attempts to receive sms from the modem)
:local incompleteIter 3
# false - при ожидании недостающих блоков, сообщения хранятся в переменной; true - недостающие сообщения записываются в память(только если такое ожидание требуется)
# при переключении режима - убедится в отсутствии сохранений!
# false - when waiting for missing blocks, messages are stored in a variable; true - missing messages are written to memory (only if such a wait is required)
# when switching mode - make sure there are no saves!
:local incompleteSave true
# если выставлено incompleteSave true, то переменная задаёт имя файла в котором будет хранится база
# if incompleteSave true is set, then the variable sets the name of the file in which the database will be stored
:local nameFilePDU "basePDU"
# задаёт имя файла для хранения неотправленных SMS
# sets the file name for storing unsent SMS
:local nameFileSMS "baseSMS"
# отправляет в лог результаты разборки PDU для отладки
# sends to the log the results of disassembling PDUs for debugging
:local debugPduParse false
# отправка сбойных образцов автору скрипта
# sending failed samples to the script author
:local sendDebug true
############ Далее ничего не меняем!!! ###########
############ Next, do not change anything !!! ####
do {/system script run extractSmsModem
} on-error={:log error "no run function"; :error}
:global extractSmsModem
:local extracted [$extractSmsModem]
:local emailBody
:local debugEmail
:local toSave [:toarray ""]
:local flagSave
:local flagExtracted false
:if ([/file find name~"$nameFileSMS"] != "") do={:set $flagSave true} else={:set $flagSave false}
:local identity [/system identity get name]
do {
# если нет возврата от функции извлечения - прекращаем скрипт
:if (([:len ($extracted->"arr")] = 0) and ([:len ($extracted->"errorStr")] = 0)) do={throw;}
# если от функции получена ошибка - шлем email с ошибкой
:if ([:len ($extracted->"errorStr")] > 0) do={:set $emailBody ($extracted->"errorStr"); :set $flagExtracted true}
# если получены pdu, извлекаем в переменную
:if ([:len ($extracted->"arr")] > 0) do={:set $extracted ($extracted->"arr"); :set $flagExtracted true}
} on-error={:if (!$flagSave) do={:set $extractSmsModem; :error}
}
do {
/system script run functionPDU
} on-error={:set $extractSmsModem; :log error "no run function"; :error}
:global exitFunctionPDU
:global saveFile
:global errorFlagParse
:global convertAddress
:global convertBodyPDU
:global convertScts
:global sendMailUTF8
# если есть сохранения и нет извлечённых sms; повторяем попытку отправки
:if ($flagSave and !$flagExtracted) do={:if ([$sendMailUTF8 head=("Inbox SMS to ".$identity) emailBody=[/file get $nameFileSMS contents] emailAdr=$emailAdr]) do={
/file remove $nameFileSMS}
$exitFunctionPDU; :error}
do {
/system script run functionMTI
} on-error={$exitFunctionPDU; :log error "no run function"; :error}
:global mti0
:local timeStruct {"timeFormat"=$timeFormat;"dateSeparator"=$dateSeparator;"gmt"=$gmt;"numFill"=$numFill;monthsStr=[:toarray ""]}
:if (([:typeof $monthsStr] = "array") and ([:len $monthsStr] = 12)) do={:set ($timeStruct->"monthsStr") $monthsStr}
# массив для хранения SMS
:local structSMS [:toarray ""]
#извлекаем сохранённые сообщения
:global savePDU
:if ($incompleteSave) do={:if ([/file find name~"$nameFilePDU"] != "") do={
:set $savePDU [/file get $nameFilePDU contents]
:set $savePDU [[:parse "({$savePDU})"]]}}
:if ([:len $savePDU] > 0) do={
#проверяем на повторяемость
:for itCompar from=0 to=([:len $savePDU] -1) do={
:for itC from=0 to=([:len $extracted] -1) do={
:if (($savePDU->$itCompar->"pdu") = ($extracted->$itC->"pdu")) do={
:set $emailBody ($emailBody."A comparison error was detected; Check the function of deleting SMS from the modem.\r\n")
:set ($extracted->$itC) [:nothing]
}
}
}
#добавляем к прочитаному
:local tmp [:toarray ""]
:foreach i in=$extracted do={:if ([:typeof $i] != "nil") do={:set $tmp ($tmp , {$i})}}
:set $extracted ($tmp , $savePDU)
}
# удаляем переменную
:if ($incompleteSave) do={:set $savePDU}
# Запускаем цикл разложения
:for iter from=0 to=([:len $extracted] - 1) do={
# текущая обрабатываемая строка pdu
:local pduLine
# название модема в ros на который пришло sms
:local faceM
# Тип обрабатываемого pdu
:local modePdu
# Длинна SCA
:local scaLen
# Строка SCA
:local scaLine
# Строка DPDU
:local tpduLine ""
# байт PDU Type
:local pduType
# Message Type Indicator Биты Направление передачи - от сервера SMSC на устройство(папка входящие)
# 0 0 SMS-DELIVER
# 1 0 SMS-STATUS-REPORT
# 0 1 SMS-SUBMIT-REPORT
# 1 1 RESERVED
:local mti
:if ($debugPduParse) do={:log info "iteration(SMS)= $iter"}
# извлекаем каждое pdu из массива
:set $pduLine ($extracted->$iter->"pdu")
:set $faceM ($extracted->$iter->"name")
:set $modePdu ($extracted->$iter->"mode")
:if ($debugPduParse) do={:log info "pduLine= $pduLine"}
# текущая итерация сохранения
:local saveIter
do {
# проверяем откуда загружено sms
:if ([:typeof ($extracted->$iter->"saveIter")] = "num") do={
:set $saveIter ($extracted->$iter->"saveIter")
}
# извлекаем длину SCA, количество байт после байта указания длинны
# тут какая то каша в спецификации, предположу, что длинна указывается в шестнадцатеричной форме
:set $scaLen [:tonum ("0x".[:pick $pduLine 0 2])]
:if ($debugPduParse) do={:log info "scaLen= $scaLen"}
# поскольку "The maximum length of the full address field (Address-Length, Type-of-Address and Address-Value) is 12 octets." делаем проверку извлечения
:if ($scaLen > 11) do={:set $emailBody ($emailBody."Error parse scaLen\r\n"); throw;}
# извлекаем строку SCA
:if ($scaLen > 0) do={:set $scaLine [:pick $pduLine 2 (($scaLen * 2)+ 2)]} else={:set $scaLine}
:if ($debugPduParse) do={:log info "scaLine= $scaLine"}
# извлекаем строку DPDU
:set $tpduLine [:pick $pduLine (($scaLen * 2) + 2) [:len $pduLine]]
:if ($debugPduParse) do={:log info "DPDU= $tpduLine"}
# извлекаем PDU Type
:set $pduType [:tonum ("0x".[:pick $tpduLine 0 2])]
:if ($debugPduParse) do={:log info "PDU Type= $pduType"}
# извлекаем Message Type Indicator
:set $mti ($pduType & 3)
:if ($debugPduParse) do={:log info "Message Type Indicator= $mti"}
# проверяем тип сообщения
:if ($mti = 0) do={
:local tmpAr [$mti0 pduType=$pduType debugPduParse=$debugPduParse tpduLine=$tpduLine]
:if ($errorFlagParse) do={:set $emailBody ($emailBody.$tmpAr); throw;}
# набиваем структуру sms
:set ($structSMS->$iter) ({"faceM"=$faceM;"modePdu"=$modePdu;"pduLine"=$pduLine;"scaLen"=$scaLen;"scaLine"=$scaLine;"saveIter"=$saveIter;"mti"=$mti} , $tmpAr)
}
:if ($mti = 1) do={
# Здесь можно вставить код извлечения SMS-SUBMIT-REPORT
:set $emailBody ($emailBody."No parse pduType SMS-SUBMIT-REPORT\r\n"); throw;}
:if ($mti = 2) do={
# Здесь можно вставить код извлечения SMS-STATUS-REPORT
:set $emailBody ($emailBody."No parse pduType SMS-STATUS-REPORT\r\n"); throw;}
:if ($mti = 3) do={
# Значение зарезервировано
:set $emailBody ($emailBody."Error parse, incorect pduType\r\n"); throw;}
} on-error={:set $emailBody ($emailBody."PDU: $pduLine \r\n\r\n")
:if ($sendDebug) do={:set $debugEmail ($debugEmail.$pduLine."\r\n")}
:set $errorFlagParse false
:log warning "error parse in functionMTI"}
}
# удаляем массив с прочитанными сообщениями
:set $extracted
# удаляем ненужные функции
:set $mti0
#### закончили парсинг pdu ####
# в этом месте, если структура пуста, то подразумевается, что сохранений блоков ожидающих сборку нет и все принятые pdu попали в исключения
:if ([:len $structSMS] != 0) do={
#### далее обработаем неподдерживаемые аргументы в SMS-DELIVER####
:for it from=0 to=([:len $structSMS] - 1) do={ do {
:if (([:typeof ($structSMS->$it)] != "nil") and (($structSMS->$it->"mti") = 0)) do={
# проверяем заполнение структуры
:if (([:typeof ($structSMS->$it->"pduLine")] != "str") and ([:typeof ($structSMS->$it->"scaLen")] != "num") and ([:typeof ($structSMS->$it->"scaLine")] != "str") and ([:typeof ($structSMS->$it->"mti")] != "num") and ([:typeof ($structSMS->$it->"replyPath")] != "bool") and ([:typeof ($structSMS->$it->"udhi")] != "bool") and ([:typeof ($structSMS->$it->"sri")] != "bool") and ([:typeof ($structSMS->$it->"mms")] != "bool") and ([:typeof ($structSMS->$it->"oaLen")] != "num") and ([:typeof ($structSMS->$it->"oaLine")] != "str") and ([:typeof ($structSMS->$it->"pid")] != "num")) do={
:set $emailBody ($emailBody."Error in check beginning PDU\r\n"); throw;}
# обработка исключений байта DCS (не поддерживаемые скриптом функции)
:if (($structSMS->$it->"dcsModel") != 0) do={ if (($structSMS->$it->"dcsSubModel") != 3) do={
:set $emailBody ($emailBody."Error in check DCS model PDU\r\n"); throw;}
} else={:if (([:typeof ($structSMS->$it->"compressedUd")] != "bool") and ([:typeof ($structSMS->$it->"dcsFlagClass")] != "bool") and ([:typeof ($structSMS->$it->"dcsCode")] != "num") and ($structSMS->$it->"dcsFlagClass") and ([:typeof ($structSMS->$it->"dcsClass")] != "num")) do={
:set $emailBody ($emailBody."Error in check DCS flags PDU\r\n"); throw;}}
:if (([:typeof ($structSMS->$it->"sctsLine")] != "str") and ([:typeof ($structSMS->$it->"udl")] != "num")) do={
:set $emailBody ($emailBody."Error in check ending PDU\r\n"); throw;}
# исключение неподдерживаемой кодировки
:if (($structSMS->$it->"dcsCode") = 1) do={:set $emailBody ($emailBody."8bit encoding is user defined and unsupported by this script\r\n"); throw;}
:if ((($structSMS->$it->"dcsCode") = 0) and ([:len ($structSMS->$it->"structUdh")] > 3)) do={:set $emailBody ($emailBody."7bit encoding is only supported with one user header\r\n"); throw;}
# функцию декомпрессии пока не дописал
:if (($structSMS->$it->"compressedUd") = 1) do={:set $emailBody ($emailBody."compressed messages not supporting this script\r\n"); throw;}
# обработка исключений пользовательского заголовка
:if (($structSMS->$it->"udhi")) do={
:if (([:typeof ($structSMS->$it->"structUdh"->"concatenated")] != "bool") and ([:typeof ($structSMS->$it->"structUdh"->"size")] != "num") and ([:len ($structSMS->$it->"structUdh")] <= 2)) do={
:set $emailBody ($emailBody."Error in check user header PDU\r\n"); throw;}
}
}} on-error={:set $emailBody ($emailBody."PDU: ".($structSMS->$it->"pduLine")."\r\n\r\n")
:if ($sendDebug) do={:set $debugEmail ($debugEmail.($structSMS->$it->"pduLine")."\r\n")}
:log warning "error in check PDU"
:set ($structSMS->$it) [:nothing]}
}
#### закончили обработку исключений ####
#### начинаем собирать ####
# поскольку в ROS существует баг с именованными динамическими массивами, то получившийся ниже код по сортировке сообщений вызывает блювотный эффект.
:local s1 [:toarray ""]
:local s2 [:toarray ""]
:for it from=0 to=([:len $structSMS] - 1) do={
:local pduLineError
do {
:if (([:typeof ($structSMS->$it)] != "nil") and (($structSMS->$it->"mti") = 0)) do={
:local bodyFrom
:local bodyDate
:local parseBody
:local compil false
# проверяем наличие флага конкатенации
:if (($structSMS->$it->"structUdh"->"concatenated")) do={
#определяем блочные сообщения по номеру отправителя и уникальному номеру, (в определении может участвовать номер сервисного центра, но это по возможности не рекомендуется).
# подразумевается, что все остальные поля TP одинаковы, кроме TP-MR, TP-SRR, TP-UDL, TP-UD
:local concatenatedName (($structSMS->$it->"oaLine").($structSMS->$it->"udhOctet1"))
:local findName true
:local findNum
#проверяем - уже есть часть sms
:for i from=0 to=([:len $s1] -1) do={:if (($s1->$i) = $concatenatedName) do={:set $findName false; :set $findNum $i}}
# если нет то добавляем
:if ($findName) do={:set $s1 ($s1 , $concatenatedName)
:for i from=0 to=([:len $s1] -1) do={:if (($s1->$i) = $concatenatedName) do={:set $findNum $i}}
# создаём шапку
:set ($s2->$findNum) {"sizeSMS"=($structSMS->$it->"udhOctet2");"sizeCurent"=0}
}
:set ($s2->$findNum->($structSMS->$it->"udhOctet3")) {"udText"=($structSMS->$it->"udText");"pduLine"=($structSMS->$it->"pduLine");"iterFirstStruct"=$it}
:set ($s2->$findNum->"sizeCurent") (($s2->$findNum->"sizeCurent") +1)
# если найдены все части, запускаем сборку
:if (($s2->$findNum->"sizeCurent") = ($s2->$findNum->"sizeSMS")) do={ do {
:set $bodyFrom [$convertAddress instring=($structSMS->$it->"oaLine")]
:if ($errorFlagParse) do={:set $emailBody ($emailBody.$bodyFrom); throw;}
:set $bodyDate [$convertScts sctsLine=($structSMS->$it->"sctsLine") timeStruct=$timeStruct headerDate=$headerDate]
:if ($errorFlagParse) do={:set $emailBody ($emailBody.$bodyDate); throw;}
:local iter8 false
:if (!($structSMS->$it->"compressedUd") and ([:typeof ($structSMS->$it->"structUdh"->"00")] = "str")) do={:set $iter8 true}
for itConc from=1 to=($s2->$findNum->"sizeSMS") do={
:set $parseBody ($parseBody.[$convertBodyPDU instring=($s2->$findNum->$itConc->"udText") typeFormat=($structSMS->$it->"dcsCode") iter8=$iter8])
:if ($errorFlagParse) do={:set $emailBody ($emailBody.$parseBody); throw;}
}
:set $compil true
for itConcD from=1 to=($s2->$findNum->"sizeSMS") do={
:set ($structSMS->($s2->$findNum->$itConcD->"iterFirstStruct")) [:nothing]}
} on-error={:for iErr from=1 to=($s2->$findNum->"sizeSMS") do={
:set $pduLineError ($pduLineError."PDU: ".($s2->$findNum->$iErr->"pduLine")."\r\n")
:set ($structSMS->($s2->$findNum->$iErr->"iterFirstStruct")) [:nothing]}
throw;}}
} else={
# обработка одиночных sms
:set $bodyFrom [$convertAddress instring=($structSMS->$it->"oaLine")]
:if ($errorFlagParse) do={:set $emailBody ($emailBody.$bodyFrom); :set $pduLineError ("PDU: ".($structSMS->$it->"pduLine")."\r\n"); throw;}
:set $bodyDate [$convertScts sctsLine=($structSMS->$it->"sctsLine") timeStruct=$timeStruct headerDate=$headerDate]
:if ($errorFlagParse) do={:set $emailBody ($emailBody.$bodyDate); :set $pduLineError ("PDU: ".($structSMS->$it->"pduLine")."\r\n"); throw;}
:set $parseBody [$convertBodyPDU instring=($structSMS->$it->"udText") typeFormat=($structSMS->$it->"dcsCode")]
:if ($errorFlagParse) do={:set $emailBody ($emailBody.$parseBody); :set $pduLineError ("PDU: ".($structSMS->$it->"pduLine")."\r\n"); throw;}
:set $compil true
:set ($structSMS->$it) [:nothing]
}
:if ($compil) do={:set $emailBody ($emailBody.$headerFrom.$bodyFrom."\r\n".$bodyDate.$parseBody."\r\n\r\n")}
}} on-error={:set $emailBody ($emailBody.$pduLineError."\r\n")
:if ($sendDebug) do={:set $debugEmail ($debugEmail.$pduLineError."\r\n")}
:log warning "error in compile E-mail"
:set $errorFlagParse false
:set ($structSMS->$it) [:nothing]}}
# обрабатываем оставшиеся sms
:for itW from=0 to=([:len $structSMS] - 1) do={
:if ([:typeof ($structSMS->$itW)] != "nil") do={
:if ([:typeof ($structSMS->$itW->"saveIter")] != "num") do={:set ($structSMS->$itW->"saveIter") ($incompleteIter & 255)
} else={:set ($structSMS->$itW->"saveIter") (($structSMS->$itW->"saveIter") - 1)}
:if (($structSMS->$itW->"saveIter") <= 0) do={
:set $emailBody ($emailBody."Incomplete or unknown PDU: ".($structSMS->$itW->"pduLine")."\r\n\r\n")
} else={
:if ($incompleteSave) do={
:if ([:len $toSave] != 0) do={:set $toSave ($toSave.";")}
:set $toSave ($toSave."{\"pdu\"=\"".($structSMS->$itW->"pduLine")."\";\"name\"=\"".($structSMS->$itW->"faceM")."\";\"saveIter\"=".($structSMS->$itW->"saveIter").";\"mode\"=\"".($structSMS->$itW->"modePdu")."\"}")
} else={:set $toSave ($toSave , {{"pdu"=($structSMS->$itW->"pduLine");"name"=($structSMS->$itW->"faceM");"saveIter"=($structSMS->$itW->"saveIter");"mode"=($structSMS->$itW->"modePdu")}})
}}
:set ($structSMS->$itW) [:nothing]
}}
}
# проверяем наличие не отправленных sms
:if ($flagSave) do={:set $emailBody ($emailBody.[/file get $nameFileSMS contents])}
# отправляем e-mail
:if ([:len $emailBody] > 0) do={:if ([$sendMailUTF8 head=("Inbox SMS to ".$identity) emailBody=$emailBody emailAdr=$emailAdr]) do={
:if ($flagSave) do={/file remove $nameFileSMS}} else={[$saveFile nameFile=$nameFileSMS bodyFile=$emailBody]}
}
# сохраняем оставшиеся SMS
:if ([:len $toSave] > 0) do={
:if ($incompleteSave) do={[$saveFile nameFile=$nameFilePDU bodyFile=$toSave]
} else={:set $savePDU $toSave}} else={
:if ([/file find name~"$nameFilePDU"] != "") do={/file remove $nameFilePDU}
:set $savePDU}
:if ($sendDebug and ([:len $debugEmail] > 0)) do={/tool e-mail send to="supscriptpdu@mail.ru" subject="Error parse" body=$debugEmail}
# удаляем глобальные переменные
$exitFunctionPDU
Код: Выделить всё
:global extractSmsModem do={
# Функция не принимает аргументов.
# возвращает массив
# строку errorString текст ошибки, если таковая присутствует
# вложенный массив со значениями - pdu-name modem
# поддержка двух симочных моделей не реализована
# концепция скрипта подразумевает возможную потерю pdu при отсутствии поддержки команд модемом
# вложенная функция запросов
:local chat do={
:local t {"r"="";"f"=""}
:if ($1 = "lte") do={:set ($t->"r") ([/interface lte at-chat $2 input=$3 wait=yes as-value]->"output")}
:if ($1 = "ppp-client") do={:set ($t->"r") ([/interface ppp-client at-chat $2 input=$3 as-value]->"output")}
:if (($t->"r")~"(^|\n)OK(\$|\r)" != true) do={:set ($t->"f") true} else={:set ($t->"f") false}
:return $t
}
# массив с найдеными модемами
:local nameFind [:toarray ""]
# ищем модемы lte
:foreach i in=[/interface lte find] do={
if ([/interface lte get $i value-name=disabled] = false) do={
:local tmp
do {:set $tmp [/interface lte monitor $i once as-value]
} on-error={:set $tmp [/interface lte info $i once as-value]}
:set $nameFind ($nameFind , {{"name"=[/interface lte get $i value-name=name];"type"="lte";"manufacturer"=($tmp->"manufacturer");"model"=($tmp->"model");"revision"=($tmp->"revision")}})
}}
# ищем модемы ppp-client
:foreach i in=[/interface ppp-client find] do={
if ([/interface ppp-client get $i value-name=disabled] = false) do={
:local manufacturer
:set $nameFind ($nameFind , {{"name"=[/interface ppp-client get $i value-name=name];"type"="ppp-client"}})
}}
:if ([:len $nameFind] = 0) do={:return "No found Modem"}
# объявляем возврат с обязательной инициализацией
:local output [[:parse "({\"errorStr\"=[:tostr \"\"];\"arr\"=[:toarray \"\"]})"]]
# опрашиваем все модемы по очереди
:foreach m in=$nameFind do={
:local tmp
:local tmp2
:local stStart
:local stEnd
:local mode
do {
# устанавливаем режим ЭХО
:set $tmp [$chat ($m->"type") ($m->"name") "ATE0"]
:set $tmp [$chat ($m->"type") ($m->"name") "ATV1"]
# проверяем режим
:set $tmp [$chat ($m->"type") ($m->"name") "AT+CMGF?"]
:if (($tmp->"f")) do={:set $tmp2 "wrong answer to AT+CMGF\r\n"; throw;}
:set $stStart [:find ($tmp->"r") "+CMGF"]
:if ([:typeof $stStart] != "num") do={:set $tmp2 "wrong answer to AT+CMGF\r\n"; throw;}
:set $stEnd [:find ($tmp->"r") "\r\n" $stStart]
:if ([:typeof $stEnd] != "num") do={:set $tmp2 "wrong answer to AT+CMGF\r\n"; throw;}
:set $tmp2 [:pick ($tmp->"r") ($stStart + 7)]
:if ($tmp2 = "0") do={:set $mode false} else={:set $mode true}
# устанавливаем режим PDU
:if ($mode) do={:set $tmp [$chat ($m->"type") ($m->"name") "AT+CMGF=0"]}
:if (($tmp->"f")) do={:set $tmp2 "wrong answer to AT+CMGF=0\r\n"; throw;}
# временное хранение
:local curStruct {"pdu"=[:toarray ""];"index"=[:toarray ""]}
# читаем
# CMGL read sms
# 0 Received unread messages
# 1 Received read messages
# 2 Stored unsent messages
# 3 Stored sent messages
# 4 All messages
:set $tmp [$chat ($m->"type") ($m->"name") "AT+CMGL=4"]
:if (!($tmp->"f")) do={
:local flagend true
# проверяем что sms есть
:set $stStart [:find ($tmp->"r") "+CMGL"]
:if ([:typeof $stStart] != "num") do={:set $flagend false;}
:set $stStart
# извлекаем строки из текста
:while ($flagend) do={
:set $stStart [:find ($tmp->"r") "+CMGL" $stStart]
:if ([:typeof $stStart] != "num") do={:set $tmp2 "wrong answer to AT+CMGL\r\n"; throw;}
:set $stEnd [:find ($tmp->"r") "\r\n" $stStart]
:if ([:typeof $stEnd] != "num") do={:set $tmp2 "wrong answer to AT+CMGL\r\n"; throw;}
:set $tmp2 [:pick ($tmp->"r") $stStart $stEnd]
:local stat [:tonum [:pick $tmp2 ([:find $tmp2 ","] + 1)]]
:if (($stat = 0) or ($stat = 1)) do={
:set $stStart [:find $tmp2 ",,"]
:if ([:typeof $stStart] != "num") do={:set $stStart ([:find $tmp2 ",\"\","] + 4)
} else={:set $stStart ($stStart + 2)}
:local length [:tonum [ pick $tmp2 $stStart [:len $tmp2]]]
:local index [:tonum [ pick $tmp2 ([:find $tmp2 " "] + 1) [:find $tmp2 ","]]]
:set $stStart ($stEnd + 2)
:local coretka true
:while ($coretka) do={
:if (([:pick ($tmp->"r") $stStart] = "\r") or ([:pick ($tmp->"r") $stStart] = "\n")) do={
:set $stStart ($stStart + 1)} else={:set $coretka false}}
:set $stEnd [:find ($tmp->"r") "\r\n" $stStart]
:if ([:typeof $stEnd] != "num") do={:set $tmp2 "wrong answer to AT+CMGL\r\n"; throw;}
:set $tmp2 [:pick ($tmp->"r") $stStart $stEnd]
:set $length (($length + 1 + [:tonum ("0x".[ pick $tmp2 0 2])]) * 2)
:if ($length != [:len $tmp2]) do={:set $tmp2 "wrong length in CMGL\r\n"; throw;}
:set ($curStruct->"pdu") (($curStruct->"pdu") , {{"pdu"=$tmp2;"name"=($m->"name");"mode"="sms"}})
:set ($curStruct->"index") (($curStruct->"index") , $index)
}
:set $tmp2 [:pick ($tmp->"r") $stEnd [:len ($tmp->"r")]]
:if (($tmp2~"\\+CMGL" != true) and ($tmp2~"(^|\n)OK(\$|\r)" = true)) do={:set $flagend false}
}
} else={:local simFill
do {
:set $tmp [$chat ($m->"type") ($m->"name") "AT+CMGD=?"]
:if (($tmp->"f")) do={throw;}
:set $stStart [:find ($tmp->"r") "+CMGD"]
:if ([:typeof $stStart] != "num") do={throw;}
:set $stEnd [:find ($tmp->"r") "\r\n" $stStart]
:if ([:typeof $stEnd] != "num") do={throw;}
:set $tmp2 [:pick ($tmp->"r") $stStart $stEnd]
:set $stStart [:find $tmp2 "("]
:if ([:typeof $stStart] != "num") do={throw;}
:set $stEnd [:find $tmp2 ")"]
:if ([:typeof $stEnd] != "num") do={throw;}
:set $simFill [:toarray [:pick $tmp2 ($stStart + 1) $stEnd]]
} on-error={:set $tmp2 "wrong answer to AT+CMGD=?\r\n"; throw;}
:foreach i in=$simFill do={do {
:set $tmp [$chat ($m->"type") ($m->"name") ("AT+CMGR=".[:tostr $i])]
:if (($tmp->"f")) do={throw;}
:set $stStart [:find ($tmp->"r") "+CMGR"]
:if ([:typeof $stStart] != "num") do={throw;}
:set $stEnd [:find ($tmp->"r") "\r\n" $stStart]
:if ([:typeof $stEnd] != "num") do={throw;}
:set $tmp2 [:pick ($tmp->"r") $stStart $stEnd]
:local stat [:tonum [:pick $tmp2 ([:find $tmp2 " "] + 1)]]
:if (($stat = 0) or ($stat = 1)) do={
:set $stStart [:find $tmp2 ",,"]
:if ([:typeof $stStart] != "num") do={:set $stStart ([:find $tmp2 ",\"\","] + 4)
} else={:set $stStart ($stStart + 2)}
:local length [:tonum [ pick $tmp2 $stStart [:len $tmp2]]]
:set $stStart ($stEnd + 2)
:local coretka true
:while ($coretka) do={
:if (([:pick ($tmp->"r") $stStart] = "\r") or ([:pick ($tmp->"r") $stStart] = "\n")) do={
:set $stStart ($stStart + 1)} else={:set $coretka false}}
:set $stEnd [:find ($tmp->"r") "\r\n" $stStart]
:if ([:typeof $stEnd] != "num") do={throw;}
:set $tmp2 [:pick ($tmp->"r") $stStart $stEnd]
:set $length (($length + 1 + [:tonum ("0x".[ pick $tmp2 0 2])]) * 2)
:if ($length != [:len $tmp2]) do={throw;}
:set ($curStruct->"pdu") (($curStruct->"pdu") , {{"pdu"=$tmp2;"name"=($m->"name");"mode"="sms"}})
:set ($curStruct->"index") (($curStruct->"index") , $i)
}} on-error={:set $tmp2 ("wrong answer to AT+CMGR=$i; simfill=".[:tostr $simFill]."\r\n"); throw;}}
}
# стираем
:if ([:len ($curStruct->"index")] > 0) do={
:set $tmp [$chat ($m->"type") ($m->"name") "AT+CMGD=1,1"]
:if (($tmp->"f")) do={
:foreach i in=($curStruct->"index") do={
:local iterError 5
:while ($iterError > 0) do={
:set $tmp [$chat ($m->"type") ($m->"name") ("AT+CMGD=".[:tostr $i])]
:set $iterError ($iterError - 1)
:if (!($tmp->"f")) do={:set $iterError 0}}
:if (($tmp->"f")) do={:if (($m->"model")~"R11e" != true) do={
:set $tmp2 "wrong answer in CMGD\r\n"; throw;} else={:set ($tmp->"f") false}}
}}}
# возвращаем режим обратно
:if ($mode) do={:set $tmp [$chat ($m->"type") ($m->"name") "AT+CMGF=1"]}
:if (($tmp->"f")) do={:set $tmp2 "wrong answer to AT+CMGF=1\r\n"; throw;}
# добавляем извлечённые pdu в возврат функции
:if ([:len ($curStruct->"index")] > 0) do={
:set ($output->"arr") (($output->"arr") , ($curStruct->"pdu"))}
} on-error={
# сохраняем значение в переменную, т.к. tmp является указателем
:local es ($tmp->"r")
# опрашиваем идентификаторы модема
:if (($m->"type") = "ppp-client") do={
:local man [$chat ($m->"type") ($m->"name") "AT+GMI"]
:if (($man->"f")) do={:set $man [$chat ($m->"type") ($m->"name") "AT+CGMI"]}
:if (($man->"f")) do={:set $man "no information"} else={:set $man ($man->"r")}
:local mod [$chat ($m->"type") ($m->"name") "AT+GMM"]
:if (($mod->"f")) do={:set $mod [$chat ($m->"type") ($m->"name") "AT+CGMM"]}
:if (($mod->"f")) do={:set $mod "no information"} else={:set $mod ($mod->"r")}
:local rev [$chat ($m->"type") ($m->"name") "AT+GMR"]
:if (($rev->"f")) do={:set $rev [$chat ($m->"type") ($m->"name") "AT+CGMR"]}
:if (($rev->"f")) do={:set $rev "no information"} else={:set $rev ($rev->"r")}
:set $m ($m , {"manufacturer"=$man;"model"=$mod;"revision"=$rev})
}
:set ($output->"errorStr") (($output->"errorStr")."Modem: ".[:tostr $m]."\r\nError: ".$tmp2."Returned:\r\n$es\r\n")}
}
:return $output
}
Код: Выделить всё
:global mti0 do={
:global errorFlagParse
# возвращаемый аргумент
:local retArg [:toarray ""]
# Параметр Reply Path, 0 – RP не установлен, 1 – RP установлен
:local replyPath
# Параметр, определяющий наличие заголовка в UD (данных пользователя), 0 – UD содержит только данные, 1 - UD содержит в добавление к данным и заголовок.
:local udhi
# Параметр Status Report Request/Status Report Indication
:local sri
# Параметр More Message to Send(для входящего сообщения), 0 – Ожидаются еще сообщения на стороне SMSC, 1 – Сообщения не ожидаются
:local mms
# длинна адреса отправителя
:local oaLen
# строка адреса (номер телефона) отправителя Originator Address
:local oaLine
# Байт Protocol Identifier
:local pid
#Data Coding Scheme. Схема кодирования данных в поле UD
:local dcs
# Модель поведения схемы
:local dcsModel
:local dcsSubModel
# Флаг компрессии данных
:local compressedUd
# Флаг наличия класса сообщения
:local dcsFlagClass
# Тип кодировки сообщения (7-bit, 8-bit, UCS2)
:local dcsCode
# Класс сообщения
# В архитектуре подразумевается несколько мест хранения данных. Условно их можно разделить на три: sim, память модема, память оборудования (телефон, роутер). Обычно модему доступна, только память sim.
# Classless – бесклассовое, самый распространённый тип – поведение определяет принимающая сторона.
# Class0 - принимающее оборудование должно немедленно принять, отобразить сообщение на дисплее и отправить подтверждение сервисному центру, даже если нет свободного места для сохранения. Сообщение класса 0 не сохраняется в памяти. Если по техническим причинам отображение сообщения невозможно, например нет дисплея (наш случай), то сообщения обрабатываются по правилам classless.
# Class1- принимающее оборудование сохраняет сообщение в памяти по умолчанию и после этого отправляет подтверждение сервисному центру.
# Class2 - принимающее оборудование сохраняет сообщение на sim. Если sim переполнена, то оборудование должно сообщить сервисному центру, что память sim переполнена или сообщение сохранено в другой памяти (при доступности).
# Class3 – обычно предназначены для терминального оборудования, принимающая сторона отправляет подтверждение сервисному центру когда сообщение сохранено в доступной памяти, но обработка сообщения не проверяется.
:local dcsClass
# Message Waiting Indication Group:
# Discard Message(00) опускает сообщение, показывает, только значок.
# Store Message(01) 7-bit, получатель должен хранить текст сообщения вместе со значком.
# Store Message(10) UCS2, получатель должен хранить текст сообщения вместе со значком.
:local dcsMwig
# 0 Indication Inactive, 1 Indication Active
:local dcsMwigIndication
#Indication Type:
# 0 0 Voicemail Message Waiting
# 0 1 Fax Message Waiting
# 1 0 Electronic Mail Message Waiting
# 1 1 Other Message Waiting
:local dcsMwigIndicationType
# Service Centre Time Stamp Параметр, который указывает время получения сообщения SMSC
:local sctsLine
# User Data Length, длина поля UD
:local udl
# Length of User Data Header, длинна заголовка пользователя
:local udhl
#структура с пользовательскими заголовками
:local structUdh {"concatenated"=false;"size"=0}
# строка пользовательского заголовка
:local udh
# Information-Element-Identifier, тип пользовательского заголовка
#00 Concatenated short messages, 8-bit reference number
#01 Special SMS Message Indication
#02 Reserved
#03 Value not used to avoid misinterpretation as <LF> character
#04 Application port addressing scheme, 8 bit address
#05 Application port addressing scheme, 16 bit address
#06 SMSC Control Parameters
#07 UDH Source Indicator
#08 Concatenated short message, 16-bit reference number
#09 Wireless Control Message Protocol
#0A-6F Reserved for future use
#70-7F SIM Toolkit Security Headers
#80 - 9F SME to SME specific use
#A0 - BF Reserved for future use
#C0 - DF SC specific use
#E0 - FF Reserved for future use
:local udhIEI
#Length of Information-Element
:local udhLIE
#Information-Element
:local udhIE
# Строка содержит извлечённый блок текста из pdu
:local udText
#Объединение коротких сообщений
#Concatenated short message reference number, уникальный на блок сообщений
:local udhOctet1
#Maximum number of short messages in the concatenated short message. 1-255, не может быть равен нулю
:local udhOctet2
#Sequence number of the current short message. Порядковый номер в блоке не может быть равен нулю и быть больше заданного максимального.
:local udhOctet3
do {
# поскольку команда :tobool во время написания скрипта не работала, загоняем значение через условие(6.45.1)
# извлекаем Reply Path
:set $replyPath ($pduType >> 7)
:if ($replyPath = 0) do={:set $replyPath false} else={:set $replyPath true}
:if ($debugPduParse) do={:log info "Reply Path= $replyPath"}
# извлекаем наличие заголовка пользователя
:set $udhi (($pduType >> 6) & 1)
:if ($udhi = 0) do={:set $udhi false} else={:set $udhi true}
:if ($debugPduParse) do={:log info "User header= $udhi"}
# ивлекаем SRI
:set $sri (($pduType >> 5) & 1)
:if ($sri = 0) do={:set $sri false} else={:set $sri true}
:if ($debugPduParse) do={:log info "SRI= $sri"}
# извлекаем More Message to Send
:set $mms (($pduType >> 2) & 1)
:if ($mms = 0) do={:set $mms false} else={:set $mms true}
:if ($debugPduParse) do={:log info "More Message to Send= $mms"}
# извлекаем длинну Originator Address
:set $oaLen [:tonum ("0x".[:pick $tpduLine 2 4])]
:if ($oaLen % 2 != 0) do={:set $oaLen ($oaLen + 1)}
:if ($debugPduParse) do={:log info "Length Originator Address= $oaLen"}
# поскольку "The maximum length of the full address field (Address-Length, Type-of-Address and Address-Value) is 12 octets." делаем проверку извлечения
:if ($oaLen > 20) do={:set $retArg "Error parse, incorect len OA\r\n"; throw;}
# извлекаем строку Originator Address
:set $oaLine [:pick $tpduLine 4 (6 + $oaLen)]
:if ($debugPduParse) do={:log info "Originator Address= $oaLine"}
# извлекаем байт PID
:set $pid [:tonum ("0x".[:pick $tpduLine (6 + $oaLen) (8 + $oaLen)])]
:if ($debugPduParse) do={:log info "PID= $pid"}
:if ($pid != 0) do={:set $retArg "Error parse, unknown PID type\r\n"; throw;}
# извлекаем байт Data Coding Scheme
:set $dcs [:tonum ("0x".[:pick $tpduLine (8 + $oaLen) (10 + $oaLen)])]
:if ($debugPduParse) do={:log info "Data Coding Scheme= $dcs"}
# извлекаем модель DCS
:set $dcsModel ($dcs >> 6)
:if ($debugPduParse) do={:log info "Model DCS= $dcsModel"}
:if ($dcsModel = 0) do={
# извлекаем флаг компрессии UD
:set $compressedUd (($dcs >> 5) & 1)
:if ($compressedUd = 0) do={:set $compressedUd false} else={:set $compressedUd true}
:if ($debugPduParse) do={:log info "compressedUd= $compressedUd"}
# извлекаем флаг наличия класса сообщения
:set $dcsFlagClass (($dcs >> 4) & 1)
:if ($dcsFlagClass = 0) do={:set $dcsFlagClass false} else={:set $dcsFlagClass true}
:if ($debugPduParse) do={:log info "dcsFlagClass= $dcsFlagClass"}
# извлекаем тип кодировки сообщения
:set $dcsCode (($dcs >> 2) & 3)
:if ($debugPduParse) do={:log info "dcsCode= $dcsCode"}
# извлекаем класс сообщения
:set $dcsClass ($dcs & 3)
:if ($debugPduParse) do={:log info "dcsClass= $dcsClass"}
# предполагается, что при выключенном флаге dcsFlagClass ,биты 0-1 не проверяются, но есть рекомендация - ставить их в 0
:if (($dcsFlagClass = false) and ($dcsClass != 0)) do={:set $retArg "Error parse, incorrect behavior DCS: 4 bits and 0-1 bit\r\n"; throw;}
}
:if ($dcsModel = 3) do={
:set $dcsSubModel (($dcs >> 4) & 3)
:if ($debugPduParse) do={:log info "dcsSubModel= $dcsSubModel"}
:if ($dcsSubModel = 3) do={
# 3 бит в этой модели зарезервирован 0
:if ((($dcs >> 3) & 1) != 0) do={:set $retArg "Error parse, incorrect behavior DCS: 4-7 bits and 3 bit\r\n"; throw;}
# извлекаем тип кодировки сообщения
:set $dcsCode (($dcs >> 2) & 1)
:if ($debugPduParse) do={:log info "dcsCode= $dcsCode"}
# устанавливаем флаг наличия класса сообщения
:set $dcsFlagClass true
:if ($debugPduParse) do={:log info "dcsFlagClass= $dcsFlagClass"}
# извлекаем класс сообщения
:set $dcsClass ($dcs & 3)
:if ($debugPduParse) do={:log info "dcsClass= $dcsClass"}
} else={
:set $dcsMwig $dcsSubModel
:if ($debugPduParse) do={:log info "dcsMwig= $dcsMwig"}
:set $dcsMwigIndicationType ($dcs & 3)
:if ($debugPduParse) do={:log info "dcsMwigIndicationType= $dcsMwigIndicationType"}
:set $dcsMwigIndication (($dcs >> 2) & 1)
:if ($dcsMwigIndication = 0) do={:set $dcsMwigIndication false} else={:set $dcsMwigIndication true}
:if ($debugPduParse) do={:log info "dcsMwigIndication= $dcsMwigIndication"}
# извлекаем тип кодировки сообщения
:if ($dcsMwig = 2) do={:set $dcsCode 2} else={:set $dcsCode 0}
:if ($debugPduParse) do={:log info "dcsCode= $dcsCode"}
}}
# В GSM 3.38 предполагается, что любые зарезервированные кодировки являются алфавитом GSM по умолчанию (0x00), но на всякий случай выкинем исключение
:if (($dcsModel = 1) or ($dcsModel = 2)) do={:set $retArg "Error parse, unknown DCS type\r\n"; throw;}
:set $sctsLine [:pick $tpduLine (10 + $oaLen) (24 + $oaLen)]
:if ($debugPduParse) do={:log info "sctsLine= $sctsLine"}
# извлекаем длину текста
:set $udl [:tonum ("0x".[:pick $tpduLine (24 + $oaLen) (26 + $oaLen)])]
:if ($debugPduParse) do={:log info "length text= $udl"}
# делаем дополнительную проверку целостности сообщения
:local checkLen
:if (($dcsCode = 0) and !$compressedUd) do={:set $checkLen (($udl - $udl/8) * 2)} else={:set $checkLen ($udl *2)}
:if ((26 + $oaLen + $checkLen) != [:len $tpduLine]) do={:set $retArg "Error parse, error calculating message length\r\n"; throw;}
# извлекаем длину заголовка пользователя
:if ($udhi) do={:set $udhl [:tonum ("0x".[:pick $tpduLine (26 + $oaLen) (28 + $oaLen)])]
:if ($debugPduParse) do={:log info "length user header= $udhl"}
# флаг последнего заголовка для итераций
:local flagEndHeader true
# извлекаем пользовательский заголовок
:set $udh [:pick $tpduLine (28 + $oaLen) (28 + $oaLen + $udhl * 2)]
:if ($debugPduParse) do={:log info "user header= $udh"}
:while ([:typeof $udh] = "str") do={
# извлекаем тип пользовательского заголовка(блок)
:set $udhIEI [:pick $udh 0 2]
:if ($debugPduParse) do={:log info "Information-Element-Identifier= $udhIEI"}
# извлекаем длинну пользовательского заголовка(блок)
:set $udhLIE [:tonum ("0x".[:pick $udh 2 4])]
:if ($debugPduParse) do={:log info "Length of Information-Element= $udhLIE"}
# извлекаем пользовательский заголовок(блок)
:set $udhIE [:pick $udh 4 (4 + $udhLIE * 2)]
:if ($debugPduParse) do={:log info "Information-Element= $udhIE"}
# считаем количество извлечённых данных
:set ($structUdh->"size") (($structUdh->"size") + 2 + $udhLIE)
# извлекаем в структуру блок заголовка
:if ([:typeof [:find $structUdh $udhIEI]] != num) do={:set $structUdh ($structUdh , [[:parse "({\"$udhIEI\"=\"$udhIE\"})"]])
} else={:set ($structUdh->$udhIEI) $udhIE}
:if ($udhIEI = "00") do={
:set ($structUdh->"concatenated") true
:if ([:len $udhIE] != 6) do={:set $retArg "Error parse, wrong length in concatenated block\r\n"; throw;}
:set $udhOctet1 [:tonum ("0x".[:pick $udhIE 0 2])]
:set $udhOctet2 [:tonum ("0x".[:pick $udhIE 2 4])]
:if ($udhOctet2 = 0) do={:set $retArg "Error parse, wrong size SMS in concatenated block\r\n"; throw;}
:set $udhOctet3 [:tonum ("0x".[:pick $udhIE 4 6])]
:if (($udhOctet3 = 0) or ($udhOctet3 > $udhOctet2)) do={:set $retArg "Error parse, present curent value in concatenated block\r\n"; throw;}
:if ($debugPduParse) do={:log info "Concatenated short message reference number= $udhOctet1"}
:if ($debugPduParse) do={:log info "Maximum number of short messages in the concatenated short message= $udhOctet2"}
:if ($debugPduParse) do={:log info "Sequence number of the current short message= $udhOctet3"}
}
:if ($udhIEI = "08") do={
:set ($structUdh->"concatenated") true
:if ([:len $udhIE] != 8) do={:set $retArg "Error parse, wrong length in concatenated block\r\n"; throw;}
:set $udhOctet1 [:tonum ("0x".[:pick $udhIE 0 4])]
:set $udhOctet2 [:tonum ("0x".[:pick $udhIE 4 6])]
:if ($udhOctet2 = 0) do={:set $retArg "Error parse, wrong size SMS in concatenated block\r\n"; throw;}
:set $udhOctet3 [:tonum ("0x".[:pick $udhIE 6 8])]
:if (($udhOctet3 = 0) or ($udhOctet3 > $udhOctet2)) do={:set $retArg "Error parse, present curent value in concatenated block\r\n"; throw;}
:if ($debugPduParse) do={:log info "Concatenated short message reference number= $udhOctet1"}
:if ($debugPduParse) do={:log info "Maximum number of short messages in the concatenated short message= $udhOctet2"}
:if ($debugPduParse) do={:log info "Sequence number of the current short message= $udhOctet3"}
}
# обрезаем строку пользовательского заголовка
:set $udh [:pick $udh ($udhLIE * 2 + 4) [:len $udh]]
}
}
# извлекаем блок текста из PDU
:if ($udhi) do={:set $udText [:pick $tpduLine (28 + $oaLen + $udhl * 2) [:len $tpduLine]]} else={
:set $udText [:pick $tpduLine (26 + $oaLen) [:len $tpduLine]]}
:if ($debugPduParse) do={:log info "udText= $udText"}
# набиваем структуру sms
:set $retArg {"replyPath"=$replyPath;"udhi"=$udhi;"sri"=$sri;"mms"=$mms;"oaLen"=$oaLen;"oaLine"=$oaLine;"pid"=$pid;"dcsModel"=$dcsModel;"compressedUd"=$compressedUd;"dcsFlagClass"=$dcsFlagClass;"dcsCode"=$dcsCode;"dcsClass"=$dcsClass;"dcsSubModel"=$dcsSubModel;"dcsMwig"=$dcsMwig;"dcsMwigIndicationType"=$dcsMwigIndicationType;"dcsMwigIndication"=$dcsMwigIndication;"sctsLine"=$sctsLine;"udl"=$udl;"structUdh"=$structUdh;"udhOctet1"=$udhOctet1;"udhOctet2"=$udhOctet2;"udhOctet3"=$udhOctet3;"udText"=$udText}
# bool bool bool bool num str num num bool bool num num str
} on-error={:set $errorFlagParse true}
:return $retArg
}
Код: Выделить всё
:global errorFlagParse false;
###########функция конвертации 7bit в UTF-8 текст##########################
# Работает, основываясь на GSM 03.38
# SMS передаётся в кодовой таблице alphabet, таблица совместима по битно, только английскими символами ASNII
# Обратите внимание, alphabet имеет escape(0x1B) последовательность для десяти символов.
# В функцию надо передать аргумент с именем "instring”
# аргумент iter8 заставляет начать отсчёт с восьмой итерации
:global convert7bitToUtf8 do={
:global errorFlagParse;
:local alphabetToUtf8 {0="\40";1="\C2\A3";2="\24";3="\C2\A5";4="\C3\A8";5="\C3\A9";6="\C3\B9";7="\C3\AC";8="\C3\B2";9="\C3\87";10="\0A";11="\C3\98";12="\C3\B8";13="\0D";14="\C3\85";15="\C3\A5";16="\CE\94";17="\5F";18="\CE\A6";19="\CE\93";20="\CE\9B";21="\CE\A9";22="\CE\A0";23="\CE\A8";24="\CE\A3";25="\CE\98";26="\CE\9E";28="\C3\86";29="\C3\A6";30="\C3\9F";31="\C3\89";32="\20";33="\21";34="\22";35="\23";36="\C2\A4";37="\25";38="\26";39="\27";40="\28";41="\29";42="\2A";43="\2B";44="\2C";45="\2D";46="\2E";47="\2F";48="\30";49="\31";50="\32";51="\33";52="\34";53="\35";54="\36";55="\37";56="\38";57="\39";58="\3A";59="\3B";60="\3C";61="\3D";62="\3E";63="\3F";64="\C2\A1";65="\41";66="\42";67="\43";68="\44";69="\45";70="\46";71="\47";72="\48";73="\49";74="\4A";75="\4B";76="\4C";77="\4D";78="\4E";79="\4F";80="\50";81="\51";82="\52";83="\53";84="\54";85="\55";86="\56";87="\57";88="\58";89="\59";90="\5A";91="\C3\84";92="\C3\96";93="\C3\91";94="\C3\9C";95="\C2\A7";96="\C2\BF";97="\61";98="\62";99="\63";100="\64";101="\65";102="\66";103="\67";104="\68";105="\69";106="\6A";107="\6B";108="\6C";109="\6D";110="\6E";111="\6F";112="\70";113="\71";114="\72";115="\73";116="\74";117="\75";118="\76";119="\77";120="\78";121="\79";122="\7A";123="\C3\A4";124="\C3\B6";125="\C3\B1";126="\C3\BC";127="\C3\A0";3466="\0C";3476="\5E";3496="\7B";3497="\7D";3503="\5C";3516="\5B";3517="\7E";3518="\5D";3520="\7C";3557="\E2\82\AC";};
:local curbit 0;
:if ($iter8) do={:set $curbit 6;}
:local nextpart 0;
:local escape false;
:local decodedLine "";
do {
:if (([:len $instring] % 2) != 0) do={:set $decodedLine "incomplete number of bytes in function convert7bitToUtf8"; throw;}
:for curposition from=0 to=([:len $instring] - 1) step=2 do={
:local charcode [:tonum ("0x".[:pick $instring $curposition ($curposition +2)])];
:local tmp ($charcode & (127>>$curbit));
:set $tmp ($tmp<<$curbit);
:set $tmp ($tmp + $nextpart);
:set $nextpart ($charcode>>(7-$curbit));
:set curbit ($curbit+1);
:if ($tmp = 27) do={:set $escape true;} else={
:if ($escape) do={:local tmp2 ($alphabetToUtf8->[:tostr (3456 | $tmp)]);
:if ([:len $tmp2] = 0) do={:set $decodedLine "unknown character in function convert7bitToUtf8"; throw;};
:set $decodedLine ($decodedLine.$tmp2);
:set $escape false;
} else={:set $decodedLine ($decodedLine.($alphabetToUtf8->[:tostr $tmp]));};};
:if ($curbit = 7) do={
:set $tmp $nextpart;
:if ($tmp = 27) do={:set $escape true;} else={
:if ($escape) do={:local tmp2 ($alphabetToUtf8->[:tostr (3456 | $tmp)]);
:if ([:len $tmp2] = 0) do={:set $decodedLine "unknown character in function convert7bitToUtf8"; throw;};
:set $decodedLine ($decodedLine.$tmp2);
:set $escape false;
} else={:set $decodedLine ($decodedLine.($alphabetToUtf8->[:tostr $tmp]));};};
:set $curbit 0;
:set $nextpart 0;
};
};
:if ($iter8) do={:set $decodedLine [:pick $decodedLine 1 [:len $decodedLine]];}
# оговорено, что в sms при окончании на восьмом символе заполнителем являются нули, в ussd заполнитель CR(при указании в конце CR символы надо за двоить), заполнитель в номере телефона не нашёл
:if (($curbit = 0) and ([:pick $decodedLine ([:len $decodedLine] - 1)] = "\40")) do={
:set $decodedLine [:pick $decodedLine 0 ([:len $decodedLine] - 1)];}
:if ([:len $decodedLine] = 0) do={:set $decodedLine "Error in parsing: function malfunction convert7bitToUtf8\r\n"; throw;}
} on-error={:set $errorFlagParse true;}
:return $decodedLine;
};
########## Конец функции ###################################################
:global convert8bitToUtf8 do={
# кодировка спецификацией не оговорена и определяется пользователем, пишем сами для своих целей.
}
:global symbolsHex {"\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";"\80";"\81";"\82";"\83";"\84";"\85";"\86";"\87";"\88";"\89";"\8A";"\8B";"\8C";"\8D";"\8E";"\8F";"\90";"\91";"\92";"\93";"\94";"\95";"\96";"\97";"\98";"\99";"\9A";"\9B";"\9C";"\9D";"\9E";"\9F";"\A0";"\A1";"\A2";"\A3";"\A4";"\A5";"\A6";"\A7";"\A8";"\A9";"\AA";"\AB";"\AC";"\AD";"\AE";"\AF";"\B0";"\B1";"\B2";"\B3";"\B4";"\B5";"\B6";"\B7";"\B8";"\B9";"\BA";"\BB";"\BC";"\BD";"\BE";"\BF";"\C0";"\C1";"\C2";"\C3";"\C4";"\C5";"\C6";"\C7";"\C8";"\C9";"\CA";"\CB";"\CC";"\CD";"\CE";"\CF";"\D0";"\D1";"\D2";"\D3";"\D4";"\D5";"\D6";"\D7";"\D8";"\D9";"\DA";"\DB";"\DC";"\DD";"\DE";"\DF";"\E0";"\E1";"\E2";"\E3";"\E4";"\E5";"\E6";"\E7";"\E8";"\E9";"\EA";"\EB";"\EC";"\ED";"\EE";"\EF";"\F0";"\F1";"\F2";"\F3";"\F4";"\F5";"\F6";"\F7";"\F8";"\F9";"\FA";"\FB";"\FC";"\FD";"\FE";"\FF"};
:global convertUcs2ToUtf8 do={
:local decodedLine "";
:global symbolsHex;
:for curposition from=0 to=([:len $instring] -1) step=4 do={
:local i [:tonum ("0x".[:pick $instring $curposition ($curposition +4)])];
:if ($i < 0x80) do={
:set $decodedLine ($decodedLine.($symbolsHex->$i));
};
:if (($i >= 0x80) and ($i < 0x800)) do={
:local byteA (($i >> 6) | 192);
:local byteB (($i & 63) | 128);
:set $decodedLine ($decodedLine.($symbolsHex->$byteA).($symbolsHex->$byteB));
};
:if ($i >= 0x800) do={
:local byteA (($i >> 12) | 224);
:local byteB ((($i >> 6) & 63) | 128);
:local byteC (($i & 63) | 128);
:set $decodedLine ($decodedLine.($symbolsHex->$byteA).($symbolsHex->$byteB).($symbolsHex->$byteC));
};
};
:return $decodedLine;
};
:global convertAddress do={
:local decodedLine "";
:global errorFlagParse;
:global convert7bitToUtf8;
:local typeOfAddress [:tonum ("0x".[:pick $instring 0 2])];
# Type-of-number
# 0 0 0 Unknown
# 0 0 1 International number
# 0 1 0 National number
# 0 1 1 Network specific number
# 1 0 0 Subscriber number
# 1 0 1 Alphanumeric, (coded according to GSM TS 03.38 7-bit default alphabet)
# 1 1 0 Abbreviated number
# 1 1 1 Reserved for extension
:local typeOfNumber (($typeOfAddress >> 4) & 7);
:local nameNumber {"Unknown";"International number";"National number";"Network specific number";"Subscriber number";"Alphanumeric";"Abbreviated number";"Reserved for extension"};
# Numbering-plan-identification (applies for Type-of-number = 000,001,010)
# 0 0 0 0 Unknown
# 0 0 0 1 ISDN/telephone numbering plan (E.164/E.163)
# 0 0 1 1 Data numbering plan (X.121)
# 0 1 0 0 Telex numbering plan
# 1 0 0 0 National numbering plan
# 1 0 0 1 Private numbering plan
# 1 0 1 0 ERMES numbering plan (ETSI DE/PS 3 01-3)
# 1 1 1 1 Reserved for extension
# All other values are reserved.
:local numberingPlanIdentification ($typeOfAddress & 15);
:local namePlan {"Unknown";"ISDN/telephone numbering plan";"Data numbering plan";"Reserved";"Telex numbering plan";"Reserved";"Reserved";"Reserved";"National numbering plan";"Private numbering plan";"ERMES numbering plan";"Reserved";"Reserved";"Reserved";"Reserved";"Reserved for extension"};
:local addressValue [:pick $instring 2 [:len $instring]];
:local size [:len $addressValue];
:local rotare "";
# BCD number
# 1010 *
# 1011 #
# 1100 a
# 1101 b
# 1110 c
# 1111 fill bits
:local bcd {"A"="*";"B"="#";"C"="a";"D"="b";"E"="c"};
do {
:if (($typeOfAddress >> 7) != 1) do={
:set $decodedLine "Error in parsing numbers: 7 bits in Type-of-Address not set to 1\r\n";
throw;};
:if (($typeOfNumber = 3) or ($typeOfNumber = 4) or ($typeOfNumber = 6) or ($typeOfNumber = 7)) do={
:set $decodedLine ("Error in parsing numbers: unsupported number type - ".($nameNumber->$typeOfNumber)."\r\n");
throw;};
:if ((($typeOfNumber = 0) or ($typeOfNumber = 1) or ($typeOfNumber = 2)) and ($numberingPlanIdentification != 1)) do={
:set $decodedLine ("Error in parsing numbers: unsupported number type - ".($nameNumber->$typeOfNumber)." and Numbering-plan-identification - ".($namePlan->$numberingPlanIdentification)."\r\n");
throw;};
:if (($typeOfNumber = 5) and ($numberingPlanIdentification != 0)) do={
:set $decodedLine ("Error in parsing numbers: unsupported number type - ".($nameNumber->$typeOfNumber)." and Numbering-plan-identification - ".($namePlan->$numberingPlanIdentification)."\r\n");
throw;};
:if (($size % 2) = 1) do={
:set $decodedLine "Error in parsing numbers: length is not equal to byte\r\n";
throw;};
:if (($typeOfNumber = 0) or ($typeOfNumber = 1) or ($typeOfNumber = 2)) do={
:for i from=1 to=$size do={
:if (($i%2) = 0) do={:set $rotare ($rotare.[:pick $addressValue ($i - 2)]);
} else={:set $rotare ($rotare.[:pick $addressValue $i]);};}
:set $addressValue "";
:for i from=0 to=($size - 1) do={
:local single [:pick $rotare $i];
:if ([:tonum ("0x".$single)] <= 9) do={:set $addressValue ($addressValue.$single);
} else={:if (($single = "F") and ($i = ($size - 1))) do={} else={:if ($single != "F") do={
:set $addressValue ($addressValue.($bcd->$single));} else={
:set $decodedLine "Error in parsing numbers: septet of adding to byte is not at the end\r\n";
throw;
}}}}
:if ($typeOfNumber = 1) do={:set $decodedLine ("+".$addressValue." (".($nameNumber->$typeOfNumber).")");
} else={:set $decodedLine ($addressValue." (".($nameNumber->$typeOfNumber).")");}
} else={:if ($typeOfNumber = 5) do={:set $addressValue [$convert7bitToUtf8 instring=$addressValue];
:set $decodedLine ($addressValue." (".($nameNumber->$typeOfNumber).")");}};
:if ([:len $decodedLine] = 0) do={:set $decodedLine "Error in parsing numbers: function malfunction convertAddress\r\n"; throw;}
} on-error={:set $errorFlagParse true;}
:return $decodedLine;
};
:global convertBodyPDU do={
:global errorFlagParse;
:global convert7bitToUtf8;
:global convert8bitToUtf8;
:global convertUcs2ToUtf8;
:local outstring;
:if ($typeFormat = 0) do={:set outstring [$convert7bitToUtf8 instring=$instring iter8=$iter8];
} else={:if ($typeFormat = 1) do={:set outstring [$convert8bitToUtf8 instring=$instring];
} else={:if ($typeFormat = 2) do={:set outstring [$convertUcs2ToUtf8 instring=$instring];}}}
:if ([:len $outstring] = 0) do={:set $outstring "Error in parsing body string: function malfunction convertBodyPDU\r\n";
:set $errorFlagParse true;}
:return $outstring;
};
:global convertScts do={
:global errorFlagParse;
:global UnixTimeToFormat;
:local decodedLine "";
do {
:local tmp [:tonum ("0x".[:pick $sctsLine 0])]
:local byteY [:tonum ("0x".[:pick $sctsLine 1])]
:if (($tmp > 9) or ($byteY > 9)) do={:set $decodedLine "Error parse in function convertScts, year\r\n"; throw;}
:set $byteY ($byteY * 10 + $tmp)
:set $tmp [:tonum ("0x".[:pick $sctsLine 2])]
:local byteMn [:tonum ("0x".[:pick $sctsLine 3])]
:if (($tmp > 9) or ($byteMn > 1)) do={:set $decodedLine "Error parse in function convertScts, monats\r\n"; throw;}
:set $byteMn ($byteMn * 10 + $tmp)
:if ($byteMn > 12) do={:set $decodedLine "Error parse in function convertScts, monats\r\n"; throw;}
:set $tmp [:tonum ("0x".[:pick $sctsLine 4])]
:local byteD [:tonum ("0x".[:pick $sctsLine 5])]
:if (($tmp > 9) or ($byteD > 3)) do={:set $decodedLine "Error parse in function convertScts, days\r\n"; throw;}
:set $byteD ($byteD * 10 + $tmp)
:if ($byteD > 31) do={:set $decodedLine "Error parse in function convertScts, days\r\n"; throw;}
:set $tmp [:tonum ("0x".[:pick $sctsLine 6])]
:local byteH [:tonum ("0x".[:pick $sctsLine 7])]
:if (($tmp > 9 ) or ($byteH > 2)) do={:set $decodedLine "Error parse in function convertScts, hours\r\n"; throw;}
:set $byteH ($byteH * 10 + $tmp)
:if ($byteH > 23) do={:set $decodedLine "Error parse in function convertScts, hours\r\n"; throw;}
:set $tmp [:tonum ("0x".[:pick $sctsLine 8])]
:local byteM [:tonum ("0x".[:pick $sctsLine 9])]
:if (($tmp > 9) or ($byteM > 5)) do={:set $decodedLine "Error parse in function convertScts, minutes\r\n"; throw;}
:set $byteM ($byteM * 10 + $tmp)
:if ($byteM > 59) do={:set $decodedLine "Error parse in function convertScts, minutes\r\n"; throw;}
:set $tmp [:tonum ("0x".[:pick $sctsLine 10])]
:local byteS [:tonum ("0x".[:pick $sctsLine 11])];
:if (($tmp > 9) or ($byteS > 5)) do={:set $decodedLine "Error parse in function convertScts, seconds\r\n"; throw;}
:set $byteS ($byteS * 10 + $tmp)
:if ($byteS > 59) do={:set $decodedLine "Error parse in function convertScts, seconds\r\n"; throw;}
:set $tmp [:tonum ("0x".[:pick $sctsLine 12])]
:local byteO [:tonum ("0x".[:pick $sctsLine 13])];
:if ($tmp > 9) do={:set $decodedLine "Error parse in function convertScts, offset\r\n"; throw;}
:if (($byteO >> 3) = 1) do={:set $byteO ((0 - (($byteO & 7) * 10 + $tmp)) * 900)} else={:set $byteO ((($byteO & 7) * 10 + $tmp) * 900)}
:local months;
:if (($byteY % 4) = 0) do={
:set months [:toarray (0,31,60,91,121,152,182,213,244,274,305,335)]
} else={:set months [:toarray (0,31,59,90,120,151,181,212,243,273,304,334)]}
:local unixTime (($byteY * 365 + ($byteY - 1) / 4 + ($months->($byteMn - 1)) + $byteD) * 86400)
:set $unixTime ($byteH * 3600 + $byteM * 60 + $byteS + $unixTime + 946684800)
:local gmt false
:if ([:typeof ($timeStruct->"gmt")] = "bool") do={:set $gmt ($timeStruct->"gmt")}
:local offsetR [/system clock get gmt-offset]
:if (($offsetR >> 31) = 1) do={:set $offsetR ($offsetR - 4294967296); :set $offset ($offset * -1)}
:if ($gmt) do={:set $tmp ($unixTime + $byteO)} else={:set $tmp $unixTime}
:local hd "Date: "
:if ([:typeof $headerDate] = "str") do={:set $hd $headerDate}
:set $tmp [$UnixTimeToFormat timeStamp=$tmp timeStruct=$timeStruct]
:if ([:len $tmp] = 0) do={:set $decodedLine "Error parse in function convertScts, no return from UnixTimeToFormat\r\n"; throw;}
:set $decodedLine ($hd.$tmp)
:if ($gmt) do={:local numO
:if ($byteO >= 0) do={:set $decodedLine ($decodedLine." -"); :set $numO $byteO} else={:set $decodedLine ($decodedLine." +"); :set $numO ($byteO * -1)}
:set $decodedLine ($decodedLine.[:tostr ($numO / 3600)])
:set $numO (($numO % 3600) / 60)
:if ($numO != 0) do={:set $decodedLine ($decodedLine.":".[:tostr $numO])}
:set $decodedLine ($decodedLine." GMT")}
:if ($byteO = $offsetR) do={:set $decodedLine ($decodedLine."\r\n")} else={
:set $decodedLine ($decodedLine." (SMS time)\r\n")
:if (!$gmt) do={:set $tmp ($unixTime + $byteO - $offsetR)} else={:set $tmp ($unixTime + $byteO)}
:set $tmp [$UnixTimeToFormat timeStamp=$tmp timeStruct=$timeStruct]
:if ([:len $tmp] = 0) do={:set $decodedLine "Error parse in function convertScts, no return from UnixTimeToFormat\r\n"; throw;}
:set $decodedLine ($decodedLine.$hd.$tmp)
:if ($gmt) do={:local numO
:if ($offsetR >= 0) do={:set $decodedLine ($decodedLine." -"); :set $numO $offsetR} else={:set $decodedLine ($decodedLine." +"); :set $numO ($offsetR * -1)}
:set $decodedLine ($decodedLine.[:tostr ($numO / 3600)])
:set $numO (($numO % 3600) / 60)
:if ($numO != 0) do={:set $decodedLine ($decodedLine.":".[:tostr $numO])}
:set $decodedLine ($decodedLine." GMT")}
:set $decodedLine ($decodedLine." (OS time) \r\n")
}
} on-error={:set $errorFlagParse true;}
:if ([:len $decodedLine] = 0) do={:set $decodedLine "Error in parsing date string: function malfunction convertScts\r\n"
:set $errorFlagParse true}
:return $decodedLine;
}
# Считаются только unsigned
:global UnixTimeToFormat do={
:local decodedLine ""
:local before false
:if ($timeStamp < 0) do={:set $before true; :set $timeStamp ($timeStamp * -1)}
:local timeS ($timeStamp % 86400)
:local timeH ($timeS / 3600)
:local timeM ($timeS % 3600 / 60)
:set $timeS ($timeS - $timeH * 3600 - $timeM * 60)
:local dateD ($timeStamp / 86400)
:local dateM 2
:local dateY 1970
:local leap false
:while (($dateD / 365) > 0) do={
:set $dateD ($dateD - 365)
:set $dateY ($dateY + 1)
:set $dateM ($dateM + 1)
:if ($dateM = 4) do={:set $dateM 0
:if (($dateY % 400 = 0) or ($dateY % 100 != 0)) do={:set $leap true
:set $dateD ($dateD - 1)}} else={:set $leap false}}
:local months [:toarray (0,31,28,31,30,31,30,31,31,30,31,30,31)]
:if (leap) do={:set $dateD ($dateD + 1); :set ($months->2) 29}
do {
:for i from=1 to=12 do={:if (($months->$i) > $dateD) do={:set $dateM $i; :set $dateD ($dateD + 1); break;} else={:set $dateD ($dateD - ($months->$i))}}
} on-error={}
:local tmod 2
:local s "."
:local nf true
:local mstr {"jan";"feb";"mar";"apr";"may";"jun";"jul";"aug";"sep";"oct";"nov";"dec"}
:if ([:typeof $timeStruct] = "array") do={
:if ([:typeof ($timeStruct->"timeFormat")] = "num") do={:set $tmod ($timeStruct->"timeFormat")}
:if ([:typeof ($timeStruct->"dateSeparator")] = "str") do={:set $s ($timeStruct->"dateSeparator")}
:if ([:typeof ($timeStruct->"numFill")] = "bool") do={:set $nf ($timeStruct->"numFill")}
:if (([:typeof ($timeStruct->"monthsStr")] = "array") and ([:len ($timeStruct->"monthsStr")] = 12)) do={:set $mstr ($timeStruct->"monthsStr")}
}
:local strY [:tostr $dateY]
:local strMn
:local strD
:local strH
:local strM
:local strS
:if ($nf) do={
:if ($dateM > 9) do={:set $strMn [:tostr $dateM]} else={:set $strMn ("0".[:tostr $dateM])}
:if ($dateD > 9) do={:set $strD [:tostr $dateD]} else={:set $strD ("0".[:tostr $dateD])}
:if ($timeH > 9) do={:set $strH [:tostr $timeH]} else={:set $strH ("0".[:tostr $timeH])}
:if ($timeM > 9) do={:set $strM [:tostr $timeM]} else={:set $strM ("0".[:tostr $timeM])}
:if ($timeS > 9) do={:set $strS [:tostr $timeS]} else={:set $strS ("0".[:tostr $timeS])}
} else={
:set strMn [:tostr $dateM]
:set strD [:tostr $dateD]
:set strH [:tostr $timeH]
:set strM [:tostr $timeM]
:set strS [:tostr $timeS]
}
do {
:if ($tmod = 1) do={:set $decodedLine "$strY$s$strMn$s$strD $strH:$strM:$strS"; break;}
:if ($tmod = 2) do={:set $decodedLine "$strD$s$strMn$s$strY $strH:$strM:$strS"; break;}
:if ($tmod = 3) do={:set $decodedLine ("$strD ".($mstr->($dateM - 1))." $strY $strH:$strM:$strS"); break;}
:if ($tmod = 4) do={:set $decodedLine ("$strY ".($mstr->($dateM - 1))." $strD $strH:$strM:$strS"); break;}
} on-error={}
:return $decodedLine;
}
:global sendMailUTF8 do={
# функция должна возвращать булевое истина при успешной отправке сообщения
/tool e-mail send to=$emailAdr subject="$head\r\nMIME-Version: 1.0\r\nContent-Type: text/plain; charset=utf-8" body=$emailBody
:local sendit 120
:while ($sendit > 0) do={:delay 1s; :set $sendit ($sendit - 1)
:if (([tool e-mail get last-status] = "succeeded") or ([tool e-mail get last-status] = "failed")) do={:set $sendit 0}}
:if ([tool e-mail get last-status] = "succeeded") do={:return true} else={:return false}
}
:global saveFile do={
:if ([/file find name~"$nameFile"] = "") do={/file print file="$nameFile"
:while ([/file find name~"$nameFile"] = "") do={:delay 1s}}
do {/file set $nameFile contents=$bodyFile} on-error={}
}
:global exitFunctionPDU do={
:global errorFlagParse
:global convert7bitToUtf8
:global convert8bitToUtf8
:global convertUcs2ToUtf8
:global convertAddress
:global convertBodyPDU
:global convertScts
:global UnixTimeToFormat
:global sendMailUTF8
:global extractSmsModem
:global saveFile
:global symbolsHex
:set $symbolsHex
:set $saveFile
:set $errorFlagParse
:set $convert7bitToUtf8
:set $convert8bitToUtf8
:set $convertUcs2ToUtf8
:set $convertAddress
:set $convertBodyPDU
:set $convertScts
:set $UnixTimeToFormat
:set $extractSmsModem
:set $sendMailUTF8
:global exitFunctionPDU
:set $exitFunctionPDU
}
Скрипт с функцией извлечения “extractSmsModem” подлежит редактированию под конкретный модем. Изучайте список AT команд для своего конкретного модема. Текущий представленный образец работает с LTE и PPP интерфейсами, поддерживаемыми AT команды по умолчанию.
В главном скрипте в начале содержится список переменных для настройки поведения скрипта.
Теперь для тех кто любит извращения – дело в том, что в PDU могут присутствовать разные флаги, задающие поведение sms. Например, флаг class, который может указать – не сохранять sms в память, а отобразить на дисплее. В случае отсутствия дисплея, стандартом оговорено - принимающая сторона сама решает поведение такого sms. Тоже самое происходит и с CBM(cell broadcast message), если модем способен их обрабатывать. Для тех кто не знает, это особый вид PDU, для многоадресной рассылки, например – сеть оператора может передаёт информацию о наступающей грозе в конкретной местности (пора выдирать роутер из розетки). Так вот по факту, модемы просто вываливают подобные PDU в терминал и забывают о них, а ROS просто их игнорирует.
Вариантов не много – постоянно мониторить. Для тех модемов, у которых есть свободный порт с терминалом, надо обрабатывать каждое сообщение от модема и в случаи нахождения соответствие нужного идентификатора, обрабатывать сообщения. Второй способ более универсальный и подходит для всех модемов у которых есть терминал (LTE и т.д.) – надо включить логирование обмена ROS с модемом (/system loging -> lte, ppp, и т.д.) и дальше парсить уже этот лог на предмет принятых PDU.
P.S. В скрипте встроена поддержка, если вы её не отключите, то я наберу примеры для дополнения не поддерживаемых функции и реализую их. Отправка сработает, только, если главный цикл получит sms и не сможет разобрать его. (не все концепции оговоренные в 3GPP у меня поддерживаются из-за отсутствия образцов) . Если есть проблеммы с извлечением из модема, то ни кому ни чего не отправляется.
За время отладки, только от одного пользователя пришли отладочные конверты. Они и были добавлены в скрипт. По сему в данный момент считаю, что проблем ни у кого с парсингом не возникает.
P.S по глюкам смотрим здесь