📜 ⬆️ ⬇️

Back in the 90s or how to send a message to a pager via java

After reading the title, you are probably a little surprised at the unusual task that I set for myself. However, oddly enough, pagers still sometimes can be useful in life, even despite the abundance of other means of communication that has appeared in the past 15 years. One of the special cases of their use is (near) a medical facility located in a reinforced concrete building that blocks WiFi and a mobile phone signal. Attendants, however, must somehow receive messages about where they need to urgently move in if anything happens. To solve this problem, the management of the institution in our case set itself an expensive station and distributed to all the employees of the pager pagers who were supposed to receive our signals among others. Accordingly, our (me and my colleagues) task was to send them.

The days have already passed when, in order to send text to a pager, you first had to communicate with a sleepy girl from a telephone hub. Now it is enough to reach the station and dial the number of the subscriber and the message in tone mode. At the same time, the arsenal is very limited: you can send only numbers, symbols * and #, sometimes the letters ABCD. But to transmit, say, a room number or an error code should suffice. This simplifies the task quite a lot and makes it related to others - with dialing into a common meeting room, for example.

Despite the seeming transparency of the decision and the secondary nature of my experience, I decided to describe my actions in detail, because there is not very much information on the Internet on the topic: the forums rarely answer questions and not a word. To some, this text may save a lot of time.
')
image

Step 1 - INVITE

The first stage - dialing a paging station - was implemented through the SIP protocol and using the corresponding jain-sip Java library. I found the best description of the principles of the protocol on Habré in the publications “Interaction of SIP clients. Part 1 " and " The interaction of SIP clients. Part 2 , and the most digestible tutorial on jane is right here (but the collection of examples from here refused better).

As a pre-prep I created a class:

public class SipNotificator implements SipListener 

with the required fields, which must first be initialized as indicated in the tutorial:

 private SipProvider sipProvider; private SipFactory sipFactory; private SdpFactory sdpFactory;//  private AddressFactory addressFactory; private HeaderFactory headerFactory; private MessageFactory messageFactory; 

As the rules dictate, we first need to send an INVITE message to the phone. Please note that the destination in To- and Request-hederah recorded differently. In the first case, the header is simply collected from the global telephone number:

 Address toNameAddress = addressFactory.createAddress( addressFactory.createTelURL(adresseenumber)); ToHeader toHeader = headerFactory.createToHeader(toNameAddress, null); 

In the second case, you must specify the host from which the message is sent that initiates communication:

 URI requestURI = addressFactory.createAddress("sip:"+adresseenumber+"@"+host+";user=phone").getURI(); 

Another interesting element is, in fact, the body of the SDP message, which is a description of what is needed for successful communication. In our case, it looked like this:

 String sdpData = "v=0\r\n" + "o=4444 123456 789054 IN IP4 "+InetAddress.getLocalHost().getHostAddress() +"\r\n" + "s=phone call\r\n" + "p="+phoneusername+"\r\n" + "c=IN IP4 "+InetAddress.getLocalHost().getHostAddress() +"\r\n" + "t=0 0\r\n" + "m=audio "+localUdpPort+" RTP/AVP 0 8 18 101\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "a=rtpmap:8 PCMA/8000\r\n" + "a=rtpmap:18 G729A/8000\r\n" + "a=fmtp:18 annexb=no\r\n" + "a=rtpmap:101 telephone-event/8000\r\n" + "a=fmtp:101 0-16\r\n" + "a=ptime:20\r\n" + "a=sendrecv\r\n"; 

Attributes "o" and "s" are not particularly important, in "p" we write our phone. The main part is “m” (media), in which the used codecs are written (in our case, they may not be installed on the sender) and the port for accepting answers on the topic.

Step 2 - Authentication

