package edu.washington.cs.oneswarm.f2f; import java.io.UnsupportedEncodingException; import java.net.InetAddress; import java.security.InvalidKeyException; import java.security.KeyFactory; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.PublicKey; import java.security.spec.InvalidKeySpecException; import java.security.spec.X509EncodedKeySpec; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.Date; import org.bouncycastle.util.encoders.Base64; import org.gudy.azureus2.core3.util.Debug; import com.aelitis.azureus.core.networkmanager.impl.osssl.OneSwarmSslTools; public class Friend { public final static int KEY_BYTES_LENGTH = 162; public final static String KEY_POST_STRING = "QAB"; public final static String KEY_PRE_STRING = "MIG"; public final static int KEY_STRING_LENGTH = 216; public final static int MAX_CONNECTING_TIME = 15 * 1000; private static MessageDigest md; public static final int NOT_CONNECTED_CONNECTION_ID = -1; public final static int STATUS_CONNECTING = 1; public final static int STATUS_HANDSHAKING = 2; public final static int STATUS_OFFLINE = 0; public final static int STATUS_ONLINE = 3; static { try { md = MessageDigest.getInstance("SHA-1"); } catch (NoSuchAlgorithmException e) { // TODO Auto-generated catch block e.printStackTrace(); } } private boolean allowChat = true; private String bannedReason = ""; private boolean blocked = false; private boolean canSeeFileList = true; private volatile int connectionId = NOT_CONNECTED_CONNECTION_ID; private StringBuffer connectionLog = new StringBuffer(); private Date dateAdded; private long friendBannedUntil = 0; private String group = null; private int hash = 0; private long lastAttempt; private Date lastConnectDate; private InetAddress lastConnectIP; private int lastConnectPort; private boolean newFriend = false; private String nick; private final byte[] publicKey; private PublicKey publicKeyObj; private boolean requestFileList = true; private byte[] dhtWriteLocation; private byte[] dhtReadLocation; private boolean dhtLocationConfirmed = false; public boolean isDhtLocationConfirmed() { return dhtLocationConfirmed; } public void setDhtLocationConfirmed(boolean dhtLocationConfirmed) { this.dhtLocationConfirmed = dhtLocationConfirmed; } public byte[] getDhtWriteLocation() { return dhtWriteLocation; } public void setDhtWriteLocation(byte[] dhtWriteLocation) { this.dhtWriteLocation = dhtWriteLocation; } public byte[] getDhtReadLocation() { return dhtReadLocation; } public void setDhtReadLocation(byte[] dhtReadLocation) { this.dhtReadLocation = dhtReadLocation; } private String sourceNetwork; private volatile int status = 0; private boolean supportsChat = false; private boolean supportsExtendedFileLists = false; private long totalDownloaded; private long totalDownloadSinceAppStart; private long totalUploaded; private long totalUploadSinceAppStart; public Friend(boolean canSeeFileList, boolean allowChat, Date dateAdded, Date lastConnectDate, InetAddress lastConnectIP, int lastConnectPort, String nick, byte[] publicKey, String sourceNetwork, long totalDownloaded, long totalUploaded, boolean blocked, boolean newFriend) { super(); this.canSeeFileList = canSeeFileList; this.allowChat = allowChat; this.dateAdded = dateAdded; this.lastConnectDate = lastConnectDate; this.lastConnectIP = lastConnectIP; this.lastConnectPort = lastConnectPort; this.nick = nick; this.publicKey = publicKey; this.sourceNetwork = sourceNetwork; this.totalDownloaded = totalDownloaded; this.totalUploaded = totalUploaded; this.blocked = blocked; this.newFriend = newFriend; /** * Fix this problem for old friends that were serialized before these bugs were fixed. */ if (dateAdded == null) { dateAdded = new Date(); } } // public Friend(boolean canSeeFileList, Date dateAdded, Date lastConnectDate, // InetAddress lastConnectIP, int lastConnectPort, String nick, // byte[] publicKey, String sourceNetwork, long totalDownloaded, // long totalUploaded, boolean blocked, boolean newFriend) { // super(); // this.canSeeFileList = canSeeFileList; // this.dateAdded = dateAdded; // this.lastConnectDate = lastConnectDate; // this.lastConnectIP = lastConnectIP; // this.lastConnectPort = lastConnectPort; // this.nick = nick; // this.publicKey = publicKey; // this.sourceNetwork = sourceNetwork; // this.totalDownloaded = totalDownloaded; // this.totalUploaded = totalUploaded; // this.blocked = blocked; // this.newFriend = newFriend; // this.allowChat = true; // } /** * used for comparison purposes * * @param publicKey */ public Friend(String sourceNetwork, String nick, byte[] publicKey, boolean canSeeFilelist) { this.sourceNetwork = sourceNetwork; this.nick = nick; this.publicKey = publicKey; this.dateAdded = new Date(); this.canSeeFileList = canSeeFilelist; this.newFriend = true; } public Friend(String sourceNetwork, String nickName, String publicKey) throws InvalidKeyException { publicKey = publicKey.replaceAll("\\s+", ""); if (nickName == null || nickName.length() == 0 || publicKey == null || publicKey.length() == 0) { throw new InvalidKeyException( "Both Nick and public key must be non empty"); } if (publicKey.length() != KEY_STRING_LENGTH) { throw new InvalidKeyException( "Public key has wrong length, did you paste it all? (" + publicKey.length() + "!=" + KEY_STRING_LENGTH + ")"); } if (!publicKey.startsWith(KEY_PRE_STRING)) { throw new InvalidKeyException("Public key usually starts with: '" + KEY_PRE_STRING + "'"); } if (!publicKey.endsWith(KEY_POST_STRING)) { throw new InvalidKeyException("Public key usually ends with: '" + KEY_POST_STRING + "'"); } this.publicKey = Base64.decode(publicKey); this.sourceNetwork = sourceNetwork; this.nick = nickName; if (this.publicKey.length != KEY_BYTES_LENGTH) { throw new InvalidKeyException( "Public key has wrong length, did you paste it all? (" + this.publicKey.length + "!=" + KEY_BYTES_LENGTH + ")"); } try { X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(this.publicKey); KeyFactory kf = KeyFactory.getInstance("RSA"); publicKeyObj = kf.generatePublic(publicKeySpec); } catch (Exception e) { throw new InvalidKeyException("Got error when creating public key: " + e.getMessage()); } this.blocked = true; this.canSeeFileList = false; this.newFriend = true; } public void connected() { synchronized (this) { if (this.status == Friend.STATUS_HANDSHAKING) { Debug.out("got connected event, even though we already are handshaking!"); } else if (this.status == Friend.STATUS_ONLINE) { Debug.out("got connected event even though we are already connected"); } status = STATUS_HANDSHAKING; } } public void connectionAttempt(String logMessage) { synchronized (this) { this.lastAttempt = System.currentTimeMillis(); updateConnectionLog(false, logMessage); if (status != STATUS_ONLINE) { status = STATUS_CONNECTING; } } } public void disconnected(int connectionIdHash) { synchronized (this) { updateConnectionLog(true, connectionIdHash, "disconnected"); connectionId = NOT_CONNECTED_CONNECTION_ID; status = STATUS_OFFLINE; } } public boolean equals(Object obj) { if (obj instanceof Friend) { Friend comp = (Friend) obj; if (Arrays.equals(comp.getPublicKey(), this.getPublicKey())) { return true; } } return false; } public String getBannedReason() { return bannedReason; } public int getConnectionId() { synchronized (this) { return connectionId; } } public String getConnectionLog() { return connectionLog.toString(); } public Date getDateAdded() { return dateAdded; } public long getFriendBannedUntil() { return friendBannedUntil; } public double getFriendScore() { return (double) totalDownloaded / (double) totalUploaded; } public String getGroup() { return group; } public Date getLastConnectDate() { return lastConnectDate; } public InetAddress getLastConnectIP() { return lastConnectIP; } public int getLastConnectPort() { return lastConnectPort; } public String getNick() { return nick; } public byte[] getPublicKey() { return publicKey; } public PublicKey getPublicKeyObj() throws NoSuchAlgorithmException, InvalidKeySpecException { if (publicKeyObj == null) { X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(this.publicKey); KeyFactory kf = KeyFactory.getInstance("RSA"); publicKeyObj = kf.generatePublic(publicKeySpec); } return publicKeyObj; } public String getSourceNetwork() { return sourceNetwork; } public int getStatus() { synchronized (this) { if (status == STATUS_CONNECTING) { if (msSinceLastAttempt() > MAX_CONNECTING_TIME) { status = STATUS_OFFLINE; } } return status; } } public long getTotalDownloaded() { return totalDownloaded; } public long getTotalDownloadSinceAppStart() { return totalDownloadSinceAppStart; } public long getTotalUploaded() { return totalUploaded; } public long getTotalUploadSinceAppStart() { return totalUploadSinceAppStart; } public void handShakeCompleted(int connectionHash, boolean extendedFileLists, boolean chat) { synchronized (this) { if (this.connectionId != NOT_CONNECTED_CONNECTION_ID) { Debug.out("got Friend.handShakeCompleted even though the connection id isn't 'not connected'"); System.err.println("old=" + this.connectionId + " new=" + connectionId); System.err.println("friend: " + this.toString()); } status = STATUS_ONLINE; this.connectionId = connectionHash; this.supportsChat = chat; this.supportsExtendedFileLists = extendedFileLists; updateConnectionLog(true, connectionHash, "handshake completed"); } } public int hashCode() { if (hash == 0) { hash = Arrays.hashCode(publicKey); } return hash; } public boolean isAllowChat() { return allowChat; } public boolean isBlocked() { return blocked; } public boolean isCanSeeFileList() { return canSeeFileList; } public boolean isConnected() { return this.getStatus() == Friend.STATUS_ONLINE; } public boolean isNewFriend() { return newFriend; } public boolean isRequestFileList() { return requestFileList; } public boolean isSupportsChat() { return supportsChat; } public boolean isSupportsExtendedFileLists() { return supportsExtendedFileLists; } public long msSinceLastAttempt() { return System.currentTimeMillis() - lastAttempt; } public void setAllowChat(boolean allowChat) { this.allowChat = allowChat; } public void setBlocked(boolean blocked) { this.blocked = blocked; } public void setCanSeeFileList(boolean canSeeFileList) { this.canSeeFileList = canSeeFileList; } public void setConnectionId(int connectionId) { this.connectionId = connectionId; } public void setDateAdded(Date inDate) { dateAdded = inDate; } public void setFriendBannedUntil(String reason, long until) { this.bannedReason = reason; this.friendBannedUntil = until; } public void setGroup(String group) { this.group = group; } public void setLastConnectIP(InetAddress lastConnectIP) { this.lastConnectIP = lastConnectIP; } public void setLastConnectPort(int lastConnectPort) { this.lastConnectPort = lastConnectPort; } public void setNewFriend(boolean newFriend) { this.newFriend = newFriend; } public void setNick(String nick) { this.nick = nick; } public void setRequestFileList(boolean requestFileList) { this.requestFileList = requestFileList; } public void setStatus(int status) { this.status = status; } public String toString() { return nick + " (" + sourceNetwork + ")"; } public void updateConnectedDate() { this.lastConnectDate = new Date(); } public void updateConnectionLog(boolean append, int connectionHashId, String message) { if (!append || connectionLog == null) { connectionLog = new StringBuffer(); } Date d = new Date(); DateFormat f = new SimpleDateFormat("hh:mm:ss");//SimpleDateFormat.getTimeInstance(SimpleDateFormat.SHORT); if (connectionHashId != 0) { connectionLog.append(f.format(d) + " " + Integer.toHexString(connectionHashId) + ": " + message + "\n"); } else { connectionLog.append(f.format(d) + ": " + message + "\n"); } } public void updateConnectionLog(boolean append, String message) { updateConnectionLog(append, 0, message); } public void updateDownloaded(long downloaded) { if (downloaded > 0) { updateConnectedDate(); } this.totalDownloaded += downloaded; this.totalDownloadSinceAppStart += downloaded; } public void updateUploaded(long uploaded) { if (uploaded > 0) { updateConnectedDate(); } this.totalUploaded += uploaded; this.totalUploadSinceAppStart += uploaded; } public static byte[] getSha1(byte[] bytes) throws NoSuchAlgorithmException, UnsupportedEncodingException { md.update(bytes, 0, bytes.length); byte[] sha1hash = md.digest(); md.reset(); return sha1hash; } }