FileDocCategorySizeDatePackage
TCPMessageChannel.javaAPI DocphoneME MR2 API (J2ME)20495Wed May 02 18:00:42 BST 2007gov.nist.siplite.stack

TCPMessageChannel.java

/*
 * Portions Copyright  2000-2007 Sun Microsystems, Inc. All Rights
 * Reserved.  Use is subject to license terms.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License version
 * 2 only, as published by the Free Software Foundation.
 * 
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License version 2 for more details (a copy is
 * included at /legal/license.txt).
 * 
 * You should have received a copy of the GNU General Public License
 * version 2 along with this work; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 * 
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
 * Clara, CA 95054 or visit www.sun.com if you need additional
 * information or have any questions.
 */
/*
 * TCPMessageChannel.java
 *
 * Created on September 3, 2002, 3:47 PM
 */

package gov.nist.siplite.stack;

import gov.nist.siplite.header.*;
import gov.nist.siplite.parser.*;
import gov.nist.siplite.message.*;
import gov.nist.siplite.SIPConstants;
import gov.nist.core.*;
import javax.microedition.io.*;
import java.io.*;

import com.sun.midp.log.Logging;
import com.sun.midp.log.LogChannels;

/**
 * Handle a TCP stream connection.
 *
 * @version 1.0
 */
