Wednesday, March 5, 2014

Genesys Routing Integration with Web Services

Summary

This is a continuation of my previous article on SIP UUI header decoding.  In this article, I'll be demonstrating how to call that web service directly from a Genesys routing strategy developed in Interaction Routing Designer (IRD).

Environmentals

Genesys Universal Router Server (URS) has provided a two-way web interface (HTTP Bridge) for several years.  To enable the interface and its logging, you need to add a 'web' section to the Options configuration for the URS object.  Figures 1 and 2 below depict the Options configuration I used.

Figure 1
Figure 2
Side Note:  You must configure the 'http_port' option to enable URS to connect to external web servers.  That option opens a server socket on the server where URS resides.  I can't make sense of the logic behind a mandate to open a server socket on URS just to enable client sockets, but it is what it is.

Routing Strategy

Figure 3 depicts a toy routing strategy written with IRD.  The strategy demonstrates the basics of calling a web service.  I execute the following logic in this strategy:

  1. I configured a Web Service object to make a call to the UUI header 'decode' web service I developed and described here.  Filling in the required info for the General Tab (Figure 4) is accomplished easiest by simply importing the WSDL for your target web service.  Here I'm setting up the hex value of 3836372d35333039;encoding=hex as the input parameter for the web service call.  Figure 5 depicts the Result tab, which corresponds to the output.
  2. I use the Function object (Figure 6) to assign the web service result to a local variable.  Genesys URS assumes all web service results are arrays, so I'm using the List function to assign the first value of that array (phoneNumList) to a local variable (phoneNum).
  3. I use Multi-Attach object to attach the variable to the call (Figure 7).
  4. Finally, URS routes the call to a skill target.


Figure 3

Figure 4

Figure 5

Figure 6

Figure 7
Execution

Below are some log excerpts showing the events that unfold when this strategy was executed.  I placed a test call to DN 4000 where I had this strategy loaded.  The TServer was Genesys SIP Server. The agent environment was the X-Lite endpoint and Genesys Support Phone.

URS Log:

received from 65200(TServer_SIPS810)genesys:7070(fd=) message EventRouteRequest
        AttributeCallState      0
        AttributeCallType       2
        AttributePropagatedCallType     2
        AttributeCallID 116
        AttributeConnID 0005024010bd6071
        AttributeCallUUID       '02ACMQ3C6S9GF9CM04000VTAES00003K'
        AttributeDNIS   '4000'
        AttributeANI    '1111111111'
        AttributeThisDN '4000'
...
11:32:10.231_H_I_ [08:0c] SOAP request 2 sent to HTTP Bridge:
        URL:        http://192.168.1.75:8080/sipuui/services/UUITranscoder.UUITranscoderHttpSoap11Endpoint
        Method:     ns:decode
        NameSpace:  ns=http://sipuui
        SOAPaction: urn:decode
        Input:      ns:header:3836372d35333039;encoding=hex
        Output:     decodeResponse.return
        HTTPAuthent:1
        SOAPSecrty:
11:32:10.231_I_I_0005024010bd6071 [09:04] <<<<<<<<<<<<suspend interp(WAIT_WEBSERVICE), func:GetWebServiceInfoEx timers:00000
11:32:10.236_H_I_0005024010bd6071 [08:08] OK InfoMessage (-1) is received from server ##HTTPSERVER, refid=2, hint=soap
  key V1 [List] value: (size=21)
    key STRN [String] value: "867-5309"
11:32:10.236_I_I_0005024010bd6071 [09:05] >>>>>>>>>>>>resume interp(0), func:GetWebServiceInfoEx
    _I_I_0005024010bd6071 [09:04] ASSIGN: __WEBReturn(SCRIPT) <- LIST: V1.STRN:867-5309
    _I_I_0005024010bd6071 [09:04] ASSIGN: phoneNumList(SCRIPT) <- STRING: "1:867-5309"
    _I_I_0005024010bd6071 [09:04] ASSIGN: phoneNum(SCRIPT) <- STRING: "867-5309"