If we were able to send the correct invite, then at best the recipient server will send us the desired OK-message with the status 200, and at worst - decide to torture with a little more identification. In the second case, the response status will be 401 or 407. Here is the code with which the response is sent. To support it, you need one of the latest versions of jain-sip (for example, 1.2.228). It must be placed in the processResponse () method, which receives ResponseEvent responseEvt as an argument.

 if (status == 401|| status == 407){ AuthenticationHelper authenticationHelper = ((SipStackExt) sipProvider.getSipStack()).getAuthenticationHelper( new AccountManagerImpl(this.phoneusername, this.password), headerFactory); transaction = authenticationHelper.handleChallenge(responceEvt.getResponse(), responceEvt.getClientTransaction(), sipProvider, 15, true); dialog = transaction.getDialog(); transaction.sendRequest(); } 

Pay attention to the fourth argument of the handleChallenge () method, without it the message format will change, it will become inappropriate and your authentication will fail.

The classes AccountManagerImpl and also the required UserCredentialsImpl should be added by you, I wrote them according to the model of those presented here .

After sending your registration data, we can safely expect the desired 200 OK, to which you need to remember to send an ACK. This type of message is made extremely simple:

 Request ackRequest = dialog.createAck( ((CSeqHeader) responseEvt.getResponse().getHeader(CSeqHeader.NAME)).getSeqNumber() ); //dialog -   


Step 3 - SIP INFO

Then the most interesting part begins - sending DTMF-signals (the same keystrokes in tone mode). Globally, this can be done through two different protocols: via SIP and via RTP . Naturally, at first it was decided to follow the path of least resistance. For each character, this request was formed, which then had to be sent to the server:

 Request info = dialog.createRequest(Request.INFO); String sdpData = "Signal="+digit+"\r\n" + "Duration=200"; byte[] contents = sdpData.getBytes(); ContentTypeHeader contentTypeHeader = headerFactory.createContentTypeHeader("application", "dtmf-relay"); info.setContent(contents, contentTypeHeader); ClientTransaction transaction = sipProvider.getNewClientTransaction(info); Dialog dialog = transaction.getDialog(); dialog.sendRequest(transaction); 

The dispatch procedure itself looks a bit strange (it seems, built on the model “through Zhmerynka to Paris”), but otherwise, nothing worked for me. In general, the library seemed to me a bit buggy: very often one of several solutions that looked, by and large, did not work the same way.

What can I say? After the implementation of this step, it turned out that not all VoIP servers are equally friendly: some had enough signals transmitted via SIP, but some didn’t have enough, since they do not produce a sound signal and therefore go unnoticed. Naturally, according to the law of meanness, my goal was a server of the second type. Therefore…

Step 4. formation of the RTP packet

In general, when I realized that I could not solve the problem with one SIP, I hoped that at least I could use another library that can send DTMF signals in a relaxed way. But it was not there. Usually, if we say “RTP through Java”, we mean JMF . But, first, it is already old and not very supported. Secondly, it is more suitable for transferring more complex media. Thirdly, the tutorials that I managed to find were not very sensible. Here is one of the examples from the documentation, in the middle of which a certain rtpSession pops up, which I could not find at all in the first few minutes of the search.

Another option was the libjitsi library, which is a whole communicator. It was also impossible to borrow anything from it, although there is a nice sendDTMF method or something like that. The code structure is such that it is taken either in its entirety or in any way. As a result, it was decided to make a human package in a normal way and send it via a UDP socket.

So, here is a significant fragment of the RtpPacket class: its main fields and a constructor with values ​​suitable for DTMF transmission. What all these things mean, it says a lot where, so I will not repeat. I will only note that, in principle, the value of the ssrc parameter does not matter, but it must be the same for all packets sent in one session. The DTMF payload format (payload type) is 101 (we registered it when we initiated SIP communication).

  private int version; private boolean padding; private boolean extension; private int csrcCount; private boolean marker; private int payloadType; private int sequenceNumber; private long timestamp; private long ssrc; private long[] csrcList; private byte[] data; public RtpPacket(){ this.setVersion(2); this.setPadding(false); this.setExtension(false); this.setCsrcCount(0); long[] list = {}; this.setCsrcList(list); } 


