Donna Bergmark
July 28, 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. |
TBD: It would be nice to make directory service optional; instead, peer applications would be allowed to specify an IP address and port number for their communications. It would also be nice to have a constructor that supported a default userid and password; just substitute some default ones instead, like guest. Individual ITX installations could choose to allow these features or not.
Because DesktopSignaling gets 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);
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:
In the default case, the ITX application invokes DesktopSignaling.Dial() to reach somebody. An extension number (a string) is the first (and perhaps the only) parameter to this call. 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.
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 package (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 extension 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 gtwysrv 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
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 conclue?
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 -split 1 -no_navigation -local_icons package.
The translation was initiated by Donna Bergmark on 1999-07-28