/*
*
*
* Copyright 1990-2007 Sun Microsystems, Inc. All Rights Reserved.
* 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 com.sun.tck.wma.mms;
import com.sun.tck.wma.PropLoader;
import com.sun.tck.wma.BinaryMessage;
import com.sun.tck.wma.Message;
import com.sun.tck.wma.MessageConnection;
import com.sun.tck.wma.MessageTransportConstants;
import com.sun.tck.wma.TextMessage;
import com.sun.tck.wma.MultipartMessage;
import com.sun.tck.wma.sms.BinaryObject;
import com.sun.tck.wma.sms.MessagePacket;
import com.sun.tck.wma.sms.TextObject;
import com.sun.midp.io.j2me.sms.TextEncoder;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Properties;
import java.util.Vector;
import java.io.IOException;
/**
* MMS message connection handler.
*/
public class MMSMessageConnection extends PropLoader
implements MessageConnection {
/** Machine name - the parsed target address from the URL. */
protected String host = null;
/** Application ID - the parsed ID from the URL. */
protected String appID = null;
/** Datagram host for sending/receiving. */
protected String clientHost;
/** Datagram transport for sending. */
protected int portOut;
/** Datagram transport for receiving. */
protected int portIn;
/** Phone number of the message sender. */
protected String phoneNumber;
/** The application ID to which replies should be sent. */
protected String replyToAppID;
/** Datagram server connection. */
DatagramSocket dgc;
/** Datagram buffer. */
byte [] buf = new byte[MessageTransportConstants.DATAGRAM_PACKET_SIZE];
/** Datagram envelope for sending or receiving messages. */
DatagramPacket mess =
new DatagramPacket(buf, MessageTransportConstants.DATAGRAM_PACKET_SIZE);
/**
* The "open" flag indicates when the connection is open. When the
* connection is closed, subsequent operations throw an exception.
*/
protected boolean open;
/**
* Construct a new MMS message connection handler.
*/
public MMSMessageConnection() {
/*
* Configurable parameters for low level transport.
* e.g.: mms://+5551234:33300 maps to datagram://129.148.70.80:123
*/
// Default values:
clientHost = "localhost";
portOut = 33300;
portIn = 33301;
phoneNumber = "+5551234";
replyToAppID = "com.sun.mms.MMSTest";
/*
* Check for overrides in the "connections.prop" configuration file.
*/
clientHost = getProp("localhost", "JSR_205_DATAGRAM_HOST",
"connections.prop", "DatagramHost");
portOut = getIntProp(33300, "JSR_205_MMS_OUT_PORT",
"connections.prop", "MMSDatagramPortOut");
portIn = getIntProp(33301, "JSR_205_MMS_PORT",
"connections.prop", "MMSDatagramPortIn");
// Sender (This connection)'s phone number.
phoneNumber = getProp("+5551234", "JSR_205_PHONE_NUMBER",
"connections.prop", "PhoneNumber");
// Sender (This connection)'s application ID.
replyToAppID = getProp("com.sun.mms.MMSTest",
"JSR_205_MMS_REPLY_TO_ID", "connections.prop", "MMSReplyToAppID");
}
/**
* Opens a connection. This method is called from
* <code>Connector.open()</code> method to obtain the destination address
* given in the <code>name</code> parameter.
* <p>
* The format for the <code>name</code> string for this method is:
* <code>mms://<em>phone_number</em>:<em>port</em></code> where the
* <em>phone_number:</em> is optional. If the <em>phone_number</em>
* parameter is present, the connection is being opened in "client" mode.
* This means that messages can be sent. If the parameter is absent, the
* connection is being opened in "server" mode. This means that messages
* can be sent and received.
* <p>
* The connection is opened to any of the following, low-level transport
* mechanisms:
* <ul>
* <li>A datagram Short Message Peer to Peer (SMPP) to a service center.
* <li>A <code>comm</code> connection to a phone device with AT-commands.
* <li>a native MMS stack.
* </ul>
*
* @param name the target of the connection
* @return this connection
* @throws IOException if the connection is closed or unavailable.
*/
public MessageConnection openPrim(String name) throws IOException {
// Invoke the implementation-specific handler.
return openPrimInternal(name);
}
/**
* Opens a connection. This method is called from
* <code>Connector.open()</code> method to obtain the destination address
* given in the <code>name</code> parameter.
* <p>
* The format for the <code>name</code> string for this method is:
* <code>mms://<em>phone_number</em>:<em>appID</em></code> where the
* <em>phone_number:</em> is optional. If the <em>phone_number</em>
* parameter is present, the connection is being opened in "client" mode.
* This means that messages can be sent. If the parameter is absent, the
* connection is being opened in "server" mode. This means that messages
* can be sent and received.
* <p>
* The connection is opened to any of the following, low-level transport
* mechanisms:
* <ul>
* <li>A datagram Short Message Peer to Peer (SMPP) to a service center.
* <li>A <code>comm</code> connection to a phone device with AT-commands.
* <li>a native MMS stack.
* </ul>
*
* @param name the target of the connection
* @return this connection
* @throws IOException if the connection is closed or unavailable.
*/
public MessageConnection openPrimInternal(String name) throws IOException {
/*
* The general form of a MMS address is <code>mms://host:appID</code>.
* The form at this point should now be <code>//host:appID</code>
*/
if ((name == null) || (name.length() <= 2) ||
(name.charAt(0) != '/') || (name.charAt(1) != '/')) {
throw new IllegalArgumentException("Missing protocol separator.");
}
String fullAddress = "mms:" + name;
MMSAddress parsedAddress = MMSAddress.getParsedMMSAddress(fullAddress);
if (parsedAddress == null) {
throw new IllegalArgumentException("Invalid MMS connection URL");
}
// Pick up the phone number to which the message will be sent.
host = null;
if (parsedAddress.address != null) {
host = new String(parsedAddress.address);
}
// Pick up the application ID to which the message will be sent.
appID = null;
if (parsedAddress.appId != null) {
appID = new String(parsedAddress.appId);
}
// Open the inbound server datagram connection. The appID is not used.
try {
dgc = open0(appID);
} catch (IOException ioe) {
throw new IOException("Unable to open MMS connection.");
}
open = true;
// Return this open connection.
return this;
}
/**
* Open the transport-layer-level connection.
*
* @param appID The application ID (Unused in this implementation).
* @return The open datagram socket (Transport mechanism).
*/
private DatagramSocket open0(String appID) throws IOException {
return new DatagramSocket(portIn);
}
/**
* Constructs a new message object of <code>MULTIPART_MESSAGE</code> type.
* <p>
* If this method is called in a sending mode, a new
* <code>Message</code> object is requested from the connection. Example:
* <p>
* <code>Message msg = conn.newMessage(MULTIPART_MESSAGE);</code>
* <p>
* The created <code>Message</code> does not have the destination
* address set. It must be set by the application before
* the message is sent.
* <p>
* If it is called in receiving mode, the <code>Message</code> object does
* have its address set. The application can act on the object to extract
* the address and message data.
* <p>
* <!-- The <code>type</code> parameter indicates the number of bytes
* that should be
* allocated for the message. No restrictions are placed on the application
* for the value of <code>size</code>.
* A value of <code>null</code> is permitted and creates a
* <code>Message</code> object
* with a 0-length message. -->
*
* @param type <code>MULTIPART_MESSAGE</code> is the only type permitted.
* @return A new <code>Message</code> object.
*/
public Message newMessage(String type) {
return newMessage(type, null);
}
/**
* Constructs a new <code>MULTIPART_MESSAGE</code> message object with the
* desired a destination address.
* <p>
* <p>
* The destination address <code>addr</code> has the following format:
* <code>sms://<em>phone_number</em>:<em>port</em></code>.
*
* @param type <code>MULTIPART_MESSAGE</code> is the only type permitted.
* @param addr The destination address of the message.
* @return A new <code>Message</code> object.
*/
public Message newMessage(String type, String addr) {
// Return the appropriate type of sub-message.
if (!(type == MessageConnection.MULTIPART_MESSAGE)) {
throw new IllegalArgumentException("Message type not supported.");
}
return new MultipartObject(addr);
}
/**
* Sends an MMS message.
*
* @param msg The MMS <code>Message</code> to be sent.
* @exception ConnectionNotFoundException if the address is invalid or if
* no address is found in the message.
* @exception IOException if an I/O error occurs.
*/
public void send(Message msg) throws IOException {
if (msg == null) {
throw new NullPointerException("Null message.");
}
if (!(msg instanceof MultipartMessage)) {
throw new IllegalArgumentException("Unsupported message type.");
}
// Create the multi-part object that will be used below.
MultipartObject mpo = (MultipartObject)msg;
/*
* Check for valid MMS URL connection format. Note that the addresses in
* the lists are not used. This is simply a check to make sure that the
* addresses can be placed into the multipart object's header when the
* header and message are bundled within MultipartObject.
*
* Process each MMS address in the to:, cc: and bcc: address lists. An
* MMS address assumes this form: address:appID
*
* Each MMS address is parsed to extract the address and application ID
* data. Those parts are then checked for validity.
*
* The loop starts by processing all addresses in the to: field (if
* any), followed by the addresses in the cc: list, then the bcc: list.
*
*/
Vector allAddresses = new Vector();
String[] addresses = mpo.getAddresses("to");
int currIndex = 0;
boolean checkedTo = false;
boolean checkedCC = false;
// The application ID extracted from an address in the address list.
String parsedAppID = null;
while (true) {
/*
* If no addresses were in the to: field, or if all addresses have
* been extracted and checked from the current address list
* (Initially, the to: list), then continue to process the cc: list
* (if any), next, followed by the bcc: list.
*/
if (addresses == null || currIndex >= addresses.length) {
if (!checkedTo) {
// The to: list has been processed. Process cc: list, next.
checkedTo = true;
addresses = mpo.getAddresses("cc");
currIndex = 0;
continue;
} else if (!checkedCC) {
// The cc: list has been processed. Process bcc: list, next.
checkedCC = true;
addresses = mpo.getAddresses("bcc");
currIndex = 0;
continue;
} else {
/*
* The to:, cc: and bcc: lists have now been checked, so
* bail out of the while() loop.
*/
break;
}
}
/*
* Pick up the next address and add it to the list. Then, parse it
* to extract the address and application ID parts.
*/
String addr = addresses[currIndex++];
allAddresses.addElement(addr);
MMSAddress parsedAddress = MMSAddress.getParsedMMSAddress(addr);
if (parsedAddress == null ||
parsedAddress.type == MMSAddress.INVALID_ADDRESS ||
parsedAddress.type == MMSAddress.APP_ID) {
throw new IllegalArgumentException(
"Invalid MMS address: " + addr);
}
if (parsedAppID == null) {
parsedAppID = parsedAddress.appId;
} else if (parsedAddress.appId != null &&
!parsedAppID.equals(parsedAddress.appId)) {
throw new IllegalArgumentException("Only one Application-ID "
+ "can be specified per message");
}
} // while
if (allAddresses.size() == 0) {
throw new IllegalArgumentException("No to, cc, or bcc addresses");
}
// Construct the target address protocol string.
String messageAppID = mpo.getApplicationID();
String toAddress = "mms://:";
if (messageAppID != null) {
toAddress = toAddress + messageAppID;
}
/*
* If no application ID was supplied, use the ID that was used to open
* the connection as the default ID.
*/
if (messageAppID != null && host == null) {
mpo.setReplyToApplicationID(replyToAppID);
}
// Preserve the original "from" address.
String oldFromAddress = ((MultipartObject)mpo).getAddress();
// Establish the return address.
String fromAddress = "mms://" + phoneNumber;
if (replyToAppID != null) {
fromAddress = fromAddress + ":" + replyToAppID;
}
mpo.setFromAddress(fromAddress);
// Send the message and reply information.
byte[] header = mpo.getHeaderAsByteArray();
byte[] body = mpo.getBodyAsByteArray();
int status =
send0(dgc, toAddress, fromAddress, messageAppID, replyToAppID,
header, body);
// Restore the "from" address.
mpo.setFromAddress(oldFromAddress);
}
/**
* Sends the MMS message (Transport-layer-specific code).
*
* @param ds The transport-layer-specific datagram socket connection.
* @param toAddress The recipient's MMS address.
* @param fromAddress The sender's MMS address.
* @param appID The application ID to be matched against incoming messages.
* @param replyToAppID The ID of the application that processes replies.
* @param mmsHeader The message header context.
* @param mmsBody The message body context.
*
* @return Unused status. Always <code>0</code>.
*/
private int send0(DatagramSocket ds, String toAddress, String fromAddress,
String appID, String replyToAppID, byte[] mmsHeader, byte[] mmsBody)
throws IOException {
// Combine the header and body parts.
int headerLen = mmsHeader.length;
int bodyLen = mmsBody.length;
byte[] msg = new byte[headerLen + bodyLen];
System.arraycopy(mmsHeader, 0, msg, 0, headerLen);
System.arraycopy(mmsBody, 0, msg, headerLen, bodyLen);
// Use MessagePacket to stream low-endian-formatted values.
MessagePacket stream = new MessagePacket();
stream.putString(fromAddress);
stream.putString(appID);
stream.putString(replyToAppID);
stream.putInt(msg.length);
stream.putBytes(msg);
byte[] buffer = stream.getData();
/*
* Write the message as a series of datagram packets.
*/
int PACKET_MAX_SIZE =
// overhead = three shorts + one int.
MessageTransportConstants.DATAGRAM_PACKET_SIZE - 10;
short packetNumber;
short totalPackets;
int offset = 0; // offset into buffer.
int count = 0;
// The total number of bytes to send.
int length = buffer.length;
// The total number of packets to send.
totalPackets =
(short)((buffer.length + PACKET_MAX_SIZE - 1) / PACKET_MAX_SIZE);
// Fragment the data buffer into multiple segments.
for (packetNumber = 1; packetNumber <= totalPackets;
packetNumber++) {
// Datagram envelope for sending messages.
mess = new DatagramPacket(buf,
MessageTransportConstants.DATAGRAM_PACKET_SIZE);
mess.setAddress(InetAddress.getByName(clientHost));
mess.setPort(portOut);
// Compute the number of bytes that can be sent in this packet.
count = length;
if (count > PACKET_MAX_SIZE) {
count = PACKET_MAX_SIZE;
}
MessagePacket mmsPacket = new MessagePacket();
mmsPacket.putShort(packetNumber);
mmsPacket.putShort(totalPackets);
mmsPacket.putShort((short)count);
mmsPacket.putInt(length);
// Now set the payload.
byte[] buf = new byte[count];
System.arraycopy(buffer, offset, buf, 0, count);
mmsPacket.putBytes(buf);
byte[] buff = mmsPacket.getData();
mess.setData(buff, 0, buff.length);
dgc.send(mess);
// Move the pointer past the bytes and send the next packet.
offset += count;
length -= count;
}
return 0;
}
/**
* Receives the bytes that have been sent over the connection,
* constructs a <code>Message</code> object, and returns it.
* <p>
* If there are no <code>Message</code>s waiting on the connection,
* this method will block until a message
* is received, or the <code>MessageConnection</code> is closed.
*
* @return A <code>Message</code> object
* @throws IOException if an I/O error occurs.
*/
public synchronized Message receive() throws IOException {
// Call the implementation-specific code to get a message byte[]
byte[] buffer = receive0(dgc);
// Decode the buffer contents to extract the proper parameters.
MessagePacket mmsPacket = new MessagePacket(buffer);
String fromAddress = mmsPacket.getString();
String appID = mmsPacket.getString();
String replyToAppID = mmsPacket.getString();
int msgLen = mmsPacket.getInt();
byte[] message = mmsPacket.getBytes(msgLen);
// Convert the data into a multipart object and return that message.
// MultipartObject mpo = MultipartObject.createFromByteArray(message);
Message msg = MultipartObject.createFromByteArray(message);
// IMPL_NOTE: FIX
// IMPL_NOTE: msg.setTimeStamp(tm.getTimeStamp());
((MultipartObject)msg).setFromAddress(fromAddress);
((MultipartObject)msg).fixupReceivedMessageAddresses(
fromAddress, phoneNumber);
return msg;
}
/**
* Internal implementation of the message-receive code. As packets are
* received, they are passed off to a routine that assembles the data
* into a large byte array.
*
* @param ds The open datagram socket.
* @return A <code>Message</code> object.
* @throws IOException if an I/O error occurs.
*/
private byte[] receive0(DatagramSocket ds) throws IOException {
while (true) {
// Wait for a datagram to arrive.
ds.receive(mess);
// Assemble the datagram contents.
if (assembleFrags(mess) == true) {
/*
* When all datagrams have been received, so break out to allow
* the data to be processed.
*/
break;
}
}
return mmsBuffer;
}
/**
* Closes the connection. Reset the connection-is-open flag so methods can
* be checked to throws an appropriate exception for operations on a closed
* connection.
*
* @exception IOException if an I/O error occurs.
*/
public void close() throws IOException {
if (open) {
dgc.close();
dgc = null;
open = false;
}
}
/** The current packet number. */
private int packetNumber = 1;
/** The offset into the assembly buffer. */
private int mmsOffset = 0;
/** The assembly buffer. */
private byte[] mmsBuffer = null;
/**
* Analyze the special datagram payload, extract the data and append the
* data to the main byte buffer.
* <p>
* This particular implementation assumes that the network has short hops
* and that datagrams arrive in the order in which they were received.
* Whenever datagrams arrive out of order, the assembly of packets is
* cancelled. This simple rule avoids the use of more complex packet
* assembly algorithms, allowing datagrams to be used instead of sockets.
* <p>
* The packet header has this format:
* <p><pre>
* +--------+--------------+-----------------+-----------------+
* | Packet | Total number | Total number of | Total number of |
* | number | of packets | bytes in packet | bytes in stream |
* +--------+--------------+-----------------+-----------------+
* 2 bytes 2 bytes 2 bytes 4 bytes</pre>
* <p>
* Data are stored in low-endian format. The packet data immediately
* follow the header.
*
* @param packet The special datagram payload.
* @return <code>true</code> when the last packet has been received and
* processed. <code>false</code> when there are more packets that are
* expected to be processed.
*/
private boolean assembleFrags(DatagramPacket packet) {
// Extract the packet header contents and data.
MessagePacket mmsPacket = new MessagePacket(packet.getData());
short packNum = mmsPacket.getShort();
short totalPackets = mmsPacket.getShort();
short count = mmsPacket.getShort();
int totalLen = mmsPacket.getInt();
byte[] data = mmsPacket.getBytes(count);
if (packNum != packetNumber) {
/*
* Mismatch in packet number. Packets have
* either arrived out of order or a packet
* has been dropped.
*/
System.err.println("ERROR: Datagram packets have been dropped.");
if (mmsBuffer != null) {
mmsBuffer = null;
}
return false;
}
/*
* If this is the first packet, initialize the total size of the
* assembly buffer and reset the writing offset.
*/
if (packNum == 1) {
mmsBuffer = new byte[totalLen];
mmsOffset = 0;
}
/*
* Append "count" bytes from the packet to the end of the assembly
* buffer. The data immediately follow the 10-byte header.
*/
for (int i = 0; i < count; i++) {
mmsBuffer[mmsOffset++] = data[i];
}
/*
* If the last packet has been received, reset the expected packet
* number and the writing offset. The assembly buffer cannot be reset,
* as it contains the latest assembled data.
*/
if (packNum == totalPackets) {
packetNumber = 1;
mmsOffset = 0;
// Indicate that all data have been assembled.
return true;
}
// Bump the expected packet number
packetNumber++;
// Indicate that more packets are expected.
return false;
}
}
|