OpenTTCN/Developer corner/Creating adapter with Java SDK
From OpenTTCN
Home | Developer's corner | Knowledge base | Working documents | Documentation | OpenTTCN IDE | Tutorials | Training | How do I | Frequently asked questions | Technical support |
Creating adapter using Java
For archived version of this article applicable to versions of OpenTTCN Tester prior to OpenTTCN Tester 2011 version 4.1.2 click here.
In this article we create an adapter in Java using OpenTTCN SDK for Java. We use IDE coming with OpenTTCN Tester 2011 to develop the adapter in question. We also use new all-Java OpenTTCN SDK for Java available starting from OpenTTCN Tester 2011 version 4.1.2. The new SDK has the benefit of not containing any native platform-specific libraries, thus enabling possibility to do cross-platform development of adapters and to execute portable adapters in environments supporting Java, such as Android and BlackBerry.
Supported Java platforms are Oracle Java 6 and 5.
Creating Java project in OpenTTCN IDE
After you have started OpenTTCN Tester 2011 IDE with a workspace of your selection, you can create projects within the current workspace.
- go to File | New | Project...
- pick the Java Project wizard
- specify
java_wiki_examplein the Project name field of the wizard, otherwise keep the default project settings as they are and click Finish - make sure you are using the Java perspective of the IDE. Proper perspective can be selected by clicking the Open perspective button in the top-right corner of the IDE.
Adding Program.java to the project
- right-click on the src subfolder of the newly created project and select New | Package option
- enter
simpleAdapterin place of the package name and click Finish
- right-click on the name of the newly created package and choose New | File option
- enter
Program.javain place of the file name and click Finish
- add the following code to the newly created
Program.javafile:
package simpleAdapter;
import com.openttcn.sdk.*;
public class Program {
//////////////////////////////////////////////////////////////////
// Utility methods used elsewhere
private static java.net.DatagramSocket _socketDesc = null;
public static java.net.DatagramSocket getSocketDesc() {
return _socketDesc;
}
public static java.net.DatagramSocket bindSocketToAnyPort() {
java.net.DatagramSocket socket = null;
try {
socket = new java.net.DatagramSocket();
}
catch (java.net.SocketException e) { return null; }
return socket;
}
private static final int SLEEP_INTERVAL_IN_MILLIS = 100000;
private static void sleepForever() {
while (true) {
try { Thread.sleep(SLEEP_INTERVAL_IN_MILLIS); } catch (Exception e) { }
}
}
//////////////////////////////////////////////////////////////////
// Program entry point
public static void main(String[] args) {
try {
// Set default values of OpenTTCN server IP + port:
String serverAddr = "127.0.0.1";
int serverPort = 5500;
StartHere.initialize(serverAddr, serverPort);
System.out.println("\nOpenTTCN SDK for Java " + StartHere.getVersion());
System.out.println("Adapter for 'hello' example.\n");
// Register codec:
// TBD
// Initialize UDP socket:
// TBD
// Start UDP listener:
// TBD
// Register adapter to a session at OpenTTCN server:
// TBD
System.out.println("Adapter initialisation complete.");
sleepForever();
}
catch (SdkException e) {
System.err.println("Error occured: " + e.getMessage());
e.printStackTrace();
}
catch (Throwable e) {
System.err.println("ERROR: Exception caught: " + e.toString());
e.printStackTrace();
}
}
}
The Program.java file contains socket-related section of code which will be used later in this article.
Customizing project settings
After you save the Program.java file, assuming Project | Build Automatically option is enabled, you will see compilation errors in the Problems tab due to incompletely performed project configuration.
This can be fixed as follows: right-click the project name and select OpenTTCN Tester | Setup Java SDK build path. After doing so, everything is configured correctly and the adapter shall build automatically without errors.
- Right-click on the project name and select Properties | Java Build Path | Libraries
- Click Add External JARs...
- Go to the following folder:
C:\Program Files (x86)\OpenTTCN\Tester2011\sdk4java\lib
- Go to the following folder:
- Select the following libraries:
jacorb.jarOpenTTCN.SDK.jarslf4j-api-1.5.6.jarslf4j-jdk14-1.5.6.jar
- then click Open, then click OK
- Make sure that all four libraries were added to the list of Referenced Libraries of the project
Preparing to run adapter for the first time
Before running the adapter, we need to create an Eclipse run configuration:
- right-click on the project name and select Run As | Run Configurations...
- click on the Java Application section, then click on the New launch configuration button in the top-left corner of the window
- enter
SimpleAdapterin place of configuration name,java_wiki_examplein place of project name,simpleAdapter.Programin place of main class and click Apply, then Close
We also need to add an example TTCN-3 test suite to be used jointly with the developed adapter:
- right-click on the
java_wiki_exampleproject and select New | Folder - enter
ttcnas folder name and click Finish - right-click on the newly created ttcn folder and select New | Other | OpenTTCN | TTCN-3 File, then click Next
- enter
exampleas module name and click Finish- do NOT opt for creation of template code
Add the following preliminary code to example.ttcn:
module Main
{
}
and save the module. If automatic build is enabled, then the file shall compile automatically, if not, you need to compile it explicitly through Project | Build Project command.
Note: an alternative way to add example.ttcn to the ttcn folder is as follows:
- right-click on the newly created ttcn folder and select New | File
- enter
example.ttcnas file name and click Finish- if asked about T-Rex nature, answer Yes (enable)
- right-click on the
java_wiki_exampleproject and select OpenTTCN Tester | Toggle OpenTTCN compiler to enable compilation of TTCN-3 modules
This requires toggling OpenTTCN compiler, so this way is less preferred.
If TTCN-3 module compilation does not work, right-click on the java_wiki_example project and select OpenTTCN Tester | Add/remove OpenTTCN TTCN-3 Compiler from Project to enable compilation of TTCN-3 modules. Make sure you have TTCN-3 perspective enabled.
Running adapter for the first time
Now we can launch our adapter as follows:
- right-click on the
java_wiki_exampleproject and select Run As | Run Configurations... | SimpleAdapter | Run
If everything goes fine, you should see several lines of console output in the Console tab with the following text as the last line of the output:
Adapter initialisation complete.
The launched adapter can be stopped by clicking the Stop button in the Console tab. The adapter console output window itself can be closed by clicking the Remove launch button.
Adding SUT adapter callback handler class (SA_impl)
Add a new file, SA_impl.java, to the simpleAdapter package.
The SA_impl class contains initial skeleton implementation of callback handlers that are invoked by the SDK library in the background when TE wants to communicate something to the adapter or to the SUT through the adapter. In this case the adapter needs to handle the TE request and do something for it.
SA_impl.java:
package simpleAdapter;
import org.etsi.ttcn.tri.*;
import com.openttcn.sdk.TriFactory;
import com.openttcn.sdk.AdapterException;
public class SA_impl implements org.etsi.ttcn.tri.TriCommunicationSA {
//////////////////////////////////////////////////////////////////////
// Custom members.
public static int sendDatagramPacket(
java.net.InetAddress host_, /* destination host */
int port_, /* destination UDP port */
byte[] data_ /* UDP frame payload to be sent */) {
java.net.DatagramPacket packet = new java.net.DatagramPacket(
data_, data_.length, host_, port_);
try {
Program.getSocketDesc().send(packet);
}
catch (Throwable e) {
com.openttcn.sdk.StartHere.reportError("Error: Cannot send datagram frame.");
e.printStackTrace();
return 1;
}
return 0;
}
//////////////////////////////////////////////////////////////////////
// ETSI-specified members.
public TriStatus triSAReset() {
return TriFactory.createTriStatus(TriStatus.TRI_OK);
}
public TriStatus triExecuteTestCase(
TriTestCaseId testCaseId,
TriPortIdList tsiPorts) {
return TriFactory.createTriStatus(TriStatus.TRI_OK);
}
public TriStatus triMap(
TriPortId compPortId,
TriPortId tsiPortId) {
return TriFactory.createTriStatus(TriStatus.TRI_OK);
}
public TriStatus triUnmap(
TriPortId compPortId,
TriPortId tsiPortId) {
return TriFactory.createTriStatus(TriStatus.TRI_OK);
}
public TriStatus triEndTestCase() {
return TriFactory.createTriStatus(TriStatus.TRI_OK);
}
public TriStatus triSend(
TriComponentId componentId,
TriPortId tsiPortId,
TriAddress address,
TriMessage sendMessage) {
// TBD
return TriFactory.createTriStatus(TriStatus.TRI_ERROR);
}
public TriStatus triSendBC(
TriComponentId componentId,
TriPortId tsiPortId,
TriMessage sendMessage) {
return TriFactory.createTriStatus(TriStatus.TRI_ERROR);
}
public TriStatus triSendMC(
TriComponentId componentId,
TriPortId tsiPortId,
TriAddressList sutAddresses,
TriMessage sendMessage) {
return TriFactory.createTriStatus(TriStatus.TRI_ERROR);
}
public TriStatus triCall(
TriComponentId componentId,
TriPortId tsiPortId,
TriAddress sutAddress,
TriSignatureId signatureId,
TriParameterList parameterList) {
return TriFactory.createTriStatus(TriStatus.TRI_ERROR);
}
public TriStatus triCallBC(
TriComponentId componentId,
TriPortId tsiPortId,
TriSignatureId signatureId,
TriParameterList parameterList) {
return TriFactory.createTriStatus(TriStatus.TRI_ERROR);
}
public TriStatus triCallMC(
TriComponentId componentId,
TriPortId tsiPortId,
TriAddressList sutAddresses,
TriSignatureId signatureId,
TriParameterList parameterList) {
return TriFactory.createTriStatus(TriStatus.TRI_ERROR);
}
public TriStatus triReply(
TriComponentId componentId,
TriPortId tsiPortId,
TriAddress sutAddress,
TriSignatureId signatureId,
TriParameterList parameterList,
TriParameter returnValue) {
return TriFactory.createTriStatus(TriStatus.TRI_ERROR);
}
public TriStatus triReplyBC(
TriComponentId componentId,
TriPortId tsiPortId,
TriSignatureId signatureId,
TriParameterList parameterList,
TriParameter returnValue) {
return TriFactory.createTriStatus(TriStatus.TRI_ERROR);
}
public TriStatus triReplyMC(
TriComponentId componentId,
TriPortId tsiPortId,
TriAddressList sutAddresses,
TriSignatureId signatureId,
TriParameterList parameterList,
TriParameter returnValue) {
return TriFactory.createTriStatus(TriStatus.TRI_ERROR);
}
public TriStatus triRaise(
TriComponentId componentId,
TriPortId tsiPortId,
TriAddress sutAddress,
TriSignatureId signatureId,
TriException exception) {
return TriFactory.createTriStatus(TriStatus.TRI_ERROR);
}
public TriStatus triRaiseBC(
TriComponentId componentId,
TriPortId tsitPortId,
TriSignatureId signatureId,
TriException exc) {
return TriFactory.createTriStatus(TriStatus.TRI_ERROR);
}
public TriStatus triRaiseMC(
TriComponentId componentId, TriPortId tsitPortId,
TriAddressList sutAddresses,
TriSignatureId signatureId,
TriException exc) {
return TriFactory.createTriStatus(TriStatus.TRI_ERROR);
}
public TriStatus triSutActionInformal(String description) {
System.out.println("triSutActionInformal() callback was received. " +
"Description: " + description);
return TriFactory.createTriStatus(TriStatus.TRI_OK);
}
}
The AdapterException class imported in the above code and the sendDatagramPacket() function defined in the above code will be used later in this article.
You can now update Program.java as follows:
...
// Register adapter to a session at OpenTTCN server:
// Tell TE that SA_impl will handle port called "tsiPort" in session called "hello".
StartHereSA.registerPort("java_wiki_example", "tsiPort", new SA_impl()); // "TRI:tsiPort"
System.out.println("Adapter initialisation complete.");
sleepForever();
...
This registers a new instance of the SA_impl callback handler in the SDK which forwards the registration to OpenTTCN server, so when test cases in the java_wiki_example session are run, instance of SA_impl registered in the SDK will receive notifications of relevant events.
For real-world adapters, you normally need to provide a sensible implementation of the SA_impl.triSend() callback handler only.
The triSend method is called in the background by the SDK library to indicate that the test execution has a frame to be sent to the SUT.
Adding coding request callback handler class (CD_impl)
Add a new file, CD_impl.java, to the simpleAdapter package.
The CD_impl class contains initial skeleton implementation of callback handlers that are invoked by the SDK library in the background when there is a need to encode or decode some data.
For more details on encode() and decode() handlers defined in the standard TCI interface, see OpenTTCN SDK User Guide and OpenTTCN SDK API reference.
CD_impl.java:
package simpleAdapter;
import org.etsi.ttcn.tci.*;
import org.etsi.ttcn.tri.*;
import com.openttcn.sdk.StartHereCD;
import com.openttcn.sdk.FormatConverter;
import com.openttcn.sdk.TriFactory;
import com.openttcn.sdk.TciFactory;
import com.openttcn.sdk.AdapterException;
import java.net.InetAddress;
public class CD_impl implements TciCDProvided {
public TriMessage encode(Value value) {
return null;
}
public Value decode(
TriMessage message,
Type decodingHypothesis) {
return null;
}
}
The import statements in the above skeleton code will be used later in this article.
You can now update Program.java as follows:
...
// Register codec:
StartHereCD.registerCodecImpl(new CD_impl());
// Initialize UDP socket:
...
This registers an instance of the CD_impl callback handler in the SDK so that SDK knows where to dispatch its coding requests.
Normally you need to implement both encode() and decode() interface methods in the CD_impl class. This will be done later in this article.
SUT definition
We will be testing a simple UDP-based protocol with a few defined frames, simple behaviour, and simple network interface.
For precise protocol definition, please read the main article of this section.
In our test configuration SUT has server role and test harness simulates a client. Frame exchange between the two peers, client and server, uses UDP communication.
SUT pre-compiled binary can be downloaded here. Source code is also included for reference.
TTCN-3 message type definitions
Add the following content to example.ttcn instead of what it currently contains:
module Main
{
/*******************************************************************
* Message type definitions
*/
type integer uint8(0..255);
type integer uint16(0..65535);
type record Message
{
uint8 typeCode,
Payload payload optional
}
type record Payload
{
uint16 len,
charstring content
}
type octetstring Raw;
}
Type Message is used to represent requests and responses in both format A and format B.
Type Raw is introduced to simplify construction of invalid frames and to provide sensible means for an adapter to communicate to test execution those frames that it is unable to decode.
Adding example test case
Add the following content to example.ttcn in addition to what it currently contains:
/*******************************************************************
* Other definitions
*/
// Address type used for UDP packets.
type record address
{
charstring host,
integer portField
}
modulepar
{
// IP address and UDP port number of the SUT.
charstring PX_SUT_IP_ADDR := "127.0.0.1";
integer PX_SUT_PORT := 7431;
}
type port PortType mixed
{
inout all;
}
type component ComponentType
{
port PortType p;
timer T_GUARD := 10.0;
var address sut_addr :=
{
host := PX_SUT_IP_ADDR,
portField := PX_SUT_PORT
};
}
type component SystemInterfaceType
{
port PortType tsiPort;
}
template Message GreetingRequest
(in template charstring phrase) :=
{
typeCode := 1,
payload :=
{
len := lengthof(phrase),
content := phrase
}
}
template Message GreetingResponse
(in template charstring phrase)
modifies GreetingRequest :=
{
typeCode := 2
}
altstep DefaultAltstep() runs on ComponentType
{
[] any port.check
{
setverdict(fail);
stop;
}
[] T_GUARD.timeout
{
setverdict(fail);
stop;
}
}
testcase TC_R2D2_001()
runs on ComponentType
system SystemInterfaceType
{
activate(DefaultAltstep());
T_GUARD.start;
map(mtc:p, system:tsiPort);
p.send(GreetingRequest("Hello, world!")) to sut_addr;
p.receive(GreetingResponse("Hello, mankind!")) from sut_addr;
p.send(GreetingRequest("Hello, gentlemen!")) to sut_addr;
p.receive(GreetingResponse("Reformulate")) from sut_addr;
setverdict(pass);
}
Building test suite
If Project | Build Automatically option is enabled, then the test suite in OpenTTCN Tester 2011 IDE shall build automatically after you save the .ttcn file. If this option is disabled, then the test suite can be built manually using Project | Build Project command.
Preparing to run test case for the first time
Before running the test case, we need to create another Eclipse run configuration, this time for TTCN-3, not Java:
- right-click on the
java_wiki_exampleproject name and select Run As | Run Configurations...
- click on the OpenTTCN Test Suite section, then click on the New launch configuration button in the top-left corner of the window
- enter
SimpleTestSuitein place of configuration name,java_wiki_examplein place of project name, then click the Test Plan tab, select Run selected test cases, selectMain.TC_R2D2_001test case, then click Apply, then Close
Running test case (first try)
Launch the adapter if it is not yet running (see Running adapter for the first time section of this article for more details), and run the test case that we have just added as follows:
- right-click on the
java_wiki_exampleproject and select Run As | Run Configurations... | SimpleTestSuite | Run
If you see adapter executable output and not the campaign text log output in the Console tab of the IDE, you can switch between consoles by clicking the Display Selected Console icon in the top right corner of the Console tab and selecting the hello_run console from the list.
The console output for the test campaign shall complain with this kind of error message:
mtc : {13:31:46.573} : // VERDICT Main.TC_R2D2_001 TEST CASE ERROR: E4205: triSend() return status is not TRI_OK.
Indeed, we return TRI_ERROR from triSend() to indicate that we did not implement the TTCN-3 send request.
We also did not implement proper encoding of the message content and address field. This will be done later in this article.
From the command line, test case can be run as follows:
tester run java_wiki_example TC_R2D2_001
Creating encoder
Currently, functionality for encoding message content and address field is not implemented in our version of CD_impl.encode().
Before we go for implementing SA_impl.triSend(), we need to implement the encoding part first, because triSend() receives message and address parameters in already encoded form.
Here is the code that needs to replace the earlier version of encode() in CD_impl.java:
public TriMessage encode(Value value) {
System.out.println("Encoding: " + value.toString());
if (value instanceof AddressValue) {
// Extract the contained record value from the address value:
RecordValue addrRecord =
(RecordValue) ((AddressValue) value).getAddress();
// Extract relevant fields 'host' and 'port':
CharstringValue hostField =
(CharstringValue) addrRecord.getField("host");
IntegerValue portField =
(IntegerValue) addrRecord.getField("portField");
// Construct the encoded representation into bytes array:
byte[] bytes = new byte[6];
// Encode address
InetAddress addr = null;
try {
addr = InetAddress.getByName(
hostField.getString().substring(1, hostField.getLength() + 1));
}
catch (java.net.UnknownHostException e) {
throw new AdapterException(e);
}
byte[] addrBytes = addr.getAddress();
System.arraycopy(addrBytes, 0, bytes, 0, addrBytes.length);
// Encode port
int a = portField.getInteger();
bytes[4] = (byte) ((a >> 8) & 0xFF);
bytes[5] = (byte) ((a >> 0) & 0xFF);
System.out.println("Address encoded to: " +
FormatConverter.byteArrayToOctetstring(bytes, '-') + "\n");
return TriFactory.createTriMessage(bytes);
}
else if (value.getType().getName().equals("Message")) {
// Encode Message (record type)
RecordValue msgRecord = (RecordValue) value;
// Begin by encoding the type code.
IntegerValue tcField =
(IntegerValue) msgRecord.getField("typeCode");
Value plRecVal = msgRecord.getField("payload");
if (plRecVal.notPresent()) {
// Payload is not present, the encoded representation
// only contains the type code:
return TriFactory.createTriMessage((byte) tcField.getInteger());
}
RecordValue plRec = (RecordValue) plRecVal;
// Proceed to encode also the payload:
System.out.println("Payload field present.");
IntegerValue lenField =
(IntegerValue) plRec.getField("len");
CharstringValue contentField =
(CharstringValue) plRec.getField("content");
String contentStr = contentField.getString().
substring(1, contentField.getLength() + 1);
byte[] contentBytes = null;
try {
contentBytes = contentStr.getBytes("UTF-8");
}
catch (java.io.UnsupportedEncodingException e) {
throw new AdapterException(e);
}
byte[] bytes = new byte[contentBytes.length + 3];
bytes[0] = (byte) tcField.getInteger();
int lenInt = lenField.getInteger();
bytes[1] = (byte) ((lenInt >> 8) & 0xFF);
bytes[2] = (byte) ((lenInt >> 0) & 0xFF);
System.arraycopy(contentBytes, 0, bytes, 3, contentBytes.length);
System.out.println("Message encoded to: " +
FormatConverter.byteArrayToOctetstring(bytes, '-') + "\n");
return TriFactory.createTriMessage(bytes);
}
else if (value.getType().getName().equals("Raw")) {
// 'Raw' data, process octet by octet:
OctetstringValue rawData = (OctetstringValue) value;
int lenInBytes = rawData.getLength();
byte[] bytes = new byte[lenInBytes];
for (int i = 0; i < lenInBytes; i++) {
bytes[i] = (byte) rawData.getOctet(i);
}
return TriFactory.createTriMessage(bytes);
}
throw new AdapterException(
"Unable to encode value '" + value.toString() + "'.");
}
Sending frame to the SUT
Now everything is ready to add proper implementation of the triSend() handler to our code. It should extract IP address and UDP port of the SUT from the address parameter, and then send encoded data frame contained in the sendMessage parameter to the SUT using extracted address fields.
Here is a new triSend() handler for SA_impl.java that does all this:
public TriStatus triSend(
TriComponentId componentId,
TriPortId tsiPortId,
TriAddress address,
TriMessage sendMessage) {
System.out.println("triSend() callback received.");
// Send the message to the SUT:
// Address field contains SUT address as IP + PORT (4 + 2 bytes).
byte[] addrAsBytes = address.getEncodedAddress();
// Port is in network byte order.
int portNumber =
((((int) addrAsBytes[4]) << 8) & 0xFF00) |
((((int) addrAsBytes[5]) << 0) & 0x00FF);
// Get the address.
byte[] ipAddr = new byte[4];
System.arraycopy(addrAsBytes, 0, ipAddr, 0, addrAsBytes.length - 2);
java.net.InetAddress inetAddr = null;
try {
inetAddr = java.net.InetAddress.getByAddress(ipAddr);
}
catch (java.net.UnknownHostException e) {
throw new AdapterException("Error: Invalid IP address format.");
}
int result = sendDatagramPacket(
inetAddr, portNumber,
sendMessage.getEncodedMessage());
return TriFactory.createTriStatus((result == 0) ?
org.etsi.ttcn.tri.TriStatus.TRI_OK :
org.etsi.ttcn.tri.TriStatus.TRI_ERROR);
}
To make the above sendDatagramPacket() call succeed, you need to update Program.java accordingly:
...
// Initialize UDP socket:
_socketDesc = bindSocketToAnyPort();
if (_socketDesc == null) {
throw new RuntimeException("Error: Cannot bind socket.");
}
// Start UDP listener:
...
SUT implementation
SUT implementation is available both in source (C/C++) and binary form. Main article of this section explains how to download, install, and run the SUT. Follow the instructions contained in the main article of this section before moving forward. You do not have to recompile the SUT because the downloadable package already contains the SUT binary.
SUT pre-compiled binary can be downloaded here. Source code is also included for reference.
Running test case (second try)
Before running the test case we need to make sure that the following conditions are met:
- SUT is up and running (use
start_sut.batto launch it) - adapter is up and running (see Running adapter for the first time section of this article for more details)
Note that you need to restart the adapter every time you change and rebuild its source code. So make sure that the new version of the adapter has been built and restart it now.
Now run the test case as it was explained in Running test case (first try) section.
Sending now succeeds, but the test case fails after expiry of a guard timer started with the duration of 10 seconds. This happens because we currently do not handle responses from the SUT server in the adapter, so the test execution never sees them.
If you switch to the SUT window, you will see that it indeed received a valid encoded frame from the test harness containing a request and it responded back with a valid response.
Here is what the SUT window shall contain:
-------------------------------------------------------------------------- RECEIVED REQUEST MESSAGE (UDP PACKET) FROM PEER Remote host (source): 127.0.0.1 Remote UDP port (source): 54758 Local UDP port (dest): 7431 <<<<< 01 00 0D 48 65 6C 6C 6F 2C 20 77 6F 72 6C 64 21 <<<<< -------------------------------------------------------------------------- SENDING RESPONSE MESSAGE (UDP PACKET) TO PEER Local UDP port (source): 7431 Remote host (dest): 127.0.0.1 Remote UDP port (dest): 54758 >>>>> 02 00 0F 48 65 6C 6C 6F 2C 20 6D 61 6E 6B 69 6E 64 21 >>>>>
Creating UDP port listener thread
Now when we have the sending channel working, we need to implement a subsystem listening for incoming frames from the SUT. This is typically done by launching a dedicated thread that uses DatagramSocket.receive() internally. This method blocks until a UDP frame arrives from the remote peer.
We use Java Runnable interface and Java Thread class to launch a dedicated thread.
Add SocketListener.java to the simpleAdapter package.
SocketListener.java:
package simpleAdapter;
public class SocketListener implements Runnable {
public static final int MAX_BUFFER_SIZE = 65536;
public void run() {
try {
org.etsi.ttcn.tri.TriCommunicationTE te =
com.openttcn.sdk.StartHereSA.getTriCommunicationTE();
java.net.DatagramPacket packet =
new java.net.DatagramPacket(
new byte[MAX_BUFFER_SIZE], MAX_BUFFER_SIZE);
/* Loop forever */
while (true) {
/* Receive a packet from the SUT */
try {
Program.getSocketDesc().receive(packet);
}
catch (Exception e) {
// Error during receiving, try again:
continue;
}
// Process and encode address:
java.net.InetAddress srcAddr = packet.getAddress();
byte[] hostAsBytes = srcAddr.getAddress();
byte[] addrBuffer = new byte[hostAsBytes.length + 2];
int len = 0;
System.arraycopy(
hostAsBytes, 0, addrBuffer, len, hostAsBytes.length);
len += hostAsBytes.length;
int port = packet.getPort();
addrBuffer[len++] = (byte) (port >> 8);
addrBuffer[len++] = (byte) (port >> 0);
org.etsi.ttcn.tri.TriAddress sutAddrEncoded =
com.openttcn.sdk.TriFactory.createTriAddress(addrBuffer);
// Process message:
byte[] msgBuffer = new byte[packet.getLength()];
System.arraycopy(packet.getData(),
packet.getOffset(), msgBuffer, 0, packet.getLength());
org.etsi.ttcn.tri.TriMessage msg =
com.openttcn.sdk.TriFactory.createTriMessage(msgBuffer);
org.etsi.ttcn.tri.TriPortId p =
com.openttcn.sdk.TriFactory.
createTriPortId("tsiPort"); /* port name is hard-coded */
te.triEnqueueMsg(p, sutAddrEncoded, null, msg);
}
}
catch (Throwable e) {
e.printStackTrace();
}
}
}
We also need to modify Program.java as follows:
...
// Start UDP listener:
Thread t = new Thread(new SocketListener());
t.start();
// Register adapter to a session at OpenTTCN server:
...
Creating decoder
Our listener implementation enqueues frames in encoded form. They need to be decoded prior to supplying them to the TE.
Here is a replacement for the skeleton implementation of decode() in CD_impl.java:
public Value decode(
TriMessage message,
Type decodingHypothesis) {
byte[] bytes = message.getEncodedMessage();
System.out.println("\nDecoding: " + FormatConverter.byteArrayToOctetstring(bytes, '-'));
// Determine the course of action based on the decoding hypothesis:
if (decodingHypothesis == null) {
System.out.println(
"No decoding hypothesis given, assuming decode of a Message type.");
return decodeMessage(bytes);
}
else if (decodingHypothesis.getTypeClass() == TciTypeClass.ADDRESS) {
System.out.println("Decoding hypothesis is address.");
return decodeAddress(bytes);
}
throw new AdapterException(
"Unable to decode a message of type '" +
decodingHypothesis.toString() + "'.");
}
private Value decodeMessage(byte[] bytes) {
TciCDRequired cdReq = StartHereCD.getTciCDRequired();
RecordValue msgValue = (RecordValue)
cdReq.getTypeForName("Message").newInstance();
// The typeCode field is always present and is represented using
// octetstring. Construct a new value, initialize it and assign to
// the containing record:
IntegerValue tcValue = (IntegerValue) cdReq.getInteger().newInstance();
int tc = (int) (((short) bytes[0]) & 0x00FF);
tcValue.setInteger(tc);
msgValue.setField("typeCode", tcValue);
// Check for the special typecode that indicates that no payload
// is present:
if (tc == 0xFF) {
msgValue.setFieldOmitted("payload");
return msgValue;
}
// Otherwise construct the decoded payload:
RecordValue plValue = (RecordValue)
cdReq.getTypeForName("Payload").newInstance();
// Length field:
int lenFieldValue =
((((int) bytes[1]) << 8) & 0xFF00) |
((((int) bytes[2]) << 0) & 0x00FF);
IntegerValue lenField = (IntegerValue) cdReq.getInteger().newInstance();
lenField.setInteger(lenFieldValue);
// Content:
CharstringValue contentField =
(CharstringValue) cdReq.getCharstring().newInstance();
byte[] contentBytes = new byte[bytes.length - 3];
System.arraycopy(bytes, 3, contentBytes, 0, contentBytes.length);
String contentFieldValue = null;
try {
contentFieldValue = new String(contentBytes, "UTF-8");
}
catch (java.io.UnsupportedEncodingException e) {
throw new AdapterException(e);
}
contentField.setString("\"" + contentFieldValue + "\"");
// Construct the final value:
plValue.setField("len", lenField);
plValue.setField("content", contentField);
msgValue.setField("payload", plValue);
System.out.println(
"Message decoded to: " + msgValue.toString() + "\n");
return msgValue;
}
private Value decodeAddress(byte[] bytes) {
System.out.println("Decoding address...");
TciCDRequired cdReq = StartHereCD.getTciCDRequired();
byte[] addrBytes = new byte[4];
System.arraycopy(bytes, 0, addrBytes, 0, addrBytes.length);
// Construct IP address from the address bytes in network byte order:
InetAddress addr = null;
try {
addr = InetAddress.getByAddress(addrBytes);
}
catch (java.net.UnknownHostException e) {
throw new AdapterException(e);
}
// Construct an address value and obtain reference to the contained
// value of record type.
AddressValue addrVal = TciFactory.createAddressValue();
if (addrVal.getAddress().getType().getTypeClass() == TciTypeClass.OCTETSTRING) {
// Addressless setup, address is represented as octetstring, return null
// meaning no address:
System.out.println("No address, returning null.");
return null;
}
RecordValue addrRec = (RecordValue) addrVal.getAddress();
// Construct a TCI integer value and initialize it with the port number.
int portFieldValue =
((((int) bytes[4]) << 8) & 0xFF00) |
((((int) bytes[5]) << 0) & 0x00FF);
IntegerValue portField = (IntegerValue) cdReq.getInteger().newInstance();
portField.setInteger(portFieldValue);
// Construct a TCI charstring value and initialize it with the hostname.
String hostFieldValue = addr.getHostAddress();
CharstringValue hostField =
(CharstringValue) cdReq.getCharstring().newInstance();
hostField.setString("\"" + hostFieldValue + "\"");
// Assign the record fields:
addrRec.setField("host", hostField);
addrRec.setField("portField", portField);
System.out.println("Address decoded to: " + addrVal.toString() + "\n");
return addrVal;
}
Running test case (third try)
It works now! The test case passes, meaning that we have implemented everything correctly. Here is the output of the Text log tab corresponding to the test campaign:
*****************************************************************************
*** MODULE PARAMETER LIST
modulepar
{
charstring PX_SUT_IP_ADDR := "127.0.0.1";
integer PX_SUT_PORT := 7431;
} // End of module parameter list.
*****************************************************************************
*** RUNNING TEST CASE Main.TC_R2D2_001
Tester : {14:41:51.007} : // Time: 14:41:51.007. Date: 19/May/2011. MOT version: TC: 4.1.0.
mtc : {14:41:51.053} : // CASE TC_R2D2_001 STARTED
mtc : {14:41:51.053} : java_wiki_example\ttcn\example.ttcn : 000102 : T_GUARD.start(10.0); // Timer is started: duration 10 s.
mtc : {14:41:51.054} : java_wiki_example\ttcn\example.ttcn : 000103 : map(mtc:p, system:tsiPort);
mtc : {14:41:51.072} : java_wiki_example\ttcn\example.ttcn : 000105 : p.send(Message GreetingRequest := {
typeCode := 1,
payload :=
{
len := 13,
content := "Hello, world!"
}
}) to {
host := "127.0.0.1",
portField := 7431
};
mtc : {14:41:51.104} : java_wiki_example\ttcn\example.ttcn : 000106 : p.receive(Message GreetingResponse := {
typeCode := 2,
payload :=
{
len := 15,
content := "Hello, mankind!"
}
}) from {
host := "127.0.0.1",
portField := 7431
};
mtc : {14:41:51.106} : java_wiki_example\ttcn\example.ttcn : 000108 : p.send(Message GreetingRequest := {
typeCode := 1,
payload :=
{
len := 17,
content := "Hello, gentlemen!"
}
}) to {
host := "127.0.0.1",
portField := 7431
};
mtc : {14:41:51.130} : java_wiki_example\ttcn\example.ttcn : 000109 : p.receive(Message GreetingResponse := {
typeCode := 2,
payload :=
{
len := 11,
content := "Reformulate"
}
}) from {
host := "127.0.0.1",
portField := 7431
};
mtc : {14:41:51.130} : java_wiki_example\ttcn\example.ttcn : 000111 : setverdict(pass);
mtc : {14:41:51.153} : // CASE Main.TC_R2D2_001 FINISHED WITH PASS
/////////////////////////////////////////////////////////////////////////////
/// TEST CASE Main.TC_R2D2_001 COMPLETE VERDICT PASS
*****************************************************************************
*** TEST EXECUTION SUMMARY
Pass Fail Inconc None Error Total Duration
1 0 0 0 0 1 00:00:01
What next
There are many ways to improve the adapter code that we have developed up to this moment. Things to consider:
- encoding and decoding of invalid frames using
Rawdata type definition; - adding support for multiple test system interface ports; currently we have the only one tsiPort port, what greatly simplified our design; having more TSI ports may mean more UDP port listeners, more threads, and more complexity;
- integrating adapter code with GUI test management software, if this is required by your project;
- disabling debug output to improve overall system performance;
- etc.
Adapter source code
You can download the full source code of the adapter example developed in this tutorial here.