request to 65200(TServer_SIPS810) message RequestAttachUserData
        AttributeReferenceID    14
        AttributeUserData       [28] 00 01 00 00..
                'Jennys_Number' '867-5309'
        AttributeConnID 0005024010bd6071
        AttributeThisDN '4000'


HTTP Bridge Log:

03/04/14@11:32:10: [HTTP Client 85d32f4] Request sent:
POST /sipuui/services/UUITranscoder.UUITranscoderHttpSoap11Endpoint HTTP/1.1
Host: 192.168.1.75:8080
User-Agent: gSOAP/2.7
Content-Type: text/xml; charset=utf-8
Content-Length: 493
Connection: keep-alive
SOAPAction: "urn:decode"

<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:ns="http://sipuui"><SOAP-ENV:Body SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><ns:decode><ns:header>3836372d35333039;encoding=hex</ns:header></ns:decode></SOAP-ENV:Body></SOAP-ENV:Envelope>
03/04/14@11:32:10: [HTTP Client 85d32f4] Received 418 bytes from server on socket 9:
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: text/xml;charset=utf-8
Transfer-Encoding: chunked
Date: Tue, 04 Mar 2014 18:32:10 GMT

101
<?xml version='1.0' encoding='utf-8'?><soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"><soapenv:Body><ns:decodeResponse xmlns:ns="http://sipuui"><ns:return>867-5309</ns:return></ns:decodeResponse></soapenv:Body></soapenv:Envelope>


Troubleshooting Tip:

If you're confident you've configured things properly but are still having issues with web service calls from the strategy - check your URS and HTTP Bridge logs.  If you notice the message below in your URS log and the HTTP Bridge log is clean - you're likely encountering a Genesys bug.  I had URS 8.1.2 loaded when I was getting these errors.  I upgraded to 8.1.3 and this error magically disappeared.

09:23:18.115 Int 22000 Web Service Access Error



Sunday, March 2, 2014

SIP UUI Header Parsing in Java

Summary

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.


Saturday, February 1, 2014

Node.js Crypto Module Examples

I recently had the need to use the node crypto module in a project.  To get this to work properly, paying attention to the last two sentences on the cipher.update function documentation is critical.

cipher.update(data, [input_encoding], [output_encoding])#

Updates the cipher with data, the encoding of which is given in input_encoding and can be 'utf8','ascii' or 'binary'. If no encoding is provided, then a buffer is expected. If data is a Buffer theninput_encoding is ignored.
The output_encoding specifies the output format of the enciphered data, and can be 'binary''base64' or'hex'. If no encoding is provided, then a buffer is returned.

To illustrate, here are 3 simple examples:

1.  Concat cipher text strings with in/out encoding specified.


var plainText = '1234567812345678'; 
var cipher1 = crypto.createCipher('aes256', 'password'); 
var cipherText1 = cipher1.update(plainText, 'ascii','binary'); 
cipherText1 += cipher1.final('binary'); 
console.log('Method 1 - plainText:' + plainText ); 
console.log('Method 1 - cipherText length:' + cipherText1.length); 
var decipher1 = crypto.createDecipher('aes256', 'password'); 
var result1 = decipher1.update(cipherText1); 
result1 += decipher1.final(); 
console.log('Method 1 - result:' + result1);

This one works as advertised. Output below:

Method 1 - plainText:1234567812345678 
Method 1 - cipherText length:32 
Method 1 - result:1234567812345678

2.  Concat buffers with no in/out encoding specified.
var plainText = '1234567812345678'; 
var cipher2 = crypto.createCipher('aes256', 'password'); 
var cipherText2 = Buffer.concat([cipher2.update(new Buffer(plainText)), cipher2.final()]); 
console.log('Method 2 - plainText:' + plainText ); 
console.log('Method 2 - cipherText length:' + cipherText2.length); 
var decipher2 = crypto.createDecipher('aes256', 'password'); 
var result2 = decipher2.update(cipherText2); 
result2 += decipher2.final(); 
console.log('Method 2 - result:' + result2);

