package uc.protocols.hub;
import helpers.GH;
import helpers.SchedulableTask;
import java.security.GeneralSecurityException;
import java.util.Collections;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetSocketAddress;
import java.net.ProtocolException;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.nio.channels.UnresolvedAddressException;
import java.nio.charset.Charset;
import logger.LoggerFactory;
import org.apache.log4j.Logger;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExtensionRegistry;
import org.eclipse.core.runtime.Platform;
import uc.Command;
import uc.DCClient;
import uc.FavHub;
import uc.ICryptoManager;
import uc.IHub;
import uc.IOperatorPlugin;
import uc.IUser;
import uc.IUserChangedListener;
import uc.Identity;
import uc.InfoChange;
import uc.LanguageKeys;
import uc.PI;
import uc.IUserChangedListener.UserChangeEvent;
import uc.crypto.HashValue;
import uc.crypto.UDPEncryption;
import uc.database.DBLogger;
import uc.files.filelist.FileListDescriptor;
import uc.files.filelist.FileListFile;
import uc.files.filelist.FileListFolder;
import uc.files.filelist.IFileListItem;
import uc.files.filelist.OwnFileList.SearchParameter;
import uc.files.search.FileSearch;
import uc.files.search.SearchResult;
import uc.protocols.ADCStatusMessage;
import uc.protocols.CPType;
import uc.protocols.Compression;
import uc.protocols.ConnectionProtocol;
import uc.protocols.ConnectionState;
import uc.protocols.IConnection;
import uc.protocols.SendContext;
import uc.protocols.UnblockingConnection;
import uc.protocols.DCProtocol;
import uc.protocols.hub.ZOn.ZON;
import uc.user.User;
public class Hub extends DCProtocol implements IHub {
private static Logger logger = LoggerFactory.make();
public static final int MAX_USERS = 35000;
private volatile boolean maxUsersReached = false;
private static ConnectionInjector inject = new ConnectionInjector();
public static void setConnectionInjector(ConnectionInjector ci) {
inject = ci;
}
public static final boolean ZLIF = false;
/**
* Used for inserting different connections for Unit-tests..
*/
public static class ConnectionInjector {
public IConnection getConnection(ICryptoManager cryptoManager,String addy, ConnectionProtocol connectionProt,boolean encryption,HashValue fingerPrint) {
return new UnblockingConnection(cryptoManager,addy,connectionProt, encryption,fingerPrint);
}
}
public static final int MAX_ACTIVE_RESULTS = 10,
MAX_PASSIVE_RESULTS = 5;
private DBLogger mcLoggerDB;
private DBLogger feedLoggerDB;
public static enum ProtocolPrefix {
DCHUB(false,true),NMDC(false,true),
NMDCS(true,true),DCHUBS(true,true),
ADC(false,false),ADCS(true,false);
ProtocolPrefix(boolean encrypted,boolean nmdc) {
this.encrypted = encrypted;
this.nmdc = nmdc;
}
public String toString() {
return name().toLowerCase();
}
private final boolean encrypted,nmdc;
}
private volatile User self;
private final Identity identity;
public Identity getIdentity() {
return identity;
}
/**
* token used to create this hub..
*/
private final FavHub favHub;
/**
* when a client receives signal for redirection
* the received address is stored here.
*/
private volatile FavHub redirectaddy;
/**
* contains only the hubs name .. not the topic
*/
private String hubname = null;
private final Set<String> othersSupports =
Collections.synchronizedSet(new HashSet<String>()); // The SupportString of the other side
/**
* containing only the name .. if possible not the whole topic
*/
private String topic = "";
/**
* VE INF field of ADC ..
*/
private String version = "";
// /**
// * summed share over all users in the hub..
// * this is manipulated by user.setShared();
// * never directly..
// */
// private volatile long totalshare = 0;
/**
* true if we needed to sent a password..
*/
private volatile boolean registered = false;
/**
* on each startup request userip if hub supports
* it and hasn't sent it to us
*/
private volatile boolean userIPReceived = false;
/**
* if we connected at least once to the hub ..
* this will be used so we can't
* be used for DDoS
* if we don't connect at least once
*
* set to True when a Lock is received..
*/
private volatile boolean onceConnected = false;
// private volatile boolean weWantToReconnect = true; // Variable that tells if the client should automatically reconnect... i.e. set false on badpass or kick..
// private volatile boolean reconnectRunning = false;
// private final Object reconnectSynch = new Object();
// private ScheduledFuture<?> reconnect = null;
private final SchedulableTask reconnectTask;
private final SchedulableTask timeOutTask;
// private volatile long waitTime = 10+ GH.nextInt(60); //how long to wait until reconnect
private volatile int unsuccessfulConnectionsInARow = 0;
// private final Object loginTimeoutSynch = new Object();
// private int timerLogintimeout;
private static final long noCommandReceivedTimeout = 45*1000;
private final Object lastCommandSynch = new Object();
private ScheduledFuture<?> lastCommandTimer;
private long lastCommandTime;
{
synchronized (lastCommandSynch) {
lastCommandTime = System.currentTimeMillis()+45 * 1000 ;
}
}
private final Map<HashValue,User> users = new ConcurrentHashMap<HashValue,User>(20,0.75f,2); //userid to User
private final Map<Integer,User> userBySid = new ConcurrentHashMap<Integer,User>(20,0.75f,2); //SID to user
private List<IOperatorPlugin> opPlugins = null;
private final List<IPMFilter> pmFilters = new ArrayList<IPMFilter>();
/**
*all usercomamnds
*/
private final List<Command> userCommands = new ArrayList<Command>();
/**
* map needed for completion proposals
*/
private final SortedMap<String,IUser> userPrefix = Collections.synchronizedSortedMap(new TreeMap<String,IUser>());
private final Map<IUser,Date> lastAwaySent = new WeakHashMap<IUser,Date>();
private final DCClient dcc;
public DCClient getDcc() {
return dcc;
}
public User getUser(HashValue id){
return users.get(id) ;
}
/**
* the new constructor
* @param favHub
* @param a
*/
public Hub(FavHub favHuba,Identity identity,DCClient dccx) throws IllegalArgumentException{
super();
this.identity = identity;
this.dcc = dccx;
defaultPort = 411;
this.favHub = favHuba;
if (!favHub.isValid()) {
throw new IllegalArgumentException("Hubaddress not valid: "+favHub.getHubaddy());
}
// setHubaddy();
ProtocolPrefix p = favHub.getProtocolPrefix();
setProtocolNMDC(p.nmdc);
if (p.nmdc) {
this.defaultCommand = new MC();
} else {
this.defaultCommand = null;
}
logger.debug("creating hub: "+favHub.getHubaddy());
self = createSelf();
reconnectTask = new SchedulableTask(new Runnable() {
@Override
public void run() {
WriteLock lock = writeLock();
lock.lock();
try {
if (dcc.isRunningHub(favHub)) {
logger.debug("reconnecting");
statusMessage(LanguageKeys.Reconnecting,0);
reconnectTask.reschedule(60, TimeUnit.SECONDS); //if reconnect fails... / not even goes to before connect..
connection.reset(favHub.getInetSocketaddress());
}
} finally {
lock.unlock();
}
}
}, dcc.getSchedulerDir());
loadPMFilters();
timeOutTask = new SchedulableTask(new Runnable() {
@Override
public void run() {
WriteLock lock = writeLock();
lock.lock();
try {
logger.debug("login Timeout: "+getName());
switch(getState()) {
case CONNECTING:
statusMessage(LanguageKeys.ConnectionTimeout,0);
break;
case CONNECTED:
statusMessage(LanguageKeys.LoginTimeout,0);
break;
case LOGGEDIN:
break;
case CLOSED:
case DESTROYED:
break;
default:
throw new IllegalStateException("timeout occured although current state is: "+getState());
}
connection.close();
} finally {
lock.unlock();
}
}
}, dcc.getSchedulerDir());
connection = inject.getConnection(identity.getCryptoManager(),favHub.getInetSocketaddress(), this,p.encrypted,favHub.getKeyPrint());
if (!favHub.isChatOnly()) {
registerCTMListener(identity.getConnectionHandler());
}
}
private String getNickSelf() {
return GH.isNullOrEmpty(favHub.getNick())? PI.get(PI.nick): favHub.getNick() ;
}
/**
* create a user that represents our self therefore
* some methods must be overwritten
* so this user proxy works properly
*/
private User createSelf() {
if (self != null && getUserBySID(self.getSid()) == self ) {
logger.info("User still found by sid "); //Happens on fast reconnects?
}
String nickname = getNickSelf();
return new User(dcc,nickname,nmdc?nickToUserID(nickname,this ): CIDToUserID(identity.getCID(), favHub) ){
private Inet6Address i6address;
@Override
public HashValue getCID() {
return identity.getCID();
}
@Override
public String getDescription() {
if (GH.isEmpty(favHub.getUserDescription())) {
return PI.get(PI.description);
} else {
return favHub.getUserDescription();
}
}
@Override
public String getEMail() {
if (GH.isEmpty(favHub.getEmail())) {
return PI.get(PI.eMail);
} else {
return favHub.getEmail();
}
}
@Override
public Inet4Address getIp() {
return identity.getConnectionDeterminator().getPublicIP();
}
@Override
public synchronized Inet6Address getI6() {
return i6address;
}
@Override
public long getShared() {
return favHub.isChatOnly() ? 0L :dcc.getFilelist().getSharesize();
}
@Override
public int getSlots() {
return dcc.getTotalSlots();
}
@Override
public String getTag() { //only used for NMDC..
return dcc.getTag(identity);
}
@Override
public void setSid(int sid) {
userBySid.remove(this.getSid());
super.setSid(sid);
userBySid.put(sid, this);
}
@Override
public Mode getModechar() {
return identity.getMode();
}
@Override
public int getNormHubs() {
return dcc.getNumberOfHubs(false)[0];
}
@Override
public int getRegHubs() {
return dcc.getNumberOfHubs(false)[1];
}
@Override
public int getOpHubs() {
return dcc.getNumberOfHubs(false)[2];
}
@Override
public HashValue getPD() {
return identity.getPID();
}
@Override
public String getVersion() {
return DCClient.VERSION;
}
@Override
public String getSupports() {
List<String> sup = Hub.this.getSupports(false);
if (isIPv4() && identity.isActive()) {
sup.add(User.TCP4);
sup.add(User.UDP4);
}
if (isIPv6()) {
sup.add(User.TCP6);
sup.add(User.UDP6);
}
if (identity.currentlyTLSSupport()) {
sup.add(User.ADCS_SUPPORT);
// if (getKeyPrint() != null) {
// sup.add(User.KEYP);
// }
}
if (UDPEncryption.isUDPEncryptionSupported() && connection.usesEncryption()) {
sup.add(User.ADCS_UDP);
}
return GH.concat(sup, ",");
}
@Override
public int getNumberOfSharedFiles() {
return favHub.isChatOnly()?
0 : dcc.getFilelist().getNumberOfFiles();
}
@Override
public int getUdpPort() {
return dcc.getUDPhandler().getPort();
}
@Override
public int getUDP6Port() {
return dcc.getUDPhandler().getPort();
}
@Override
public long getUs() {
//UP Speed ... as is in settings..
long speedLimit = PI.getInt(PI.uploadLimit)*1024;
if (speedLimit == 0) {
return PI.getLong(PI.connectionNew);
}
return speedLimit;
}
/**
* Flag indicates our state ..
* AFK which has a value of 2 or 3 for long time AFK .. 1 is normal
*
* @return always 1 in normal state.. 2 in away state..
* add 16 if tls support for nmdc is enabled..
*
*/
public byte getFlag() {
int flag = dcc.isAway()? 2:1;
flag += identity.currentlyTLSSupport()? User.FLAG_ENC :0;
return (byte)flag;
}
@Override
public synchronized FileListDescriptor getFilelistDescriptor() {
return dcc.getFilelistself().getFilelistDescriptor();
}
@Override
public boolean hasDownloadedFilelist() {
return true;
}
@Override
public void setProperty(INFField inf, String val)throws NumberFormatException,IllegalArgumentException {
super.setProperty(inf, val);
switch(inf) {
case CT: setWeAreOp(super.isOp()); break;
case I4:
logger.debug("getting IP set by hub: "+favHub.getHubaddy(),new Throwable());
identity.getConnectionDeterminator().userIPReceived(super.getIp(), favHub); //super call important -> otherwise it gets the IP from COnnection determinator..
userIPReceived = true;
break;
case I6:
logger.debug("getting IPv6 set by hub: "+favHub.getHubaddy(),new Throwable());
try {
i6address = (Inet6Address)Inet6Address.getByName(val);
} catch (UnknownHostException e) {
logger.warn("Error parsing ipv6: "+e, e);
}
userIPReceived = true;
break;
default: break;
}
}
@Override
public HashValue getKeyPrint() {
return identity.getCryptoManager().getFingerPrint();
}
private boolean changeInProgress = false;
@Override
public String getNick() {
if (!nmdc && !super.getNick().equals(getNickSelf()) && !changeInProgress) {
changeInProgress = true;
setProperty(INFField.NI, getNickSelf());
changeInProgress = false;
}
return super.getNick();
}
@Override
public int hashCode() {
final int PRIME = 31;
int result = 1;
result = PRIME * result + ((getUserid() == null) ? 0 : getUserid().hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
final User other = (User) obj;
if (!getUserid().equals(other.getUserid()))
return false;
return true;
}
};
}
public void start() {
WriteLock lock = writeLock();
lock.lock();
try {
super.start();
connection.start();
} catch(UnresolvedAddressException uae) {
statusMessage(LanguageKeys.AddressCouldNotBeResolved,0);
} finally {
lock.unlock();
}
}
/**
* @return - not the full "topic" string .. only the name
*/
public String getName() {
if (hubname == null) {
return favHub.getHubname();
} else {
return hubname;
}
}
public void beforeConnect() {
logger.debug("beforeConnect()");
topic = ""; //delete topic
version = "";
registered = false;
self = createSelf();
othersSupports.clear();
userIPReceived = false;
reconnectTask.cancelScheduled();
// logger.info(favHub.getHubname()+" cancelling reconnect");
timeOutTask.reschedule(40, TimeUnit.SECONDS);
userCommands.clear();
super.beforeConnect();
if (nmdc) {
// addCommand(
// // new Lock(),
// // new HubName(),
// // new Supports(),
// // new Hello(),
// // new LogedIn(),
// new GetPass(),
// new HubIsFull(),
// new ValidateDenide(),
// new ForceMove());
} else {
// addCommand(new SUP(),new SID()
// ,new INF(),new MSG()
// ,new STA(),new GPA()
// ,new CMD(),new GET());
}
if (Hub.ZLIF) {
addCommand(nmdc?new ZOn():new ZON());
}
logger.debug("end beforeConnect()");
}
public void onConnect() throws IOException { //not needed... in NMDC Client hub protocol...
logger.debug("onConnect()");
super.onConnect();
// statusMessage(LanguageKeys.Connected,0);
if (!nmdc) { //in ADC clients sends the first command not the hub
SUP.sendSupports(this);
}
}
protected void onUnexpectedCommandReceived(String command) {
if (!getFavHub().isChatOnly()) {
super.onUnexpectedCommandReceived(command);
}
logger.debug(command);
}
/**
*
* @param usr the user that sent the message.. null if undetermined
* @param message - the message.. if user is not null the <nick> is striped off
*/
void mcMessageReceived(User usr, String message, boolean me) {
for (IHubListener listener : hubl) {
if (usr != null) {
listener.mcReceived(usr, message,me);
} else {
listener.mcReceived( message);
}
}
logMainchatMessage(usr,message,me);
}
private void logMainchatMessage(final User usr,final String message,boolean me) {
if (PI.getBoolean(PI.logMainChat)) {
if (mcLoggerDB == null) {
mcLoggerDB = new DBLogger(favHub, true,dcc);
}
String mes = (usr != null ? (me?"*":"<") + usr.getNick()+ (me?" ":"> ") : "")+ message;
mcLoggerDB.addLogEntry(mes, System.currentTimeMillis());
}
}
/**
* pumps status messages to main chat..
* may be this should be done with a logger.
* @param message
*/
public void statusMessage(String message, int severity) {
for (IHubListener listener : hubl) {
listener.statusMessage( message , severity);
}
}
@Override
public void onDisconnect() throws IOException {
super.onDisconnect();
disconnectAllUsers();
timeOutTask.cancelScheduled();
synchronized (lastCommandSynch) {
if (lastCommandTimer != null) {
lastCommandTimer.cancel(true);
}
}
if (checkReconnect()) {
logger.debug("scheduling reconnect");
// logger.info(favHub.getHubname()+": reconnect running " + reconnectTask.getDelay(TimeUnit.SECONDS));
if (!reconnectTask.isScheduled()) {
unsuccessfulConnectionsInARow++;
int waitTime = Math.min(50,unsuccessfulConnectionsInARow) *(10 + GH.nextInt(30)); //reset the wait time..
reconnectTask.reschedule(waitTime, TimeUnit.SECONDS);
}
}
}
public boolean checkReconnect() {
return dcc.isRunningHub(favHub)
&& (onceConnected || favHub.isFavHub(dcc.getFavHubs()) );
}
public void increaseSharesize(long difference) {
// WriteLock lock = writeLock();
// lock.lock();
// try {
// totalshare += difference;
// } finally {
// lock.unlock();
// }
}
public void onLogIn() throws IOException {
super.onLogIn();
unsuccessfulConnectionsInARow = 0;
timeOutTask.cancelScheduled();
//now information if we are registered or operator may have changed
dcc.notifyChangedInfo(InfoChange.Hubs);
//hubs may fail to send user IP to us therefore ask for it if not sent
dcc.getSchedulerDir().schedule(
new Runnable() {
public void run() {
logger.debug("requesting userip1");
if (!userIPReceived && identity.isActive() && getState() == ConnectionState.LOGGEDIN) {
logger.debug("requesting userip2");
requestUserIP();
}
}
},5, TimeUnit.SECONDS);
synchronized (lastCommandSynch) {
lastCommandTimer = dcc.getSchedulerDir().scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
if (isNoTrafficTimeOut()) {
//logger.info(favHub.getHubname()+" Timeouttimer 2");
WriteLock lock = writeLock();
try {
lock.lockInterruptibly();
} catch (InterruptedException e) {
if (Thread.interrupted()) {
return;
}
}
try {
if (nmdc) {
MyINFO.sendMyINFO(Hub.this,true);
//by sending a MyINFO we check if the connection of the hub is fine
} else {
//in ADC we have STA as NOP
STA.sendSTAtoHub(Hub.this, new ADCStatusMessage("PING",
ADCStatusMessage.SUCCESS,ADCStatusMessage.Generic));
}
} finally {
lock.unlock();
}
}
}
}, 60, 10, TimeUnit.SECONDS);
}
}
/**
* queues a raw message for sending to the hub
* @param message
*/
public void sendRaw(String message) {
sendRaw(message,new SendContext());
}
/**
* queues a raw message for sending to the hub
*
* @param context - information to fill out %[attribs]
*/
public void sendRaw(String message,SendContext context) {
WriteLock lock = writeLock();
lock.lock();
try {
context.setHub(this);
String mes = context.format(message);
sendUnmodifiedRaw(mes);
} finally {
lock.unlock();
}
}
/**
* sends a raw without doing any tinkering
* like formatting by context..
* @param mes - the message to be sent..
*/
void sendUnmodifiedRaw(String mes) {
WriteLock lock = writeLock();
lock.lock();
try {
super.sendRaw(mes);
setLastCommand();
} finally {
lock.unlock();
}
}
void sendUnmodifiedRaw(byte[] mes) {
WriteLock lock = writeLock();
lock.lock();
try {
super.sendRaw(mes);
setLastCommand();
} finally {
lock.unlock();
}
}
void passwordRequested() {
FavHub favHub = getFavHub();
if (favHub.getPassword() != null && !GH.isEmpty(favHub.getPassword())) {
sendPassword(favHub.getPassword());
} else {
statusMessage(LanguageKeys.HubRequestedPassword,0);
}
}
/**
* sends the password if no login was done
* @param pass - the password
* @param base32Rand - random sent by the hub ... null for nmdc
*/
public void sendPassword(String pass) {
if (getState() == ConnectionState.CONNECTED) {
statusMessage(LanguageKeys.SendingPassword,0);
if (nmdc) {
GetPass.sendPass(this, pass);
} else {
GPA.sendPass(this, pass);
}
setRegistered(true);
}
}
public void requestUserIP() {
requestUserIP(getSelf());
}
public void requestUserIP(IUser usr) {
if (nmdc) {
UserIP.sendUserIPRequest(this,usr);
}
}
public boolean supportsUserIP() {
if (nmdc) {
return UserIP.supportsUserIp(this);
} else {
return false;
}
}
/**
* called by produce user to insert the users in the users map
* and by hello to insert the self in the users map
*
* minimum requirements that this works is that the user
* has his nick set
*/
void insertUser(User user) {
if (users.size() > MAX_USERS) {
if (!maxUsersReached) {
dcc.logEvent("Hub tried adding too many users");
maxUsersReached = true;
}
} else {
users.put(user.getUserid(),user);
if (!nmdc) {
userBySid.put(user.getSid() , user);
}
userPrefix.put(GetPrefix(user), user);
if (user.getHub() != this) {
increaseSharesize(user.getShared());
}
user.userConnected(this);
}
}
public void internal_userChangedNick(User user,String newNick) {
if (nmdc) {
throw new IllegalStateException("In NMDC nick change is not allowed");
}
userPrefix.remove(GetPrefix(user));
user.internal_setNick(newNick);
userPrefix.remove(GetPrefix(user));
}
/**
* called when a PM is received
* the PM is then forwarded to interested listeners..
*
* @param from - who sent the PM (name of the window)
* @param sender - <%[nick of sender]> message who sent
* it originally.. usually the same as from but differs
* for chat rooms
* @param message - what was typed..
*/
void pmReceived(PrivateMessage pm) {
boolean veto = false;
for (IPMFilter filter : pmFilters) {
veto = filter.vetoPM(this,pm) | veto;
}
if (!veto) {
for (IHubListener listener: hubl) {
listener.pmReceived(pm);
}
// if the other is either chatroom or bot afk message is not needed
if (dcc.isAway() && pm.fromEqualsSender() && !pm.getFrom().isBot()) {
Date sent = lastAwaySent.get(pm.getFrom());
if (sent == null || sent.before(new Date(System.currentTimeMillis()-60 *60 *1000))) {
lastAwaySent.put(pm.getFrom(), new Date());
sendPM(pm.getFrom(), dcc.getAwayMessage(),false);
}
}
logPMMessage(pm);
}
}
/**
* logs a message
*
* @param from
* @param sender
* @param message
*/
private void logPMMessage(PrivateMessage pm) {
if (PI.getBoolean(PI.logPM)) {
new DBLogger(pm.getFrom(),dcc).addLogEntry(pm.toString(), pm.getTimeReceived());
}
}
/**
* prints a message to the feed label
* when used with standard UI (message is forwarded to the
* HubListener)
*
*/
void feedReceived(FeedType ft,String message) {
for (IHubListener feedListener: hubl) {
feedListener.feedReceived(ft, message);
}
logFeed(ft,message);
}
private void logFeed(FeedType ft,String message) {
if (PI.getBoolean(PI.logFeed)) {
if (feedLoggerDB == null) {
feedLoggerDB = new DBLogger(favHub,false,dcc);
}
feedLoggerDB.addLogEntry(ft+message, System.currentTimeMillis());
}
}
/**
*
* @param target - the one that should receive the PM
* @param message - the message to send..
*/
public void sendPM(IUser target, String message,boolean me){
PrivateMessage pm = new PrivateMessage(target, getSelf(),message,me);
if (nmdc) {
To.sendPM(this, target, message,me);
} else {
MSG.sendPM(this, target, message,me);
}
pmReceived(pm);
}
public void sendMM(String message,boolean me) {
logger.debug("Sending Mainchat message "+message);
if (nmdc) {
MC.sendMainchatMessage(this, message,me);
} else {
MSG.sendMM(this, message, me);
}
}
public void requestConnection(IUser target,String token) {
boolean nmdc = isNMDC();
boolean encryption = target.hasSupportForEncryption() &&
identity.currentlyTLSSupport();
CPType protocol = CPType.get(encryption, nmdc);
if (identity.isActive()) {
logger.debug("sending CTM "+target+
" " + protocol+" "+
self.getIp().getHostAddress());
sendCTM(target, protocol ,token);
} else {
logger.debug("sending RCM "+target);
sendRCM(target, protocol ,token);
}
}
/**
* sends a CTM message
*
* @param target - to whom
* @param token - token for ADC - null for NMDC
* @param protocol - protocol used in ADC - null for NMDC
*/
public void sendCTM(IUser target, CPType protocol,String token) {
logger.debug("called sendCTM("+target.getNick()+")");
if (!favHub.isChatOnly()) {
for (ICTMListener ctml : ctmrl) {
//notifies listeners that PM was sent -->
//needed for example so ConnectionHandler knows who is expected to connect...
ctml.ctmSent(target, protocol, token);
}
if (nmdc) {
ConnectToMe.sendCTM(this, target,protocol);
} else {
CTM.sendCTM(this,target,protocol, token);
}
}
}
public void sendRCM(IUser target,CPType protocol,String token) {
if (!favHub.isChatOnly() && target.isTCPActive()) {
if (nmdc) {
RevConnectToMe.sendRCM(this, target);
} else {
RCM.sendRCM(this, target, protocol, token);
}
}
//here may be tell user that he can't connect there..
}
/**
* send a search to the hub.
* @param search - the pattern containing all information needed to search
*/
public void search(FileSearch search) {
if (!favHub.isChatOnly()) {
if (nmdc) {
Search.sendSearch(this, search);
} else {
SCH.sendSearch(this, search);
}
}
}
// /**
// * allows the UDP handler to pump a command into the normal command handling routine..
// * @param sr
// */
// public void searchResultReceived(String sr) {
// try {
// receivedCommand(sr);
// } catch (IOException pe) {
// logger.debug(pe);
// }
// logger.debug("sr received: "+sr);
// }
InetSocketAddress getHubIPAndPort() {
InetSocketAddress isa = connection.getInetSocketAddress();
return isa;
}
/**
* when a CTM is received this methods calls anyone interested in
* specially the connection handler
* @param isa - the InetSocketaddress of the other
* @param other - who we should connect to (null for namdc)
* @param protocol What should be used in the connection..
*/
void ctmReceived(InetSocketAddress isa,IUser other,CPType protocol,String token) {
/*
* here check could be checking against common ips used to spam against..
* "jucy.eu" "dcpp.net" "hublist.org" "hubtracker.com" "dchublist.com"
* "adchublist.com" "adcportal.com"
*
* Though Ref field is better.. to punish hubs... -> therefore no blocking of ips..
*/
if (isa.getPort() > 0) {
for (ICTMListener ctmr: ctmrl) {
ctmr.ctmReceived(getSelf(), isa,other,protocol,token);
}
} else {
logger.debug("IP with port 0 received: "+isa);
}
}
void userQuit(User usr) {
finalizeUser(usr,true);
}
/**
*
* @param keys - search strings
* @param sizerestricted - if restrictions on size should be taken into account
* @param maxsize - is it maximum or minimum restriction
* @param size - what restriction
* @param st - search type restriction
* @param passive - if the user is passive
* @param searcher - if passive this is a User object .. if active this is an
* InetSocketAddress address or a User Object (which holds an UDP socket and an InetAddress)
*/
void searchReceived(SearchParameter sp, boolean passive, User searcherusr,InetSocketAddress searcherip,String token,byte[] encryptionKey) {
sp.maxResults = passive? MAX_PASSIVE_RESULTS:MAX_ACTIVE_RESULTS;
sp.hub = favHub;
Set<IFileListItem> found = dcc.getFilelist().search(sp);
dcc.searchReceived(sp.keys, searcherusr != null? searcherusr:searcherip, found.size());
if (!found.isEmpty()) {
sendFoundBack(found,passive,searcherusr,searcherip,token,encryptionKey);
}
}
/**
* search Received for TTHRoot
* @param hash - the TTH root that was searched
* @param passive - if the searcher was active or passive
* @param searcher - if passive a User object if active an InetSocketAddress
* @param token ... ADC token .. needed to send the search back
*/
void searchReceived(HashValue hash,boolean passive,User searcherusr,InetSocketAddress searcherip,String token,byte[] encryptionKey) {
FileListFile found = dcc.getFilelist().search(hash);
dcc.searchReceived(Collections.singleton(hash.toString()), searcherusr !=null ? searcherusr:searcherip,found == null? 0:1);
if (found != null) {
sendFoundBack(Collections.<IFileListItem>singleton( found),passive,searcherusr,searcherip,token,encryptionKey);
}
}
private void sendFoundBack(Set<IFileListItem> found,boolean passive,User searcherusr,InetSocketAddress searcherip,String token,byte[] encryptionKey) {
Set<SearchResult> srs = new HashSet<SearchResult>();
for (IFileListItem ff: found) {
if (ff.isFile()) {
srs.add(new SearchResult((FileListFile)ff,getSelf(),dcc.getCurrentSlots(),
dcc.getTotalSlots(),token));
} else {
srs.add(new SearchResult((FileListFolder)ff,getSelf(),dcc.getCurrentSlots(),
dcc.getTotalSlots(),token));
}
}
if (passive) {
sendSearchResultbackPassive(srs,searcherusr);
} else {
sendSearchResultbackActive(srs,searcherusr , searcherip,encryptionKey);
}
}
/**
*
* @param srs - the searchresult that contains all infos that should be send
* @param target - the one that initiated the search that should receive the sr
*/
private void sendSearchResultbackPassive(Set<SearchResult> srs, User target) {
if (nmdc) {
SR.sendSR(srs, target, this);
} else {
RES.sendSR(srs, target, this);
}
}
private void sendSearchResultbackActive(Set<SearchResult> srs, User target,InetSocketAddress searcherIp,byte[] encryptionKey) {
try {
for (SearchResult sr: srs) {
String command = getUDPSRPacket(sr);
byte[] packet = command.getBytes(getCharset().name());
if (encryptionKey != null && target.hasSupportForUDPEncryption()) {
//byte[] key = UDPEncryption.tokenStringToKey(sr.getToken());
packet = UDPEncryption.encryptMessage(packet, encryptionKey);
if (Platform.inDevelopmentMode()) {
logger.warn("sending encrypted packet to: "+target+" in:"+favHub.getSimpleHubaddy());
}
}
dcc.getUDPhandler().sendPacket(ByteBuffer.wrap(packet), searcherIp);
}
} catch (UnsupportedEncodingException cee) {
throw new IllegalStateException();
} catch (GeneralSecurityException e) {
logger.warn(e,e);
}
}
public String getUDPSRPacket(SearchResult sr) {
if (nmdc) {
return SR.getUDPSrString(sr, this);
} else {
return RES.getUDPRESString(sr, this);
}
}
/**
*
* @param usr the usr to be disconnected..
* @param quit if it is a quit
*/
private void disconnectAllUsers() {
for (User usr : new ArrayList<User>(users.values())) {
finalizeUser(usr,false);
}
// totalshare = 0;
}
private void finalizeUser(User usr,boolean quit) {
users.remove(usr.getUserid());
if (!nmdc) {
userBySid.remove(usr.getSid());
}
userPrefix.remove(GetPrefix(usr));
usr.disconnected(quit);
}
/**
* retrieves a user by nick
*/
public User getUserByNick(String nick) {
if (nick == null) {
throw new IllegalArgumentException();
}
if (nmdc) {
return getUser(DCProtocol.nickToUserID(nick,this ));
} else {
for (User usr: users.values()) {
if (nick.equals(usr.getNick())) {
return usr;
}
}
return null;
}
}
public User getUserByCID(HashValue cid) {
if (nmdc) {
throw new IllegalArgumentException("only in adc possible, nmdc has no CID");
}
return getUser(DCProtocol.CIDToUserID(cid, favHub));
}
User getUserBySID(int sid) {
return userBySid.get(sid);
}
public User getSelf() {
return self;
}
/**
*
* notify all listeners registered with the hub
* and the user itself of a change
* may only be called by the user -> use notiyfyUserChanged( if needed)
*
* @param usr - who has changed
* @param type - and how it has changed
*/
public void internal_notifyUserChanged(UserChangeEvent uce) {
for (IUserChangedListener listener : ucl) {
listener.changed(uce);
}
}
// /**
// * disconnects and reconnects after the specified time
// * @param reconnectTime - if -1 never
// */
// private void disconnect(int reconnectTime) {
// // weWantToReconnect = true;
// waitTime = reconnectTime < 0? Integer.MAX_VALUE:reconnectTime;
//
// connection.close();
// }
/**
* reconnects the hub ..
* closing the connection
* if needed..
*/
public void reconnect(int waitTime) {
if (getState() != ConnectionState.CLOSED) {
connection.close();
}
reconnectTask.reschedule(waitTime, TimeUnit.SECONDS);
}
// private void scheduleReconnect(int reconnectTime) {
// logger.info(favHub.getHubname()+ " scheduling reconnect: "+reconnectTime);
//
// }
void redirectReceived(FavHub addy) {
if (addy.isValid()) {
redirectaddy = addy ;
for (IHubListener hul: hubl) {
hul.redirectReceived(addy);
}
if (PI.getBoolean(PI.autoFollowRedirect)) {
followLastRedirect(false);
}
}
}
/**
* if there is a reconnect pending..
* this function will
* @param immediate if true immediately.. otherwise wait 15 secs..
*/
public void followLastRedirect(boolean immediate) {
if (redirectaddy != null && redirectaddy.isValid()) {
if (getState() != ConnectionState.DESTROYED) {
close();
}
dcc.getSchedulerDir().schedule(new Runnable() {
public void run() {
if (redirectaddy != null && redirectaddy.isValid()) {
redirectaddy.connect(dcc);
redirectaddy = null;
}
}
},immediate?1:15,TimeUnit.SECONDS);
}
}
/**
* override to set charset to accommodate
* for Russian and other non European charsets instead of
* default in NMDC
*/
protected void setCharSet() {
if (GH.isEmpty(favHub.getCharset())) {
super.setCharSet();
} else {
setCharset(Charset.forName(favHub.getCharset()));
}
}
/**
* disconnects from the hub
* and removes the hub from the client
* called when the user closes the editorpart associated with this hub
*/
public void close() {
WriteLock lock = writeLock();
lock.lock();
try {
reconnectTask.cancelScheduled();
if (timeOutTask.isScheduled()) {
if (Platform.inDevelopmentMode()) {
logger.warn("time out task still scheduled"); //happened once on early close..after startup
}
timeOutTask.cancelScheduled();
}
synchronized (lastCommandSynch) {
if (lastCommandTimer != null) {
lastCommandTimer.cancel(true);
}
}
unregisterCTMListener(identity.getConnectionHandler());
dcc.internal_unregisterHub(favHub);
} finally {
lock.unlock();
}
connection.close(); //call to close may not be locked as synch itself
end();
}
/**
* function to send a MyINFO about our self to the hub
* @param if force is true the MyINFO will be sent no matter what ignoring
* all previous info
* if force is false it will only be sent if there is any new information
*
*/
void sendMyInfo(boolean force) {
if (nmdc) {
MyINFO.sendMyINFO(this,force);
} else {
INF.sendINF(this, force);
}
}
/**
* sends MyINFO/INF to hub updating data ..
*/
public void sendMyInfo() {
sendMyInfo(false);
}
// public void timer() {
// WriteLock lock = writeLock();
// lock.lock();
// try {
// super.timer();
// if (isNoTrafficTimeOut() && ConnectionState.LOGGEDIN == getState()) {
// synchronized(lastCommandSynch) {
// logger.debug("no command received for long time - checking connection: " + (System.currentTimeMillis()-lastCommandTime) );
// }
// if (nmdc) {
// MyINFO.sendMyINFO(this,true);
// //by sending a MyINFO we check if the connection of the hub is fine
// } else {
// //in ADC we have STA as NOP
// STA.sendSTAtoHub(this, new ADCStatusMessage("PING",
// ADCStatusMessage.SUCCESS,ADCStatusMessage.Generic));
// }
// }
//
// } finally {
// lock.unlock();
// }
// }
private void setLastCommand() {
synchronized(lastCommandSynch) {
lastCommandTime = System.currentTimeMillis();
}
}
private boolean isNoTrafficTimeOut() {
synchronized(lastCommandSynch) {
return System.currentTimeMillis() - lastCommandTime > noCommandReceivedTimeout;
}
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((favHub == null) ? 0 : favHub.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
final Hub other = (Hub) obj;
if (favHub == null) {
if (other.favHub != null)
return false;
} else if (!favHub.equals(other.favHub))
return false;
return true;
}
/**
*
* @return the users only for the gui
*/
public Map<HashValue, User> getUsers() {
return Collections.unmodifiableMap(users);
}
private final CopyOnWriteArraySet<IHubListener> hubl =
new CopyOnWriteArraySet<IHubListener>();
private final CopyOnWriteArraySet<IUserChangedListener> ucl =
new CopyOnWriteArraySet<IUserChangedListener>();
private final CopyOnWriteArraySet<ICTMListener> ctmrl =
new CopyOnWriteArraySet<ICTMListener>();
public void registerCTMListener(ICTMListener listener) {
ctmrl.add(listener);
}
public void unregisterCTMListener(ICTMListener listener) {
ctmrl.remove(listener);
}
public void registerHubListener(IHubListener listener) {
super.registerProtocolStatusListener(listener);
hubl.add(listener);
}
public void unregisterHubListener(IHubListener listener) {
super.unregisterProtocolStatusListener(listener);
hubl.remove(listener);
}
public void registerUserChangedListener(IUserChangedListener listener) {
ucl.add(listener);
}
public void unregisterUserChangedListener(IUserChangedListener listener) {
ucl.remove(listener);
}
/**
* @return the totalshare
*/
public long getTotalshare() {
long totalShare = 0;
for (IUser usr:users.values()) {
totalShare+= usr.getShared();
}
return totalShare;
}
/**
* @return the HubName without Topic
*/
public String getHubname() {
return hubname;
}
public FavHub getFavHub() {
return favHub;
}
/**
*
*
* sets the hubname without topic ..
* and notifies all listeners
*
*
* @param hubname - only hubname without topic..
*/
void setHubname(String hubname) {
if (!hubname.equals(this.hubname)) {
this.hubname = hubname;
for (IHubListener ihcl: hubl) {
ihcl.hubnameChanged(hubname,topic);
}
}
}
void setTopic(String topic) {
if (!topic.equals(this.topic)) {
this.topic = topic;
for (IHubListener ihcl: hubl) {
ihcl.hubnameChanged(hubname,topic);
}
}
}
void setVersion(String version) {
if (!version.equals(this.version)) {
this.version = version;
statusMessage(version, 0);
}
}
public String getVersion() {
return version;
}
Set<String> getOthersSupports() {
return othersSupports;
}
/*void addOthersSupports(String othersSupports) {
this.othersSupports = othersSupports;
} */
public boolean isOpHub() {
return getSelf().isOp();
}
private void setWeAreOp(boolean weAreOp) {
if (weAreOp && opPlugins == null) {
opPlugins = new ArrayList<IOperatorPlugin>();
for (IOperatorPlugin iop : dcc.getOperatorPlugins()) {
if (iop.init(this)) {
opPlugins.add(iop);
registerHubListener(iop);
registerUserChangedListener(iop);
}
}
}
}
private void loadPMFilters() {
IExtensionRegistry reg = Platform.getExtensionRegistry();
IConfigurationElement[] configElements = reg
.getConfigurationElementsFor(IPMFilter.POINT_ID);
for (IConfigurationElement element : configElements) {
try {
IPMFilter pmf = (IPMFilter) element.createExecutableExtension("filter");
pmFilters.add(pmf);
} catch (CoreException e) {
logger.error("Can't load the filter extension: "+element.getAttribute("id"),e);
}
}
}
@Override
public void receivedCommand(String command) throws IOException,
ProtocolException {
logger.debug("receivedCommand("+command+")");
setLastCommand();
super.receivedCommand(command);
}
public boolean pendingReconnect() {
return redirectaddy != null;
}
public List<Command> getUserCommands() {
return Collections.unmodifiableList(userCommands);
}
/**
* adds a command received by $UserCommand
* to the hub..
* @param command - protocol independent description of the command
*/
void addUserCommand(Command command) {
if (command.isSeparator()) {
userCommands.add(command);
} else if (userCommands.contains(command)) {
int i = userCommands.indexOf(command);
userCommands.remove(i);
userCommands.add(i, command);
} else {
userCommands.add(command);
}
}
/**
*
* @return the last Command received.. possibly null if none..
* (Separators are not considered)
*/
Command getLastUserCommand() {
if (!userCommands.isEmpty()) {
for (int i = userCommands.size()-1; i >= 0;i--) {
if (!userCommands.get(i).isSeparator()) {
return userCommands.get(i);
}
}
}
return null;
}
void removeUserCommand(Command com) {
userCommands.remove(com);
}
/**
* deletes all user commands in the specified place..
* @param where - the place to delete UserCommands
*/
void deleteUserCommands(int where) {
for (Iterator<Command> iter = userCommands.iterator(); iter.hasNext();) {
if (iter.next().delete(where)) {
iter.remove();
}
}
}
public boolean isRegistered() {
return registered;
}
public void setRegistered(boolean registered) {
this.registered = registered;
}
private static String GetPrefix(IUser usr) {
return usr.getNick().toLowerCase();
}
public SortedMap<String, IUser> getUserPrefix() {
return userPrefix;
}
public String getTopic() {
return topic;
}
void setOnceConnected() {
this.onceConnected = true;
}
void enableDecompression() throws IOException {
connection.setIncomingDecompression(Compression.ZLIB_FAST);
}
// public boolean isReconnectRunning() {
// return reconnectRunning;
// }
@Override
public String toString() {
return "Hub [Hubname=" + favHub.getHubname() + "]";
}
}