Назад в 90-е або як відправити повідомлення на пейджер через java
Незважаючи на гадану прозорість рішення і вторинність мого досвіду, я вирішила докладно описати свої дії, тому що інформації в інтернеті по темі представлено не дуже багато: на форумах на питання відповідають рідко і невлучно. Кому-то цей текст, можливо, сильно заощадить час.

Крок 1 - INVITE
Перший етап - дозвон на пейджинговую станцію - був реалізований через протокол SIP і за допомогою відповідної Java-бібліотеки jain-sip. Найкраще опис принципів роботи протоколу я знайшла на Хабре в публікаціях «Взаємодія клієнтів SIP. Частина 1 »і« Взаємодія клієнтів SIP. Частина 2". а самий прийнятний туторіал по Джейн - ось тут (але колекція прикладів звідси відмовилася краще).
Як предпоготовкі я створила клас:
c необхідними полями, які спочатку повинні бути ініційовані так, як зазначено в туторіали:
У другому випадку необхідно вказати хост, з якого виробляється відправка повідомлення, що ініціює спілкування:
Іншим цікавим елементом є, власне, тіло SDP-повідомлення, що представляє собою опис того, що знадобиться для успішної комунікації. У нашому випадку воно виглядало приблизно так:
Крок 2 - аутентифікація
Якщо нам вдалося відправити правильний інвайт, то в кращому випадку сервер-одержувач надішле нам бажане OK-повідомлення зі статусом 200, а в гіршому - вирішить ще трохи помучити ідентифікацією. У другому випадку відповідь статус буде 401 або 407. Ось код, за допомогою якого надсилається відповідь. Для його підтримки знадобиться одна з останніх версій jain-sip (наприклад, 1.2.228). Його треба помістити в метод processResponse (), який одержує в якості аргументу ResponseEvent responseEvt.
Зверніть увагу на чотирьох аргумент методу handleChallenge (), без нього формат повідомлення зміниться, стане невідповідним і ваша аутентифікація провалиться.
Класи AccountManagerImpl і теж неоходимости UserCredentialsImpl повинні бути дописані вами, я їх писала по моделі тих, що представлені тут.
Після відправки своїх реєстраційних даних ми можемо сміливо очікувати шуканий 200 OK, на який треба не забути відправити ACK. Такий тип повідомлення виготовляється вкрай просто:
Крок 3 - SIP INFO
Далі починається найцікавіше - відправка DTMF-сигналів (ті самі натискання в тоновому режимі). Глобально це можна зробити через два різних протоколу: через SIP і через RTP. Природно, спочатку було вирішено піти по шляху найменшого опору. Для кожного символу формувався ось такий запит, який потім потрібно було відправити на сервер:
Сама процедура відправки виглядає трохи дивно (здається, побудованої за моделлю «через Жмеринку в Париж»), але інакше у мене нічого не працювало. Взагалі бібліотека мені здалася трохи глючной: дуже часто одне з декількох рішень, виглядали за великим рахунком однаково, не спрацьовувало.
Що я можу сказати? Після реалізації цього кроку виявилося, що не всі VoIP-сервера однаково доброзичливі: декому достатньо було сигналів, які передаються через SIP, а кому-то їх не вистачило, так як вони не виробляють звукового сигналу і тому залишаються непоміченими. Природно, за законом підлості моєю метою був сервер другого типу. Тому ...
Крок 4. формування RTP-пакета
Взагалі коли я усвідомила, що одним SIP-му проблему не вирішити, я сподівалася, що хоча б зможу скористатися іншою бібліотекою, яка вміє ненапряжно відправляти DTMF-сигнали. Але не тут-то було. Зазвичай, якщо ми говоримо «RTP через Джава», то маємо на увазі JMF. Але, по-перше, вона вже старенька і не особливо підтримується. По-друге, вона більше підходить для передачі складніших медіа. По-третє, туторіали, які мені вдалося знайти, були не дуже розумними. Ось один із прикладів з документації, в середині якого спливає якась rtpSession, слідів якої в перші скількись хвилин пошуку мені знайти взагалі не вдалося.
Іншим варіантом була бібліотека libjitsi. вдає із себе цілий комунікатор. З неї нічого запозичити теж не вдалося, хоча там є мила методу sendDTMF або щось в цьому дусі. Структура коду така, що він береться або цілком, або ніяк. В результаті було вирішено по-нормальному зробити людський пакет і відправити його через UDP-соккет.
Отже, ось істотний фрагмент класу RtpPacket: його основні поля і конструктор зі значеннями, відповідними для передачі DTMF. Що означають всі ці речі, написано багато де, тому повторюватися не буду. Зазначу тільки, що значення параметра ssrc в принципі ролі не грає, але у всіх відправляються в одній сесії пакетів воно повинно збігатися. Номер формату корисного навантаження у DTMF-пакетів (payload type) - 101 (його ми прописали, коли ініціювали SIP-комунікацію).
Найважливіший етап створення пакета - заповнення байтового масиву даних. У DTMF, природно свій формат: перший байт - це, власне, значення сигналу, що передається (від 0 до 16), перша половина другого байта - різні маркети (зазвичай 0), друга половина другого байта- гучність (стандартне значення - 10), інші два - це тривалість (стандартне значення - 160).
Для кожного сигналу створюється близько 10 пакетів (число може варіюватися):
- перший, початковий, має marker = 1, інші - 0;
- останні три - кінцеві, marker = 0, зате перший біт другого байта блоку даних = 1. Блок даних в неконечную пакеті для передачі сигналу 1 буде виглядати так:
А в кінцевому ось так:
Мітка часу у всіх DTMF-пакетів, які стосуються одного сигналу, може залишатися однаковою (припустимо, T). Зате час наступного пакета повинно бути:
Крок 5. RTP-канал
Далі, як я наївно вважала, мені залишалося тільки наробити з моїх байтів DatagramPacket'ов, засунути їх в сокет і запулить в сервер. Але не тут-то було. У відповідь сервер продовжував обривати комунікацію на півслові, начебто нічого і не отримував. А Wireshark в принципі не приймав мої повідомлення за RTP, відображаючи з як прості UDP.
Крок 6. RTP-комунікація
На те, щоб зрозуміти, в якому напрямку рухатися далі, пішло багато часу. Я вклала багато зусиль в те, щоб перечитати всі наявні в наявності специфікації і сто разів перевірити свої пакети на правильність. На сьомий же день Зоркий Око в моїй особі зауважив, що стандартна RTP-комунікація не починається відразу ж з відправки DTMF-даних, а що їй передує нетривалий обмін пакетами з сервером, які виглядають трохи інакше.
Формат корисного навантаження, оголошений в заголовку, дорівнює 0, даних немає, зате є власне сама корисне навантаження (payload), яка займає 160 байт. Цей набір байтів різниться у всіх приходили і відходили повідомленнях і виглядає складеним досить випадково. Так чи інакше, я не змогла знайти інформації про те, як саме він повинен формуватися, тому кожен раз забивала його Рандома.
Після того, як я стала відправляти ці допоміжні пакети перед кожним DTMF-сигналом, Wireshark нарешті визнав RTP-формат. Все виглядало краще, але комунікація як і раніше переривалася, хоча сервер тепер від радості теж став мене закидати «пейлоднимі» пакетами.
Я вже й не знала, що ще б могла зробити, але тут згадала, що RTP є брат-нерозлучник - RTCP. Проблема, як видно, дійсно була в ньому: сервер намагався мені щось відправити, але йому від мене постійно приходили повідомлення про те, що відповідний порт закритий. Оскільки я не хотіла морочитися відправкою ще й RTCP-пакетів, я почала просто з відкриття чакри порту:
Це зробило вирішальний вплив: абонент отримав моє повідомлення «305 * 1 * 66» на пейджер!
висновок
В останніх рядках моїй вози хотілося б підкреслити, що це мій перший пост на Хабре, так що не судіть мене строго. Я абсолютно не вважаю себе гуру телематики або чого-небудь ще. Просто при написанні вихідного коду дуже багато часу пішло на пошук інформації. Щось я знаходила в специфікаціях, які від початку до кінця в один присід подужати було важкувато, щось було описано нормальною мовою, але як-то неяскраво дрібним шрифтом на полях, щось я робила навмання. Так що в якийсь момент просто вирішила, що якщо у мене все вийде, я опишу всі свої дії в одному місці і залишу це індексуватися куди-небудь в інтернет.