Also works. Output below
Method 2 - plainText:1234567812345678 Method 2 - cipherText length:32 Method 2 - result:1234567812345678
3. String concat with NO encoding specification.
var cipher3 = crypto.createCipher('aes256', 'password'); 
var cipherText3 = cipher3.update(plainText); 
cipherText3 += cipher3.final(); 
console.log('Method 3 - plainText:' + plainText ); 
console.log('Method 3 - cipherText length:' + cipherText3.length); 
var decipher3 = crypto.createDecipher('aes256', 'password'); 
var result3 = decipher3.update(cipherText3); 
result3 += decipher3.final(); //Boom 
console.log('Method 3 - result:' + result3);
Method 3 - plainText:1234567812345678 
Method 3 - cipherText length:29 error:0606506D:digital envelope routines:EVP_DecryptFinal_ex:wrong final block length
The tip off as to what is happening is that cipherText length. It's only 29 in this example,but was 32 in the others. cipher.update() is returning a Buffer object, just as advertised. Concat'ing those buffers leads to an implicit toString() call on those buffer objects. That call leads to a default UTF-8 conversion in Buffer (also as advertised in the documentation).Cipher text is mangled in that conversion and the result can't be decrypted. Source code used in this discussion here.

Copyright ©1993-2024 Joey E Whelan, All rights reserved.

Tuesday, January 28, 2014

Node vs. Java - Web Service Performance

Summary

I recently did some comparative testing of web service implementations for a simple in-memory cache. I built functionally equivalent interfaces in Java (REST + SOAP) and Node.js (REST only) for the cache.  As expected, the Node implementation outperformed the Java variants significantly (>100% faster response times).

Cache Implementation

Figure 1 depicts the high-level structure of this cache application.  The cache supports inserts, fetches, and deletes of key/value pairs.
Figure 1
Figure 2 depicts a bit more detail on the physical layout of the application.

In the cases of the REST variants for Java and Node, cache operations are implemented as HTTP verbs (Insert = PUT, Fetch = GET, Remove = DELETE).  Stale entries are cleared from the cache using timeouts with Node and scheduled threads in Java.  Additionally, cache redundancy (loose coherence) is supported simply by utilizing REST calls between the server peers (PUT's and DELETE's).

For the Java SOAP variant, cache ops are implemented with the typical HTTP POST of SOAP envelopes.

Figure 2
Application Organization

Figure 3 below depicts the organization of the Java REST variant of the cache.  Apache Tomcat + Jersey (servlet) are leveraged.

Figure 3


Figure 4 below depicts the organization of the Java SOAP variant of the cache app.  Tomcat + Apache Axis2(servlet) are leveraged.


Figure 4


Figure 5 below depicts the Node.js implementation.  A single worker process is utilized.

Figure 5
Testing
Figure 6 depicts the test environment I used.





Java + Node REST Cache Insert Test
ab -A username:password -u restput.txt -n 1000 -c 1 https://server/ctispan/rest/key/111 > results.txt

restput.txt
value=test111

Java SOAP Cache Fetch Test (I used TCP/IP Monitor in Eclipse to figure out the SOAP formats for ab)
ab -A client:password -T "application/soap+xml; charset=UTF-8" -p soapget.xml -n 1000 -c 1 https://server/ctispan/services/CacheProxyWS.CacheProxyWSHttpSoap11Endpoint/ > results.txt

soapget.xml
<?xml version='1.0' encoding='UTF-8'?><soapenv:Envelope xmlns:soapenv="http://www.w3.org/2003/05/soap-envelope"><soapenv:Body><ns1:getValue xmlns:ns1="http://server.ctispan.jwisoft.com"><ns1:key>111</ns1:key></ns1:getValue></soapenv:Body></soapenv:Envelope>

Java SOAP Cache Insert Test
ab -A client:password -T "application/soap+xml; charset=UTF-8" -p soapput.xml -n 1000 -c 1 https://server/ctispan/services/CacheProxyWS.CacheProxyWSHttpSoap11Endpoint/ > results.txt

