SipUnit Home
SipUnit API (Javadoc)
Developer Howto
|
SipUnit User
Guide
This guide explains what SipUnit is and how to use it to write
automated tests that verify SIP applications.
SipUnit - What Is It?
SipUnit is an open
source Java testing framework used to
write and run automated tests for verifying SIP applications. It
extends the JUnit
test
framework and adds SIP-specific
assertions along with a high-level API for performing the SIP
operations needed to interact with or invoke a test target. SipUnit
uses the JAIN-SIP
reference implementation as its underlying SIP stack/engine.
A test
program using the SipUnit API is written in Java and looks just like a
JUnit test program - a class consisting of testXxx() methods that
perform some actions and assert the expected results. Since the test
target is a SIP entity running somewhere in the network, a SipUnit
test acts as a network
element that sends/receives SIP requests and responses to interact with
the test target. The
SipUnit API includes SIP User Agent Client (UAC), User Agent Server
(UAS), and basic UAC/UAS Core functionality - the set of
processing functions that resides above the SIP transaction and
transport
layers - for the purpose of interacting with the test target.
A test program using SipUnit API:
- Extends SipTestCase
- Creates SipUnit API objects - SipStack, SipPhone, SipCall,
etc. - to handle the SIP operations that will invoke the test target
- Calls methods on the object(s) to set up and initiate
action toward a SIP test target. For example:
The method SipPhone.makeCall("sip:roger@nist.gov", SipResponse.OK,
....) makes a
vanilla call to sip:roger@nist.gov and blocks until an OK is received
or a timeout occurs. The test target could be any node up to
and including the final destination of the INVITE request message.
- Verifies the results of the action involving the test
target using both the SIP-specific assert methods provided by SipUnit
and the standard JUnit assert methods. For example:
assertHeaderContains(sipCall.getLastReceivedResponse(), "From",
"sip:amit@nist.gov"), assertEquals("Unexpected response received",
SipResponse.OK, sipCall.getReturnCode()).
SipUnit Goals
The
goals of SipUnit are:
- to provide a useful set of SIP-related assert methods
- to provide a flexible API that abstracts the
details of SIP
messaging/call handling so that a test program can interact with a SIP
test target at the level it needs to - but no more - and focus on the
tests themselves
- to facilitate free-flowing, sequential test
code so
that a test target can be exercised quickly and painlessly.
What You Need to Know Before
Using SipUnit
In order to use SipUnit effectively, you need to understand and be
proficient with:
It will help if you know the following JAIN-SIP
API classes for purposes of using the low-level methods of the
SipUnit API and examining received requests and responses in detail,
but if you need to you can learn them as you go along:
- Request, Response
- AddressFactory, HeaderFactory, MessageFactory.
Click here to see an
example of test code using the JAIN-SIP API classes.
Installing
SipUnit
Before installing SipUnit: Java (J2SE) 1.4.x or 1.5.x is required - if
you
don't
already have it, get
and install J2SE.
To install SipUnit, unzip the sipunit-<version>.zip file.
When you do this, you'll see the following contents:
sipunit
|
|------------ sipunit_license.txt
|
|------------ README
|
|------------ docs // contains
documentation
|
|
|
|------------ license
// contains the different licenses of bundled third-party software
|
|
|
|------------
api
// contains the javadoc
|
|
|
|------------ developer // diagrams for
explanation and documentation
|
|------------
lib
// contains the jars required to build, trace, and run SipUnit tests
|
|------------ examples/org/cafesip/sipunit/test
// some example SipUnit tests (used to test SipUnit
itself)
Add the jars in the sipunit/lib directory to your CLASSPATH before
executing any of the examples or your own SipUnit test class. That's
all
there is to it. Try testing out the installation, below.
Testing the Installation -
Running an Example Test
You can check SipUnit out right away by running the TestNoProxy class
in the
sipunit/examples directory (instructions below). Those tests should
pass OK. For running some of the other
example
tests,
a proxy is required. See the section on proxy
considerations for details on how to do this and also look at the
header comments in each test java file for proxy settings specific to a
particular test class.
TO RUN THE EXAMPLE TESTS: Since any test programs using SipUnit are an
extension of the JUnit framework, they need to be run like a JUnit test
program, using a TestRunner. The command line way of doing this
is shown in the example below. IDEs are pretty handy for running JUnit
tests. More detail on running JUnit
tests... Make sure the sipunit/examples directory and the jars in
sipunit/lib are in the classpath, either by setting the CLASSPATH
environmental variable beforehand to include them or by including them
in the run command using the -cp option (as below). Also, when
specifying the example test class to run, include the package prefix
(ie, org.cafesip.sipunit.test.classname).
EXAMPLE: to run the TestNoProxy test class, execute the following java
command all
on one line (for Windows, in this example) from the sipunit
directory:
C:\sipunit> java -cp
examples;lib\junit.jar;lib\sipunit.jar;lib\JainSipApi1.2.jar;lib\JainSipRi1.2.jar;lib\concurrent.jar;lib\log4j-1.2.8.jar
junit.textui.TestRunner org.cafesip.sipunit.test.TestNoProxy
You should see the time it took to run and "OK (n tests)" when it
finishes.
SipUnit Architecture - Where
Your Test Fits In
The diagram below shows
the overall architecture. Your test class uses the SipUnit API, and the
SipUnit API uses the JUnit
API and the JAIN
SIP API. The
JUnit framework provides the test
execution environment and some generic assert methods. The
JAIN-SIP engine provides the capability to interact with the test
target.
Items in blue are used to invoke and interact with the test target.
Which SipUnit classes and methods you use depends on the level of
abstraction your test can get away with. The higher, the better - the
easier and quicker it will be to write/maintain tests. More on this
next.
Items in green are used to verify the results of the interaction and
provide the test execution environment.
If you want to see a more detailed illustration, check out
the class diagram.

