/*
* 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.
*/
/*
*/
package gov.nist.siplite.stack;
import java.io.*;
import java.util.*;
import javax.microedition.io.*;
import com.sun.midp.log.LogChannels;
import com.sun.midp.log.Logging;
import gov.nist.core.*;
import gov.nist.siplite.SIPConstants;
import gov.nist.siplite.header.*;
import gov.nist.siplite.message.*;
import gov.nist.siplite.parser.*;
/**
* This is the UDP Message handler that gets created when a UDP message
* needs to be processed. The message is processed by creating a String
* Message parser and invoking it on the message read from the UPD socket.
* The parsed structure is handed off via a SIP stack request for further
* processing. This stack structure isolates the message handling logic
* from the mechanics of sending and recieving messages (which could
* be either udp or tcp.
* Acknowledgement:
* Kim Kirby of Keyvoice suggested that duplicate checking should be added
* to the stack.
*
*@see gov.nist.siplite.parser.StringMsgParser
*@see gov.nist.siplite.stack.SIPServerRequestInterface
*@since v1.0
*
*/
public class UDPMessageChannel
extends MessageChannel
implements ParseExceptionListener, Runnable {
/** SIP Stack structure for this channel. */
protected SIPMessageStack stack;
/** Sender address (from getPeerName()) */
private String myAddress;
/** Local port number. */
private int myPort;
/** Remote host address. */
private String peerAddress;
/** Remote port number. */
private int peerPort;
/** Remote connection transport. */
private String peerProtocol;
/* Inbound message transport. */
// private String receiverProtocol;
/** Raw message contents. */
private byte[] msgBytes;
/** Input data length. */
private int packetLength;
/** Datagram connection for transport. */
private DatagramConnection datagramConnection;
/** Current serevr request message. */
private Datagram incomingPacket;
/** Pool of duplicate messages. */
protected static Hashtable duplicates;
/* Current SIP message. */
// private Message sipMessage;
/** Timestamp of current inbound message. */
private long receptionTime;
/**
* Constructor - takes a datagram packet and a stack structure
* Extracts the address of the other from the datagram packet and
* stashes away the pointer to the passed stack structure.
* @param packet is the UDP Packet that contains the request.
* @param sipStack stack is the shared SipStack structure
* @param messageProcessor the UDP input message channel
*/
public UDPMessageChannel(Datagram packet,
SIPMessageStack sipStack,
MessageProcessor messageProcessor) {
incomingPacket = packet;
stack = sipStack;
// format: "protocol://address:port"
String address = packet.getAddress();
try {
int firstColon = address.indexOf("://");
int secondColon = address.indexOf(":", firstColon+1);
peerAddress = address.substring(firstColon+3, secondColon);
if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
Logging.report(Logging.INFORMATION, LogChannels.LC_JSR180,
"DEBUG, UDPMessageChannel, UDPMessageChannel(), " +
"sender address: " + peerAddress);
}
String portString = address.substring(secondColon+1);
this.peerPort = Integer.parseInt(portString);
if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
Logging.report(Logging.INFORMATION, LogChannels.LC_JSR180,
"DEBUG, UDPMessageChannel, UDPMessageChannel(), " +
"sender port: " + peerPort);
}
} catch (NumberFormatException e) {
if (Logging.REPORT_LEVEL <= Logging.ERROR) {
Logging.report(Logging.ERROR, LogChannels.LC_JSR180,
"DEBUG, UDPMessageChannel, UDPMessageChannel(), " +
"exception raised: " + e);
}
this.peerPort = -1;
}
packetLength = packet.getLength();
if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
Logging.report(Logging.INFORMATION, LogChannels.LC_JSR180,
"DEBUG, UDPMessageChannel, UDPMessageChannel(), " +
"packet length: " + packetLength);
}
byte[] bytes = packet.getData();
msgBytes = new byte[packetLength];
for (int i = 0; i < packetLength; i++) {
msgBytes[i] = bytes[i];
}
String msgString = new String(msgBytes, 0, packetLength);
if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
Logging.report(Logging.INFORMATION, LogChannels.LC_JSR180,
"DEBUG, UDPMessageChannel, UDPMessageChannel(), " +
" message received: " + msgString.trim());
}
this.messageProcessor = messageProcessor;
// Supports only the single threaded model.
this.run();
}
/**
* Constructor. We create one of these when we send out a message.
* @param targetAddr internet address of the place where we want to send
* messages.
* @param port target port (where we want to send the message).
* @param sipStack our SIP Stack.
* @param processor inbound message processor
*/
public UDPMessageChannel(String targetAddr, int port,
SIPMessageStack sipStack,
UDPMessageProcessor processor) {
if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
Logging.report(Logging.INFORMATION, LogChannels.LC_JSR180,
"DEBUG, UDPMessageChannel, UDPMessageChannel(), " +
"Creating message channel on "
+ targetAddr
+ "/" + port);
}
this.peerPort = port;
this.peerAddress = targetAddr;
this.myPort = port;
this.myAddress = processor.getSIPStack().getHostAddress();
this.messageProcessor = processor;
this.peerProtocol = "UDP";
stack = sipStack;
}
/**
* Runs method specified by runnnable.
*/
public void run() {
Message sipMessage = null;
// Create a new string message parser to parse the list of messages.
// This is a huge performance hit -- need to optimize by pre-create
// parser when one is needed....
StringMsgParser myParser = new StringMsgParser();
try {
this.receptionTime = System.currentTimeMillis();
sipMessage = myParser.parseSIPMessage(msgBytes);
} catch (ParseException ex) {
if (Logging.REPORT_LEVEL <= Logging.ERROR) {
Logging.report(Logging.ERROR, LogChannels.LC_JSR180,
"DEBUG, UDPMessageChannel, run(), " +
"Rejecting message during parsing " +
"An exception has been raised: " + ex);
}
// ex.printStackTrace();
return;
}
if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
Logging.report(Logging.INFORMATION, LogChannels.LC_JSR180,
"DEBUG, UDPMEssageChannel, run(), sipMessage parsed: " +
sipMessage.encode());
}
if (sipMessage == null) {
return;
}
ViaList viaList = sipMessage.getViaHeaders();
// For a request first via header tells where the message
// is coming from.
// For response, just get the port from the packet.
// format: address:port
String address = incomingPacket.getAddress();
try {
int firstColon = address.indexOf("://");
int secondColon = address.indexOf(":", firstColon+1);
peerAddress = address.substring(firstColon+3,
secondColon);
if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
Logging.report(Logging.INFORMATION, LogChannels.LC_JSR180,
"DEBUG, UDPMessageChannel, run(), sender address: " +
peerAddress);
}
String senderPortString = address.substring(secondColon+1);
peerPort = Integer.parseInt(senderPortString);
if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
Logging.report(Logging.INFORMATION, LogChannels.LC_JSR180,
"DEBUG, UDPMessageChannel, run(), sender port: " +
peerPort);
}
} catch (NumberFormatException e) {
if (Logging.REPORT_LEVEL <= Logging.ERROR) {
Logging.report(Logging.ERROR, LogChannels.LC_JSR180,
"DEBUG, UDPMessageChannel, run(), " +
"exception raised: " + e);
}
// e.printStackTrace();
peerPort = -1;
}
// Check for the required headers.
if (sipMessage.getFromHeader() == null ||
// sipMessage.getFromHeader().getTag() == null ||
sipMessage.getTo() == null ||
sipMessage.getCallId() == null ||
sipMessage.getCSeqHeader() == null ||
sipMessage.getViaHeaders() == null) {
String badmsg = new String(msgBytes);
if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
Logging.report(Logging.INFORMATION, LogChannels.LC_JSR180,
"bad message " + badmsg);
Logging.report(Logging.INFORMATION, LogChannels.LC_JSR180,
">>> Dropped Bad Msg " +
"FromHeader = "
+ sipMessage.getFromHeader() +
"ToHeader = " + sipMessage.getTo() +
"CallId = " + sipMessage.getCallId() +
"CSeqHeader = "
+ sipMessage.getCSeqHeader() +
"Via = " + sipMessage.getViaHeaders());
}
stack.logBadMessage(badmsg);
return;
}
// For a request first via header tells where the message
// is coming from.
// For response, just get the port from the packet.
if (sipMessage instanceof Request) {
ViaHeader v = (ViaHeader)viaList.first();
if (v.hasPort()) {
if (sipMessage instanceof Request) {
this.peerPort = v.getPort();
}
} else this.peerPort = SIPConstants.DEFAULT_NONTLS_PORT;
this.peerProtocol = v.getTransport();
Request sipRequest = (Request) sipMessage;
// This is a request - process it.
SIPServerRequestInterface sipServerRequest =
stack.newSIPServerRequest(sipRequest, this);
// Drop it if there is no request returned
if (sipServerRequest == null) {
if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
Logging.report(Logging.INFORMATION,
LogChannels.LC_JSR180,
"Null request interface returned");
}
return;
}
int stPort = -1;
try {
stPort = stack.getPort(this.getTransport());
if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
Logging.report(Logging.INFORMATION,
LogChannels.LC_JSR180,
"About to process " +
sipRequest.getFirstLine() + "/" +
sipServerRequest);
}
sipServerRequest.processRequest(sipRequest, this);
if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
Logging.report(Logging.INFORMATION,
LogChannels.LC_JSR180,
"Done processing " +
sipRequest.getFirstLine() + "/" +
sipServerRequest);
}
// So far so good -- we will commit this message if
// all processing is OK.
if (ServerLog.needsLogging(ServerLog.TRACE_MESSAGES)) {
if (sipServerRequest.getProcessingInfo() == null) {
ServerLog.logMessage(sipMessage,
sipRequest.getViaHost() + ":" +
sipRequest.getViaPort(),
stack.getHostAddress() + ":" +
stPort,
false,
new Long(receptionTime)
.toString());
} else {
ServerLog.logMessage(sipMessage,
sipRequest.getViaHost() + ":" +
sipRequest.getViaPort(),
stack.getHostAddress() + ":" +
stPort,
sipServerRequest
.getProcessingInfo(),
false, new Long(receptionTime)
.toString());
}
}
} catch (Exception ex) {
if (ex instanceof SIPServerException) {
handleException((SIPServerException)ex);
}
}
} else {
// Handle a SIP Reply message.
Response sipResponse = (Response) sipMessage;
SIPServerResponseInterface sipServerResponse =
stack.newSIPServerResponse(sipResponse, this);
try {
if (sipServerResponse != null) {
sipServerResponse.processResponse(sipResponse, this);
// Normal processing of message.
} else {
if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
Logging.report(Logging.INFORMATION,
LogChannels.LC_JSR180,
"null sipServerResponse!");
}
}
} catch (Exception ex) {
if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
Logging.report(Logging.INFORMATION,
LogChannels.LC_JSR180,
">>>>>Message = " + new String(msgBytes));
}
if (ServerLog.needsLogging
(ServerLog.TRACE_MESSAGES)) {
this.logResponse(sipResponse,
receptionTime,
ex.getMessage()+ "-- Dropped!");
}
ServerLog.logException(ex);
}
}
}
/**
* Returns a reply from a pre-constructed reply. This sends the message
* back to the entity who caused us to create this channel in the
* first place.
* @param sipMessage Message string to send.
* @throws IOException If there is a problem with sending the
* message.
*/
public void sendMessage(Message sipMessage) throws IOException {
byte[] msg = sipMessage.encodeAsBytes();
sendMessage(msg, peerAddress, peerPort, peerProtocol);
}
/**
* Sends a message to a specified receiver address.
* @param msg message string to send.
* @param receiverAddress Address of the place to send it to.
* @param receiverPort the port to send it to.
* @throws IOException If there is trouble sending this message.
*/
protected void sendMessage(byte[] msg,
String receiverAddress,
int receiverPort)
throws IOException {
// msg += "\r\n\r\n";
// Via is not included in the request so silently drop the reply.
if (receiverPort == -1) {
if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
Logging.report(Logging.INFORMATION, LogChannels.LC_JSR180,
"DEBUG, UDPMessageChannel, sendMessage(), " +
"The message is not sent: the receiverPort=-1");
}
throw new IOException("Receiver port not set ");
}
try {
/*
* Original NIST method for opening the datagram 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 :
*
* //format: "datagram://address:port"
* String url = "datagram://"+peerAddress+":"+peerPort;
* this.datagramConnection =
* (DatagramConnection)Connector.open(url);
*
*/
String url = "//"+peerAddress+":"+peerPort;
com.sun.midp.io.j2me.datagram.Protocol conn =
new com.sun.midp.io.j2me.datagram.Protocol();
this.datagramConnection =
(DatagramConnection)conn.openPrim(
stack.getSecurityToken(), url,
Connector.WRITE, true);
// timeouts are ignored for datagrams
Datagram reply = datagramConnection.newDatagram(msg, msg.length);
datagramConnection.send(reply);
String msgString = new String(msg, 0, msg.length);
if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
Logging.report(Logging.INFORMATION, LogChannels.LC_JSR180,
"DEBUG, UDPMessageChannel, sendMessage(), " +
"Datagram sent on datagram:" + url + ", message sent:\n" +
msgString.trim());
}
datagramConnection.close();
} catch (IOException ex) {
if (Logging.REPORT_LEVEL <= Logging.ERROR) {
Logging.report(Logging.ERROR, LogChannels.LC_JSR180, "toto");
Logging.report(Logging.ERROR, LogChannels.LC_JSR180,
"DEBUG, UDPMessageChannel, sendMessage(), " +
"The message is not sent: exception raised: " + ex);
}
}
}
/**
* Sends a message to a specified receiver address.
* @param msg message string to send.
* @param receiverAddress Address of the place to send it to.
* @param receiverPort the port to send it to.
* @param receiverProtocol protocol to use to send.
* @throws IOException If there is trouble sending this message.
*/
protected void sendMessage(byte[] msg,
String receiverAddress,
int receiverPort,
String receiverProtocol)
throws IOException {
// msg += "\r\n\r\n";
// Via is not included in the request so silently drop the reply.
if (receiverPort == -1) {
if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
Logging.report(Logging.INFORMATION, LogChannels.LC_JSR180,
"DEBUG, UDPMessageChannel, sendMessage(), " +
"The message is not sent: " +
"the receiverPort=-1");
}
throw new IOException("Receiver port not set ");
}
if (Utils.compareToIgnoreCase(receiverProtocol, "UDP") == 0) {
try {
/*
* Original NIST method for opening the datagram 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 :
*
* String url = "datagram://"
* + receiverAddress + ":" + receiverPort;
* // format: "datagram://address:port"
* datagramConnection =
* (DatagramConnection)Connector.open(url);
*
*/
String url = "//" + receiverAddress + ":" + receiverPort;
com.sun.midp.io.j2me.datagram.Protocol conn =
new com.sun.midp.io.j2me.datagram.Protocol();
datagramConnection =
(DatagramConnection)conn.openPrim(
stack.getSecurityToken(), url,
Connector.WRITE, true);
// timeouts are ignored
Datagram datagram = datagramConnection
.newDatagram(msg, msg.length);
datagramConnection.send(datagram);
String msgString = new String(msg, 0, msg.length);
if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
Logging.report(Logging.INFORMATION, LogChannels.LC_JSR180,
"DEBUG, UDPMessageChannel, sendMessage(), " +
" Datagram sent on datagram: " +
url + ", message sent:\n" + msgString.trim());
}
datagramConnection.close();
} catch (IOException ex) {
if (Logging.REPORT_LEVEL <= Logging.ERROR) {
Logging.report(Logging.ERROR, LogChannels.LC_JSR180,
"DEBUG, UDPMessageChannel, sendMessage(), " +
"The message is not sent: exception raised:" + ex);
}
}
} else {
// TCP is not supported
throw new IOException("Unsupported protocol");
}
}
/**
* Gets the stack pointer.
* @return The sip stack for this channel.
*/
public SIPMessageStack getSIPStack() {
return stack;
}
/**
* Returns a transport string.
* @return the string "udp" in this case.
*/
public String getTransport() {
return "udp";
}
/**
* Gets the stack address for the stack that received this message.
* @return The stack address for our stack.
*/
public String getHost() {
return stack.stackAddress;
}
/**
* Gets the port.
* @return Our port (on which we are getting datagram
* packets).
*/
public int getPort() {
return this.myPort;
}
/**
* Handles an exception - construct a sip reply and send it back to the
* caller.
* @param ex The exception thrown at us by our
* application.
*/
public void handleException(SIPServerException ex) {
// Return a parse error message to the client on the other end
// if he is still alive.
// ex.printStackTrace();
int rc = ex.getRC();
Request request = (Request) ex.getSIPMessage();
Response response;
String msgString = ex.getMessage();
if (rc != 0) {
response = request.createResponse(rc, msgString);
// messageFormatter.newResponse(rc,request,msgString);
try {
sendMessage(response);
} catch (IOException ioex) {
ServerLog.logException(ioex);
}
} else {
// Assume that the message has already been formatted.
try {
sendMessage(msgString);
} catch (IOException ioex) {
ServerLog.logException(ioex);
}
}
}
/**
* Compares two UDP Message channels for equality.
* @param other The other message channel with which to compare oursleves.
* @return true if the objects match
*/
public boolean equals(Object other) {
if (other == null)
return false;
boolean retval;
if (!this.getClass().equals(other.getClass())) {
retval = false;
} else {
UDPMessageChannel that = (UDPMessageChannel) other;
retval = this.peerAddress.equals(that.peerAddress);
}
return retval;
}
/**
* Gets the key.
* @return the key
*/
public String getKey() {
return myAddress + ":" + myPort + "/UDP";
}
/**
* Sends the message.
* @param msg the message to be processed.
* @exception IOException if the send can not be processed
*/
private void sendMessage(String msg)
throws IOException {
sendMessage(msg.getBytes(), peerAddress,
peerPort, peerProtocol);
}
/**
* Sends the message.
* @param msg the message to be processed.
* @exception IOException if the send can not be processed
*/
private void sendMessage(byte[] msg)
throws IOException {
sendMessage(msg, peerAddress, peerPort, peerProtocol);
}
/**
* Gets the logical originator of the message (from the top via header).
* @return topmost via header sentby field
*/
public String getViaHost() {
return this.myAddress;
}
/**
* Gets the logical port of the message orginator (from the top via hdr).
* @return the via port from the topmost via header.
*/
public int getViaPort() {
return this.myPort;
}
/**
* Checks if the transport is reliable
* @return always false
*/
public boolean isReliable() {
return false;
}
/**
* Closes the message channel.
*/
public void close() {
}
/**
* Gets the peer address of the machine that sent us this message.
* @return a string contianing the ip address or host name of the sender
* of the message.
*/
public String getPeerAddress() {
return this.peerAddress;
}
/**
* Gets the sender port (the port of the other end that sent me
* the message).
* @return the remote port number
*/
public int getPeerPort() {
return this.peerPort;
}
/**
* Returns true if this is a secure channel.
* @return always false
*/
public boolean isSecure() {
return false;
}
/**
* This gets called from the parser when a parse error is generated.
* The handler is supposed to introspect on the error class and
* header name to handle the error appropriately. The error can
* be handled by :
* <ul>
* <li>1. Re-throwing an exception and aborting the parse.
* <li>2. Ignoring the header (attach the unparseable header to
* the Message being parsed).
* <li>3. Re-Parsing the bad header and adding it to the sipMessage
* </ul>
*
* @param ex parse exception being processed.
* @param sipMessage sip message being processed.
* @param hdrClass header parsing class
* @param headerText header/RL/SL text being parsed.
* @param messageText message where this header was detected.
*/
public void handleException(ParseException ex, Message sipMessage,
Class hdrClass,
String headerText,
String messageText) 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(messageText);
throw ex;
} else {
sipMessage.addUnparsed(headerText);
}
}
}
|