The most important step in creating a packet is filling a byte array of data. DTMF naturally has its own format: the first byte is, in fact, the value of the transmitted signal (from 0 to 16), the first half of the second byte is different markets (usually 0), the second half of the second byte is loudness (the standard value is 10), the rest two is the duration (the standard value is 160).

About 10 packets are created for each signal (the number may vary):

- the first, initial, has a marker = 1, the rest - 0;
- the last three - the end, marker = 0, but the first bit of the second byte of the data block = 1. The data block in the non-final packet for signal transmission 1 will look like this:

 0000 0001 0000 1010 0000 0000 1010 0000    

And in the end like this:

 0000 0001 1000 1010 0000 0000 1010 0000  end   

The time stamp for all DTMF packets related to the same signal may remain the same (suppose T). But the time of the next package should be:

 T+( DTMF- *  ) 


Step 5. RTP channel

Only one socket could be created for sending and receiving messages. But - in any case - no sending, of course, will not happen if you do not know the host and port of destination. This is not the data through which SIP-communication took place. The telephone server sends us its coordinates in response SIP messages during our dial-up. You can get them by inserting this code into the processResponce () method (here you can see why we initialized sdpFactory earlier):

 Response resp = responceEvent.getResponse(); int remoteHost; int remotePort; if (resp.getRawContent()!=null){ String sdpContent = new String(resp.getRawContent()); SessionDescription requestSDP = sdpFactory.createSessionDescription(sdpContent); remoteHost = requestSDP.getConnection().getAddress();//   Connection Information Vector<MediaDescription> media = requestSDP.getMediaDescriptions(false); for (MediaDescription m:media){ if (m.getMedia()!=null ) remotePort =m.getMedia().getMediaPort();//     media } } 

Further, as I naively believed, I could only put DatagramPackets from my bytes, shove them into a socket and send them to the server. But it was not there. In response, the server continued to terminate the communication in half a word, as if it had not received anything. And Wireshark basically did not take my messages for RTP, displaying them as simple UDP.

Step 6. RTP communication

It took a long time to understand in which direction to move on. I put a lot of effort into re-reading all the available specifications and checking my packages for correctness a hundred times. On the seventh day, the keen eye in my face noticed that standard RTP communication does not begin immediately with sending DTMF data, but that it is preceded by a short exchange of packets with the server, which look slightly different.
The format of the payload declared in the header is 0, there is no data, but there is actually the payload itself, which takes 160 bytes. This set of bytes is different in all incoming and outgoing messages and looks composed rather randomly. One way or another, I could not find information on how exactly it should be formed, so every time I scored it with random numbers.

After I started sending these auxiliary packets before each DTMF signal, Wireshark finally recognized the RTP format. Everything looked better, but the communication was still interrupted, although the server now also began to throw me with "payloom" packets for joy.

I already did not know what else I could do, but then I remembered that RTP is a brother-love-love - RTCP . The problem, apparently, was really in it: the server tried to send me something, but he constantly received messages from me that the corresponding port was closed. Since I didn’t want to bother sending RTCP packets, I just started by opening the port chakra :

 DatagramSocket socket = new DatagramSocket(localUdpPort, InetAddress.getLocalHost()); DatagramSocket controlSocket = new DatagramSocket(localUdpPort+1, InetAddress.getLocalHost());//    1 ,   RTP 

This had a decisive impact: the subscriber received my message "305 * 1 * 66" on the pager!

Conclusion

In the last lines of my cart, I would like to emphasize that this is my first post on Habré, so do not judge me harshly. I absolutely do not consider myself a guru of telematics or anything else. Just when writing the source code, a lot of time was spent searching for information. Something I found in the specifications, which from the beginning to the end in one sitting was difficult to master, something was described in normal language, but somehow softly in small print on the margins, something I did at random. So at some point I simply decided that if I succeed, I will describe all my actions in one place and leave it indexed somewhere on the Internet.

So I really hope that at least someone my article is useful, or at least seems interesting.

Source: https://habr.com/ru/post/250185/


All Articles