The Key SipUnit API Classes - How to
Interact With Your Test Target
A major SipUnit goal is to have a flexible API that
abstracts the
details of SIP
messaging/call handling so that a test program can interact with a SIP
test target at the level it needs to - but no more.
This means that if you need an established (answered) SIP call before
your first test can be executed, you should be able to establish the
call (and assert that it was successful) in one step, as part of the
setUp() method. This would be the highest level of abstraction - ie,
SipPhone.makeCall(). You tell that method what response you are
expecting (in this case, OK) and it takes care of performing the call
from sending the INVITE to receiving responses up to and including the
one you are looking for (OK).
On the other hand, if your test is focused on the call establishment
process itself, then you may want to break the call establishment down
at least to the next level so that you can see intermediate responses
as they come in,
insert your test logic at the
appropriate place and validate the result. For example, suppose you
want to verify that your test target responds to an INVITE with a
provisional response followed by OK. You could use
SipCall.initiateOutgoingCall() to send the INVITE to the test target
and then call
SipCall.waitOutgoingCallResponse() repeatedly to see each response sent
by the test target as it comes in. If your test program doesn't need to
alter the call/test sequence based on what happens during setup, you
can let SipPhone.makeCall() handle the entire call establishment and
then call getAllReceivedResponses() to see the messages that came in
during setup.
At a lower level still, you may need to handle the SIP messages
yourself (for example, to control the call messaging/handling in a
certain, non-standard
way). The SipSession class allows you to do this. However, note that if
you just need to insert extra or invalid message content in message
headers and/or body, or replace valid message content with invalid
content of your own, most of the high level SipUnit methods provide
options for you to specify additional headers, replacement headers, and
message body contents so that you can control the message contents to
be sent out.
Providing these various levels of abstraction are the primary SipUnit
classes used for interacting with test targets:
- SipPhone - high
level - notable SIP operations include register(), unregister(),
makeCall(), refer(), addBuddy(), fetchPresenceInfo(), credentials
management.
- SipCall - high
level - provides outbound call handling methods, inbound call handling
methods, and various miscellaneous functions such as cancel and
reinvite.
- PresenceSubscriber -
high level - provides step by step control of presence/buddy
SUBSCRIBE/NOTIFY
sequences and maintains presence information.
- ReferSubscriber -
high level - provides step by step control of REFER subscription
sequences including refresh and unsubscribe.
- SipSession -
low level - provides send-request/receive-response and
receive-request/send-response methods which are used by the above
classes and which are available
to user test programs to use. SipSession
methods require use of the JAIN-SIP API Request and
Response related classes including the SipStack.getXxxFactory() objects
for dealing with message, address and header content.
For an example of test code using the high level call processing
classes and methods,
click here.
For an example of test code using the presence (buddy list)
subscription
capabilities, click here.
For an example of test code using the REFER subscription capabilities,
click here.
For an example of test code using the low level SipSession methods,
click here.
As always, you want to try to pick the best tool for the job at hand.
How Do You
Decide Which SipUnit Classes/Methods to Use for a
Particular Test Task?
Call Processing
Tasks
First check out the available
SipCall/SipPhone methods. If those are sufficient for the task,
then interact with the test target at that level - those will keep your
test program simple. You'll notice that many of the SipCall/SipPhone
methods have multiple method signatures accepting additional headers,
replacement headers and message body - the purpose of that is so that
you can specify optional header/body content and/or
override default message content for test purposes when you need to
while still using the highest level API.
If your test needs to
be more reactive than is allowed at the above level, then you'll need
to
use the SipSession methods.
For example: suppose your test is expecting an incoming call. You can
use the SipCall.waitForIncomingCall() method which will block until an
INVITE message comes in. Note from the SipCall
javadoc for this method that any non-INVITE requests received prior to
the INVITE are collected, and you can look at them if you need to once
the INVITE has been received (since this is a blocking method). If you
don't care about them, then SipCall.waitForIncomingCall()
is fine to use here. But if you need to react to a received pre-INVITE
request that arrives while waiting for the INVITE, at the time it comes
in, then you'll need to use the
SipSession.waitRequest() method instead which you can call repeatedly
to see and process each received request message including the INVITE
you were
waiting for.
Another reason to use SipSession is
if you need to have nonstandard signaling sequences in your test. Why
bother with SipUnit at this point?
Because still, it is handling all of the details of using the
SipStack/engine and providing conveniences such as blocking while
waiting for a response.
A quick word on blocking follows in the next subsection, then you can
see a call flow example illustrating use
of the SipUnit API calls.
Presence Tasks
This category of tasks is more straight-forward. You'll use SipPhone
methods to initiate a subscription (addBuddy()) and also whenever you
want
to initiate a single-shot fetch. After that, for a given subscription
(a persistent buddy or a fetch), you'll use the returned
PresenceSubscriber object
methods to step through the SUBSCRIBE/NOTIFY sequence(s) and to view
the
presence status and information which is maintained by the
PresenceSubscriber object.
As far as customizing the content of presence-related messages sent by
the test program
side: you may vary the default (correct) content of SUBSCRIBE requests
and NOTIFY responses sent out or provide your own incorrect/malformed
message (with the exception of the very first SUBSCRIBE message) to
send out anywhere along the sequence in order to see what the test
target does. The reason for the exception is because a correct initial
SUBSCRIBE request is needed to initialize the subscription
properly. If you want to send out a bad initial SUBSCRIBE message to
see what your test target does, use SipSession to send the request and
get the resulting response.
Refer Tasks
This category of tasks is also straight-forward. You'll use
SipPhone.refer() to send an in-dialog or out-of-dialog REFER message
and initiate a subscription. After that, you'll use the returned
ReferSubscriber object
methods to step through the REFER/NOTIFY and subsequent
SUBSCRIBE/NOTIFY sequence(s) and to view the
subscription status information which is maintained by the
ReferSubscriber object.
As far as customizing the content of refer subscription messages sent
by the test program
side: you may vary the default (correct) content of outgoing SUBSCRIBE
requests
and NOTIFY responses or provide your own incorrect/malformed
message (with the exception of the very first REFER message) to
send out anywhere along the sequence in order to see what the test
target does. The reason for the exception is because a correct initial
REFER request is needed to initialize the subscription
properly. If you want to send out a bad initial REFER message to
see what your test target does, use SipSession to send the request and
get the resulting response.
Blocking vs. Non-Blocking
Operations
Many of the methods on the SIP operation objects (SipPhone, SipCall,
etc.) block until the
invoked operation is completed. The reason for this is so that the code
in the user test class can flow logically and sequentially, in a
step-by-step fashion, without worrying about handling asynchronous
callbacks, timeouts, etc.
However, some methods are intentionally non-blocking so that the test
code can mark a point in the test execution where the API should start
doing
something, the test can continue with some other task(s), then when it
is ready, the test can block on the API action previously
initiated. This is especially needed so that a test method can
handle both endpoints of a call.
For example:
SipCall a = ua.createSipCall(); //
calling side
SipCall b = ub.createSipCall(); // callee side
b.listenForIncomingCall(); //
non-blocking - start looking for a received INVITE request and save it
a.initiateOutgoingCall("sip:becky@nist.gov", null); // this is
where the INVITE will get sent to b
assertLastOperationSuccess("a initiate call - " + a.format(), a);
b.waitForIncomingCall(10000); //
blocking - retrieve the INVITE
received by now, or wait for it to come in
assertLastOperationSuccess("b wait incoming call - " + b.format(),
b);
b.sendIncomingCallResponse(Response.RINGING, "Ringing", 0);
assertLastOperationSuccess("b send RINGING - " + b.format(), b);
etc.
This is an example of a test acting out both sides of a call ('a' calls
'b'). The non-blocking version of waitForIncomingCall() -
listenForIncomingCall() - is needed so that the test program can tell
the API to start collecting message(s) coming in (with the 'listen')
but the test can continue executing and doing what it needs to do until
it is at a point where it is ready to be blocked waiting for the
message(s) coming in. This is also shown in the following example.
Call Flow
Example
This example shows sample SipUnit API calls (on the left) that would
appear in a test
program interacting with a test target and the resulting SIP call flow.
The blocking SipUnit methods are shown in italics. See the test class code that
was used to
generate this trace. The trace graphic was generated by the trace viewer tool.
Here, user a calls user b (both on the same box -
192.168.1.101 - but with a proxy - 127.0.0.1 - inbetween), user
b sends RINGING and OK, the test verifies these are received
by user a, then the call proceeds forward.
Execution flow in a test class method using SipUnit:
b.listenForIncomingCall()
SEND INVITE:
a.initiateOutgoingCall("sip:becky@nist.gov", null)
WAIT FOR INVITE:
b.waitForIncomingCall(5000)
SEND RESPONSE:
b.sendIncomingCallResponse(Response.RINGING, "Ringing", 0)
WAIT FOR RESPONSE:
a.waitOutgoingCallResponse(10000)
(waitOutgoingCallResponse() returns and 'a' processes ringing)
SEND ANOTHER RESPONSE:
b.sendIncomingCallResponse(Response.OK, "Answer - Hello world", 0)
WAIT FOR RESPONSE:
a.waitOutgoingCallResponse(10000)
(waitOutgoingCallResponse() returns and 'a' processes OK)
a.sendInviteOkAck();
etc.
|
|
Authentication Support
If your test environment
involves authentication and you are using the high-level classes
(SipPhone, SipCall, Subscription, etc.), you'll likely need to make use
of the
Credential class. For each secure realm that a test call will be
sending requests to, create an instance of Credential
and pass it to SipPhone's credentials list (via the
SipPhone.addUpdateCredential() method). Do this before invoking the SIP
operation that will result in sending a request. Add as many credential
objects as there are distinct
secure realms that your test call will be accessing. You can add/remove
credentials as needed using the SipPhone credential methods.
When an
authentication challenge is received during registration, call
processing, subscribing, etc. the appropriate credential(s) from
SipPhone's list will be
automatically used to respond to the challenge. These authorization
headers
created by SipUnit in response to authentication
challenges are stored by SipPhone on a per Call-ID, per realm, basis
and are included in subsequent requests sent by SipUnit class
methods for the same Call-ID. See design
notes if you'd like more detail.
Alternatively, if all you need is to simply handle one authentication
challenge for registration, you can just use the SipPhone.register()
method that takes user name and password parameters. In this case you
don't need to bother with using the Credential class.
SipUnit supports Digest
Access Authentication.
Event
Subscription & Notification
This section describes the
event subscription/notification capabilities implemented in SipUnit and
how you can
use them in your test program.
General
What Event
Packages
Does SipUnit Support?
SipUnit supports the following PRESENCE capabilities:
- a buddy list per SipPhone that maintains presence
information for active and expired subscriptions
- buddy list operations - add/refresh/remove buddies from the
buddy list
- "application/pidf+xml" presence data format
- multiple devices for a presentity (ie, multiple tuples in a
NOTIFY message) along with other RFC3863 NOTIFY xml body elements
(except for extensions)
- validation per RFC of received SUBSCRIBE responses, NOTIFY
requests
- authentication challenge handling for sent SUBSCRIBEs
(DIGEST method only)
- user control over the event "id" and other information sent
in a given SUBSCRIBE request or NOTIFY response.
SipUnit supports the following REFER capabilities:
- outbound REFER handling, in-dialog or out-of-dialog
- implicit subscription support including refresh, unsubscribe
- "message/sipfrag" NOTIFY body format
- validation per RFC of received REFER/SUBSCRIBE responses,
NOTIFY
requests
- authentication challenge handling for sent REFERs and
SUBSCRIBEs
(DIGEST method only)
- user control over the event "id" and other information sent
in a given REFER/SUBSCRIBE request or NOTIFY response.
What RFCs Does
SipUnit Implement?
Pertinent RFCs that SipUnit follows with respect to
"Subscriber"-side presence and refer event package handling
(see limitations
next section):
- RFC-3261 - SIP: Session Initiation Protocol
- RFC-3265 - Session Initiation Protocol (SIP)-Specific Event
Notification
- RFC-3856 - A Presence Event Package for the Session
Initiation Protocol (SIP)
- RFC-3863 - Presence Information Data Format (PIDF)
- RFC-3515 - The Session Initiation Protocol (SIP) Refer
Method
What are the
Limitations? What Doesn't It Do?
Currently the following is NOT supported:
- the NOTIFY-sender side, officially (although there is an
utility/simulator for both presence and refer, for test purposes)
- multiple subscriptions in the same dialog
- automatic refresh/keep alive of subscriptions
- sending authentication challenge for received NOTIFY
messages
- extensions and mustUnderstands in the presence NOTIFY body
(currently ignored)
- PUBLISH method at a high level (can be done using low-level
SipSession methods to send a request)
More detail on the SipUnit presence and refer support follows.
Presence
What Type of
Presence Does SipUnit Support? What Types Are There?
A test program using the
primary SipUnit presence capabilities (SipPhone buddy methods and
PresenceSubscriber class methods) can act as a Type II (Distributed
Agents*)
presence client for the purpose of testing a presence server or
server-side application. That is, it can send SUBSCRIBE messages to a
presence server and receive/validate NOTIFY messages from the presence
server. Your test program
can control the contents of SUBSCRIBE requests and NOTIFY responses
sent to the server. By default, the messages are correctly formed and
the content is valid. Type II client "Subscriber" emulation for testing
presence server and server-side applications is the primary focus of
the SipUnit presence feature so far.
A test program using
the primary SipUnit presence capabilities (SipPhone buddy methods,
PresenceSubscriber class methods) does
not communicate directly with other clients like a Type I (Co-located
Agents*) client does - that is, it does not also receive SUBSCRIBE and
send NOTIFY messages. It cannot be used to test a Type I client.
A test program using the SipUnit PresenceNotifySender utility class can
simulate the presence server side for the purpose of testing Type II
clients. That is, it can receive SUBSCRIBE messages and send NOTIFY
messages. Your test program tells it the values to send in SUBSCRIBE
responses (status code, reason phrase) and NOTIFY requests (presence
info, subscription state, etc.). However, the PresenceNotifySender
doesn't validate messages it receives - your test program will have to
do that if you use it. There are numerous presence tests in the
examples directory that use it so look there if you need an example.
A test class method using both the primary SipUnit presence capabilities (SipPhone buddy methods and
PresenceSubscriber
class methods) for one side
of a subscription (the "Subscriber") and the PresenceNotifySender utility class for
the other side of the subscription (the "Notifier") can be used to
test/generate SUBSCRIBE/NOTIFY traffic through network elements such as
a proxy.
*Check out this white paper for a nice
explanation of presence and the different types of server-side
capabilities and clients.
Overview of
Operation
Here's how it works (see accompanying diagram below):
- Just like a normal call processing test, your presence test
class creates one or more SipPhone objects. SipPhone supports buddy
list operations and single-shot fetches in addition to its call
processing capabilities. If authentication will be required, give the
necessary
credentials to the SipPhone by calling addUpdateCredential().
- Next, call addBuddy() or fetchPresenceInfo() on the
SipPhone. This causes a SUBSCRIBE message to be created and sent
to the server/target, and your test program is blocked on the
method call until a first response is received. Assuming a
favorable response is received (as opposed to a fatal error response),
the SipPhone method
returns a PresenceSubscriber object that will represent this
buddy/subscription for the life of the subscription.
- At this point, you'll now use methods of the returned
PresenceSubscriber class to step through the
remainder of this SUBSCRIBE/NOTIFY sequence. (Note, you may also be
doing other things with the SipPhone or with other buddies as well,
besides this particular sequence). You may check the status code of the
initial response that was received before calling
processResponse() which will complete the SUBSCRIBE
transaction handling. This is another blocking method that processes
the response just received and handles the remainder of the SUBSCRIBE
operation, collecting and validating message(s) received from the Far
End. When the
SUBSCRIBE transaction is over, processResponse() returns a
success/fail indication to your test program. Assuming success, you can
proceed by checking resulting info as shown in the diagram below.
- Your next step, whenever your test program is ready, is to
collect the NOTIFY message from the Far End by calling waitNotify().
This is a blocking method that waits for a NOTIFY message to come in if
one hasn't come in already as a result of the SUBSCRIBE sequence or for
any other reason. Once a NOTIFY is received, waitNotify() returns the
received request to your
program as a RequestEvent object. You may examine the NOTIFY request
yourself if you want to use the JAIN SIP API to do it, but you
don't have to. Pass the NOTIFY object to processNotify()
which will do several things:
- validate the content of the received NOTIFY, body
included
- update the PresenceSubscriber object with the
received
information (presence tuples, subscription state, etc)
- create the proper, correct NOTIFY response that
should be
sent back to the Far End (but don't sent it yet)
The composed response is returned to you by processNotify(). You may
examine it yourself, or modify/corrupt it if you want to use the
JAIN SIP API to do it. Regardless, you can see the NOTIFY
processing
results by calling the getXyz() methods on the PresenceSubscriber
object as
shown in the diagram below.
- Finally, call replyToNotify(), passing it the response to
send to the Far End. You get back an indication of whether or not the
message was sent
successfully.
That completes the handling of the SUBSCRIBE/NOTIFY sequence. It might
seem like alot
just to get through one sequence, but you'll see that your test program
is straight-line and straigtforward. Also, these steps can be put in a
single blocking convenience method if this sequence is not the focus of
the test method. A refreshBuddy() and removeBuddy() call on the
PresenceSubscriber object
works just the same way, except a new PresenceSubscriber
object isn't created (the existing one continues to be used).

Where's An
Example I Can Look At?
For an example of test code using the buddy list subscription
capabilities, click here.
Also look in the examples directory of the download package for many
more examples.
Refer
Implementation
A test program using the
primary SipUnit refer capabilities (SipPhone.refer() and
ReferSubscriber class methods) can act as a "Subscriber" side User
Agent. That is, it can send REFER and SUBSCRIBE messages and
receive/validate NOTIFY messages. Your test program
can control the contents of outgoing REFER/SUBSCRIBE requests and
NOTIFY responses. By default, the messages are correctly formed and
the content is valid.
A test program using
the primary SipUnit refer capabilities (SipPhone.refer() and ReferSubscriber
class methods) does
not receive REFER/SUBSCRIBE messages or
send NOTIFY messages. It cannot be used to act as the "Notifier" side
User Agent.
A test program using the SipUnit ReferNotifySender utility class can
simulate the refer-ee (Notifier-side) UA for the purpose of testing a
referrer (Subscriber-side) UA. That is, it can receive REFER/SUBSCRIBE
messages and send NOTIFY
messages. Your test program tells it the values to send in
REFER/SUBSCRIBE
responses (status code, reason phrase) and NOTIFY requests
(subscription state, etc.). However, the ReferNotifySender doesn't validate messages it receives -
your test program will have to
do that if you use it. There are numerous refer tests in the
examples directory that use it so look there if you need an example.
A test class method using both the primary SipUnit refer capabilities (SipPhone.refer() and ReferSubscriber
class methods) for one side
of a subscription (the referrer "Subscriber") and the ReferNotifySender utility class for
the other side of the subscription (the refer-ee "Notifier") can be
used to
test/generate REFER/SUBSCRIBE/NOTIFY traffic through network elements
such as
a proxy.
Overview of
Operation
Here's how it works (see accompanying diagram below):
- Just like a normal call processing test, your refer test
class creates one or more SipPhone objects. If authentication will be
required, give the
necessary
credentials to the SipPhone by calling addUpdateCredential().
- You can use the convenience method SipPhone.getUri() to
prepare a 'referTo' SipURI based on method, join, replaces, etc.
- Next, call refer() on the SipPhone. There are multiple
versions of this method that support in-dialog or out-of-dialog REFER.
This causes a REFER message to be created and sent
to the Far End, and your test program is blocked on the
method call until a first response is received. Assuming a
favorable response is received (as opposed to a fatal error response),
the SipPhone method
returns a ReferSubscriber object that will represent the implicit
subscription for the life of the subscription.
- At this point, you'll now use methods of the returned
ReferSubscriber class to step through the
remainder of this REFER/NOTIFY sequence. (Note, you may also be
doing other things with the SipPhone or with its buddy list as well,
besides this particular sequence). You may check the status code of the
initial response that was received before calling
processResponse() which will complete the REFER transaction handling.
This is another blocking method that processes
the response just received and handles the remainder of the REFER
operation, collecting and validating message(s) received from the Far
End. When the
REFER transaction is over, processResponse() returns a
success/fail indication to your test program. Assuming success, you can
proceed by checking resulting info as shown in the diagram below.
- Your next step, whenever your test program is ready, is to
collect the NOTIFY message from the Far End by calling waitNotify().
This is a blocking method that waits for a NOTIFY message to come in if
one hasn't come in already as a result of the REFER sequence or for
any other reason. Once a NOTIFY is received, waitNotify() returns the
received request to your
program as a RequestEvent object. You may examine the NOTIFY request
yourself if you want to use the JAIN SIP API to do it, but you
don't have to. Pass the NOTIFY object to processNotify()
which will do several things:
- validate the content of the received NOTIFY, body
included
- update the ReferSubscriber object with the
received
information (subscription state, etc)
- create the proper, correct NOTIFY response that
should be
sent back to the Far End (but don't sent it yet)
The composed response is returned to you by processNotify(). You may
examine it yourself, or modify/corrupt it if you want to use the
JAIN SIP API to do it. Regardless, you can see the NOTIFY
processing
results by calling the getXyz() methods on the ReferSubscriber object
as
shown in the diagram below.
- Finally, call replyToNotify(), passing it the response to
send to the Far End. You get back an indication of whether or not the
message was sent
successfully.
That completes the handling of the REFER/NOTIFY sequence. It might
seem like alot
just to get through one sequence, but you'll see that your test program
is straight-line and straigtforward. Also, these steps can be put in a
single blocking convenience method if this sequence is not the focus of
the test method. A refresh() and unsubscribe() call on the
ReferSubscriber object
works just the same way, except a SUBSCRIBE is sent instead of REFER,
and a new ReferSubscriber
object isn't created (the existing one continues to be used).

Where's An
Example I Can Look At?
For an example of test code using the REFER, click here.
Also look in the examples directory of the download package for more
examples.
How to Get the
JAIN SIP Objects (Transaction, Dialog) From SipUnit Classes
The way to get to the JAIN SIP objects depends on which SipUnit objects
you're using and the message send/receive context:
If
you're using the below and you are: --->
|
Sending
a request
|
Receiving
a response
|
Receiving
a request
|
Sending
a response
|
Low level SipSession
methods
|
Use the SipTransaction
returned by sendRequestWithTransaction()
|
See left, or use the
EventObject returned by waitResponse() when the response comes in
|
Use the RequestEvent
returned by waitRequest(). For a dialog-creating request, some JAIN SIP
info isn't available until a response is sent, see right.
|
See left, or use the
SipTransaction returned by sendReply()
|
High level call processing
objects (SipPhone, SipCall)
|
After calling a SipPhone
or SipCall INVITE-sending non-blocking method, before a response is
received you
can call SipCall.getLastTransaction(). Once an initial response has
been received, you can call SipCall.getDialog(). There's also a
SipCall.getDialogId() if that's all you need. |
Use
SipCall.getLastReceivedResponse() or SipCall.getDialog(). There's also
a SipCall.getDialogId() if that's all you need.
|
Use
SipCall.getLastReceivedRequest() unless the dialog has already
been established in which case you can call SipCall.getDialog().
There's also a SipCall.getDialogId() if that's all you need. |
See left. Once you've sent
an initial response you can call SipCall.getDialog(). There's also a
SipCall.getDialogId() if that's all you need. |
High level subscription
objects (PresnceSubscriber, ReferSubscriber)
|
After calling a
SUBSCRIBE/REFER-sending method (ex: SipPhone.addBuddy()/refer()), call
getLastReceivedResponse() or getDialog() on the returned subscription
object. It also has a getDialogId() if that's all you need.
|
Call
getLastReceivedResponse() or getDialog() on the subscription object. It
also has a getDialogId() if that's all you need. |
Use the RequestEvent
returned by waitNotify() or call getLastReceivedRequest() or
getDialog() on the subscription object. It also has a getDialogId() if
that's all you need. |
See left
|
STUN Support
If you want to run your SipUnit test from behind a NAT and register
with a proxy on the public internet, you need to do a couple of extra
things in your test program.
First, at the beginning your test and before creating your SIP Stack
and any SipPhones/SipSessions, determine the public IP address/port for
your SipUnit test program to use. For this, you can use the stun4j API
(the jar
is in the SipUnit lib) to access a STUN server on the internet and send
messages to it, and from that, you'll be able to find out your public
address/port. Use the example code below
- the getPublicAddress() method will do all of this job for you. If the
STUN server used in the example code (stun.fwdnet.net) doesn't work,
there are some public ones freely available for
this purpose - google for it or visit
http://www.voip-info.org/wiki-STUN which lists some STUN servers and
also has other useful information.
Second, once you've determined your public address as described above
and then created your SipUnit SipStack and SipPhone/SipSession
(as usual, using your private address), you can then call the method
setPublicAddress(publichostaddress, publicport) on
your SipPhone/SipSession. This method replaces the private host/port
values in
this Sip agent's contact address, via, and listening point 'sentby'
components with the public host and port parameters you pass in (ie,
the publicly accessible address). Then whenever your
SipPhone/SipSession communicates with
the public SIP server on the internet, the SIP server will be able to
communicate back to your test program via its public address. See the
TestWithStun.java file in the sipunit test/examples directory for a
full example of how to do it (snippets below). You can try out that
test file by following the instructions in the file header comments.
At any point in your test program, if you need to find out the public
address you're using, call the SipPhone or SipSession method
getStackAddress().
Example Code
Here are snippets from the TestWithStun.java test file in the sipunit
test/examples directory. The setUp() method calls getPublicAddress()
which uses a public STUN server to determine the publicly accessible IP
address and port to use. Then the setUp() method creates the SipStack
and SipPhone, calling setPublicAddress() on the SipPhone so that it
will be able to receive messages from the SIp server on the internet.
See TestWithStun.java test file in the sipunit test/examples directory
for complete details.
public void setUp() throws Exception
{
// use the stun server to
find out my public address
assertTrue(getPublicAddress());
try
{
sipStack = new SipStack(testProtocol, myPort, properties);
SipStack.setTraceEnabled(true);
}
catch (Exception ex)
{
fail("Exception: " + ex.getClass().getName() + ": "
+ ex.getMessage());
throw ex;
}
SipStack.trace("My public IP
address = " + publicIP + ", port = "
+ publicPort);
try
{
ua =
sipStack.createSipPhone(properties
.getProperty("sipunit.proxy.host"), testProtocol,
proxyPort, myUrl);
ua.setPublicAddress(publicIP, publicPort);
}
catch (Exception ex)
{
fail("Exception creating SipPhone: " + ex.getClass().getName()
+ ": " + ex.getMessage());
throw ex;
}
}
public boolean getPublicAddress()
{
StunAddress localAddr = null;
StunAddress serverAddr =
null;
try
{
localAddr = new StunAddress(InetAddress.getLocalHost()
.getHostAddress(), myPort);
}
catch (UnknownHostException
e)
{
e.printStackTrace();
return false;
}
serverAddr = new
StunAddress("stun.fwdnet.net", 3478);
NetworkConfigurationDiscoveryProcess addressDiscovery = new
NetworkConfigurationDiscoveryProcess(
localAddr, serverAddr);
try
{
addressDiscovery.start();
}
catch (StunException e)
{
e.printStackTrace();
return false;
}
StunDiscoveryReport
stunReport = null;
try
{
stunReport = addressDiscovery.determineAddress();
}
catch (StunException e)
{
e.printStackTrace();
return false;
}
StunAddress stunAddress =
stunReport.getPublicAddress();
publicIP =
stunAddress.getSocketAddress().getAddress().getHostAddress();
publicPort =
stunAddress.getSocketAddress().getPort();
// must shutdown stun
process, otherwise, stun4j is holding on the same
// udp port
addressDiscovery.shutDown();
return true;
}
The SipTestCase Class
The SipTestCase class is nothing more than a simple class that extends
the JUnit TestCase class and has some of its own SIP-specific
assertXxx() methods. It serves as the base class for a SIP application
test program, or test class. While other SipUnit API objects like
SipCall and SipSession interact with a test target, SipTestCase methods
only play a role in the validation of results. The SipTestCase assert
methods along with the JUnit Assert class methods are used by a test
class to check the results of each test action it performs.
The SipTestCase assert methods focus on SIP-specific items of interest
such as checking if a certain header is present in a SIP message, or if
the SIP message body contains a specific string, etc. Also there are
methods to check if the last SIP operation was successful or not, given
the SipUnit API object that performed the operation (ie, SipCall,
SipPhone, or SipSession). These API objects support an interface that
allows all error/status details to be output if an assertion fails.
Failure Output
Like JUnit, messages are output only if a test fails. The SIP
application test class controls what message text output, if any,
should be generated if an assertion fails. Additionally, when a test
fails, you'll see a stack trace dump just like with JUnit.
Unlike JUnit, your test class line of code won't be the first entry in
the stack trace output, in the current release - there's a TODO to
adjust the stack so that entries at the SipTestCase level are
discarded. In the meantime, you'll have to skip over the first couple
or few lines when looking at the stack trace to find the line of code
in your test class where the test failed.
Putting It All Together -
How To Write Your Test Class
Writing a test class that uses the SipUnit API is just like
writing a JUnit test class, except for the first step:
- implement a subclass of SipTestCase
- define instance attributes that store information needed
by each test in the class
- initialize the setup for each test method by overriding
method
setUp()
- clean-up after each test by overriding tearDown()
- write one or more tests, each of which is a method
beginning with the letters "test" (ie, testMakeCall()).
The code in each of the test methods will:
- create SipUnit
API objects (ie, SipPhone, SipCall, etc.)
- call method(s) on the objects to set up and take some kind
of action against the test target
- check the results using assert methods found in the SipTestCase
class and the JUnit Assert
class.
Check out the following example. You can also look at the source code
in the sipunit examples directory which consists of test classes that
test
out the API itself.
Some Example Test Class Excerpts
Here are some example test class snippets that use the SipUnit API.
You may want to look at the SipUnit javadoc while looking at this so you
can see what some methods
like format() do. This test uses a proxy - not important for
purposes of this example, but you can see Proxy Considerations for details on
testing with and without a proxy.
Just Like JUnit
Like with JUnit, a test class using SipUnit extends
JUnit TestCase - by way of SipTestCase - and contains setUp(),
tearDown(), and one or more testXxx()
methods.
import
org.cafesip.sipunit.SipCall;
import
org.cafesip.sipunit.SipPhone;
import
org.cafesip.sipunit.SipResponse;
import
org.cafesip.sipunit.SipStack;
import
org.cafesip.sipunit.SipTestCase;
import
org.cafesip.sipunit.SipTransaction;
public class
TestWithProxyNoAuthentication extends
SipTestCase
{
private
SipStack sipStack;
private
SipPhone ua;
....
public void setUp() throws
Exception
{
sipStack = new SipStack(null, -1);
ua = sipStack.createSipPhone(..., "sip:amit@nist.gov"); // create user a
}
public void tearDown() throws
Exception
{
ua.dispose();
sipStack.dispose();
}
/**
* This
test illustrates usage of SipTestCase. In it, user a calls user b, user
b sends RINGING and OK,
* the test verifies these are received
by user a, then the call proceeds through disconnect (BYE).
*/
public void testBothSidesCallerDisc()
{
ua.register(null, 1800);
assertLastOperationSuccess("Caller registration failed - "
+ ua.format(), ua);
try
{
SipPhone ub = sipStack.createSipPhone(..., "sip:becky@nist.gov"); // create
user b
ub.register(null, 600);
assertLastOperationSuccess(ub); // standard JUnit fail output
SipCall a = ua.createSipCall();
SipCall b = ub.createSipCall();
b.listenForIncomingCall();
Thread.sleep(10);
a.initiateOutgoingCall("sip:becky@nist.gov",
null);
.... <etc., like Call Flow Example
above>
A Little Can Do
Alot
A short test method can exercise alot of functionality.
public
void testBothSidesAsynchMakeCall() //
a calls b via proxy, testing the
proxy
{
assertTrue(ua.register("amit", "a1b2c3d4", null, 0, 10000));
try
{
SipPhone ub = sipStack.createSipPhone(..., "sip:becky@nist.gov");
assertTrue(ub.register("becky", "a1b2c3d4", null, 600, 5000));
SipCall b = ub.createSipCall();
// prepare the called side
b.listenForIncomingCall();
Thread.sleep(50);
// make the call - a calls b
SipCall a = ua.makeCall("sip:becky@nist.gov", null);
assertLastOperationSuccess(ua.format(), ua);
assertTrue(b.waitForIncomingCall(0)); // blocks until b receives INVITE
// b sends RINGING
assertTrue(b.sendIncomingCallResponse(Response.RINGING, "Ringing", 0));
Thread.sleep(500);
// b sends answer
assertTrue(b.sendIncomingCallResponse(Response.OK, "Answer - Hello
world", 0));
Thread.sleep(500);
// verify RINGING was received
by a
assertResponseReceived("Should have gotten RINGING response",
SipResponse.RINGING, a)
;
// verify OK was received
by a
assertAnswered("Outgoing call leg not answered", a);
//
finish off the call
assertTrue(a.sendInviteOkAck());
assertTrue(a.listenForDisconnect());
Thread.sleep(100);
assertTrue(b.disconnect());
assertTrue(a.waitForDisconnect(5));
assertTrue(a.respondToDisconnect());
ub.dispose();
}
catch (Exception e)
{
e.printStackTrace();
fail("Exception: " + e.getClass().getName() + ": " + e.getMessage());
}
}
There are Many
Ways to Skin a Cat
Your test code can verify expected results in different ways - they all
have their plusses and minuses.
--------------------------------------------------
a.initiateOutgoingCall("sip:becky@nist.gov",
null);
assertLastOperationSuccess("a
initiate call - " + a.format(), a);
Outputs operation error details if the test fails, but requires two
lines of code.
--------------------------------------------------
boolean status_ok =
a.initiateOutgoingCall("sip:becky@nist.gov", null);
assertTrue("Initiate outgoing
call failed - " + a.format(), status_ok);
Same as above - except now the SIP action (initiateOutgoingCall) is
hidden in the middle of a line and harder to see.
--------------------------------------------------
assertTrue("Initiate outgoing
call failed", a.initiateOutgoingCall("sip:becky@nist.gov", null));
Nice and compact, but...
- no operation error details if the test fails - a.format() as a first
parameter won't work (won't have the results yet to format)
- the SIP action is hidden in the middle and harder to see.
An Example of Using
Low Level SipUnit Methods & the JAIN-SIP API Request/Response
Related Classes
When using the lower-level SipSession methods to send/receive SIP
messages, you'll be using the JAIN
SIP API message, address, header, request, and response classes to
build/view the SIP messages. Here is an example of test class code
operating at this level.
import javax.sip.address.Address;
import javax.sip.address.AddressFactory;
import javax.sip.header.HeaderFactory;
import javax.sip.header.ToHeader;
import javax.sip.header.ViaHeader;
import javax.sip.message.Request;
SipPhone ua = sipStack.createSipPhone("sip:amit@nist.gov");
// build the INVITE Request message
AddressFactory addr_factory = ua.getParent().getAddressFactory();
HeaderFactory hdr_factory = ua.getParent().getHeaderFactory();
Request invite = ua.getParent().getMessageFactory().createRequest(
"INVITE sip:becky@nist.gov SIP/2.0\n");
invite.addHeader(ua.getParent().getSipProvider().getNewCallId());
invite.addHeader(hdr_factory.createCSeqHeader(1, Request.INVITE));
invite.addHeader(hdr_factory.createFromHeader(ua.getAddress(), ua
.generateNewTag()));
Address to_address = addr_factory.createAddress(addr_factory
.createURI("sip:becky@nist.gov"));
invite.addHeader(hdr_factory.createToHeader(to_address, null));
Address contact_address = addr_factory.createAddress("sip:amit@"
+ thisHostAddr + ":5060");
invite.addHeader(hdr_factory.createContactHeader(contact_address));
invite.addHeader(hdr_factory.createMaxForwardsHeader(5));
ArrayList via_headers = ua.getViaHeaders();
invite.addHeader((ViaHeader) via_headers.get(0));
Address route_address = addr_factory.createAddress("sip:becky@"
+ thisHostAddr + ":5060");
invite.addHeader(hdr_factory.createRouteHeader(route_address));
// send the Request message
SipTransaction trans = ua.sendRequestWithTransaction(invite, false,
null);
assertNotNull(ua.format(), trans);
// call sent
Etc.
A Presence Example Using SUBSCRIBE/NOTIFY
This example shows a complete test method that establishes a
subscription and then verifies subscription termination from the client
side. Please read the Event Subscription (Presence) section of this
document that explains the procedure in detail. The main methods your
test uses to step through the SUBSCRIBE/NOTIFY sequence are highlighted
in bold.
NOTE: this test was written to validate the SipUnit API itself
(subscriber client-side handling) - it uses a utility class,
PresenceNotifySender, to simulate the presence server. Your test method
wouldn't need that (simulator) side of it since your server/application
under test will respond to the SUBSCRIBE messages sent out by this test
and send NOTIFY messages to this test object. Also, this test contains
numerous asserts to verify the SipUnit API handling which your test
wouldn't need. But, your test will use the same principles you see
below:
- you'll use a SipPhone object ("ua" below) that will have a
buddy list or can do a fetch (each buddy/fetch has a PresenceSubscriber
object respresenting it);
- you'll call a method on the SipPhone (such as addBuddy())
which will initiate a SUBSCRIBE/NOTIFY
sequence to your server/application;
- you'll use methods of the returned PresenceSubscriber
object to step
through the SUBSCRIBE/NOTIFY sequence and validate the messages and
presence information sent by your server/application, like you see
being done below.
private SipStack sipStack;
private SipPhone ua;
etc.
/*
* @see SipTestCase#setUp()
*/
public void setUp() throws Exception
{
try
{
sipStack = new SipStack(testProtocol, myPort, properties);
SipStack.setTraceEnabled(properties.getProperty("sipunit.trace")
.equalsIgnoreCase("true")
|| properties.getProperty("sipunit.trace")
.equalsIgnoreCase("on"));
}
catch (Exception ex)
{
fail("Exception: " + ex.getClass().getName() + ": "
+ ex.getMessage());
throw ex;
}
try
{
ua =
sipStack.createSipPhone("sip:amit@nist.gov");
}
catch (Exception ex)
{
fail("Exception creating SipPhone: " + ex.getClass().getName()
+ ": " + ex.getMessage());
throw ex;
}
}
/*
* @see SipTestCase#tearDown()
*/
public void tearDown() throws Exception
{
ua.dispose();
sipStack.dispose();
}
public void testEndSubscription()
{
String buddy =
"sip:becky@cafesip.org"; // I am amit
try
{
//
create far end (presence server simulator, fictitious buddy)
PresenceNotifySender ub = new PresenceNotifySender(sipStack
.createSipPhone(host, testProtocol, myPort, buddy));
//
prepare far end to receive SUBSCRIBE
assertTrue(ub.processSubscribe(5000, SipResponse.OK, "OK"));
Thread.sleep(500);
//
do the SUBSCRIBE sequence
PresenceSubscriber s = ua.addBuddy(buddy,
2000);
assertNotNull(s);
assertTrue(s.processResponse(1000));
assertTrue(s.isSubscriptionActive());
//
do the NOTIFY sequence
String notify_body = "<?xml version=\"1.0\"
encoding=\"UTF-8\"?>\n<presence entity=\"sip:becky@cafesip.org\"
xmlns=\"urn:ietf:params:xml:ns:pidf\"><tuple
id=\"1\"><status><basic>closed</basic></status></tuple></presence>";
assertTrue(ub.sendNotify(SubscriptionStateHeader.ACTIVE, null,
notify_body, 2400, false));
RequestEvent reqevent = s.waitNotify(1000);
assertNotNull(reqevent);
assertNoSubscriptionErrors(s);
Response response =
s.processNotify(reqevent);
assertNotNull(response);
assertTrue(s.isSubscriptionActive());
//
check PRESENCE info
HashMap<String, PresenceDeviceInfo> devices = s
.getPresenceDevices();
assertEquals(1, devices.size());
PresenceDeviceInfo dev = devices.get("1");
assertNotNull(dev);
assertEquals("closed", dev.getBasicStatus());
assertEquals(0, s.getPresenceExtensions().size());
assertEquals(0, s.getPresenceNotes().size());
//
reply to the NOTIFY
assertTrue(s.replyToNotify(reqevent,
response));
//
check buddy lists
assertEquals(1, ua.getBuddyList().size());
assertEquals(0, ua.getRetiredBuddies().size());
assertNoSubscriptionErrors(s);
//
Now, end the subscription from our side
//
prepare far end to receive SUBSCRIBE
assertTrue(ub.processSubscribe(5000, SipResponse.OK, "OK Ended"));
Thread.sleep(500);
//
remove buddy from contacts, do the SUBSCRIBE sequence
assertTrue(s.removeBuddy(300));
assertFalse(s.isRemovalComplete());
//
check immediate impacts - buddy lists, subscription state
assertEquals(0, ua.getBuddyList().size());
assertEquals(1, ua.getRetiredBuddies().size());
assertNoSubscriptionErrors(s);
assertNotNull(ua.getBuddyInfo(buddy)); // can still be found
assertEquals(s.getTargetUri(), ua.getBuddyInfo(buddy)
.getTargetUri());
assertFalse(s.isSubscriptionActive());
assertFalse(s.isSubscriptionPending());
assertTrue(s.isSubscriptionTerminated());
String reason = s.getTerminationReason();
assertNotNull(reason);
//
check the return info
assertEquals(SipResponse.OK, s.getReturnCode());
ResponseEvent resp_event = s.getCurrentResponse();
response = resp_event.getResponse();
assertEquals("OK Ended", response.getReasonPhrase());
assertEquals(0, response.getExpires().getExpires());
assertEquals(response.toString(), s.getLastReceivedResponse()
.getMessage().toString());
ArrayList<SipResponse> received_responses = s
.getAllReceivedResponses();
assertEquals(2, received_responses.size());
assertEquals(response.toString(), received_responses.get(1)
.toString());
//
process the received response
assertTrue(s.processResponse(300));
//
check the response processing results
assertFalse(s.isSubscriptionActive());
assertFalse(s.isSubscriptionPending());
assertTrue(s.isSubscriptionTerminated());
assertEquals(reason, s.getTerminationReason());
assertEquals(0, s.getTimeLeft());
assertEquals(0, ua.getBuddyList().size());
assertEquals(1, ua.getRetiredBuddies().size());
assertNoSubscriptionErrors(s);
//
tell far end to send a NOTIFY
notify_body = "<?xml version=\"1.0\"
encoding=\"UTF-8\"?>\n<presence entity=\"sip:becky@cafesip.org\"
xmlns=\"urn:ietf:params:xml:ns:pidf\"> <tuple id=\"3\">
<status> <basic>open</basic> </status>
</tuple> </presence>";
assertTrue(ub.sendNotify(SubscriptionStateHeader.TERMINATED,
"done", notify_body, 0, false));
//
get the NOTIFY
reqevent = s.waitNotify(1000);
assertNotNull(reqevent);
assertNoSubscriptionErrors(s);
//
process the NOTIFY
response = s.processNotify(reqevent);
assertNotNull(response);
assertEquals(0, ua.getBuddyList().size());
assertEquals(1, ua.getRetiredBuddies().size());
assertNoSubscriptionErrors(s);
//
check the processing results
assertTrue(s.isSubscriptionTerminated());
assertNotNull(s.getTerminationReason());
assertFalse(reason.equals(s.getTerminationReason())); // updated
assertEquals(0, s.getTimeLeft());
assertEquals(SipResponse.OK, s.getReturnCode()); // response code
//
check PRESENCE info - devices/tuples
//
-----------------------------------------------
devices = s.getPresenceDevices();
assertEquals(1, devices.size());
dev
= devices.get("3");
assertNotNull(dev);
assertEquals("open", dev.getBasicStatus());
assertEquals(-1.0, dev.getContactPriority(), 0.001);
assertNull(dev.getContactURI());
assertEquals(0, dev.getDeviceExtensions().size());
assertEquals(0, dev.getDeviceNotes().size());
assertEquals("3", dev.getId());
assertEquals(0, dev.getStatusExtensions().size());
assertNull(dev.getTimestamp());
//
check PRESENCE info - top-level extensions
//
-----------------------------------------------
assertEquals(0, s.getPresenceExtensions().size());
//
check PRESENCE info - top-level notes
//
-----------------------------------------------
assertEquals(0, s.getPresenceNotes().size());
//
reply to the NOTIFY
assertTrue(s.replyToNotify(reqevent,
response));
}
catch (Exception e)
{
e.printStackTrace();
fail("Exception: " + e.getClass().getName() + ": " + e.getMessage());
}
}
A REFER Example Using REFER/NOTIFY,
SUBSCRIBE/NOTIFY (refresh subscription)
This example shows a complete test method that sends out an in-dialog
REFER message (from the called side), establishing a
subscription via the REFER/NOTIFY sequence and then refreshes the
subscription via a SUBSCRIBE/NOTIFY sequence. Please read the Event
Subscription (Refer) section of this document that explains the
procedure in detail. The main methods your test uses to step through
the REFER/NOTIFY and SUBSCRIBE/NOTIFY sequences are highlighted in bold.
NOTE: this test was written to validate the SipUnit API itself
(referrer client-side handling, which in this example is the called
party of an established session) - it uses a utility class,
ReferNotifySender, at the calling side to simulate the refer-ee. Your
test method
wouldn't need that (simulator) side of it since your test target would
act as the calling ("ua") side of this example. That is, in this
scenario your test target would respond to the REFER/SUBSCRIBE messages
sent out by this test, send NOTIFY messages to this test, and this test
would just have the code for the called/referrer side ("ub" below).
Also, this test contains extra asserts to verify the SipUnit API
handling which your test wouldn't need. But, your test will use the
same principles you see below:
- you'll use a SipPhone object ("ub" in the example below) as
the referrer;
- you'll call a method on the SipPhone (refer()) which will
initiate a REFER/NOTIFY
sequence with your server/application;
- you'll use methods of the returned ReferSubscriber object
to step
through the REFER/NOTIFY sequence and validate the messages sent by
your server/application.
- you'll use the methods of the ReferSubscriber to initiate a
refresh (as in the example below) or unsubscribe sequence - IE, send
SUBSCRIBE, receive NOTIFY.
private SipStack sipStack;
private SipPhone ua;
etc.
/*
* @see SipTestCase#setUp()
*/
public void setUp() throws Exception
{
try
{
sipStack = new SipStack(testProtocol, myPort, properties);
SipStack.setTraceEnabled(properties.getProperty("sipunit.trace")
.equalsIgnoreCase("true")
|| properties.getProperty("sipunit.trace")
.equalsIgnoreCase("on"));
}
catch (Exception ex)
{
fail("Exception: " + ex.getClass().getName() + ": "
+ ex.getMessage());
throw ex;
}
try
{
ua =
sipStack.createSipPhone("sip:amit@nist.gov");
}
catch (Exception ex)
{
fail("Exception creating SipPhone: " + ex.getClass().getName()
+ ": " + ex.getMessage());
throw ex;
}
}
/*
* @see SipTestCase#tearDown()
*/
public void tearDown() throws Exception
{
ua.dispose();
sipStack.dispose();
}
public void
testOutboundIndialogBrefersAwithRefresh() throws Exception
{
// A calls B, call
established
// B sends in-dialog REFER
to A, gets 202 Accepted
// A sends state-active
NOTIFY to B, gets OK in response
// B refreshes the
subscription
// A sends
subscription-terminating NOTIFY to B, gets OK in response
// create and set up the B
party
SipPhone ub =
sipStack.createSipPhone("sip:becky@cafesip.org");
SipCall b =
ub.createSipCall();
assertTrue(b.listenForIncomingCall());
// make the call from A
SipCall a =
ua.makeCall("sip:becky@cafesip.org", properties
.getProperty("javax.sip.IP_ADDRESS")
+ ':' + myPort + '/' + testProtocol);
assertLastOperationSuccess(ua.format(), ua);
// B side answer the call
assertTrue(b.waitForIncomingCall(1000));
assertTrue(b.sendIncomingCallResponse(Response.RINGING, "Ringing", 0));
Thread.sleep(20);
assertTrue(b.sendIncomingCallResponse(Response.OK,
"Answer - Hello world", 0));
Thread.sleep(200);
// A side finish call
establishment
assertAnswered("Outgoing
call leg not answered", a);
a.sendInviteOkAck();
assertLastOperationSuccess("Failure sending ACK - " + a.format(), a);
Thread.sleep(1000);
// B sends in-dialog REFER
to A, gets 202 Accepted
//
****************************************************************
// A side - prepare to
receive REFER
ReferNotifySender
referHandler = new ReferNotifySender(ua);
referHandler.setDialog(a.getDialog());
assertTrue(referHandler.processRefer(4000, SipResponse.ACCEPTED,
"Accepted"));
// B side - create referTo
URI and send a REFER message
SipURI referTo =
ub.getUri("sip:", "dave@denver.example.org", "udp",
"INVITE", null, null, null, null, null);
ReferSubscriber subscription =
ub.refer(b.getDialog(), referTo,
"myeventid", 4000);
if (subscription == null)
{
fail(ub.getReturnCode() + ':' + ub.getErrorMessage());
}
// A side - verify received
REFER contents
RequestEvent requestEvent =
referHandler.getLastReceivedRequest()
.getRequestEvent();
assertNotNull(requestEvent);
Request req =
requestEvent.getRequest();
assertEquals(SipRequest.REFER, req.getMethod());
assertEquals(a.getDialogId(), requestEvent.getDialog().getDialogId());
assertEquals("myeventid",
((EventHeader) req
.getHeader(EventHeader.NAME)).getEventId());
// B side - check the
initial results
assertEquals(SipResponse.ACCEPTED, subscription.getReturnCode());
assertTrue(subscription.isSubscriptionPending());
assertEquals(1,
ub.getRefererList().size());
assertEquals(subscription,
ub.getRefererInfoByDialog(b.getDialogId())
.get(0));
// B side - process the
received response
assertTrue(subscription.processResponse(1000));
assertNoSubscriptionErrors(subscription);
// B side - check the
response processing results
assertTrue(subscription.isSubscriptionPending());
assertNull(subscription.getTerminationReason());
assertEquals(0,
subscription.getTimeLeft());
// A sends state-active
NOTIFY to B, gets OK in response
//
**************************************************************
// A side - send a NOTIFY
Thread.sleep(20);
Request notifyRequest =
a.getDialog().createRequest(SipRequest.NOTIFY);
notifyRequest =
referHandler.addNotifyHeaders(notifyRequest, null,
null, SubscriptionStateHeader.ACTIVE, null,
"SIP/2.0 100 Trying\n", 60);
SipTransaction trans =
referHandler.sendStatefulNotify(notifyRequest,
false);
assertNotNull(trans);
// B side - wait for the
NOTIFY
RequestEvent reqevent =
subscription.waitNotify(1000);
assertNotNull(reqevent);
// B side - examine the
NOTIFY request object, verify subscription
// message getters
Request request =
reqevent.getRequest();
assertEquals(60,
((SubscriptionStateHeader) request
.getHeader(SubscriptionStateHeader.NAME)).getExpires());
SipRequest sipreq =
subscription.getLastReceivedRequest();
assertBodyContains(sipreq,
"SIP/2.0 100 Trying");
assertHeaderContains(sipreq,
EventHeader.NAME, "myeventid");
// B side - process the
NOTIFY
resp = subscription.processNotify(reqevent);
assertNotNull(resp);
assertNoSubscriptionErrors(subscription);
// B side - check the NOTIFY
processing results on subscription
assertTrue(subscription.isSubscriptionActive());
assertTrue(subscription.getTimeLeft() <= 60
&& subscription.getTimeLeft() > 55);
// B side - check the NOTIFY
response that was created
assertEquals(SipResponse.OK,
resp.getStatusCode());
assertEquals(SipResponse.OK,
subscription.getReturnCode());
// B side - reply to the
NOTIFY
assertTrue(subscription.replyToNotify(reqevent,
resp));
// A side - verify the
NOTIFY response got sent by B
Object obj =
referHandler.waitResponse(trans, 10000);
assertNotNull(obj);
assertTrue(obj instanceof
ResponseEvent);
assertEquals(SipResponse.OK,
((ResponseEvent) obj).getResponse()
.getStatusCode());
// B refreshes the
subscription
//
**************************************************************
// prepare A to receive
SUBSCRIBE
assertTrue(referHandler.processSubscribe(2000, SipResponse.OK, "OK"));
// refresh
assertTrue(subscription.refresh(10,
"eventid-x", 500));
assertEquals(SipResponse.OK,
subscription.getReturnCode());
assertEquals("eventid-x",
((EventHeader) subscription
.getLastSentRequest().getHeader(EventHeader.NAME)).getEventId());
assertTrue(subscription.processResponse(200));
assertTrue(subscription.isSubscriptionActive());
assertTrue(subscription.getTimeLeft() <= 10
&& subscription.getTimeLeft() > 5);
// A sends
subscription-terminating NOTIFY to B, gets OK in response
//
**************************************************************
// A side - send a NOTIFY
Thread.sleep(20);
notifyRequest =
a.getDialog().createRequest(SipRequest.NOTIFY);
notifyRequest =
referHandler.addNotifyHeaders(notifyRequest, null,
null, SubscriptionStateHeader.TERMINATED, "noresource",
"SIP/2.0 100 Trying\n", 0);
trans =
referHandler.sendStatefulNotify(notifyRequest, false);
assertNotNull(trans);
// B side - wait for the
NOTIFY
reqevent = subscription.waitNotify(1000);
assertNotNull(reqevent);
// B side - examine the
NOTIFY request object, verify subscription
// message getters
request =
reqevent.getRequest();
assertTrue(((SubscriptionStateHeader) request
.getHeader(SubscriptionStateHeader.NAME)).getExpires() < 1);
sipreq =
subscription.getLastReceivedRequest();
assertTrue(sipreq.isNotify());
assertBodyContains(sipreq,
"SIP/2.0 100 Trying");
// B side - process the
NOTIFY
resp = subscription.processNotify(reqevent);
assertNotNull(resp);
assertNoSubscriptionErrors(subscription);
// B side - check the NOTIFY
processing results on subscription
assertTrue(subscription.isSubscriptionTerminated());
assertEquals("noresource",
subscription.getTerminationReason());
// B side - check the NOTIFY
response that was created
assertEquals(SipResponse.OK,
resp.getStatusCode());
assertTrue(resp.getReasonPhrase().equals("OK"));
assertEquals(SipResponse.OK,
subscription.getReturnCode());
// B side - reply to the
NOTIFY
assertTrue(subscription.replyToNotify(reqevent,
resp));
// A side - verify the
NOTIFY response got sent by B
obj =
referHandler.waitResponse(trans, 10000);
assertNotNull(obj);
assertTrue(obj instanceof
ResponseEvent);
assertEquals(SipResponse.OK,
((ResponseEvent) obj).getResponse()
.getStatusCode());
}
Running Your Test
Since your test class extends SipTestCase and SipTestCase extends JUnit
TestCase, execute your test class the same way you would execute any
JUnit test class. Your CLASSPATH must have in it:
- sipunit.jar along with the other
jar files in the sipunit lib directory (nist-sip-1.2.jar, junit.jar,
etc.)
- your SipUnit test classes, and any other class files you
might be using along with the libraries they need.
It is very common to run JUnit/SipUnit test
classes using an IDE or Ant, which comes with a junit task
for running tests. For a command line example, see the instructions in
this document on testing the installation.
If you'd like to see lots of details and options on
running JUnit tests, the JUnit site has a comprehensive faq on Running
Tests. If you refer to any of these materials, keep in mind that
wherever junit.jar is referred to, the same applies to the sipunit lib
jar files.
If your test class is using a proxy, have that available and running
with the appropriate settings. See Proxy
Considerations for more detail on this subject.
Debugging Tests
To debug what's happening during a SipUnit test, turn on the SipUnit
trace by adding this line of code to your test class setUp() method:
SipStack.setTraceEnabled(true);
You'll get some console output to help you see what is happening.
Another good way to see what's going on during a test run is to use the
trace
viewer tool from the JAIN-SIP project. That jar is already in the
sipunit/lib dir. The tool shows a
graphical trace of SIP messages sent and received by your test class.
An example trace is shown at the end of this section.
The trace tool uses a trace file generated by the underlying JAIN-SIP
stack. You can control what level of tracing is to be done by the
properties parameter you pass in to the SipUnit SipStack constructor.
For the default, you don't have to pass in anything (pass in null).
This will result in minimal but usually sufficient tracing (level =
16). At this level, when you execute your test you'll see that a
testAgent_log.txt file is generated. This is the file that you'll give
as an argument to the trace viewer. For a more detailed debug-level
trace, you'll have to pass in the properties parameter to SipStack with
the trace level set to 32. At this level, when you execute your test
you'll see that a testAgent_debug.txt file is generated. This file has
alot more detail in it. This is the
file that you'll give as an argument to the trace viewer. See further
below for details on the properties you'll need to pass to SipStack for
getting the debug-level tracing turned on or take a look at some of the
test
classes in the examples directory.
Viewing
the traces using the trace viewer tool
The jar for the trace viewer is
in the sipunit lib directory. It needs to be in your CLASSPATH or
you can specify it using the '-cp' argument.
To view level=16 (default) traces, execute the command (windows in this
example):
java -cp
C:\path-to-sipunit\lib\tracesviewer.jar tools.tracesviewer.TracesViewer
-server_file C:\path-to-execution-dir\testAgent_log.txt
To view debug-level traces, execute the command:
java -cp
C:\path-to-sipunit\lib\tracesviewer.jar tools.tracesviewer.TracesViewer
-debug_file C:\path-to-execution-dir\testAgent_debug.txt
Viewing
the trace files directly
You can skip the graphical viewing and look
at the generated trace files using an editor. You'll see that the
level-16 gives all incoming and outgoing SIP messaging traces while the
debug-level tracing adds to that alot of internal information such as
the transaction/dialog states/handling by the stack.
Details on SipStack
properties needed for generating debug-level trace files
Currently the SipUnit SipStack properties parameter is all-or-nothing,
so if you want to change any stack property - like the trace level -
you'll have to pass in the whole set of properties. Note that the
log/debug file names referred to above are also specified by the
properties.
Here is an example of what you would have in your test class:
....
private Properties properties = new Properties();
....
String host = null;
try
{
host = InetAddress.getLocalHost().getHostAddress();
}
catch (UnknownHostException e)
{
host = "localhost";
}
properties.setProperty("javax.sip.IP_ADDRESS", host);
properties.setProperty("javax.sip.RETRANSMISSION_FILTER", "true");
properties.setProperty("javax.sip.STACK_NAME", "testAgent");
// You need 16 for logging traces. 32 for debug + traces.
// Your code will limp at 32 but it is best for debugging.
properties.setProperty("gov.nist.javax.sip.TRACE_LEVEL", "32");
properties.setProperty("gov.nist.javax.sip.DEBUG_LOG",
"testAgent_debug.txt");
properties.setProperty("gov.nist.javax.sip.SERVER_LOG",
"testAgent_log.txt");
// Guard against starvation.
properties.setProperty("gov.nist.javax.sip.READ_TIMEOUT", "1000");
// properties.setProperty("gov.nist.javax.sip.MAX_MESSAGE_SIZE",
// "4096");
properties.setProperty("gov.nist.javax.sip.CACHE_SERVER_CONNECTIONS",
"false");
....
.sipStack = new SipStack(null, -1,
properties);
An example trace
Here's an example of the default (level 16) trace. Note in the call
flow that the "OK-Answer - Hello world" message has been selected (it's
in red) and its contents are detailed in the lower left part of the
screen. You can see the contents of any message by selecting it in the
call flow diagram.

Proxy Considerations
& Routing Policies
As far as routing policies go, a test class using SipUnit can:
- define an outbound proxy on a per-SipStack basis (done via
a Properties parameter to SipUnit SipStack constructor which is the
same list of properties the JAIN-SIP SipStack class takes) to be used
at the SIP stack level for outgoing initial requests. This cannot
be changed during the life of the SipStack.
- define an outbound proxy on a per-SipPhone(/SipSession)
basis (done via host/port/protocol parms to the SipPhone constructor).
At the SipSession (low-) level, this is applied by the caller on a
per-request basis. If applied, the request URI of the given request is
modified to reflect the proxy destination. If not applied, no
modification of the request is done and no additional headers are added
before sending the request out (caller must handle details such as
adding a RouteHeader). At the SipCall/SipPhone (high-) level, this is
applied for all registrations and outbound calls, but can be overridden
on a per-outbound-call basis by specifying an alternate route to use
instead of the SipPhone's proxy.
- do neither of the above. This is only an option when using
low-level API calls and handling all message/routing details yourself,
or using high-level API calls (except SipPhone.un/registration()) and
always specifying an outbound call alternate route.
For an example of a test class not using a proxy, see the
TestNoProxy.java file in the sipunit examples directory.
For an example of a test class using a proxy, see one of the other test
files in the sipunit examples directory. It's class header comment will
indicate the required proxy settings for running that test.
Where Can I Get
a Proxy to Use?
There are free, open source servers available. Check out cafesip's SipExchange
server. Also see
the jain-sip-presence-proxy
project page. There may be other free available
SIP proxies
elsewhere on the internet as well.
Acknowledgements
We would like to acknowledge
and thank the National Institute of Standards and Technology (NIST) for
providing the following useful software. This software is provided by NIST as a
service. NIST owns the software. You can find their projects page here.
The JUnit test framework was written by Erich Gamma and Kent Beck.
Visit the JUnit website
for useful links and references to other XUnit testing frameworks.
Special thanks to external contributors to SipUnit including:
- Alcatel-Lucent - for adding CANCEL
support.
- M. Ranganathan - for miscellaneous bug fixes and
improvements.
- manchi - for notes on STUN support and some example code.
- Venkita S. - for changes to SipSession and SipStack for
creating multiple SIP stacks independent of IP address
- D. Shutt - for the most requirements and feedback we've
gotten from someone.
|