/* The Java RTP/I library, Version 0.1 alpha.
*
* This library provides the functionality of RTP/I as it is specified in
* Internet Draft draft-mauve-rtpi-00.txt.
*
* Copyright (C) 2000 Martin Mauve
* University of Mannheim / Germany
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* Martin Mauve
*
* e-mail:
* mauve@informatik.uni-mannheim.de
*
* paper mail:
* Martin Mauve
* University of Mannheim
* Lehrstuhl Praktische Informatik IV
* L15, 16
* 68131 Mannheim
* Germany
*/
package rtpi.reliability.ORTA;
import java.net.InetAddress;
import java.io.IOException;
import java.util.LinkedList;
import java.util.ListIterator;
import java.util.Random;
import java.util.Hashtable;
import java.util.Iterator;
import rtpi.reliability.Reliable;
import rtpi.reliability.ReliableRecipient;
import rtpi.reliability.QosNotSupportedException;
import rtpi.IllegalValueException;
import rtpi.packets.RtpiDataPacket;
import rtpi.util.SyncQueue;
import rtpi.transport.TransportRecipient;
import rtpi.transport.orta.ORTATransport;
import rtpi.transport.TransportPacket;
/**
* This is a dummy implementation of the Reliableinterface. It uses UDP
* multicast and does not provide any other QoS than UNRELIABLE. It does also not
* provide redundant transmission. This reliability service uses the IPMCTransport
* class as a foundation for transmitting and receiving packets.
*
* @author Martin Mauve
*/
public final class ORTA extends Thread implements Reliable, TransportRecipient {
private SyncQueue messages = new SyncQueue();
private ReliableRecipient recipient = null;
private ORTATransport transport = null;
private Hashtable senders = new Hashtable();
private int bufferTime=0;
private long lastTimePacketsChecked=System.currentTimeMillis();
/**
* Create a new instance of UdpUnreliable.
*
* @param taddr The multicast address that is used to transmit and receive the packets.
* @param tport The port.
* @param rate The maximum datrate at which packets may be sent. This rate is
* enforced by traffic shaping in the underlying IPMCTransport. The
* datarate is given in bits/second.
* @param bufferT The maximum time a received packet will be buffered before it is
* discarded. Buffering is required when some packets have arrived
* for an ADU while others are still missing. A reliability service
* hands only complete ADUs to its recipient.
*/
public ORTA(InetAddress taddr, int tport, int lport, int rate, int bufferT) {
transport = new ORTATransport(taddr, tport, lport, rate);
bufferTime = bufferT;
transport.registerTransportRecipient(this);
start();
}
/**
* This returns the size of the payload that can
* be transported in a single packet. This payload
* size INCLUDES all RTP/I and reliability headers.
*/
public int getTransportPayloadSize() {
return transport.getTransportPayloadSize();
}
/**
* This returns the size of the combined RTP/I and
* reliability headers.
*/
public int getCombinedHeaderSize() {
return RtpiDataPacket.HEADER_SIZE;
}
public void run() {
Message message=null;
while (true) {
try {
message=(Message) messages.get();
} catch (Exception exe) {
System.err.println("UdpUnreliable: could not get next message from message queue");
continue;
}
switch(message.opCode) {
case Message.QUIT:
execQuit(); // clean up!
return; // return to leave the run method!
case Message.CONNECTION_CLOSED:
execConnectionClosed();
break;
case Message.RECEIVE_TRANSPORT_PACKET:
execReceiveTransportPacket((TransportPacket) message.args);
break;
case Message.TRANSMIT_RTPI_ADU:
execTransmitRtpiAdu((LinkedList) message.args);
break;
case Message.JOIN_GROUP:
execJoinGroup();
break;
case Message.LEAVE_GROUP:
execLeaveGroup();
break;
case Message.REGISTER_RECIPIENT:
execRegisterRecipient((ReliableRecipient) message.args);
break;
case Message.SET_RATE:
execSetRate(((Integer) message.args).intValue());
break;
default:
System.err.println("UdpUnreliable: illegal message opcode: "+message.opCode+message.args);
break;
}
}
}
/**
* This terminates the reliability service.
*/
public void quit(){
messages.put(new Message(Message.QUIT, null));
}
private void execQuit() {
return;
}
/**
* This method is used to transmit an ADU. The ADU may consist of
* multiple RTP/I packets if the ADU is too large to fit into a
* single (network) packet. The RTP/I packets MUST form a single
* valid RTP/I ADU.
* As an option the ADU may be transmitted with a certain amount of
* redundancy that is to be transmitted in a certain time interval
* to compensate for packet loss. It is important to notice that a
* given implementation may not support redundancy.
*
* @param packet A linked list of the RTP/I packets that belong to a single ADU.
* @param redundancy The amount of redundancy that should be added to this ADU.
* @param transmissionInterval The interval during which redundancy information
* may be transmitted.
*/
public void transmitRtpiAdu(LinkedList packets, float redundancy, int transmissionInterval)
throws QosNotSupportedException, IllegalValueException {
if (redundancy!=0) {
throw new QosNotSupportedException("UdpUnreliable: redundancy not supported by UdpUnreliable");
}
ListIterator iter = packets.listIterator();
if (!iter.hasNext()) {
throw new IllegalValueException("UdpUnreliable: empty RTP/I ADU");
}
RtpiDataPacket packet = (RtpiDataPacket) iter.next();
if (packet.getFragmentCount()!=0) {
throw new IllegalValueException("UdpUnreliable: fragment count of the first packet is not 0");
}
int sequenceNumber=packet.getSequenceNumber();
int fragmentCount=packet.getFragmentCount();
int type=packet.getType();
int payloadType=packet.getPayloadType();
int priority=packet.getPriority();
int participantID=packet.getParticipantID();
long subcomponentID=packet.getSubcomponentID();
long timestamp=packet.getTimestamp();
while (iter.hasNext()) {
packet=(RtpiDataPacket) iter.next();
if (sequenceNumber!=packet.getSequenceNumber()) {
throw new IllegalValueException("UdpUnreliable: packets of the same RTP/I ADU have different sequence numbers");
}
if (((++fragmentCount)%0x0000FFFF)!=packet.getFragmentCount()) {
throw new IllegalValueException("UdpUnreliable: packets of the same RTP/I ADU have non contiguous fragment counts");
}
if (type!=packet.getType()) {
throw new IllegalValueException("UdpUnreliable: packets of the same RTP/I ADU have different types");
}
if (payloadType!=packet.getPayloadType()) {
throw new IllegalValueException("UdpUnreliable: packets of the same RTP/I ADU have different payload types");
}
if (priority!=packet.getPriority()) {
throw new IllegalValueException("UdpUnreliable: packets of the same RTP/I ADU have different priorities");
}
if (participantID!=packet.getParticipantID()) {
throw new IllegalValueException("UdpUnreliable: packets of the same RTP/I ADU have different paticipant IDs");
}
if (subcomponentID!=packet.getSubcomponentID()) {
throw new IllegalValueException("UdpUnreliable: packets of the same RTP/I ADU have different sub-component IDs");
}
if (timestamp!=packet.getTimestamp()) {
throw new IllegalValueException("UdpUnreliable: packets of the same RTP/I ADU have different timestamps");
}
}
if (packet.getEnd()!=1) {
throw new IllegalValueException("UdpUnreliable: end bit not set in last packet of RTP/I ADU");
}
messages.put(new Message(Message.TRANSMIT_RTPI_ADU, packets));
}
private void execTransmitRtpiAdu(LinkedList packets) {
ListIterator iter = packets.listIterator();
RtpiDataPacket packet=null;
while (iter.hasNext()) {
packet=(RtpiDataPacket) iter.next();
packet.setReliabilityType(0);
packet.setExtension(0);
try {
packet.flush();
} catch (Exception ex) {
System.err.println("UdpUnreliable: error while flushing RtpiDataPacket "+ex);
}
TransportPacket tpacket = new TransportPacket(packet.getLength()+
RtpiDataPacket.HEADER_SIZE, packet.getPacketData());
transport.sendTransportPacket(tpacket);
}
}
/**
* This method is called when a transport packet has been received. This
* packet is checked and if it is the last packet of an ADU, then the ADU
* is handed to the recipient of this reliability service.
*
* @param packet The transport packet that has been received.
*/
public void receiveTransportPacket(TransportPacket packet){
messages.put(new Message(Message.RECEIVE_TRANSPORT_PACKET, packet));
}
private void execReceiveTransportPacket(TransportPacket tpacket) {
int position=0;
do {
RtpiDataPacket packet = new RtpiDataPacket(tpacket.getData(), position);
try {
packet.parse();
} catch (Exception ex) {
System.err.println("UdpUnreliable: Exception while parsing RTP/I packet "+ex+" - packet ignored!");
return;
}
if (packet.getReliabilityType()!=0) {
System.err.println("UdpUnreliable: Illegal reliability type in receive RtpiDataPacket "+
packet.getReliabilityType()+" - packet ignored!");
return;
}
if (packet.getType()!=RtpiDataPacket.EVENT &&
packet.getType()!=RtpiDataPacket.STATE &&
packet.getType()!=RtpiDataPacket.DELTA_STATE &&
packet.getType()!=RtpiDataPacket.STATE_QUERY) {
System.err.println("UdpUnreliable: Illegal ADU type in receive RtpiDataPacket "+
packet.getType()+" - packet ignored!");
return;
}
if (packet.getFragmentCount()==0 && packet.getEnd()==1) {
LinkedList packets = new LinkedList();
packets.add(packet);
recipient.receiveRtpiAdu(packets);
} else {
Hashtable subcomponents = (Hashtable) senders.get(new Integer(packet.getParticipantID()));
if (subcomponents==null) {
subcomponents = new Hashtable();
senders.put (new Integer(packet.getParticipantID()),subcomponents);
Hashtable aduTypes=new Hashtable();
subcomponents.put(new Long(packet.getSubcomponentID()),aduTypes);
Hashtable adus = new Hashtable();
aduTypes.put(new Long(packet.getType()), adus);
RtpiDataPacketBuffer buffer = new RtpiDataPacketBuffer(packet);
adus.put(new Integer(packet.getSequenceNumber()), buffer);
} else {
Hashtable aduTypes=(Hashtable) subcomponents.get(new Long(packet.getSubcomponentID()));
if (aduTypes==null) {
aduTypes = new Hashtable();
subcomponents.put(new Long(packet.getSubcomponentID()), aduTypes);
Hashtable adus = new Hashtable();
aduTypes.put(new Long(packet.getType()), adus);
RtpiDataPacketBuffer buffer = new RtpiDataPacketBuffer(packet);
adus.put(new Integer(packet.getSequenceNumber()), buffer);
} else {
Hashtable adus = (Hashtable) aduTypes.get(new Long (packet.getType()));
if (adus==null) {
adus = new Hashtable();
aduTypes.put(new Long(packet.getType()), adus);
RtpiDataPacketBuffer buffer = new RtpiDataPacketBuffer(packet);
adus.put(new Integer(packet.getSequenceNumber()), buffer);
} else {
RtpiDataPacketBuffer buffer = (RtpiDataPacketBuffer) adus.get(new Integer(packet.getSequenceNumber()));
if (buffer==null) {
buffer = new RtpiDataPacketBuffer(packet);
adus.put(new Integer(packet.getSequenceNumber()), buffer);
} else {
buffer.put(packet);
if (buffer.isComplete()) {
recipient.receiveRtpiAdu(buffer.getPackets());
adus.remove(new Integer(packet.getSequenceNumber()));
if (adus.isEmpty()) { // we remove all empty hashtables this may be inefficient
// an optimization could be to keep the tables and delete
// them periodically or when the become to memory consumant!
aduTypes.remove(new Integer(packet.getType()));
if (aduTypes.isEmpty()) {
subcomponents.remove(new Long(packet.getSubcomponentID()));
if (subcomponents.isEmpty()) {
senders.remove(new Integer(packet.getParticipantID()));
}
}
}
}
}
}
}
}
}
position+=packet.getLength()+RtpiDataPacket.HEADER_SIZE;
position+=(4-position%4)%4;
} while(position < tpacket.getLength());
removeOutdatedPackets();
}
void removeOutdatedPackets() {
if (System.currentTimeMillis()-lastTimePacketsChecked<bufferTime) {
return;
}
Hashtable subcomponents=null;
Hashtable aduTypes=null;
Hashtable adus=null;
RtpiDataPacketBuffer buf=null;
for (Iterator senderIt=senders.values().iterator();senderIt.hasNext();) {
subcomponents = (Hashtable) senderIt.next();
for (Iterator subcomponentIt=subcomponents.values().iterator();subcomponentIt.hasNext();) {
aduTypes = (Hashtable) subcomponentIt.next();
for (Iterator aduTypeIt=aduTypes.values().iterator();aduTypeIt.hasNext();) {
adus = (Hashtable) aduTypeIt.next();
for (Iterator aduIt=adus.values().iterator();aduIt.hasNext();) {
buf = (RtpiDataPacketBuffer) aduIt.next();
if (buf.timedOut(bufferTime)) {
System.err.println("UdpUnreliable ADU timed out!!!!!");
aduIt.remove();
if (adus.isEmpty()) {
aduTypeIt.remove();
if (aduTypes.isEmpty()) {
subcomponentIt.remove();
if (subcomponents.isEmpty()) {
senderIt.remove();
}
}
}
}
}
}
}
}
lastTimePacketsChecked=System.currentTimeMillis();
}
/**
* This is called if the undelying IPMCTransport has suffered a fatal error.
*/
public void connectionClosed() {
messages.put(new Message(Message.CONNECTION_CLOSED,null));
}
private void execConnectionClosed() {
quit();
if (recipient!=null) {
recipient.connectionClosed();
}
}
/**
* This joins the multicast group. This MUST be called before any ADUs can be
* transmitted or received.
*/
public void joinGroup() {
//System.out.println("reliable joinGroup");
messages.put(new Message(Message.JOIN_GROUP, null));
}
private void execJoinGroup() {
//System.out.println("reliable execjoinGroup");
try {
transport.joinGroup();
} catch (Exception ex) {
System.err.println("UdpUnreliable: "+ex);
}
}
/**
* This leaves the multicast group. After this method has been called no ADUs may
* be transmitted and no further ADUs can be received.
* However, already received ADUs may still be delivered.
*/
public void leaveGroup() {
messages.put(new Message(Message.LEAVE_GROUP, null));
}
private void execLeaveGroup() {
transport.leaveGroup();
}
/**
* This registers the recipient of the ADUs and
* loss notifications.
*
* @param rec The reipient.
*/
public void registerRecipient(ReliableRecipient rec) {
messages.put(new Message(Message.REGISTER_RECIPIENT, rec));
}
private void execRegisterRecipient(ReliableRecipient rec) {
recipient = rec;
transport.registerTransportRecipient(this);
}
/**
* This sets the maximum datarate of the underlying IPMCTransport.
*
* @param The new rate in bits/second.
*/
public void setRate(int rate) {
messages.put(new Message(Message.SET_RATE, new Integer(rate)));
}
private void execSetRate(int rate) {
transport.setRate(rate);
}
/**
* This method is used to set the QoS for a given subcomponent and
* RTP/I ADU type pair. It is important to notice that a given implementation
* of the Reliable interface may only support certain QoS settings.
*
* @param subcomponentID The ID of the subcomponent.
* @param type The RTP/I ADU type.
* @param qos The desired QoS.
*/
public void setInterest(long subcomponentID, int type, int qos) throws QosNotSupportedException {
if (qos!=Reliable.NONE) {
throw new QosNotSupportedException("UdpUnreliable: only NONE qos supported");
}
}
/**
* This method is used to set a default QoS for all subcomponent and
* RTP/I ADU type pairs to which no other QoS has been specified.
* It is important to notice that a given implementation
* of the Reliable interface may only support certain QoS settings.
*
* @param subcomponentID The ID of the subcomponent.
* @param type The RTP/I ADU type.
* @param qos The desired QoS.
*/
public void setDefaultInterest(int type, int qos) throws QosNotSupportedException {
if (qos!=Reliable.NONE) {
throw new QosNotSupportedException("UdpUnreliable: only NONE qos supported");
}
}
}