/*
* 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;
}
}
|