/* This code is part of Freenet. It is distributed under the GNU General
* Public License, version 2 (or at your option any later version). See
* http://www.gnu.org/ for further details of the GPL. */
package freenet.node.useralerts;
import static java.util.concurrent.TimeUnit.DAYS;
import freenet.l10n.NodeL10n;
import freenet.node.NodeStats;
import freenet.node.PeerManager;
import freenet.node.updater.NodeUpdateManager;
import freenet.support.HTMLNode;
public class PeerManagerUserAlert extends AbstractUserAlert {
final NodeStats n;
final NodeUpdateManager nodeUpdater;
// FIXME see comments in update().
public int conns = 0;
public int peers = 0;
public int neverConn = 0;
public int clockProblem = 0;
public int connError = 0;
public int disconnDarknetPeers = 0;
int bwlimitDelayTime = 1;
int nodeAveragePingTime = 1;
long oldestNeverConnectedPeerAge = 0;
private boolean bwlimitDelayAlertRelevant;
private boolean nodeAveragePingAlertRelevant;
public int darknetConns = 0;
public int darknetPeers = 0;
public int tooNewPeersDarknet = 0;
public int tooNewPeersTotal = 0;
public boolean isOpennetEnabled;
public boolean darknetDefinitelyPortForwarded;
public boolean opennetDefinitelyPortForwarded;
public boolean opennetAssumeNAT;
public boolean darknetAssumeNAT;
private boolean isOutdated;
/** How many connected peers we need to not get alert about not enough */
public static final int MIN_CONN_ALERT_THRESHOLD = 3;
/** How many connected darknet peers we can have without getting alerted about too many */
public static final int MAX_DARKNET_CONN_ALERT_THRESHOLD = 100;
/** How many disconnected peers we can have without getting alerted about too many */
public static final int MAX_DISCONN_PEER_ALERT_THRESHOLD = 50;
/** How many never-connected peers can we have without getting alerted about too many */
public static final int MAX_NEVER_CONNECTED_PEER_ALERT_THRESHOLD = 5;
/** How many peers with clock problems can we have without getting alerted about too many */
public static final int MIN_CLOCK_PROBLEM_PEER_ALERT_THRESHOLD = 5;
/** How many peers with unknown connection errors can we have without getting alerted */
public static final int MIN_CONN_ERROR_ALERT_THRESHOLD = 5;
/** How high can oldestNeverConnectedPeerAge be before we alert (in milliseconds)*/
public static final long MAX_OLDEST_NEVER_CONNECTED_PEER_AGE_ALERT_THRESHOLD = DAYS.toMillis(14); // 2 weeks
public PeerManagerUserAlert(NodeStats n, NodeUpdateManager nodeUpdater) {
super(false, null, null, null, null, (short) 0, true, NodeL10n.getBase().getString("UserAlert.hide"), false, null);
this.n = n;
this.nodeUpdater = nodeUpdater;
}
@Override
public String getTitle() {
synchronized(this) {
if(isOutdated)
return l10n("outdatedUpdateTitle");
if(!isOpennetEnabled) {
if(peers == 0)
return l10n("noPeersTitle");
if(conns == 0)
return l10n("noConnsTitle");
if(conns < MIN_CONN_ALERT_THRESHOLD)
return l10n("onlyFewConnsTitle", "count", Integer.toString(conns));
}
if(bwlimitDelayAlertRelevant && (bwlimitDelayTime > NodeStats.MAX_BWLIMIT_DELAY_TIME_ALERT_THRESHOLD))
return l10n("tooHighBwlimitDelayTimeTitle");
if(nodeAveragePingAlertRelevant && (nodeAveragePingTime > NodeStats.MAX_NODE_AVERAGE_PING_TIME_ALERT_THRESHOLD))
return l10n("tooHighPingTimeTitle");
if(clockProblem > MIN_CLOCK_PROBLEM_PEER_ALERT_THRESHOLD)
return l10n("clockProblemTitle");
if(neverConn > MAX_NEVER_CONNECTED_PEER_ALERT_THRESHOLD)
return l10n("tooManyNeverConnectedTitle");
if(connError > MIN_CONN_ERROR_ALERT_THRESHOLD)
return l10n("connErrorTitle");
if(disconnDarknetPeers > MAX_DISCONN_PEER_ALERT_THRESHOLD && !darknetDefinitelyPortForwarded && !darknetAssumeNAT)
return l10n("tooManyDisconnectedTitle");
if(darknetConns > MAX_DARKNET_CONN_ALERT_THRESHOLD)
return l10n("tooManyConnsTitle");
if(oldestNeverConnectedPeerAge > MAX_OLDEST_NEVER_CONNECTED_PEER_AGE_ALERT_THRESHOLD)
return l10n("tooOldNeverConnectedPeersTitle");
else throw new IllegalArgumentException("Not valid");
}
}
@Override
public String getShortText() {
return getTitle();
}
private String l10n(String key, String pattern, String value) {
return NodeL10n.getBase().getString("PeerManagerUserAlert."+key, pattern, value);
}
private String l10n(String key, String[] pattern, String[] value) {
return NodeL10n.getBase().getString("PeerManagerUserAlert."+key, pattern, value);
}
private String l10n(String key) {
return NodeL10n.getBase().getString("PeerManagerUserAlert."+key);
}
@Override
public String getText() {
String s;
synchronized(this) {
if(isOutdated)
return l10n("outdatedUpdate");
if(peers == 0 && !isOpennetEnabled) {
return l10n("noPeersDarknet");
} else if(conns < 3 && clockProblem > MIN_CLOCK_PROBLEM_PEER_ALERT_THRESHOLD) {
s = l10n("clockProblem", "count", Integer.toString(clockProblem));
} else if(conns < 3 && connError > MIN_CONN_ERROR_ALERT_THRESHOLD && !isOpennetEnabled) {
s = l10n("connError", "count", Integer.toString(connError));
} else if(conns == 0 && !isOpennetEnabled) {
return l10n("noConns");
} else if(conns == 1 && !isOpennetEnabled) {
return l10n("oneConn");
} else if(conns == 2 && !isOpennetEnabled) {
return l10n("twoConns");
} else if(bwlimitDelayAlertRelevant && (bwlimitDelayTime > NodeStats.MAX_BWLIMIT_DELAY_TIME_ALERT_THRESHOLD)) {
s = l10n("tooHighBwlimitDelayTime", new String[] { "delay", "max" },
new String[] { Integer.toString(bwlimitDelayTime), Long.toString(NodeStats.MAX_BWLIMIT_DELAY_TIME_ALERT_THRESHOLD)});
// FIXME I'm not convinced about the next one!
} else if(nodeAveragePingAlertRelevant && (nodeAveragePingTime > NodeStats.MAX_NODE_AVERAGE_PING_TIME_ALERT_THRESHOLD)) {
s = l10n("tooHighPingTime", new String[] { "ping", "max" },
new String[] { Integer.toString(nodeAveragePingTime), Long.toString(NodeStats.MAX_NODE_AVERAGE_PING_TIME_ALERT_THRESHOLD) });
} else if(clockProblem > MIN_CLOCK_PROBLEM_PEER_ALERT_THRESHOLD) {
s = l10n("clockProblem", "count", Integer.toString(clockProblem));
} else if(neverConn > MAX_NEVER_CONNECTED_PEER_ALERT_THRESHOLD) {
s = l10n("tooManyNeverConnected", "count", Integer.toString(neverConn));
} else if(connError > MIN_CONN_ERROR_ALERT_THRESHOLD) {
s = l10n("connError", "count", Integer.toString(connError));
} else if(disconnDarknetPeers > MAX_DISCONN_PEER_ALERT_THRESHOLD && !darknetDefinitelyPortForwarded && !darknetAssumeNAT){
s = l10n("tooManyDisconnected", new String[] { "count", "max" },
new String[] { Integer.toString(disconnDarknetPeers), Integer.toString(MAX_DISCONN_PEER_ALERT_THRESHOLD)});
} else if(darknetConns > MAX_DARKNET_CONN_ALERT_THRESHOLD) {
s = l10n("tooManyConns", new String[] { "count", "max" },
new String[] { Integer.toString(conns), Integer.toString(MAX_DARKNET_CONN_ALERT_THRESHOLD)});
} else if(oldestNeverConnectedPeerAge > MAX_OLDEST_NEVER_CONNECTED_PEER_AGE_ALERT_THRESHOLD) {
return l10n("tooOldNeverConnectedPeers");
} else throw new IllegalArgumentException("Not valid");
return s;
}
}
static public String replace(String text, String find, String replace) {
return replaceCareful(text, find, replace);
}
static public String replaceAll(String text, String find, String replace) {
int i;
while((i = text.indexOf(find)) >= 0) {
text = text.substring(0, i) + replace + text.substring(i + find.length());
}
return text;
}
static public String replaceCareful(String text, String find, String replace) {
String[] split = text.split(find, -1);
StringBuilder sb = new StringBuilder(text.length() + (split.length-1)*(replace.length() - find.length()));
for(int i=0;i<split.length;i++) {
sb.append(split[i]);
if(i < split.length - 1)
sb.append(replace);
}
return sb.toString();
}
@Override
public HTMLNode getHTMLText() {
HTMLNode alertNode = new HTMLNode("div");
synchronized(this) {
if(isOutdated)
// Arguably we should provide a button to turn on auto-update,
// but very few users will turn off auto-update completely.
// This is useful to not lose those who do however.
alertNode.addChild("#", l10n("outdatedUpdate"));
else if (peers == 0 && !isOpennetEnabled) {
alertNode.addChild("#", l10n("noPeersDarknet"));
} else if(conns < 3 && clockProblem > MIN_CLOCK_PROBLEM_PEER_ALERT_THRESHOLD) {
alertNode.addChild("#", l10n("clockProblem", "count", Integer.toString(clockProblem)));
} else if(conns < 3 && connError > MIN_CONN_ERROR_ALERT_THRESHOLD) {
alertNode.addChild("#", l10n("connError", "count", Integer.toString(connError)));
} else if (conns == 0 && !isOpennetEnabled) {
alertNode.addChild("#", l10n("noConns"));
} else if (conns == 1 && !isOpennetEnabled) {
alertNode.addChild("#", l10n("oneConn"));
} else if (conns == 2 && !isOpennetEnabled) {
alertNode.addChild("#", l10n("twoConns"));
} else if (bwlimitDelayAlertRelevant && (bwlimitDelayTime > NodeStats.MAX_BWLIMIT_DELAY_TIME_ALERT_THRESHOLD)) {
alertNode.addChild("#", l10n("tooHighBwlimitDelayTime", new String[] { "delay", "max" },
new String[] { Integer.toString(bwlimitDelayTime), Long.toString(NodeStats.MAX_BWLIMIT_DELAY_TIME_ALERT_THRESHOLD)}));
} else if (nodeAveragePingAlertRelevant && (nodeAveragePingTime > NodeStats.MAX_NODE_AVERAGE_PING_TIME_ALERT_THRESHOLD)) {
alertNode.addChild("#", l10n("tooHighPingTime", new String[] { "ping", "max" },
new String[] { Integer.toString(nodeAveragePingTime), Long.toString(NodeStats.MAX_NODE_AVERAGE_PING_TIME_ALERT_THRESHOLD) }));
} else if (clockProblem > MIN_CLOCK_PROBLEM_PEER_ALERT_THRESHOLD) {
alertNode.addChild("#", l10n("clockProblem", "count", Integer.toString(clockProblem)));
} else if (neverConn > MAX_NEVER_CONNECTED_PEER_ALERT_THRESHOLD) {
NodeL10n.getBase().addL10nSubstitution(alertNode, "PeerManagerUserAlert.tooManyNeverConnectedWithLink",
new String[] { "link", "count" },
new HTMLNode[] { HTMLNode.link("/friends/myref.fref"), HTMLNode.text(neverConn) });
} else if(connError > MIN_CONN_ERROR_ALERT_THRESHOLD) {
alertNode.addChild("#", l10n("connError", "count", Integer.toString(connError)));
} else if (disconnDarknetPeers > MAX_DISCONN_PEER_ALERT_THRESHOLD && !darknetDefinitelyPortForwarded && !darknetAssumeNAT) {
alertNode.addChild("#", l10n("tooManyDisconnected", new String[] { "count", "max" }, new String[] { Integer.toString(disconnDarknetPeers), Integer.toString(MAX_DISCONN_PEER_ALERT_THRESHOLD)}));
} else if (darknetConns > MAX_DARKNET_CONN_ALERT_THRESHOLD) {
alertNode.addChild("#", l10n("tooManyConns", new String[] { "count", "max" },
new String[] { Integer.toString(conns), Integer.toString(MAX_DARKNET_CONN_ALERT_THRESHOLD)}));
} else if (oldestNeverConnectedPeerAge > MAX_OLDEST_NEVER_CONNECTED_PEER_AGE_ALERT_THRESHOLD) {
alertNode.addChild("#", l10n("tooOldNeverConnectedPeers"));
} else throw new IllegalArgumentException("not valid");
}
return alertNode;
}
private boolean calculateIsOutdated() {
// Do not show the message if updater is enabled.
if(nodeUpdater.isEnabled()) return false;
if(nodeUpdater.isBlown()) return false;
synchronized(this) {
if(tooNewPeersDarknet >= PeerManager.OUTDATED_MIN_TOO_NEW_DARKNET)
return true;
return conns < PeerManager.OUTDATED_MAX_CONNS &&
tooNewPeersTotal >= PeerManager.OUTDATED_MIN_TOO_NEW_TOTAL;
}
}
@Override
public short getPriorityClass() {
synchronized(this) {
if(isOutdated) {
if(conns == 0)
return UserAlert.CRITICAL_ERROR;
else
return UserAlert.ERROR;
}
if(peers == 0 && !isOpennetEnabled)
return UserAlert.CRITICAL_ERROR;
if(conns == 0 && !isOpennetEnabled)
return UserAlert.ERROR;
if(conns < 3 && clockProblem > MIN_CLOCK_PROBLEM_PEER_ALERT_THRESHOLD)
return ERROR;
if(conns < 3 && connError > MIN_CONN_ERROR_ALERT_THRESHOLD)
return ERROR;
if(conns < 3 && !isOpennetEnabled)
return ERROR;
if(bwlimitDelayAlertRelevant && (bwlimitDelayTime > NodeStats.MAX_BWLIMIT_DELAY_TIME_ALERT_THRESHOLD))
return ERROR;
if(nodeAveragePingAlertRelevant && (nodeAveragePingTime > NodeStats.MAX_NODE_AVERAGE_PING_TIME_ALERT_THRESHOLD))
return ERROR;
if(clockProblem > MIN_CLOCK_PROBLEM_PEER_ALERT_THRESHOLD)
return ERROR;
if(neverConn > MAX_NEVER_CONNECTED_PEER_ALERT_THRESHOLD)
return WARNING;
if(connError > MIN_CONN_ERROR_ALERT_THRESHOLD)
return WARNING;
if(disconnDarknetPeers > MAX_DISCONN_PEER_ALERT_THRESHOLD && !darknetDefinitelyPortForwarded && !darknetAssumeNAT)
return WARNING;
if(darknetConns > MAX_DARKNET_CONN_ALERT_THRESHOLD)
return WARNING;
if(oldestNeverConnectedPeerAge > MAX_OLDEST_NEVER_CONNECTED_PEER_AGE_ALERT_THRESHOLD)
return WARNING;
return ERROR;
}
}
@Override
public boolean isValid() {
// only update here so we don't get odd behavior with it fluctuating
update();
boolean ret;
synchronized(this) {
ret = ((peers == 0 && !isOpennetEnabled) ||
(conns < 3 && !isOpennetEnabled) ||
(neverConn > MAX_NEVER_CONNECTED_PEER_ALERT_THRESHOLD) ||
(disconnDarknetPeers > MAX_DISCONN_PEER_ALERT_THRESHOLD && !darknetDefinitelyPortForwarded && !darknetAssumeNAT) ||
(darknetConns > MAX_DARKNET_CONN_ALERT_THRESHOLD) ||
(clockProblem > MIN_CLOCK_PROBLEM_PEER_ALERT_THRESHOLD) ||
(connError > MIN_CONN_ERROR_ALERT_THRESHOLD) ||
(bwlimitDelayAlertRelevant && (bwlimitDelayTime > NodeStats.MAX_BWLIMIT_DELAY_TIME_ALERT_THRESHOLD)) ||
(nodeAveragePingAlertRelevant && (nodeAveragePingTime > NodeStats.MAX_NODE_AVERAGE_PING_TIME_ALERT_THRESHOLD)) ||
(oldestNeverConnectedPeerAge > MAX_OLDEST_NEVER_CONNECTED_PEER_AGE_ALERT_THRESHOLD));
}
if(!ret)
ret = isOutdated;
return ret;
}
private void update() {
bwlimitDelayTime = (int) n.getBwlimitDelayTime();
nodeAveragePingTime = (int) n.getNodeAveragePingTime();
oldestNeverConnectedPeerAge = (int) n.peers.getOldestNeverConnectedDarknetPeerAge();
bwlimitDelayAlertRelevant = n.bwlimitDelayAlertRelevant;
nodeAveragePingAlertRelevant = n.nodeAveragePingAlertRelevant;
isOutdated = calculateIsOutdated();
// FIXME move PeerManager.updatePMUserAlert here.
// FIXME then make this subscribe to PeerManager's listener thingy.
}
}