package org.limewire.rudp; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.net.DatagramPacket; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.NoRouteToHostException; import java.net.SocketAddress; import java.net.SocketException; import java.net.UnknownHostException; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Iterator; import java.util.Random; import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.ExecutorService; import org.limewire.concurrent.ExecutorsHelper; import org.limewire.io.NetworkUtils; import org.limewire.rudp.messages.RUDPMessageFactory; import org.limewire.rudp.messages.MessageFormatException; import org.limewire.rudp.messages.RUDPMessage; import org.limewire.service.ErrorService; /** * 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. */ @SuppressWarnings( { "unchecked", "cast" } ) public class UDPServiceStub implements UDPService { /** The queue that processes packets to send. */ private final ExecutorService SEND_QUEUE; /** The UDPMultiplexor to forward msgs to. */ private volatile UDPMultiplexor multiplexor; /** The factory to create messages from. */ private final RUDPMessageFactory factory; /** The active receivers of messages */ private final ArrayList RECEIVER_LIST = new ArrayList(); /** Constructs a new <tt>UDPServiceStub</tt>. */ public UDPServiceStub(RUDPMessageFactory factory) { SEND_QUEUE = ExecutorsHelper.newProcessingQueue("UDPServiceStub-Sender"); this.factory = factory; } public void setUDPMultiplexor(UDPMultiplexor plexor) { this.multiplexor = plexor; } /** 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 UDPMultiplexor _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 = multiplexor; _random = new Random(); _timer = new Timer(true); } public int getPort() { return _toPort; } public void add(DatagramPacket dp) { // drop message if flaky if (_pctFlaky > 0) { int num = _random.nextInt(100); if (num < _pctFlaky) return; } if (_delay > 0) { _timer.schedule(new MessageWrapper(dp, _delay, this),_delay); } else { receive(new MessageWrapper(dp, _delay, this)); } } public void stop() { _timer.cancel(); } private void receive(MessageWrapper msg) { final DatagramPacket datagram = msg._dp; // swap the port to the sender from the receiver datagram.setPort(_fromPort); if(!NetworkUtils.isValidAddress(datagram.getAddress())) return; if(!NetworkUtils.isValidPort(datagram.getPort())) return; byte[] data = datagram.getData(); ByteBuffer buffer = ByteBuffer.wrap(data); try { RUDPMessage message = factory.createMessage(buffer); if(message != null) _router.routeMessage(message, (InetSocketAddress)datagram.getSocketAddress()); } catch (MessageFormatException e) { return; } } } private static 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; } @Override public String toString() { if (_dp != null) return _dp.toString(); else return "null"; } @Override 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"); } } public void send(RUDPMessage msg, SocketAddress host) { if (msg == null) throw new IllegalArgumentException("Null Message"); if (!NetworkUtils.isValidSocketAddress(host)) throw new IllegalArgumentException("invalid host: " + host); ByteArrayOutputStream baos = new ByteArrayOutputStream(); try { msg.write(baos); } catch(IOException e) { ErrorService.error(e); return; } byte[] data = baos.toByteArray(); try { DatagramPacket dg = new DatagramPacket(data, data.length, host); SEND_QUEUE.execute(new Sender(dg)); } catch(SocketException se) { throw new RuntimeException("unexpected exception", se); } } // 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; Sender(DatagramPacket dp) { _dp = dp; } public void run() { // send away // ------ try { internalSend(_dp); } catch(NoRouteToHostException nrthe) { // oh well, if we can't find that host, ignore it ... } } } /** */ public boolean isListening() { return true; } /** * Overrides Object.toString to give more informative information * about the class. */ @Override public String toString() { return "UDPServerStub\r\n loopback"; } public InetAddress getStableListeningAddress() { try { return InetAddress.getByName("127.0.0.1"); } catch(UnknownHostException uhe) { return null; } } public int getStableListeningPort() { return 0; } public boolean isNATTraversalCapable() { return true; } }