Back in the 90s or how to send a message to a pager through Java

After reading the headline, you probably were a little surprised at the unusual nature of the task that I set for myself. However, strangely enough, pagers can still sometimes 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 (about) a medical institution located in a reinforced concrete building that damps WiFi and a mobile phone signal. The attendants, however, must somehow receive messages about where they urgently need to move in case of something. To solve this problem, the management of the institution in our case set itself an expensive station and distributed pagers to all employeespagers who were supposed to receive our signals among others. Accordingly, our (me and my colleagues) task was to send them.

The times have already passed when, in order to send text to a pager, you first had to talk with a sleepy girl from the telephone node. Now it’s enough to get through to the station and dial the subscriber’s number and message in tone mode. 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 be enough. This greatly simplifies the task and makes it related to others - with dialing into a common meeting room, for example.

Despite the apparent transparency of the decision and the secondary nature of my experience, I decided to describe my actions in detail, because there is not much information on the Internet on the topic: on the forums, questions are seldom and quietly answered. To some, this text may save a lot of time.

image

Step 1 - INVITE

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

As a prerequisite, I created a class:

public class SipNotificator implements SipListener

with the necessary 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 tell us, you must first send an INVITE message to your phone. Please note that the destination in the To and Request headers is written differently. In the first case, the header is simply collected from the global phone 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 initiating communication is sent:

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 something 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";

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

Step 2 - Authentication

If we were able to send the correct invite, then in the best case, the recipient server will send us the desired OK message with a status of 200, and in the worst, it will decide to torment it a little more with identification. In the second case, the response status will be 401 or 407. Here is the code by which the response is sent. To support it, you will need one of the latest versions of jain-sip (for example, 1.2.228). It must be placed in the processResponse () method, which receives the 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, become inappropriate, and your authentication will fail.

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

After sending your registration data, we can safely expect the desired 200 OK, to which we must not forget to send the 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 fun begins - sending DTMF signals (those same tones in the tone mode). Globally, this can be done through two different protocols: through SIP and through RTP . Naturally, at first it was decided to follow the path of least resistance. For each character, such a request was generated, 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 sending procedure itself looks a little strange (it seems, built on the model “through Zhmerynka to Paris”), but otherwise nothing worked for me. In general, the library seemed a bit buggy to me: very often one of several solutions that looked basically the same did not work.

What can I say? After implementing this step, it turned out that not all VoIP servers were equally friendly: some of them had enough signals transmitted through SIP, but others did not have enough, since they do not produce an audio 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 it was impossible to solve the problem with one SIP, I hoped that at least I could use another library that can send DTMF signals without tension. But it was not there. Usually, if we say “RTP through Java”, then we mean JMF . But, firstly, it is already old and not particularly 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 example 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. Nothing could be borrowed from it either, although there is a nice sendDTMF method or something like that. The structure of the code is such that it is taken either in its entirety or not at all. As a result, it was decided in the normal way to make a human packet and send it through a UDP socket.

So, here is a significant fragment of the RtpPacket class: its main fields and constructor with values ​​suitable for transmitting DTMF. What all these things mean is written a lot where, therefore I will not repeat. I only note that the value of the ssrc parameter in principle does not play a role, but for all packets sent in the same session, it must match. The payload type of the 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 package is filling in a byte data array. 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 various markets (usually 0), the second half of the second byte is volume (the standard value is 10), the rest two is the duration (the default is 160).

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

- the first, initial, has marker = 1, the rest - 0;
- the last three are final, marker = 0, but the first bit of the second byte of the data block = 1. The data block in the non-final packet for transmitting signal 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 timestamp for all DTMF packets belonging to the same signal can remain the same (suppose T). But the time of the next package should be:

T+(количество DTMF-пакетов * длительность пакета)


Step 5. RTP channel

You could create only one socket for sending and receiving messages. But - in any case - no sending will naturally happen unless you know the host and destination port. This is not at all the data through which SIP communication passed. The telephone server sends its coordinates to us in response SIP messages during our dialer. You can get them by inserting the following 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 media = requestSDP.getMediaDescriptions(false);
      for (MediaDescription m:media){
		if (m.getMedia()!=null )
			remotePort =m.getMedia().getMediaPort();//порт можно найти среди данных media
		}
	}

Further, as I naively thought, I could only do DatagramPackets from my bytes, put them into a socket and pull them into a server. But it was not there. In response, the server continued to break off communication at a glance, as if it had not received anything. And Wireshark, in principle, 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 a hundred times for correctness. On the seventh day, the Vision Eye in my face noticed that standard RTP communication does not start immediately with sending DTMF data, but that it is preceded by a short exchange of packets with the server, which look slightly different.
The payload format 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 differs in all incoming and outgoing messages and looks composed quite randomly. One way or another, I could not find information about how it should be formed, so every time I beat it with random numbers.

After I began 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 began to shower me with “payload” packets for joy.

I didn’t know what else I could have done, but then I remembered that RTP is an inseparable brother - RTCP. Apparently, the problem was really in it: the server was trying to send me something, but it constantly sent me messages 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 effect: 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 strictly. I do not consider myself a telematics guru or anything else. It’s just that when writing the source code a lot of time was spent searching for information. Something I found in the specifications, which from start to finish in one sitting was difficult to master, something was described in normal language, but somehow in softly small print on the margins, I did something at random. So at some point I just decided that if everything worked out for me, I will describe all my actions in one place and leave it to be indexed somewhere on the Internet.

So I really hope that at least someone will find my article useful or at least seem interesting.

Also popular now: