This document explains the structure of the code in the IAPs, the main interfaces with the other components of Iceberg. This is to be used as a guide in writing new IAPs to extends Iceberg to new end-points.
An IAP is a proxy between the device or service specific side and the Internet core. Hence a portion of it is device or service specific and a portion of it is generic.
I originally wrote IAPs without call-agents (since call-agents did not exist then). In that version, call signaling between the IAPs was using a Java RMI call call2phone(). When I later changed the code to operate with call agents, I did not remove the old code (so that changes were minimal). This is a little unclean, but not too messy since both versions shared a lot of the code.
In design, the IAP is supposed to interact with its call-agent through heart-beats. But this has not yet been implemented (nor fully designed). Hence, right now, all interaction between the IAP and the CA is through RMI calls. The CA uses IAPIF and the IAP uses portions of CallAgentIF.
There are three main routines implemented by an IAP that are called by the call agent: ring(), beHungUp(), and handleDTMF(). These are for call setup, call tear-down, and passing DTMF tones, respectively. The routines for the other direction (calls from IAP to the CA) are: handleOutgoingCall(), terminateCall(), and handleOutgoingDTMF() respectively.
Passing DTMF can be thought of as a primitive form of user-level signaling. Unlike in the telephone network (and like in the GSM air interface), DTMF tones go along the control path instead of the data path. An IAP need not necessarily support incoming or outgoing DTMF. In fact, an IAP need not necessarily support incoming calls as well as outgoing calls. Accordingly, we define server and client IAPs as follows.
Server IAPs: Those that implement the interface for incoming calls (incoming from the Internet-core side).
Client IAPs: Those that support outgoing calls (outgoing to the Internet-core side).
An IAP can serve as only as a client, or only as a server, or both.
The IAP does not directly interact with the APC. But the IAP end is expected to implement a portion of the ConnectorMgrIF. This is so that the APC can pass relevant arguments for spawning the source operator at the IAP end. In some cases, there may not be any source operator at the IAP end. There are two possible reasons for this:
This ConnectorMgrIF can also be used as an indication of when to start the source operator. Since the IAP end only needs to implement a portion of the ConnectorMgrIF, ideally, a different interface should be used for the purpose. However, for ease of implementation, something equivalent has been done. All IAPs extend an abstract class IAPWithCA which implements most of the ConnectorMgrIF (with dummy routines) and leaves the relevant routines to be implemented by the actual IAP.
There are two ways to implement the ConnectorMgrIF at the IAP end. The IAP itself can implement this (by extending the IAPWithCA class as mentioned earlier). An alternative way is to have a ConnectorMgr or its derivative running as a separate service in the same end as the IAP. But in this case, the IAP will not have direct control over the source operator (may or may not be desirable).
I'll explain only the portions that are relevant for operation with a call agent. I'll refer to different IAPs to illustrate the different aspects.
IAPWithCA
As mentioned earlier, IAPs extend IAPWithCA. The main purpose of this is to provide a default implementation of most of the routines of the ConnectorMgrIF so that the IAP itself is not cluttered.
IAPWithCA has to be instantiated with two parameters: the input and output block size of data from the IAP. This is mostly relevant when the input or output data is an RTP stream. For instance, if the data is a GSM encoded RTP stream, the block size would be 33 (bytes) -- this is the size of the payload in the RTP packet (assuming that one GSM frame is sent in each RTP packet). If the IAP does not use streaming RTP data, the block size can be set as follows. It is the set of bytes that can be read from the data stream at a time. The operators in the path wait for that many bytes to collect before processing the data. The block size can simply be set to 1 safely (may be this has some performance implications?) if there are no logical blocks of data.
The non-relevant routines of ConnectorMgrIF are implemented as final class methods in IAPWithCA and the relevant ones are implemented as abstract class methods. These are: loadLocalOperator(), createStartConn(), and cleanUp().
DummyOperator
The IAP writer ideally does not need to worry about this. This is hidden under the IAPWithCA class. This is present just because the ConnectorMgrIF needs to return an OperatorIF. The relevant routines of OperatorIF are: ipBlockSize(), opBlockSize(), start(), and getMgr(). Note that the start() routine does not do actually start the operator.
rmi2phone, rmi2phone_req, rmi2phone_rep
These are related to the interface used between IAPs when there was no call agent. These are still used internally by the IAPs -- but are not part of the external interface.
Calls from IAP to CA
The CallAgentIF for making these calls is obtained from the iPoP through a iPoP.dispatch() call during call setup.
The OutgoingCallRequest structure has the following fields:
The EndPointInfo structure has the following fields:
The EndPointInfo structure is not looked at by the CallAgent (at least, it should not). The "array of objects" argument to the operator has a format specific to the way the data is sent. That is, the connector -- which is turn is decided by the format of the data in the current implementation. Currently defined argument formats are:
The tones argument in the handleOutgoingDTMF call is a cumulative set of DTMF buttons pressed so far. This needs to be cumulative because of the following reason. The only control path between the CAs at the two ends of a session is a heartbeat. This is what is used for passing DTMF tones as well (which by the way has a lot of latency). Since heart-beat messages could be lost, the entire history of DTMF tones for a session needs to be passed each time.
Calls from CA to IAP
The calls from CA to the IAP are as follows:
The ring() has the following arguments: the CallAgentIF for the IAP to use for this call, the caller id (in LDAP DN format), and the network specific id of the callee for the call (in LDAP DN format). (I'm not sure what the others argument is for -- this is possibly in the CallAgent doc).
The RingReply structure has the following fields:
The EndPointInfo passed in the beHungUp and handleDTMF calls is used to identify the session to which the calls belong (from the sessionID field in the EndPointInfo structure). The tone argument in the handleDTMF call is once again cumulative, like in the handleOutgoingDTMF call described earlier.
Calls from the APC to the IAP
These are the calls to the ConnectorMgrIF part of the IAP end (as mentioned earlier, this need not necessarily be implemented by the IAP -- it just needs to be present at the IAP end). There are three calls:
The OperatorIF returned in the loadLocalOperator() call is the DummyOperator. The corresponding arguments to createStartConn() and cleanUp() are also the same -- they are ignored by the IAP since it already has knowledge of that argument (of course, one could imagine making use of that argument some way in some IAPs).
The pathid argument in loadLocalOperator and cleanUp gives the (APC generated) identifier for the path associated with this session. This can be (and is) ignored by the IAPs.
The name argument in loadLocalOperator is supposed to denote the class-name of the operator to load. This is not relevant for the source operator though -- since the APC does not know about the nature of the source operator. The APC fills this argument with the value "dummy" and the IAPs ignore it. The connType argument is always ConnType.RTP (since this call is relevant only for RTP stream based operators) and is ignored. The return value of the createStartConn call is relevant only when the operator is an intermediate operator, and hence not for the source operator. This is simply set to null in all the IAPs.
The sessionID argument in all of the calls in from the EndPointInfo that was passed from this IAP to the CA during call establishment. This helps identify the session to which this particular call (to either of the three routines) belongs.
The DescrOperator argument is an important one. It gives info on (a) where the source operator should send its data, and (b) where the operator should get data from. The latter assumes that the source operator is the same as the destination operator for the reverse path. This also means that we assume the existence of a reverse path for each path -- this is not necessarily the case and needs to be fixed. Currently, if the reverse path does not exist, the APC replaces it with a null path and continues.
The ip_argList of the DescrOperator gives the argument list describing the source from which the operator should receive data. The op_argList describes the destination to which the operator should send data. As you can see, the format of the argument list is interpreted differently by different IAPs (i.e., different end operators).
The op_argList in the loadLocalOperator() call is valid only when there is an intermediate (data transforming) operator in the path. Else, this information comes from the IPAddr:port parameters of the createStartConn() call. (Ideally, the IPAddr and port fields of the createStartConn() call should be replaced with an abstract argument list like in EndPointInfo).
The per-session thread
Most IAPs have a per-session Java thread that does things like watching over any exec'ed process (e.g., vat), handling DTMF (e.g., the voice-mail or media-manager IAPs), etc.. This thread is usually started during the loadLocalOperator() call, or the createStartConn() call depending on when the IAP gets the appropriate arguments describing the destination of data from this IAP (see the op_argList and IPAddr:port description earlier of the loadLocalOperator and createStartConn calls, respectively).
The Call State
The call state at an IAP is captured in a separate structure. This is maintained per-session. This structure is indexed by the sessionID that was generated during call setup by the IAP. The generated sessionID could be any unique string (e.g., myutil.uuid() generates a UUID - Universally unique ID). However, all IAPs simply use ca.toString() for the sessionID where ca is the CallAgentIF corresponding to the call.
The call state structure may contain some redundant information since it was originally written when there was no notion of call agents.
When adding a new device or service to the system, you need to write an IAP. This involves two parts -- the service/device specific side, and the generic Internet-core side. The complexity of the former could be as much as you can imagine -- depending on the device/service you are interfacing with. The latter is quite simple -- as explained earlier in this document. For this, you only need to implement the interfaces to be called by the CA and the APC. And, you need to implement the logic for outgoing call establishments, outgoing call terminates, and outgoing DTMF (as necessary).