Donna Bergmark
August 12, 1999
ITX signaling is handled by the cnrg.itx.signal package. Each ITX application has exactly one DesktopSignaling object, which handles all signaling on behalf of the application. We would like to make ITX signaling SIP [2] compliant. The first step is to understand how the package currently works.
An important aspect of the signaling component is that it uses an event model to handle the complex logic of changes in call state. Section 4 describes this event model.
Application Specific Information | ||
myUID | UserID | Email address or application name under which the application is registering itself |
myPassword | Password | Password being used by the application to authenticate itself |
myDesc | String | Optional description of the application using this DesktopSignaling |
myName | String | String representation of the IP address at which this DesktopSignaling is waiting for input from other DesktopSignaling objects |
Object Handles | ||
myDirS | DirectoryService | The DirectoryService object being used by this application and this DesktopSignaling. |
myApp | SignalingObserver | The application on whose behalf we are handling the signaling function. This is the SignalingObserver that will handle any signaling events that arise. Typically myApp is the object that instantiated this DesktopSignaling. |
myDSS | DesktopSignalingServer | This is the runnable object that listens for incoming packets and disposes of them appropriately |
Internal Variables | ||
myConnectionList | Hashtable | Keeps track of peer applications to whom we are connected. |
myConnSeqNumber | long | A sequence number to use when we are creating a new SignalConnection. |
Location Descriptors | ||
myIAdd | InetAddress | The IP address at which this DesktopSignaling is listening for incoming packets. |
myLoc | Location | This is our IP address and port number, in Directory Service format. In general, a Location can also be an email address or telephone number, but for DesktopSignaling objects, it is always an IP address. |
myServerPort | int | The port on which this DesktopSignaling is listening for incoming packets. |
Miscellaneous | ||
TIMEOUT | int | The constant declared in cnrg.itx.signal.DesktopSignal as the number of milliseconds to wait for responses to signaling packets. Currently this is set to 20 seconds. After 20 seconds with no response, the peer application is declared dead and the connection is removed from myConnectionList. It is also used by myDSS to set a limit on how long to block while waiting for an incoming connection request. The TIMEOUT can be changed via the setTimeout() method. |
For a DesktopSignaling to get a DirectoryService for the application, DesktopSignaling needs a UserID, Password, and optionally a configuration file, all of which are passed to the DirectoryService constructor. myDirS is used by DesktopSignaling to look up other users:
LocationList ll = myDirS.getLocationListByID(u);where u is the UserID of somebody our application is trying to call. myDirS is valid (usable) or not depending on whether myUID and myPassword passed the authentication test.
If the application needs to do some directory service stuff directly, it can get a handle to myDirS by calling DesktopSignaling.getDirectory(). The DesktopSignaling object not only takes care of authentication, but also registers the current roaming location of the user in the database:
myDirS.registerLocation(Location.ROAMING, myLoc);
In addition, if ITX applications want to make non-blocking calls (for example, to be able to attempt calling a number of locations in parallel) then the application must also implement the SignalConnectionObserver interface, to track the progress of the call.
Exactly one DesktopSignalingServer, myDSS, is created for each DesktopSignaling object. myDSS is a thread which listens on a server socket for incoming packets (it sets up its own server socket, letting Java choose the port number). This port number is saved in myServerPort. As can be seen from the following, myDSS is what causes myApp's signal event handlers to be called.
The thread enters an endless loop, accepting incoming connection requests. Whenever a request arrives, myDSS spawns a SigPacketHandler thread with the server socket on which the request was made, an incremented request number, and a handle to DesktopSignaling. The endless loop continues until killServer() is called during the logout() process.
The SigPacketHandler reads in and decodes a signal packet, treating the socket as an ObjectInputStream.1 In SigPacketHandler.run() we have:
mySP = (SigPacket) myIn.readObject();
Upon decoding the packet (which must be an InvitePacket2), the SigPacketHandler constructs a SignalEvent and calls the appropriate method in DesktopSignaling:
Kinds of Packets and Methods Called | ||
Method ID | method called | event |
DIAL | handleDialInvite | InviteSignalEvent |
HANGUP | handleHangupInvite() | HangupSignalEvent |
SENDDTMF | handleSendDTMFInvite() | DTMFSignalEvent |
ALIVEQUERY | handleAliveInvite() | AliveSignalEvent |
The SigPacketHandler thread then dies, while further processing of the InvitePacket proceeds in DesktopSignaling and then in myApp. In the meantime myDSS could well field another request. We rely on the Java scheduler to run the handlers concurrently.
The only time a connection gets added to the table is when our application invokes the Dial() method and our INVITE is accepted by the peer, or when we receive an incoming INVITE packet and the peer confirms our ACCEPT. The only time a SignalConnection is deleted from the table is when our peer hangs up on us, or our application invokes our Hangup() method with some signal connection. Each SignalConnection contains two sequence numbers: ours and our peer's. We use our sequence number as the hash key for myConnectionList. (Hashtable get() and remove() methods are sufficient to maintain myConnectionList.)3
Field | Type | Description |
myC | Connection | The audio connection used in this SignalConnection |
myPeerIP | String | IP address of our peer DesktopSignaling |
myPeerPort | int | Port number on which our peer is listening |
myPeerUID | UserID | Use this to look up the peer application in Directory Service |
myDialer | DialThread | The thread that dialed or is dialing or will dial this SignalConnection |
The primarily methods on a SignalConnection are field accessors and setters, open() and close(), startCall() to start a call to a peer and abortCall(), and startKeepAlive().4 The SignalConnection is used primary as a bundle of data, containing all the state of the call.
An ITX application can implement SignalingObserver, which is an interface, or it can extend AbstractSignalingObserver, which is an adapter, or it can pass AbstractSignalingObserver as a handle when instantiating a DesktopSignaling. Typically, an ITX application passes itself:
mySig = new DesktopSignaling(this, ...When changes occur in the environment, an event is generated; see the subpackage cnrg.itx.signal.SignalEvent for details. After constructing the event, DesktopSignaling calls one of the application handlers. There are seven signal events in all. Here are the events and their handlers:
Signal event | Handler | Description |
AbortSignalEvent | onAbortCall() | tells an app to abandon call setup |
AliveSignalEvent | handleAliveEvent() | (internal to DesktopSignaling) |
DialSignalEvent | onAccept() | tells app something happened |
onReject() | during non-blocking Dial() | |
onBusy() | ||
onInvalid() | ||
onTimeout() | ||
onIncompatible() | ||
onError() | ||
DTMFSignalEvent | onDTMF() | tells app that DTMF has been received |
HangupSignalEvent | onHangup() | tells app that peer has hung up |
InviteSignalEvent | onInvite() | tells app that peer is trying to call it |
SignalEvent | (none) | base class for other signal events |
The application specifies its disposition of invitation events by calling a method on the SignalEvent. For example:
ise.accept();There is an InvitePacket inside that SignalEvent, and its resultFlag is set to SignalID.ACCEPT, flagging the packet as accepted.
To recap, InvitePacket extends SigPacket and is serializable. It is part of a SignalEvent object, which is contained in a SignalConnection, along with a data connection (Connection).
Setting up a call begins when one ITX application decides that it wants to make a call to another party. The ITX application begins the process by invoking the Dial() method on its DesktopSignaling. The parameters to the Dial() method determine who the callee is and what the characteristics of the resulting audio connection should be.
Like SIP, ITX uses a three-way handshake between peers in order to set up a call. The successful connection is established in the following three steps:
The 3-way handshake can be aborted at the following junctures:
ITX applications invoke DesktopSignaling.Dial() to reach somebody. If no Directory Service is available, then the location (IP address and port number) must be given as a parameter. Otherwise, the parameter can be a string representing who is being called. In the current version of ITX, this string can be one of the following:
DesktopSignaling tries each location in turn until the INVITE gets back an ACCEPT or until the end of the list is reached. If the location is another user in the database, DesktopSignaling will recurse to one level.
NetworkSource --> SpeakerDestination
for input and
MicrophoneSource --> NetworkDestination
for output.
The audio connection is set up (i.e. resources allocated) but not opened.
It is up to the calling
application to actually open the signal connection.
The application can override the input and output channels by passing them as parameters to the Dial() method. In particular, half-duplex applications might choose to specify (Channel)null as one of the channels. Either both channels must be given in the Dial() call, or neither.
In each case, DesktopSignaling closes the SignalConnection, which releases all the audio resources associated with the call. In most cases the SignalConnection is also removed from myConnectionList.7
In ITX, signaling is bound into the application. There is no central signaling daemon that keeps track of which processes are alive and which are not. This ``shared-fate'' scenario makes it simple for the control layer to keep track of its peer processes by using a ``hello'' protocol.
A SignalConnection has a KeepAlive thread once its startKeepAlive() method is invoked. A DesktopSignaling calls this method during the Dial() sequence if its INVITE has been accepted by its peer8:
sc = (SignalConnection) myConnectionList.get(seq); sc.startKeepAlive(this);or when our ACCEPT of an INVITE has been CONFIRMed:
sc = new SignalConnection(...); : myApp.onStartCall(sc); // does app want to start the call? sc.startKeepAlive(this); // if onStartCall returns
The call to startKeepAlive() causes a KeepAlive object to be created, which is a thread that periodically opens a socket to a peer. If the socket open fails three times in a row, the peer is assumed to be dead; DesktopSignaling's handlePeerNotAlive method is invoked to shut down the connection.
The thread is stopped when our application calls our Hangup() method on some active signal connection, or when DesktopSignaling receives a HANGUP packet from its peer:
KeepAlive ka = sc.getKeepAlive(); // sc is SignalConnection if (ka != null) ka.cleanup();
BUG: I believe that the call to ka.cleanup() should be moved into SignalConnection.close(), since the KeepAlive thread is part of the SignalConnection object.
BUG: Currently we have a bug in signal connection's implementation of abortCall() in that the signal connection stops its DialThread but not its KeepAlive. I think that if a signal connection is in the IDLE state, it should also have been closed or never have been opened.
The other objects related to KeepAlive are AliveSignalEvent and its handler handleAliveInvite(). If an application wants to check to see whether its peer is still alive, it can invoke the isAlive(SignalConnection) on its DesktopSignaling. This will cause an ``alive'' INVITE packet to be created and sent to the peer.
If successful, the peer DesktopSignaling object will invoke its handleAliveInvite() method, which returns a CONFIRM packet. Otherwise, DesktopSignaling has inadvertently discovered that the peer is dead and will treat this via its handleHangupInvite, which closes the signal connection. That is, discovery of peer death is treated as a HANGUP request.
When a DesktopSignaling object is first created by an ITX application, some of its parameters are the userid and password under which the application is authenticating itself. For example, when CUPS begins running, it provides its userid (adm) and password to the DesktopSignaling constructor, which in turn obtains a DirectoryService and calls DeclareIdentity() on it. If successful, further directory services are allowed.
As a side effect, this DesktopSignaling's current IP address and port number (on which it is listening) are registered in the database. This will allow other ITX applications to send Dial invitations to this DesktopSignaling. This location is known as the user or application's ``roaming location''.
If a user logs into CUPS, since CUPS is running with management-level privileges, CUPS can obtain the user's NETid and ITX password and use them to register the user's current roaming location, which again, is the IP address and port number on which CUPS' DesktopSignaling is listening for invites.
The Gateway and PBX are part of signaling, even though they are in their own packages (cnrg.itx.gtwy and cnrg.itx.gtwy.pbx). The Gateway manager is a special ITX application which gets a DesktopSignaling just like any other ITX application.
The difference is that the Gateway extends the signaling interface to handle real telephone numbers and the PSTN, with or without the PBX package.
If a CUPS user types in a real telephone number rather than the name of another ITX user or application, then CUPS will send a call invitation to the gateway (it finds out where the gateway is running by looking up ITX application gatewaysrv in the database). The Gateway's DesktopSignaling is listening for invitations, gets one, and invokes the Gateway's onInvite() handler. The Gateway simply extracts the desired telephone number from the InviteSignalEvent and uses one of its own lines to place a call to that number. The Gateway also sets up an audio connection between it and CUPS:
Gateway in-channel: dx_play <- networkSource Gateway out-channel: dx_record -> networkDestination
An interesting situation is when a user calls the Gateway and leaves the extension of another user, who turns out to be available only at another telephone. In this case, the AudioConnection will go from the telephone to the Gateway, to the network, in from the network, to the Gateway, and out on the other telephone line. A future optimization would be to catch this happening, and reroute the first call directly to the second.
Rather than having each DesktopSignaling writing to its own disk file, we would probably choose to have the signaling objects maintain a Stats structure (currently in the cnrg.itx.datax package) from which a billing or logging application can pull the desired data.
Here is one suggested list of CDR fields (from the Pulver Report) [1]:
What can one conclude?
This document was generated using the LaTeX2HTML translator Version 98.1p1 release (March 2nd, 1998)
Copyright © 1993, 1994, 1995, 1996, 1997, Nikos Drakos, Computer Based Learning Unit, University of Leeds.
The command line arguments were:
latex2html -local_icons -split 1 -no_navigation package.
The translation was initiated by Donna Bergmark on 1999-08-12