Further, from an interfacing standpoint, we tried to make a minimal set of interfaces. To do this, wanted to create an event observer model allowing anyone who wants information about events to be notified of them. Thus, the management and billing groups do not have to provide us an interface when making or receiving calls. Rather, they listen to the specific messages that signify those actions. Therefore, much of the work shifts from generating interfaces to generating well defined protocols. Since the protocols are no longer internal to signaling code (other people need to know the sequence of messages in a handshake, we set out to create a flexible, well-known messaging protocol. On top of this, we wanted to design handshaking protocols that would hide much of the underlying endpoints. For example, the handshake for phone to internet should be the same as for internet to internet.
We also wanted to try to create a design that was highly modular and built out of components. We wanted to create a server that would start the many different components created by our team and other teams in the group. With this model, we could easily extend the server by starting a new component. Likewise, it allowed us to easily control which portions of the server that we started by passing in simple command line arguments.
Even though our project did not require scalability, we wanted to put in support for the ability to run with a distributed SigServer farm. Thus, we tried to take this point into account during our early design decisions. With a few key decisions early on, we hoped that we could make our system easily scalable for the future.
MessageCenters also make it easy for one component to watch "over the shoulder" of another component; many components may be registered as listeners, and each will receive Messages destined for the MessageCenter. For example, both the billing and management teams eavesdrop on signaling protocols by listening to the messages pasted between hosts. This frees us from making explicit invocations of billing/management methods when, for example, a call is completed. This is in line with our philosophy of component oriented software: our code executes completely independently of either of these two groups, and also integrates cleanly.
The SigServer starts the following componets:
At the time of the demo, we have successfully integrated with both the data exchange and gateway teams for two party calls. Calls can be made both from one computer to another or from a telephone handset to a computer running a client. In both cases, the same protocol is used, a three-way handshake, outlined in the diagram below.
During the initial handshake, data exchange setup parameters are exchanged and negotiated as well as the ports on which data will be transfered. In the case of IP-to-IP calls, the parameters suggested by the initiating party (which are set by management) are always accepted by the receiving end. In the case of the calls involving the gateway, however, the gateway is able to dictate which parameters are used. (If the gateway is initiating the connection then it suggests parameters and denies any response which doesn't consent to these parameters. If the gateway is on the receiving end then it is able to override the suggested parameters, with the parameters it requires.)
During the course of the call, signaling steps out of the way for the most part. If one of the parties wants to senf DTMF, then a ConnectionEventMessage is sent through the control plane to the other client, where it is sent up to the client through a DTMFEvent. If the gateway detects incoming DTMF from the telephone network, then a ConnectionEventMessage is instantiated and sent to the IP client. Going the other way, if the client sends a DTMF through the signaling control plane to the gateway, then the gateway produces the actual tones and sends then through the analog interface to the telephone network.
At the end of a call, or if an error is sent up from the data plane (through a callback), a GoodByeMessage is sent to the other party, instructing them to breakdown the call, terminate the data connection, and send an event to the application.
At this time the multi-party signaling code is not in place. The protocol, diagramed below, has been partially implemented. However, we didn't have the time to integrate data exchange code, nor to test our code completely.
The multi-party signaling uses a central GroupManager to maintain a definitive list of call participants and information about them. When a user wants to start a multi-party (conference) call, it asks the MPM (MultiPartyManager) to create a new group. The MPM responds with the address of the new GroupManager. The client can then ask the GroupManager to add itself to the group though an InvitationMessage. It then asks other users to join the group though a JoinMessage.
Each time the GroupManager receives an Invitation message it updates its internal table of group members and then sends an AddMessage to each existing member of the group. Similarily, when a GoodByeMessage is received, a DeleteMessage is sent out to each group member.
There were also the problems of logistics of merging everyone's code. Since signaling code touches most, if not all of the low level API's between teams, it was quite an undertaking to fold other groups' code into our own. Part of this task was made easier by the observer model that we used, since there wasn't much to do for management to be added to our code. But, it would have been nice to have been able to integrate with the data exchange group a little earlier since they were such a critical group to the success of the project. In addition to specifying interfaces (and even message protocols) we should have been more careful about the specifics of each function. For example, blocking versus non-blocking read() became a major issue when integrating data exchange. If we had considered this earlier, we might have saves some time in the last week.
We have also refined our process of effectively working on the same codebase (within signaling) to team up on complex problems and split up to increase parallelization when speed is necessary. The effectiveness of CVS was also evident throughout the project, as it allowed a an easy system to manage the code. There were, however, groups who were reluctant to use the system which caused problems for others.
In addition, we have learned much about developing robust protocls that are able to withstand failures throughout. As part of this, passing error messages on any failure help to track the state of the system to provide reliable feedback to the user and programmer. Likewise, since we spent much time just thinking through the development of the actual handshake protocols, we became better at developing protocols that can withstand
We would have liked to get multiparty code working well. We put in much of the groundwork, so it would have been nice to get the final results working.
We were also extremely close to get be able to call out from an Internet client to a telephone. We have all of the logic in place, and faced a few last minute bugs that prevented us from executing. The last minute time crunch was the most frustrating since we had much of the groundwork but were not able to deliver the final results.
Given more time, we would have liked to have been able to move to a more production-like system that would implement some sort of security at the messaing layer. We currently would suffer some problems if a malicious client were to send random GoodBye messages, for exmaple. The fix could be implemnted by putting some sort of encryption and authentication at the message center layer. This would provide a reliable and secure transport layer. This type of functionality would be nice to put in to move more towards a production system.
/**
* A Connection at the data layer. The constructor sets up the connection.
* Data to and from the client (application) is passed through signalling
* straight to the VoiceConnection object. The VoiceConnectionObserver
* interface allows the DataX to notify the signalling component when
* errors occur.
*/
public interface VoiceConnection
{
public byte[] read(); // read samples
public void write(byte [] s); // write samples
protected void close(); // shuts down the connection
protected void open(); // starts the connection
protected ConnectionTable getConnectionTable();
public String toString();
}
/**
* Holds list of all active connections on the VoiceConnection.
*/
public interface ConnectionTable extends Enumeration
{
public static final int MAX_CONNECTIONS = 16;
public void add(InetAddress hostAddress, int hostPort);
public void delete(InetAddress hostAddress, int hostPort);
public boolean isMemeber(InetAddress hostAddress, int hostPort);
public Enumeration connections();
public boolean connectionsExist();
int getMyPort();
byte getUniqueID(InetAddress hostAddress, int hostPort);
}
public abstract class DirClient { public abstract void newAddress(String user, String phoneNumber, Vector services) throws DirectoryException; public abstract void newAddress(String userName) throws DirectoryException; public abstract void addPhoneNumber(String userName, String phoneNumber, Vector services) throws DirectoryException; public abstract void updateServices(String user, Vector sevices) throws DirectoryException; public abstract void registerIP(String userName, String ipAddress, int port, Vector services) throws DirectoryException; public abstract void killIP(IPAddress addr) throws DirectoryException; public abstract void removeUser(String userName) throws DirectoryException; public abstract void removeUser(String userName, String phoneNumber) throws DirectoryException; public abstract void close(); public abstract void askPacket(String a, String p); //take out }
MiaAddress
is an abstract class representing a generic "terminal." MiaAddresses may coorespond to a telephone terminal, an IP telephony application, or possibly to a multiparty GroupManager.
public class Signal { public static native int gw_initialize(boolean useIP); public static native int gw_setupConnection(int wire, Connection h); public static native int gw_terminateConnection(int status); public static native int gw_dial(int wire, char[] dialstr); }
public interface Terminal
{
/**
* Initiate a Connection with another user.
*
* @param multi enables multi-party conferencing
* @param redirectable if true then connections will automatically be redirected if the given user
* reponds with REDIRECT, o.w. a RedirectedException will be thrown.
*/
public Connection invite( String user, String[] services, boolean multi, boolean redirectable) throws SignalingException;
/**
* Initiate a Connection with another user.
*
* @param multi enables multi-party conferencing
* @param redirectable if true then connections will automatically be redirected if the given user
* reponds with REDIRECT, o.w. a RedirectedException will be thrown.
*/
public Connection invite( MiaAddress address, boolean multi, boolean redirectable) throws SignalingException;
/**
* Register an observer to receive events for this terminal.
*/
public void addTerminalObserver( TerminalObserver o);
public void removeTerminalObserver( TerminalObserver o);
}
public interface Connection
{
/**
* Read all data available. (blocking read)
*/
public byte[] read();
/**
* Read only the specified length. (blocking read)
*
* @return The number of bytes actually read.
*/
public int read( byte[] b, int length, int offset);
/**
* Write an entire array of data to the Connection.
*/
public void write( byte[] b);
/**
* Write only the specifed data to the Connection.
*/
public void write( byte[] b, int offset, int length);
/**
* To invite a 3rd (or 4th) party into an existing conversation.
*/
public Connection invite( String user, String[] services);
/**
* Redirect an established Connection to another user.
*
* This will terminate the current Connection.
*/
public void redirect( String user);
public void close();
/**
* Send an event to everyone else on this connection.
*
* Most likely DTMF.
*/
public void sendEvent( ConnectionEvent);
/**
* Register an observer to receive events for this connection.
*
* See report-1 for a definition of ConnecetionObserver.
*/
public void addConnectionObserver( ConnectionObserver o);
public void removeConnectionObserver( ConnectionObserver o);
}
public interface MessageCenter
{
/**
allows you to be notified of messages that are LEAVING this MessageCenter
**/
void addOutgoingListener(MessageListener ml);
/**
allows you to be notified of messages that are ENTERING this MessageCenter
**/
void addIncomingListener(MessageListener ml);
/**
sends a message to the address as returned by m.getDestination()
**/
void sendMessage(Message m) throws MessageException;
}
public interface Message extends Serializable
{
MiaAddress getDestination();
}
public interface MessageListener
{
/**
Function is called after a MessageListener has registered to receive
messages and a message has arrived.
@param m Message that the MessageCenter processed.
@param ia is the source address of the message. It is null if the message
is an outgoing message (and hence it originated here).
**/
void handleMessage(Message m, InetAddress iaSource);
}
Signaling seemed like a big job, perhaps it should have been split between more than just two students. Though with four people it might be difficult to coordinate, three might have helped us accomplish more.
As far as the gateway, it would have been nice for the machine to be located in CSUG (though we realize the security issues associated with this machine) so that it would be in a room with machines on the same side of the firewall. Also, it was difficult to get as much time on the gateway as we would have liked in the last few weeks. (We couldn't complete testing and integration earlier because other teams weren't ready to go.) Breifly, I'm aware of many of the students and faculty of the Systems Lab that were disrupted by the number (and volume) of the CS519 students in the lab.
Finally, it seems like development of the core components: signaling, gateway, and data exchange, should really be near completion well before the end of the semister. Perhaps the course staff should have encouraged this by enforcing an earlier due date. This way apps would have more to work from and not have to worry about implementing their own versions of data/signaling code.