soapput.xml
<?xml version='1.0' encoding='UTF-8'?><soapenv:Envelope xmlns:soapenv="http://www.w3.org/2003/05/soap-envelope"><soapenv:Body><ns1:putValue xmlns:ns1="http://server.ctispan.jwisoft.com"><ns1:key>111</ns1:key><ns1:value>text111</ns1:value></ns1:putValue></soapenv:Body></soapenv:Envelope>


Results
Below are some graphs of the numbers ab produced.  I wasn't really surprised by the Node vs. Java results.  The Java REST vs. SOAP numbers were a little surprising.  I expected a wide margin between Java REST and SOAP (in REST's favor) due to the overhead of SOAP.  All I can surmise is the Apache Axis2 API is significantly more efficient than the Jersey API.






Copyright ©1993-2024 Joey E Whelan, All rights reserved.

Sunday, January 12, 2014

Apache Cassandra Start-up Problem - UseCondCardMark


Problem

You receive the error below when attempting to start up Cassandra

Unrecognized VM option 'UseCondCardMark'

Diagnosis

The issue is that JVM option is for the server JVM, only.  You're likely attempting to start Cassandra with the 'client' JVM.

Solution

Add the '-server' option to the JVM_OPTS variable that Cassandra utilizes.  That shell variable is build up in the conf/cassandra-env.sh file.  Below I've edited that file and added the '-server' option as the first option to JVM_OPTS.

# Here we create the arguments that will get passed to the jvm when
# starting cassandra.

JVM_OPTS="$JVM_OPTS -server"


Copyright ©1993-2024 Joey E Whelan, All rights reserved.

Thursday, January 9, 2014

SIP UUI Data Encoding

Per the IETF draft, strings passed via SIP UUI are encoded as hexadecimal digits representing their ASCII code.  Interestingly enough, the draft doesn't address how to pass strings from the larger character set covered by Unicode.

Example ASCII  to Hex Translation

ASCII Character string: cake
ASCII Decimal code:  99 97 107 101
ASCII Hexadecimal code (what goes on the wire): 63616B65

Below are a couple simple Java functions to perform these string translations:

Code


public class Decoder
{
 public String hexToString(String hex)
 {
  String str = "";
  if (hex != null)
   for (int i=0; i < hex.length()-1; i+=2)
    try
    {
     str += (char) Integer.parseInt(hex.substring(i, i+2),16);
    }
    catch (Exception e)
    {
     return "";
    }
  return str;
 }

 public String StringtoHex(String str)
 {
  String hex = "";
  if (str != null)
   for (int i=0; i < str.length(); i++)
    hex += Integer.toHexString((int)str.charAt(i));

  return hex;
 }

