package edu.washington.cs.oneswarm.f2f.friends;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.MulticastSocket;
import java.net.NetworkInterface;
import java.net.SocketAddress;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import java.util.logging.Logger;
import org.gudy.azureus2.core3.config.COConfigurationManager;
import org.gudy.azureus2.core3.config.ParameterListener;
import org.gudy.azureus2.core3.util.Debug;
import edu.washington.cs.oneswarm.f2f.Friend;
import edu.washington.cs.oneswarm.f2f.FriendInvitation;
import edu.washington.cs.oneswarm.f2f.OSF2FMain;
import edu.washington.cs.oneswarm.f2f.dht.DHTConnector;
import edu.washington.cs.oneswarm.f2f.invitations.InvitationManager;
import edu.washington.cs.oneswarm.f2f.xml.OSF2FXMLBeanReader;
public class LanFriendFinder {
private static final String OSF2F_LAN_FRIEND_FINDER = "OSF2F.LanFriendFinder";
private final static Logger logger = Logger.getLogger(LanFriendFinder.class.getName());
private final static String MULTICAST_GROUP = "239.255.17.17";
private final static int FRIEND_FINDER_PORT = 44811;
private static final int REANNOUNCE_PERIOD = 5 * 1000;
private static final int TRY_ALL_INTERFACES_EVERY_X_MINUTES = 5;
private long lastAllInterfaceSend = 0;
// remove friends from discovered set if we miss 3 packets
private static final int FRIEND_EXPIRED_TIME = 3 * REANNOUNCE_PERIOD + 1000;
private static final int MAX_FRIEND_CONNECT_FREQ = 30 * 1000;
private HashMap<Integer, FriendFinderFriend> discoveredFriends = new HashMap<Integer, FriendFinderFriend>();
private final DHTConnector friendConnector;
private final FriendManager friendManager;
private FriendFinderFriend me;
private List<MulticastSocket> sendSockets = new LinkedList<MulticastSocket>();
private FriendFinderAnnounceTask announceTask;
private volatile boolean running = false;
private Timer friendFinderAnnouncer;
private FriendFinderListener friendFinderListener;
private Thread friendFinderListenerThread;
private final byte[] ownPublicKey;
private final InvitationManager invitationManager;
public LanFriendFinder(final DHTConnector friendConnector, final FriendManager friendManager,
InvitationManager invitationManager, final byte[] ownPublicKey) throws IOException {
this.friendConnector = friendConnector;
this.friendManager = friendManager;
this.ownPublicKey = ownPublicKey;
this.invitationManager = invitationManager;
COConfigurationManager.addParameterListener(OSF2F_LAN_FRIEND_FINDER,
new ParameterListener() {
public void parameterChanged(String parameterName) {
boolean enabled = isLanFinderEnabled();
if (enabled) {
if (!running) {
start();
}
} else {
stop();
}
}
});
if (isLanFinderEnabled()) {
start();
}
}
public void start() {
logger.fine("starting lan friend finder");
running = true;
announceTask = new FriendFinderAnnounceTask();
friendFinderAnnouncer = new Timer("LANFriendFinderAnnouncer", true);
friendFinderAnnouncer.schedule(announceTask, 0, REANNOUNCE_PERIOD);
friendFinderListener = new FriendFinderListener();
friendFinderListenerThread = new Thread(friendFinderListener);
friendFinderListenerThread.setDaemon(true);
friendFinderListenerThread.setName("LanFriendFinderListener");
friendFinderListenerThread.start();
}
public void stop() {
logger.fine("stoping lan friend finder");
if (announceTask != null) {
announceTask.cancel();
}
if (friendFinderAnnouncer != null) {
friendFinderAnnouncer.cancel();
}
if (friendFinderListener != null) {
friendFinderListener.quit();
}
if (friendFinderListenerThread != null) {
try {
friendFinderListenerThread.interrupt();
} catch (Throwable t) {
}
}
running = false;
}
class FriendFinderAnnounceTask extends TimerTask {
@Override
public void run() {
synchronized (discoveredFriends) {
for (Iterator<FriendFinderFriend> iterator = discoveredFriends.values().iterator(); iterator
.hasNext();) {
FriendFinderFriend fff = (FriendFinderFriend) iterator.next();
if (fff.isExpired()) {
logger.finest("removing: " + fff);
iterator.remove();
}
}
}
if (isLanFinderEnabled()) {
logger.fine("Running LanFriendFinder");
try {
/*
* wait for the filelist
*/
OSF2FMain.getSingelton().getOverlayManager().getFilelistManager()
.waitForFileListCreation();
long timeSinceLastAllIfaceSend = System.currentTimeMillis()
- lastAllInterfaceSend;
if (timeSinceLastAllIfaceSend > TRY_ALL_INTERFACES_EVERY_X_MINUTES * 60 * 1000) {
lastAllInterfaceSend = System.currentTimeMillis();
tryAllInterfaces(ownPublicKey);
} else {
DatagramPacket p = createPacket(ownPublicKey);
// send it on all send sockets
for (Iterator<MulticastSocket> iterator = sendSockets.iterator(); iterator
.hasNext();) {
MulticastSocket s = iterator.next();
try {
s.send(p);
try {
logger.finest("sent fff packet on: "
+ s.getNetworkInterface().getName());
} catch (Throwable t) {
logger.finest("sent fff packet on: "
+ s.getLocalSocketAddress());
}
} catch (Throwable t) {
logger.fine("unable to send LanFriendFinder packet on "
+ s.getLocalAddress() + ", got error: " + t.getMessage());
/*
* if we can't use the socket, close it and
* remove it
*/
try {
s.close();
iterator.remove();
} catch (Exception e) {
}
}
}
}
} catch (Exception e) {
Debug.out("got error in LanFriendFinder-SendTimer,canceling future lan announces");
e.printStackTrace();
friendFinderAnnouncer.cancel();
}
} else {
logger.fine("Not Running LanFriendFinder, disabled in config");
}
}
}
private void tryAllInterfaces(final byte[] ownPublicKey) {
logger.finer("probing for new network interfaces to send lan friend finder packets on");
for (MulticastSocket s : sendSockets) {
try {
logger.finer("closing lan friend finder socket on: "
+ s.getInetAddress().getHostAddress());
s.close();
} catch (Exception e) {
}
}
sendSockets.clear();
try {
Enumeration<NetworkInterface> nets = NetworkInterface.getNetworkInterfaces();
DatagramPacket p = createPacket(ownPublicKey);
for (NetworkInterface netIf : Collections.list(nets)) {
MulticastSocket s = null;
try {
s = new MulticastSocket();
s.setNetworkInterface(netIf);
s.setTimeToLive(1);
s.send(p);
logger.finest("sent fff packet on: " + netIf.getName());
sendSockets.add(s);
} catch (Throwable t) {
logger.finest("unable to send LanFriendFinder packet on " + netIf.getName()
+ ", got: " + t.getMessage());
/*
* if we can't use the socket, close it
*/
if (s != null) {
try {
s.close();
} catch (Exception e) {
}
}
}
}
/*
* if no socket worked
*/
if (sendSockets.size() == 0) {
Debug.out("unable to bind the Lan Friend Finder sockets to any specific interfaces, trying default interface instead");
MulticastSocket s = null;
try {
s = new MulticastSocket();
s.setTimeToLive(1);
s.send(p);
logger.finest("sent fff packet on default interface");
sendSockets.add(s);
} catch (Throwable t) {
Debug.out("unable to send LanFriendFinder packet on any interface, got: "
+ t.getMessage());
t.printStackTrace();
/*
* if we can't use the socket, close it
*/
if (s != null) {
try {
s.close();
} catch (Exception e) {
}
}
}
}
} catch (Exception e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
logger.finer("Got " + sendSockets.size() + " usable interfaces for LanFriendFinder");
}
private DatagramPacket createPacket(final byte[] ownPublicKey) throws Exception, IOException,
UnknownHostException {
FriendFinderFriend me = new FriendFinderFriend(null, getComputerName(), getTcpPort(),
ownPublicKey, getSecurityLevel());
byte[] serialized = me.serialize();
DatagramPacket p = new DatagramPacket(serialized, serialized.length,
InetAddress.getByName(MULTICAST_GROUP), FRIEND_FINDER_PORT);
return p;
}
private byte getSecurityLevel() {
int level = COConfigurationManager.getIntParameter("security level");
return (byte) level;
}
private String getComputerName() {
try {
String name = COConfigurationManager.getStringParameter("Computer Name", null);
if (name == null) {
InetAddress addr = InetAddress.getLocalHost();
// Get hostname
name = addr.getHostName();
if (name != null && name.split("\\.").length > 0) {
name = name.split("\\.")[0];
}
COConfigurationManager.setParameter("Computer Name", name);
}
return name;
} catch (UnknownHostException e) {
}
return null;
}
private int getTcpPort() {
return COConfigurationManager.getIntParameter("TCP.Listen.Port");
}
private boolean isLanFinderEnabled() {
return COConfigurationManager.getBooleanParameter(OSF2F_LAN_FRIEND_FINDER);
}
private static class FriendFinderFriend {
private static final int MAX_NICK_BYTES = 512;
private static final int MAX_PUBKEY_BYTES = 512;
InetAddress addr;
int port;
byte[] publicKey;
String nickName;
private final long createdDate;
private final int hash;
private final byte securityLevel;
public int getSecurityLevel() {
return securityLevel;
}
public FriendFinderFriend(InetAddress addr, String nickName, int port, byte[] publicKey,
byte securityLevel) throws Exception {
this.createdDate = System.currentTimeMillis();
this.addr = addr;
this.nickName = nickName;
this.port = port;
this.publicKey = publicKey;
this.securityLevel = securityLevel;
if (nickName.getBytes("UTF-8").length > MAX_NICK_BYTES) {
throw new Exception("nick length must be less than " + MAX_NICK_BYTES + " bytes");
}
if (publicKey.length > MAX_PUBKEY_BYTES) {
throw new Exception("publickey length must be less than " + MAX_PUBKEY_BYTES
+ " bytes");
}
this.hash = Arrays.hashCode(publicKey);
}
public FriendFinderFriend(DatagramPacket p) throws IOException {
this.createdDate = System.currentTimeMillis();
this.addr = p.getAddress();
DataInputStream in = new DataInputStream(new ByteArrayInputStream(p.getData()));
this.port = in.readInt();
int nickLen = in.readInt();
if (nickLen > MAX_NICK_BYTES) {
throw new IOException(
"Unable to parse nickname in friend finder packet (nick len to large)");
}
byte[] nick = new byte[nickLen];
in.read(nick, 0, nick.length);
this.nickName = new String(nick);
int pubKeyLen = in.readInt();
if (pubKeyLen > MAX_PUBKEY_BYTES) {
throw new IOException(
"Unable to parse publickey in friend finder packet (len to large)");
}
this.publicKey = new byte[pubKeyLen];
in.read(publicKey, 0, publicKey.length);
this.hash = Arrays.hashCode(publicKey);
/*
* read the
*/
if (in.available() > 0) {
this.securityLevel = in.readByte();
// System.out.println("got lan friend packet from " + nick +
// " with security: " + securityLevel);
} else {
this.securityLevel = 0;
}
in.close();
}
public String toString() {
return "FriendFinderFriend: '" + nickName + "' at:" + addr.getHostAddress() + ":"
+ port;
}
public byte[] serialize() throws IOException {
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
DataOutputStream o = new DataOutputStream(byteOut);
/*
* format of the packet:
*
* 4 bytes oneswarm listening port
*
* 4 bytes len of nickname
*
* x bytes nickname
*
* 4 bytes len of key
*
* x bytes key
*/
o.writeInt(port);
byte[] nick = nickName.getBytes("UTF-8");
o.writeInt(nick.length);
o.write(nick);
o.writeInt(publicKey.length);
o.write(publicKey);
o.writeByte(securityLevel);
o.close();
return byteOut.toByteArray();
}
public boolean equals(Object o) {
if (o instanceof FriendFinderFriend) {
return Arrays.equals(publicKey, ((FriendFinderFriend) o).publicKey);
}
return false;
}
public int hashCode() {
return hash;
}
public boolean isExpired() {
long age = System.currentTimeMillis() - createdDate;
return age > FRIEND_EXPIRED_TIME;
}
}
public List<Friend> getNearbyUsers() {
List<Friend> l = new LinkedList<Friend>();
synchronized (discoveredFriends) {
for (FriendFinderFriend fff : discoveredFriends.values()) {
Friend f = new Friend("Lan", "(" + fff.nickName + ")", fff.publicKey, false);
f.setBlocked(true);
f.setRequestFileList(false);
f.setLastConnectIP(fff.addr);
f.setLastConnectPort(fff.port);
l.add(f);
}
}
return l;
}
private class FriendFinderListener implements Runnable {
private boolean quit = false;
public void run() {
try {
/*
* wait for the filelist
*/
OSF2FMain.getSingelton().getOverlayManager().getFilelistManager()
.waitForFileListCreation();
/*
* We need to join the multicast group on all interfaces
*/
Enumeration<NetworkInterface> nets = NetworkInterface.getNetworkInterfaces();
MulticastSocket socket = new MulticastSocket(FRIEND_FINDER_PORT);
SocketAddress socketAddress = new InetSocketAddress(
InetAddress.getByName(MULTICAST_GROUP), FRIEND_FINDER_PORT);
socket.joinGroup(InetAddress.getByName(MULTICAST_GROUP));
for (NetworkInterface netIf : Collections.list(nets)) {
try {
socket.joinGroup(socketAddress, netIf);
} catch (Throwable t) {
logger.finer("unable to join LanFriendFinder multicast group on network interface: "
+ netIf.getDisplayName());
}
}
while (!quit) {
byte[] b = new byte[1500];
DatagramPacket p = new DatagramPacket(b, b.length);
socket.receive(p);
try {
FriendFinderFriend f = new FriendFinderFriend(p);
logger.finest("got fff packet: " + f);
// check if me
if (!f.equals(me)) {
// add to discovered friends
synchronized (discoveredFriends) {
discoveredFriends.put(f.hashCode(), f);
}
// ok, check if connected
Friend friend = friendManager.getFriend(f.publicKey);
if (friend != null) {
Date lastConnectDate = friend.getLastConnectDate();
long time = 0;
if (lastConnectDate != null) {
time = lastConnectDate.getTime();
}
if (friend != null
&& (friend.getStatus() == Friend.STATUS_OFFLINE || friend
.getStatus() == Friend.STATUS_CONNECTING)
&& (System.currentTimeMillis() > time
+ MAX_FRIEND_CONNECT_FREQ)) {
// try to connect
logger.finest("fff packet is from friends, trying to connect: "
+ friend);
friendConnector.connectToFriend(friend, f.addr, f.port);
}
} else {
/*
* this is not a friend, check if it is an
* invitation
*/
FriendInvitation invitation = invitationManager
.getInvitationFromPublicKey(f.publicKey);
if (invitation != null) {
if (invitation.hasConnectableStatus()) {
invitation.setLastConnectIp(f.addr.getHostAddress());
invitation.setLastConnectPort(f.port);
friendConnector.connectToInvitation(invitation);
}
}
}
}
} catch (Throwable t) {
if (t instanceof InterruptedException) {
quit = true;
} else {
Debug.out("got error in lan friend finder listener", t);
}
}
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
logger.fine("LanFriendFinderListener stopped");
}
public void quit() {
quit = true;
}
}
public static void main(String[] args) {
byte[] b = new byte[511];
Arrays.fill(b, (byte) 'a');
try {
FriendFinderFriend f = new FriendFinderFriend(
InetAddress.getByName("www.cs.washington.edu"), "testnickasdfdsf", 12345, b,
(byte) 0);
System.out.println(f);
byte[] s = f.serialize();
DatagramPacket p = new DatagramPacket(s, s.length);
p.setAddress(InetAddress.getByName("www.cs.washington.edu"));
FriendFinderFriend f2 = new FriendFinderFriend(p);
System.err.println(f2);
if (!f2.equals(f)) {
System.err.println("decoding problem (!=)");
}
} catch (UnknownHostException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}