This article is going to expand on the SIP UUI decoding topic I introduced in a previous post.
I'm going to discuss the development of a recursive descent parser for the SIP UUI header per the definition in the current IETF draft. I wrote the parser in Java and implemented it as SOAP-based web service. The motivation for this exercise is to build an end-to-end SIP UUI integration with Genesys call routing. I'll discuss that in a later post.
Application Layout
Figure 1 |
Parser Implementation
That IETF draft includes a Backus-Naur Form definition of the SIP UUI header. This definition corresponds to a context-free grammar (CFG). A CFG consists of production rules, terminals, non-terminals, and one special non-terminal known as the 'start' symbol. Terminals equate to base symbols such as keywords or literals. Production rules define how non-terminals expand to terminals. The left side of a production rule is a non-terminal. The right side is a sequence of terminals and non-terminals. Example, using the IETF spec:
UUI -> "User-to-User" HCOLON uui-value *(COMMA uui-value)
This is a production rule. UUI is a non-terminal and also the start symbol. uui-value is another non-terminal in this production. The terminals of this production are: the "User-to-User" keyword, HCOLON (aka ':'), and COMMA (aka ','). *(COMMA uui-value) is a regular expression that means zero or more occurrences of a comma (',') and uui-value can occur after the first uui-value.
Developing a parser for simple CFG's such as this is straightforward. In fact, there are many tools out there to auto-generate a parser. For this simple grammar, I wrote the parser by hand. Fortunately, this is a quick process in Java.
I leveraged the Java built-in StringTokenizer class to create a simple lexical analyzer. The analyzer ignores white space and breaks a SIP UUI header input up into a series of tokens. Those tokens are then 'match'ed per the grammar's production rules. Code snippet below:
tokenizer = new StringTokenizer(header.replaceAll("\\s", ""), ":;,\"=", true);
lookahead = tokenizer.nextToken();
....
private void match(String token) throws Exception
{
logger.debug("Entering match(token=" + token + ")");
if (lookahead.equalsIgnoreCase(token))
{
if (tokenizer.hasMoreTokens())
lookahead = tokenizer.nextToken();
else
lookahead = "\0";
logger.debug("Exiting match()");
}
else
{
String errMsg = "Syntax Error - Expected:" + token + ", Found:" + lookahead;
logger.error("Error in parse():" + errMsg);
throw new Exception(errMsg);
}
}
Each non-terminal of the grammar corresponds a Java method. That method 'match'es terminals and/or calls further methods for non-terminals, per the production rules. In some cases, a recursive call is made for the non-terminal hence the name 'recursive' descent parser. Example methods below for UUI and uui-value non-terminals. Genesys strips the 'User-to-User:' preamble, so I'm not 'match'ing it below (commented out).
private void uui() throws Exception
{
logger.debug("Entering uui()");
/*
The next 2 tokens get stripped during the Genesys SIP to TLib conversion hence they're commented out so as to be ignored.
*/
//match(USERTOUSER);
//match(COLON);
uuiValue();
while (lookahead.equals(COMMA))
{
match(COMMA);
uuiValue();
}
logger.debug("Exiting uui()");
}
With the UUI header parser in place, decoding and encoding a header to/from hex and ASCII is a simple matter.
public static String[] decode(String header)
{
logger.debug("Entering decode(header=" + header + ")");
UUIParser parser = new UUIParser(header);
String[] strValues = null;
try
{
ArrayList<String> hexValues = parser.parse();
if (hexValues.size() > 0)
{
strValues = new String[hexValues.size()];
for (int i = 0; i < hexValues.size(); i++)
strValues[i] = Decoder.hexToString(hexValues.get(i));
}
}
catch (Exception e)
{
String errMsg = "error: " + e.toString();
logger.error("Error in decode():" + errMsg);
}
logger.debug("Exiting decode()");
return strValues;
}
Next step was turning this into a web service. Auto-generating a web service from Java code is super easy in Eclipse. A very nice tutorial on how to do that is here. Using the Eclipse tools, I generated the web service and client stub (for testing).
Execution
Below are sample encode and decode SOAP calls from curl and their output:
UUI encoding the string 'test'.
SOAP input envelope - soapencode.xml:
-------------------------------------------------------
<?xml version='1.0' encoding='UTF-8'?><soapenv:Envelope xmlns:soapenv="http://www.w3.org/2003/05/soap-envelope"><soapenv:Body><ns1:encode xmlns:ns1="http://sipuui"><ns1
:values>test</ns1:values></ns1:encode></soapenv:Body></soapenv:Envelope>
------------------------------------------------------
curl -v -H 'Content-Type: application/soap+xml; charset=UTF-8; action="urn:encode"' -X POST -d "@soapencode.xml" http://localhost:8080/sipuui/services/UUITranscoder.UUITranscoderHttpSoap11Endpoint/
* Adding handle: conn: 0xa54e90
* Adding handle: send: 0
* Adding handle: recv: 0
* Curl_addHandleToPipeline: length: 1
* - Conn 0 (0xa54e90) send_pipe: 1, recv_pipe: 0
* About to connect() to localhost port 8080 (#0)
* Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 8080 (#0)
> POST /sipuui/services/UUITranscoder.UUITranscoderHttpSoap11Endpoint/ HTTP/1.1
> User-Agent: curl/7.32.0
> Host: localhost:8080
> Accept: */*
> Content-Type: application/soap+xml; charset=UTF-8; action="urn:encode"
> Content-Length: 240
>
* upload completely sent off: 240 out of 240 bytes
< HTTP/1.1 200 OK
* Server Apache-Coyote/1.1 is not blacklisted
< Server: Apache-Coyote/1.1
< Content-Type: application/soap+xml; action="urn:encodeResponse";charset=UTF-8
< Transfer-Encoding: chunked
< Date: Sun, 02 Mar 2014 16:44:07 GMT
<
* Connection #0 to host localhost left intact
<?xml version='1.0' encoding='UTF-8'?><soapenv:Envelope xmlns:soapenv="http://www.w3.org/2003/05/soap-envelope"><soapenv:Body><ns:encodeResponse xmlns:ns="http://sipuui"><ns:return>74657374;encoding=hex</ns:return></ns:encodeResponse></soapenv:Body></soapenv:Envelope>
Now, parsing and decoding that same header string.
SOAP input envelope - soapdecode.xml:
-------------------------------------------------------
<?xml version='1.0' encoding='UTF-8'?><soapenv:Envelope xmlns:soapenv="http://www.w3.org/2003/05/soap-envelope"><soapenv:Body><ns1:decode xmlns:ns1="http://sipuui"><ns1
:header>74657374;encoding=hex</ns1:header></ns1:decode></soapenv:Body></soapenv:Envelope>
-------------------------------------------------------
curl -v -H 'Content-Type: application/soap+xml; charset=UTF-8; action="urn:decode"' -X POST -d "@soapdecode.xml" http://localhost:8080/sipuui/services/UUITranscoder.UUITranscoderHttpSoap11Endpoint/
* Adding handle: conn: 0xf42ea0
* Adding handle: send: 0
* Adding handle: recv: 0
* Curl_addHandleToPipeline: length: 1
* - Conn 0 (0xf42ea0) send_pipe: 1, recv_pipe: 0
* About to connect() to localhost port 8080 (#0)
* Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 8080 (#0)
> POST /sipuui/services/UUITranscoder.UUITranscoderHttpSoap11Endpoint/ HTTP/1.1
> User-Agent: curl/7.32.0
> Host: localhost:8080
> Accept: */*
> Content-Type: application/soap+xml; charset=UTF-8; action="urn:decode"
> Content-Length: 257
>
* upload completely sent off: 257 out of 257 bytes
< HTTP/1.1 200 OK
* Server Apache-Coyote/1.1 is not blacklisted
< Server: Apache-Coyote/1.1
< Content-Type: application/soap+xml; action="urn:decodeResponse";charset=UTF-8
< Transfer-Encoding: chunked
< Date: Sun, 02 Mar 2014 17:25:16 GMT
<
* Connection #0 to host localhost left intact
<?xml version='1.0' encoding='UTF-8'?><soapenv:Envelope xmlns:soapenv="http://www.w3.org/2003/05/soap-envelope"><soapenv:Body><ns:decodeResponse xmlns:ns="http://sipuui"><ns:return>test</ns:return></ns:decodeResponse></soapenv:Body></soapenv:Envelope>
Copyright ©1993-2024 Joey E Whelan, All rights reserved.