public class TCPMessageChannel extends MessageChannel
        implements SIPMessageListener {
    /** Stream connection handle. */
    private SocketConnection mySock;
    /** Message parser handle. */
    private PipelinedMsgParser myParser;
    /** Input stream for client thread. */
    private InputStream myClientInputStream;
    /** Output stream for client thread. */
    private OutputStream myClientOutputStream;
    /** Current SIP message stack. */
    private SIPMessageStack stack;
    /** Local address. */
    private String myAddress;
    /** Local port number. */
    private int myPort;
    /** Remote address. */
    private String peerAddress;
    /** Remote port number. */
    private int peerPort;
    /** Remote transport type. */
    private String peerProtocol;
    /** Indicates channel is shutting down. */
    private boolean exitFlag;
    /** Number of transaction that uses this channel. */
    private int useCounter;
    
    /**
     * Constructor - gets called from the SIPMessageStack class with a socket
     * on accepting a new client. All the processing of the message is
     * done here with the stack being freed up to handle new connections.
     * The sock input is the socket that is returned from the accept.
     * Global data that is shared by all threads is accessible in the Server
     * structure.
     * @param sock Socket from which to read and write messages. The socket
     * is already connected (was created as a result of an accept).
     * @param sipStack Ptr to SIP Stack
     * @param msgProcessor handler for TCP message communication
     */
    protected TCPMessageChannel(SocketConnection sock, SIPMessageStack sipStack,
                                TCPMessageProcessor msgProcessor) 
    throws IOException {
        mySock = sock;
        myClientInputStream = sock.openInputStream();
        myClientOutputStream = sock.openOutputStream();
        stack = sipStack;
        messageProcessor = msgProcessor;
        // peerAddress will be updated when 
        // first Request packet is received
        // see processMessage(...)
        peerAddress = sock.getAddress();
        peerPort = sock.getPort();
        myAddress = sock.getLocalAddress();
        myPort = sock.getLocalPort();
        start();
        incrementUseCounter();
    }

    /**
     * Constructor - connects to the given inet address.
     * @param targetHostPort inet address to connect to.
     * @param sipStack is the sip stack from which we are created.
     * @param msgProcessor TCP message processor
     * @throws IOException if we cannot connect.
     */
    protected TCPMessageChannel(HostPort targetHostPort,
                                SIPMessageStack sipStack,
                                TCPMessageProcessor msgProcessor)
    throws IOException {
        stack = sipStack;
        messageProcessor = msgProcessor;
        myAddress = sipStack.getHostAddress();
        peerAddress = targetHostPort.getHost().getHostname();
        peerPort = targetHostPort.getPort();
        makeSocket(peerAddress, peerPort);
        start();
        incrementUseCounter();
    }

    /**
     * Creates a socket connection.
     * @param host Host name
     * @param port Port number
     * @exception IOException if the socket can not be created.
     */
    private void makeSocket(String host, int port) throws IOException {
        if (host == null ||
            host.length() == 0 ||
            port < 0) {
            throw new IOException("Invalid hostname or port number");
        }
        
        String name = "//" + host + ":" + port;
        
        if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
            Logging.report(Logging.INFORMATION, LogChannels.LC_JSR180,
                "Opening outbound socket connection :" + host + ":" + port);
        }
        
        /*
         * Original NIST method for opening the socket connection
         * has been replaced by direct calls to instantiate the protocol 
         * handler, in order to pass the security token for use of lower 
         * level transport connection. 
         * Original NIST sequence is :
         *
         * socket = (SocketConnection)Connector.open(name);
         *
         */
        com.sun.midp.io.j2me.socket.Protocol conn =
                new com.sun.midp.io.j2me.socket.Protocol();
        try {
            mySock = (SocketConnection)conn.openPrim(
                        stack.getSecurityToken(), name);
        } catch (ConnectionNotFoundException ex) {
            throw new IOException("Can't connect to "+name);
        }
        myClientInputStream = mySock.openInputStream();
        myClientOutputStream = mySock.openOutputStream();
    }
    
    /**
     * Returns "true" as this is a reliable transport.
     * @return true if reliable stream transport
     */
    public boolean isReliable() {
        return true;
    }
    
    /**
     * Closes the message channel.
     */
    synchronized public void close() {
        if (0 != useCounter) {
            useCounter--;
        } 
        if (0 == useCounter) {
            exit();
            if (null != messageProcessor) {
                ((TCPMessageProcessor)messageProcessor).notifyClose(this);
            }
        }
    }

    /**
     * Closes the message channel regardless whether it is used or not.
     */
    synchronized protected void exit() {
        useCounter = 0;
        exitFlag = true;
        shutDownConnection();
        // allow gc to collect MP
        messageProcessor = null;
    }
    
    /**
     * Gets my SIP Stack.
     * @return The SIP Stack for this message channel.
     */
    public SIPMessageStack getSIPStack() {
        return stack;
    }
    
    /**
     * Gets the transport string.
     * @return "tcp" in this case.
     */
    public String getTransport() {
        return SIPConstants.TRANSPORT_TCP;
    }
    
    /**
     * Gets the address of the client that sent the data to us.
     * @return Address of the client that sent us data
     * that resulted in this channel being
     * created.
     */
    public String getPeerAddress() {
        return peerAddress;
    }
    
    /**
     * Sends message to whoever is connected to us.
     * Uses the topmost via address to send to.
     * @param msg is the message to send.
     */
    synchronized private void sendMessage(byte[] msg) throws IOException {
        if (exitFlag) {
            return;
        }

        if (mySock == null) {
            reConnect();
        }

        myClientOutputStream.write(msg);
    }
    
    /**
     * Returns a formatted message to the client.
     * We try to re-connect with the peer on the other end if possible.
     * @param sipMessage Message to send.
     * @throws IOException If there is an error sending the message
     */
    public void sendMessage(Message sipMessage) throws IOException {
        if (sipMessage == null) {
            throw new NullPointerException("null arg!");
        }

        byte[] msg = sipMessage.encodeAsBytes();
        long time = System.currentTimeMillis();

        sendMessage(msg);

        if (ServerLog.needsLogging(ServerLog.TRACE_MESSAGES)) {
            logMessage(sipMessage, peerAddress, peerPort, time);
        }
    }
    
    /**
     * Sends a message to a specified address.
     * @param message Pre-formatted message to send.
     * @param receiverAddress Address to send it to.
     * @param receiverPort Receiver port.
     * @throws IOException If there is a problem connecting or sending.
     */
    public void sendMessage(byte message[], String receiverAddress,
            int receiverPort)
            throws IOException, IllegalArgumentException {
        if (message == null || receiverAddress == null) {
            throw new IllegalArgumentException("Null argument");
        }

        if (!receiverAddress.equals(peerAddress) || peerPort != receiverPort) {
            throw new IOException("This channel is bound to different peer");
        }
        
        sendMessage(message);
    }
    
    /**
     * Exception processor for exceptions detected from the application.
     * @param ex The exception that was generated.
     */
    public void handleException(SIPServerException ex) {
        // Return a parse error message to the client on the other end
        // if he is still alive.
        int rc = ex.getRC();
        String msgString = ex.getMessage();
        if (rc != 0) {
            // Do we have a valid Return code ? --
            // in this case format the message.
            Request request =
                    (Request) ex.getSIPMessage();
            Response response =
                    request.createResponse(rc);
            try {
                sendMessage(response);
            } catch (IOException ioex) {
                if (Logging.REPORT_LEVEL <= Logging.ERROR) {
                    Logging.report(Logging.ERROR, LogChannels.LC_JSR180,
                        ioex.getMessage());
                }
            }
        } else {
            // Otherwise, message is already formatted --
            // just return it
            try {
                sendMessage(msgString.getBytes());
            } catch (IOException ioex) {
                if (Logging.REPORT_LEVEL <= Logging.ERROR) {
                    Logging.report(Logging.ERROR, LogChannels.LC_JSR180,
                        ioex.getMessage());
                }
            }
        }
    }
    
    
    /**
     * Exception processor for exceptions detected from the parser. (This
     * is invoked by the parser when an error is detected).
     * @param ex parse exception detected by the parser.
     * @param sipMessage message that incurred the error.
     * @param hdrClass header parsing class
     * @param header header that caused the error.
     * @param message descriptive text for exception
     * @throws ParseException Thrown if we want to reject the message.
     */
    public void handleException(ParseException ex,
            Message sipMessage,
            Class hdrClass,
            String header,
            String message)
            throws ParseException {
                
        if (Logging.REPORT_LEVEL <= Logging.ERROR) {
                Logging.report(Logging.ERROR, LogChannels.LC_JSR180,
                    ex.getMessage());
        }
        
        // Log the bad message for later reference.
        if (hdrClass .equals(FromHeader.clazz) ||
                hdrClass.equals(ToHeader.clazz) ||
                hdrClass.equals(CSeqHeader.clazz) ||
                hdrClass.equals(ViaHeader.clazz) ||
                hdrClass.equals(CallIdHeader.clazz) ||
                hdrClass.equals(RequestLine.clazz)||
                hdrClass.equals(StatusLine.clazz)) {
            stack.logBadMessage(message);
            throw ex;
        } else {
            sipMessage.addUnparsed(header);
        }
        
    }
    
    
    /**
     * Gets invoked by the parser as a callback on successful message
     * parsing (i.e. no parser errors).
     * @param sipMessage Mesage to process (this calls the application
     * for processing the message).
     */
    public void processMessage(Message sipMessage) {
        if (sipMessage.getFromHeader() == null ||
                sipMessage.getTo() == null ||
                sipMessage.getCallId() == null ||
                sipMessage.getCSeqHeader() == null ||
                sipMessage.getViaHeaders() == null) {
            String badmsg = sipMessage.encode();
            
            if (Logging.REPORT_LEVEL <= Logging.ERROR) {
                ServerLog.logMessage("bad message " + badmsg);
                ServerLog.logMessage(">>> Dropped Bad Msg");
            }
            
            stack.logBadMessage(badmsg);
            
            return;
        }
        
        ViaList viaList = sipMessage.getViaHeaders();

        // For a request
        // first via header tells where the message is coming from.
        // For response, this has already been recorded in the outgoing
        // message.
        long receptionTime = 0;
        
        if (sipMessage instanceof Request) {
            ViaHeader v = (ViaHeader)viaList.first();
            if (v.hasPort()) {
                peerPort = v.getPort();
            } else {
                peerPort = SIPConstants.DEFAULT_NONTLS_PORT;
            }
            peerProtocol = v.getTransport();

            // System.out.println("receiver address = " + receiverAddress);
        
            // Foreach part of the request header, fetch it and process it
            receptionTime = System.currentTimeMillis();

            // This is a request - process the request.
            Request sipRequest = (Request)sipMessage;
            
            // Create a new sever side request processor for this
            // message and let it handle the rest.
            
            if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
                Logging.report(Logging.INFORMATION, LogChannels.LC_JSR180,
                    "----Processing Message---");
            }
            
            SIPServerRequestInterface sipServerRequest =
                    stack.newSIPServerRequest(sipRequest, this);
            try {
                sipServerRequest.processRequest(sipRequest, this);
                
                ServerLog.logMessage(sipMessage,
                        sipRequest.getViaHost() + ":" +
                        sipRequest.getViaPort(),
                        stack.getHostAddress() + ":" +
                        stack.getPort(this.getTransport()),
                        false,
                        receptionTime);
                
            } catch (SIPServerException ex) {
                ServerLog.logMessage(sipMessage,
                        sipRequest.getViaHost() + ":"
                        + sipRequest.getViaPort(),
                        stack.getHostAddress() + ":" +
                        stack.getPort(this.getTransport()),
                        ex.getMessage(), false, receptionTime);
                handleException(ex);
            }
        } else {
            // This is a response message - process it.
            Response sipResponse = (Response)sipMessage;
            SIPServerResponseInterface sipServerResponse =
                    stack.newSIPServerResponse(sipResponse, this);
                    
            if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
                Logging.report(Logging.INFORMATION, LogChannels.LC_JSR180,
                    "got a response interface " + sipServerResponse);
            }
            
            try {
                sipServerResponse.processResponse(sipResponse, this);
            } catch (SIPServerException ex) {
                // An error occured processing the message -- just log it.
                ServerLog.logMessage(sipMessage,
                        getPeerAddress().toString() + ":"
                        + getPeerPort(),
                        stack.getHostAddress() + ":" +
                        stack.getPort(this.getTransport()),
                        ex.getMessage(), false, receptionTime);
                // Ignore errors while processing responses??
            }
        }
    }
    
    /**
     * This gets invoked when thread.start is called from the constructor.
     * Implements a message loop - reading the tcp connection and processing
     * messages until we are done or the other end has closed.
     */
    private void start() {
        // Create a pipelined message parser to read and parse
        // messages that we write out to him.
        myParser = new PipelinedMsgParser(this, myClientInputStream);
        // Enable the flag to parse message content.
        // Start running the parser thread.
        myParser.processInput();
        
    }

    /**
     * Increments the number of user of this channel.
     */
    synchronized protected void incrementUseCounter() {
        if (!exitFlag) {
            useCounter++;
        }
    }
    
    
    /**
     * Called when the pipelined parser cannot read input because the
     * other end closed the connection.
     */
    public void handleIOException() {
        if (!exitFlag) {
            shutDownConnection();
        }
    }

    /** 
     * Closes all input and output stream and socket.
     */
    synchronized private void shutDownConnection() {
        try {
            if (null != myClientInputStream) {
                myClientInputStream.close();
            }
        } catch (IOException ioe) {
            // intentionally ignored
        }

        try {
            if (null != myClientOutputStream) {
                myClientOutputStream.close();            }

        } catch (IOException ioe) {
            // intentionally ignored
        }

        try {
            if (null != mySock) {
                mySock.close();
            }
        } catch (IOException ioe) {
            // intentionally ignored
        }
        // null mySock indicates closed connection
        // see sendMessage()
        mySock = null;
    }
    
    /**
     * Reconnect to the server.
     * @exception IOException if the connection can not be established
     */
    private void reConnect() throws IOException {
        shutDownConnection();
        makeSocket(peerAddress, peerPort);
        myParser = new PipelinedMsgParser(this, myClientInputStream);
        myParser.processInput();
    }
    
    /**
     * Equals predicate.
     * @param other is the other object to compare ourselves to for equals
     * @return true if object matches
     */
    public boolean equals(Object other) {
        if (!this.getClass().equals(other.getClass()))
            return false;
        else {
            TCPMessageChannel that = (TCPMessageChannel)other;
            if (this.mySock != that.mySock)
                return false;
            else return true;
        }
    }
    
    /**
     * Gets an identifying key. This key is used to cache the connection
     * and re-use it if necessary.
     * @return the identifying key
     */
    public String getKey() {
        return getKey(peerAddress, peerPort, SIPConstants.TRANSPORT_TCP);
    }
    
    /**
     * Gets the host to assign to outgoing messages.
     * @return the host to assign to the via header.
     */
    public String getViaHost() {
        return myAddress;
    }
    
    /**
     * Gets the port for outgoing messages sent from the channel.
     * @return the port to assign to the via header.
     */
    public int getViaPort() {
        return myPort;
    }
    
    /**
     * Gets the port of the peer to whom we are sending messages.
     * @return the peer port.
     */
    public int getPeerPort() {
        return peerPort;
    }
    
    /**
     * TCP Is not a secure protocol.
     * @return always false
     */
    public boolean isSecure() {
        return false;
    }
}