 public static void main(String[] args)
 {
  Decoder decoder = new Decoder();
  System.out.println(decoder.StringtoHex("test"));
  System.out.println(decoder.hexToString("74657374"));
}
Output

74657374

test

Copyright ©1993-2024 Joey E Whelan, All rights reserved.


Friday, January 3, 2014

Implementing a Cisco GED-145 Gateway with Node.js

Summary
Cisco's GED-145 is an API + protocol definition for integrating their enterprise-grade contact center product with 3rd-party systems.    This document discusses using JavaScript with node.js to implement a GED-145 application gateway.

Background
Cisco's enterprise-grade call routing engine is called Intelligent Contact Management (ICM).  ICM is a suite of software components that provide ACD, CTI, and reporting for large-scale contact centers (thousands of agents).

The ICM routing script editor interface provides drag/drop functionality for creating call routing scripts.  Those scripts can employ of wide variety of out-of-box routing logic to deliver a call to the appropriate agent target - such as time of day, longest available agent, most appropriately skilled agent, etc.  ICM provides a mechanism to extend that routing logic using the GED-145 API.  I believe 'GED' is an acronym for 'Geotel Engineering Document'.  Geotel was the company that created ICM.  Cisco acquired Geotel way back in 1999, but some of the Geotel naming conventions clearly still exist.

GED-145 is a TCP socket-based API that provides the ability to query 3rd party systems for routing information from within an ICM routing script.  A middleware application, called an 'Application Gateway', can be developed to act as a server to the ICM routing engine and client to the 3rd party system being queried.  Figure 1 depicts the overall GED-145 architecture with a web server as the 3rd party system.


GED-145 defines a low-level byte format for messaging between the ICM Router and Application Gateway.  Figure 2 depicts the byte format for GED-145 messages.



In addition to message formats, GED-145 defines the messaging protocol between the Router and Application Gateway.  The protocol is of a simple Request/Response type.  Figure 3 shows message format and protocol for one of the simplest message and exchange types - the Heartbeat.  The Heartbeat request/response protocol provides application-level keep-alive functionality.

Implementation

Figure 4 below depicts my overall approach for this application.
  • Application Gateway - node.js TCP server application.  It acts a server to the ICM router and speaks GED-145 in that direction.  It also acts as a client to a web server and speaks REST in that direction.
  • ICM Router Simulator - node.js  TCP client application, simulating an ICM Router.  Provides a mechanism to test the GED-145 protocol and apply load to the Application Gateway.
  • Web/Rest Simulator - node.js/express.js web server providing a contrived REST interface.
  • App Logger - winston.js logging implementation.  Provides debug and error-level log messages to console and log files.
  • Message Handler - miscellaneous node.js functions to manage creation and parsing of GED-145 messages.
  • Rest Client - node.js client that is utilized within the Application Gateway.  Provides a HTTP client to the REST simulator.
  • Test Runner - node.js interface to the Router Simulator.  Implements test suites for speed testing, TCP socket error injection, and load testing.

Performance
The Application Gateway code was deployed on a Centos 6.4 VM with 1 vCPU and 1 GB vRAM.  Below are the results of the following test sets.  
  • Speed Test:  tests the latency of a series of simple Heartbeat Requests and Responses
  • Error Test:  series of Open/Close/Query/Param/Heartbeat messages that are artificially broken up during socket writes to simulate the streaming nature of TCP.  Additionally, nonsensical messages are injected.
  • Load Test:  similar message set to the Error Test, but with no invalid messages nor artificial message breaks.
2014-01-03 07:49:20.257 - Starting Raw Speed Test
Speed Test Completed
Number of Transactions: 10
Elapsed Time: 13 ms
Average Elapsed Time per transaction: 1.3 ms

2014-01-03 07:49:20.399 - Starting Error Test
2014-01-03 07:50:20.405 - Error Test Completed
Random Synthetic Message Fragmentation:0-4
Transaction Count:2569
Induced Error Count:428
Caught Error Count:428
Elapsed Time:1 min
Average number of transactions/sec: 42.812

2014-01-03 07:50:20.406 - Starting Load Test
2014-01-03 08:50:20.447 Load Test Completed
Synthetic Message Fragmentation:none
Transaction Count:233756
Error Count:0
Elapsed Time:60.001 min
Average number of transactions/sec: 64.931

RAM/CPU Usage

Pidstat was utilized to log resource usage of the app gateway process during execution.  CPU usage ranged from 1-13%.  RSS memory usage ranged from 82 - 104 MB.  V8 garbage collection kicked in at 104 MB and reduced RSS down to 84 MB.   Below is the pidstat output reflecting the high-water marks on CPU and RSS usage.

Max CPU
#      Time       PID    %usr %system  %guest    %CPU   CPU  minflt/s  majflt/s     VSZ    RSS   %MEM  Command
 1388756055     16289   12.35    1.21    0.00   13.56     0    143.72      0.00 1059116  82060   8.04  node

Max RSS
#      Time       PID    %usr %system  %guest    %CPU   CPU  minflt/s  majflt/s     VSZ    RSS   %MEM  Command
 1388763210     16289    6.61    1.20    0.00    7.82     0     15.03      0.00 1076988 104292  10.22  node

Copyright ©1993-2024 Joey E Whelan, All rights reserved.