package com.limegroup.gnutella.stubs;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.NoRouteToHostException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.Random;
import java.util.Timer;
import java.util.TimerTask;
import com.limegroup.gnutella.ErrorCallback;
import com.limegroup.gnutella.ErrorService;
import com.limegroup.gnutella.GUID;
import com.limegroup.gnutella.MessageRouter;
import com.limegroup.gnutella.RouterService;
import com.limegroup.gnutella.UDPService;
import com.limegroup.gnutella.messages.BadPacketException;
import com.limegroup.gnutella.messages.Message;
import com.limegroup.gnutella.util.IpPort;
import com.limegroup.gnutella.util.ManagedThread;
import com.limegroup.gnutella.util.NetworkUtils;
import com.limegroup.gnutella.util.ProcessingQueue;
/**
* This class allows the creation of a UDPService instances with
* controlled delay times and loss rates for testing UDP communication.
* It routes outgoing messages to itself after the delay time.
*/
public final class UDPServiceStub extends UDPService {
/**
* The queue that processes packets to send.
*/
private final ProcessingQueue SEND_QUEUE;
/**
* Constant for the single <tt>UDPService</tt> instance.
*/
private final static UDPService INSTANCE1 = new UDPServiceStub();
/**
* Constant for the size of UDP messages to accept -- dependent upon
* IP-layer fragmentation.
*/
private final int BUFFER_SIZE = 1024 * 32;
/**
* Instance accessor.
*/
public static UDPService instance() {
return INSTANCE1;
}
/**
* Stub Instance accessor.
*/
public static UDPServiceStub stubInstance() {
return (UDPServiceStub) INSTANCE1;
}
/**
* Constructs a new <tt>UDPAcceptor</tt>.
*/
private UDPServiceStub() {
SEND_QUEUE = new ProcessingQueue("UDPServiceStub-Sender");
}
/**
*/
public GUID getConnectBackGUID() {
return null;
}
/**
*/
public GUID getSolicitedGUID() {
return null;
}
/**
*/
DatagramSocket newListeningSocket(int port) throws IOException {
throw new IOException("no one should be calling me :"+port);
}
/**
* Does nothing
*/
void setListeningSocket(DatagramSocket datagramSocket) {
}
/** Default wait time */
private final long LONG_TIME = 24 * 60 * 60 * 1000;
/** The active receivers of messages */
private final ArrayList RECEIVER_LIST = new ArrayList();
/**
* Create receiver for each simulated incoming connection
*/
public void addReceiver(int toPort, int fromPort, int delay, int pctFlaky) {
Receiver r = new Receiver(toPort, fromPort, delay, pctFlaky);
synchronized(RECEIVER_LIST) {
RECEIVER_LIST.add(r);
}
}
/**
* Clean up the receiver list
*/
public void clearReceivers() {
synchronized(RECEIVER_LIST) {
for (Iterator iter = RECEIVER_LIST.iterator();iter.hasNext();) {
Receiver rec = (Receiver)iter.next();
iter.remove();
rec.stop();
}
}
}
private class Receiver {
private final int _toPort;
private final int _fromPort;
private final int _delay;
private final int _pctFlaky;
private MessageRouter _router;
private Random _random;
private Timer _timer;
Receiver(int toPort, int fromPort, int delay, int pctFlaky) {
_toPort = toPort;
_fromPort = fromPort;
_delay = delay;
_pctFlaky = pctFlaky;
_router = RouterService.getMessageRouter();
_random = new Random();
_timer = new Timer(true);
}
public int getPort() {
return _toPort;
}
public void add(DatagramPacket dp) {
// drop message if flaky
int num = _random.nextInt(100);
if (num < _pctFlaky)
return;
_timer.schedule(new MessageWrapper(dp, _delay, this),_delay);
}
public void stop() {
_timer.cancel();
}
private void receive(MessageWrapper msg) {
DatagramPacket datagram = msg._dp;
// swap the port to the sender from the receiver
datagram.setPort(_fromPort);
// ----------------------------*
// process packet....
// *----------------------------
if(!NetworkUtils.isValidAddress(datagram.getAddress()))
return;
if(!NetworkUtils.isValidPort(datagram.getPort()))
return;
byte[] data = datagram.getData();
try {
// we do things the old way temporarily
InputStream in = new ByteArrayInputStream(data);
Message message = Message.read(in, Message.N_UDP);
if(message == null) return;
_router.handleUDPMessage(message, (InetSocketAddress)datagram.getSocketAddress());
} catch (IOException e) {
return;
} catch (BadPacketException e) {
return;
}
// ----------------------------*
}
}
private class MessageWrapper extends TimerTask {
public final DatagramPacket _dp;
public final long _scheduledTime;
public final int _delay;
private final Receiver _receiver;
MessageWrapper(DatagramPacket dp, int delay, Receiver receiver) {
_dp = dp;
_scheduledTime = System.currentTimeMillis() + (long) delay;
_delay = delay;
_receiver = receiver;
}
public int compareTo(Object o) {
MessageWrapper other = (MessageWrapper) o;
if (_scheduledTime < other._scheduledTime)
return -1;
else if (_scheduledTime > other._scheduledTime)
return 1;
return 0;
}
public String toString() {
if (_dp != null)
return _dp.toString();
else
return "null";
}
public void run() {
_receiver.receive(this);
}
}
/**
* This code replaces the socket.send. It internally routes the message
* to a receiver. This allows multiple local receivers
* with different ip/ports to be simulated.
*/
public void internalSend(DatagramPacket dp) throws NoRouteToHostException {
Receiver r;
synchronized(RECEIVER_LIST) {
for (int i = 0; i < RECEIVER_LIST.size(); i++) {
r = (Receiver) RECEIVER_LIST.get(i);
if ( r.getPort() == dp.getPort() ) {
r.add(dp);
return;
}
}
throw new NoRouteToHostException("I don't see this ip/port");
}
}
/**
* Sends the specified <tt>Message</tt> to the specified host.
*
* @param msg the <tt>Message</tt> to send
* @param host the host to send the message to
*/
public void send(Message msg, IpPort host) {
send(msg, host.getInetAddress(), host.getPort());
}
/**
* Sends the <tt>Message</tt> via UDP to the port and IP address specified.
* This method should not be called if the client is not GUESS enabled.
*
* If sending fails for reasons such as a BindException,
* NoRouteToHostException or specific IOExceptions such as
* "No buffer space available", this message is silently dropped.
*
* @param msg the <tt>Message</tt> to send
* @param ip the <tt>InetAddress</tt> to send to
* @param port the port to send to
*/
public void send(Message msg, InetAddress ip, int port)
throws IllegalArgumentException {
send(msg, ip, port, ErrorService.getErrorCallback());
}
/**
* Sends the <tt>Message</tt> via UDP to the port and IP address specified.
* This method should not be called if the client is not GUESS enabled.
*
* If sending fails for reasons such as a BindException,
* NoRouteToHostException or specific IOExceptions such as
* "No buffer space available", this message is silently dropped.
*
* @param msg the <tt>Message</tt> to send
* @param ip the <tt>InetAddress</tt> to send to
* @param port the port to send to
* @param err an <tt>ErrorCallback<tt> if you want to be notified errors
* @throws IllegalArgumentException if msg, ip, or err is null.
*/
public void send(Message msg, InetAddress ip, int port, ErrorCallback err)
throws IllegalArgumentException {
if (err == null)
throw new IllegalArgumentException("Null ErrorCallback");
if (msg == null)
throw new IllegalArgumentException("Null Message");
if (ip == null)
throw new IllegalArgumentException("Null InetAddress");
if (!NetworkUtils.isValidPort(port))
throw new IllegalArgumentException("Invalid Port: " + port);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
msg.write(baos);
} catch(IOException e) {
// this should not happen -- we should always be able to write
// to this output stream in memory
ErrorService.error(e);
// can't send the hit, so return
return;
}
byte[] data = baos.toByteArray();
DatagramPacket dg = new DatagramPacket(data, data.length, ip, port);
SEND_QUEUE.add(new Sender(dg, err));
}
// the runnable that actually sends the UDP packets. didn't wany any
// potential blocking in send to slow down the receive thread. also allows
// received packets to be handled much more quickly
private class Sender implements Runnable {
private final DatagramPacket _dp;
private final ErrorCallback _err;
Sender(DatagramPacket dp, ErrorCallback err) {
_dp = dp;
_err = err;
}
public void run() {
// send away
// ------
try {
internalSend(_dp);
} catch(NoRouteToHostException nrthe) {
// oh well, if we can't find that host, ignore it ...
} catch(IOException ioe) {
String errString = "ip/port: " +
_dp.getAddress() + ":" +
_dp.getPort();
_err.error(ioe, errString);
}
}
}
/**
*/
public boolean isGUESSCapable() {
return false;
}
/**
* Returns whether or not this node is capable of receiving UNSOLICITED
* UDP packets.
*/
public boolean canReceiveUnsolicited() {
return false;
}
/**
* Returns whether or not this node is capable of receiving SOLICITED
* UDP packets.
*/
public boolean canReceiveSolicited() {
return true;
}
/**
*/
public void setReceiveSolicited(boolean value) {
}
/**
*/
public boolean isListening() {
return true;
}
/**
* Overrides Object.toString to give more informative information
* about the class.
*/
public String toString() {
return "UDPServerStub\r\n loopback";
}
}