/* 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;
import java.util.Calendar;
import java.util.TimeZone;
import freenet.support.Fields;
import freenet.support.LogThresholdCallback;
import freenet.support.Logger;
import freenet.support.Logger.LogLevel;
/**
* Central spot for stuff related to the versioning of the codebase.
*
* Note: when using the fields of this class, you must note that the Java
* compiler compiles <b>constant final static</b> fields into the class
* definitions of classes that use them. This might not be the most appropriate
* behaviour when, eg. creating a class that reports statistics.
*
* A final static field can be made "non-constant" in the eyes of the compiler
* by initialising it with the results of a method, eg <code>T identity(T o)
* { T o; }</code>; however the "constant" behaviour might be required in some
* cases. A more flexible solution is to add a method that returns the field,
* eg {@link #publicVersion()}, and choose between the method and the field
* as necessary.
*
* <pre>$ grep -R "\WVersion\.\w*[^(a-zA-Z]" src</pre>
*
* can be used to find the places in the source code whose logic needs to be
* checked for this.
*
*/
public class Version {
/** FReenet Reference Daemon */
public static final String nodeName = "Fred";
/** The current tree version.
* FIXME: This is part of the node compatibility computations, so cannot be
* safely changed!!! Hence publicVersion ... */
public static final String nodeVersion = "0.7";
/** The version for publicity purposes i.e. the version of the node that has
* been released. */
public static final String publicVersion = "0.7.5";
/** The protocol version supported */
public static final String protocolVersion = "1.0";
/** The build number of the current revision */
private static final int buildNumber = 1478;
/** Oldest build of fred we will talk to *before* _cal */
private static final int oldLastGoodBuild = 1474;
/** Oldest build of fred we will talk to *after* _cal */
private static final int newLastGoodBuild = 1475;
static final long transitionTime;
static {
final Calendar _cal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
// year, month - 1 (or constant), day, hour, minute, second
_cal.set( 2016, Calendar.JULY, 15, 0, 0, 0 );
transitionTime = _cal.getTimeInMillis();
}
private static volatile boolean logMINOR;
private static volatile boolean logDEBUG;
static {
Logger.registerLogThresholdCallback(new LogThresholdCallback(){
@Override
public void shouldUpdate(){
logMINOR = Logger.shouldLog(LogLevel.MINOR, this);
logDEBUG = Logger.shouldLog(LogLevel.DEBUG, this);
}
});
}
/**
* Use this method when you want the value returned to be the run-time
* version, not the build-time version.
*
* For example, if you compile your class, then modify this class and
* compile it (the build script does this automatically), but don't
* re-compile your original class, then that will still have the old
* version compiled into it, since it is a static constant.
*
* @return The build number (not SVN revision number) of this node.
*/
public static int buildNumber() {
return buildNumber;
}
/**
* Analogous to {@link #buildNumber()} but for {@link #publicVersion}.
*/
public static String publicVersion() {
return publicVersion;
}
/**
* Analogous to {@link #buildNumber()} but for {@link #transitionTime}.
*/
public static long transitionTime() {
return transitionTime;
}
/**
* @return The lowest build number with which the node will connect and exchange
* data normally.
*/
public static int lastGoodBuild() {
if(System.currentTimeMillis() >= transitionTime)
return newLastGoodBuild;
else
return oldLastGoodBuild;
}
/** The highest reported build of fred */
private static int highestSeenBuild = buildNumber;
/** The current stable tree version */
public static final String stableNodeVersion = "0.7";
/** The stable protocol version supported */
public static final String stableProtocolVersion = "STABLE-0.7";
/** Oldest stable build of Fred we will talk to */
public static final int lastGoodStableBuild = 1;
/** Revision number of Version.java as read from CVS */
public static final String cvsRevision = "@custom@";
/**
* Analogous to {@link #buildNumber()} but for {@link #cvsRevision}.
*/
public static String cvsRevision() {
return cvsRevision;
}
/**
* @return the node's version designators as an array
*/
public static String[] getVersion() {
String[] ret =
{ nodeName, nodeVersion, protocolVersion, String.valueOf(buildNumber) };
return ret;
}
public static String[] getLastGoodVersion() {
String[] ret =
{ nodeName, nodeVersion, protocolVersion, String.valueOf(lastGoodBuild()) };
return ret;
}
/**
* @return the version string that should be presented in the NodeReference
*/
public static String getVersionString() {
return Fields.commaList(getVersion());
}
/**
* @return is needed for the freeviz
*/
public static String getLastGoodVersionString() {
return Fields.commaList(getLastGoodVersion());
}
/**
* @return true if requests should be accepted from nodes brandishing this
* protocol version string
*/
private static boolean goodProtocol(String prot) {
if (prot.equals(protocolVersion)
// uncomment next line to accept stable, see also explainBadVersion() below
// || prot.equals(stableProtocolVersion)
)
return true;
return false;
}
/**
* @return true if requests should be accepted from nodes brandishing this
* version string
*/
public static boolean checkGoodVersion(
String version) {
if(version == null) {
Logger.error(Version.class, "version == null!",
new Exception("error"));
return false;
}
String[] v = Fields.commaList(version);
if ((v.length < 3) || !goodProtocol(v[2])) {
return false;
}
if (sameVersion(v)) {
try {
int build = Integer.parseInt(v[3]);
int req = lastGoodBuild();
if (build < req) {
if(logDEBUG) Logger.debug(
Version.class,
"Not accepting unstable from version: "
+ version
+ "(lastGoodBuild="
+ req
+ ')');
return false;
}
} catch (NumberFormatException e) {
if(logMINOR)
Logger.minor(Version.class,
"Not accepting (" + e + ") from " + version);
return false;
}
}
if (stableVersion(v)) {
try {
int build = Integer.parseInt(v[3]);
if(build < lastGoodStableBuild) {
if(logDEBUG) Logger.debug(
Version.class,
"Not accepting stable from version"
+ version
+ "(lastGoodStableBuild="
+ lastGoodStableBuild
+ ')');
return false;
}
} catch (NumberFormatException e) {
Logger.minor(
Version.class,
"Not accepting (" + e + ") from " + version);
return false;
}
}
if(logDEBUG)
Logger.minor(Version.class, "Accepting: " + version);
return true;
}
/**
* @return true if requests should be accepted from nodes brandishing this
* version string, given an arbitrary lastGoodVersion
*/
public static boolean checkArbitraryGoodVersion(
String version, String lastGoodVersion) {
if(version == null) {
Logger.error(Version.class, "version == null!",
new Exception("error"));
return false;
}
if(lastGoodVersion == null) {
Logger.error(Version.class, "lastGoodVersion == null!",
new Exception("error"));
return false;
}
String[] v = Fields.commaList(version);
String[] lgv = Fields.commaList(lastGoodVersion);
if ((v == null || v.length < 3) || !goodProtocol(v[2])) {
return false;
}
if ((lgv == null || lgv.length < 3) || !goodProtocol(lgv[2])) {
return false;
}
if (sameArbitraryVersion(v,lgv)) {
try {
int build = Integer.parseInt(v[3]);
int min_build = Integer.parseInt(lgv[3]);
if (build < min_build) {
if(logDEBUG) Logger.debug(
Version.class,
"Not accepting unstable from version: "
+ version
+ "(lastGoodVersion="
+ lastGoodVersion
+ ')');
return false;
}
} catch (NumberFormatException e) {
if(logMINOR)
Logger.minor(Version.class,
"Not accepting (" + e + ") from " + version + " and/or " + lastGoodVersion);
return false;
}
}
if (stableVersion(v)) {
try {
int build = Integer.parseInt(v[3]);
if(build < lastGoodStableBuild) {
if(logDEBUG) Logger.debug(
Version.class,
"Not accepting stable from version"
+ version
+ "(lastGoodStableBuild="
+ lastGoodStableBuild
+ ')');
return false;
}
} catch (NumberFormatException e) {
Logger.minor(
Version.class,
"Not accepting (" + e + ") from " + version);
return false;
}
}
if(logDEBUG)
Logger.minor(Version.class, "Accepting: " + version);
return true;
}
/**
* @return string explaining why a version string is rejected
*/
public static String explainBadVersion(String version) {
String[] v = Fields.commaList(version);
if ((v.length < 3) || !goodProtocol(v[2])) {
return "Required protocol version is "
+ protocolVersion
// uncomment next line if accepting stable, see also goodProtocol() above
// + " or " + stableProtocolVersion
;
}
if (sameVersion(v)) {
try {
int build = Integer.parseInt(v[3]);
int req = lastGoodBuild();
if (build < req)
return "Build older than last good build " + req;
} catch (NumberFormatException e) {
return "Build number not numeric.";
}
}
if (stableVersion(v)) {
try {
int build = Integer.parseInt(v[3]);
if (build < lastGoodStableBuild)
return "Build older than last good stable build " + lastGoodStableBuild;
} catch (NumberFormatException e) {
return "Build number not numeric.";
}
}
return null;
}
/**
* @return the build number of an arbitrary version string
*/
public static int getArbitraryBuildNumber(
String version ) throws VersionParseException {
if(version == null) {
Logger.error(Version.class, "version == null!",
new Exception("error"));
throw new VersionParseException("version == null");
}
String[] v = Fields.commaList(version);
if ((v.length < 3) || !goodProtocol(v[2])) {
throw new VersionParseException("not long enough or bad protocol: "+version);
}
try {
return Integer.parseInt(v[3]);
} catch (NumberFormatException e) {
throw (VersionParseException)new VersionParseException("Got NumberFormatException on "+v[3]+" : "+e+" for "+version).initCause(e);
}
}
public static int getArbitraryBuildNumber(
String version, int defaultValue ) {
try {
return getArbitraryBuildNumber(version);
} catch (VersionParseException e) {
return defaultValue;
}
}
/**
* Update static variable highestSeenBuild anytime we encounter
* a new node with a higher version than we've seen before
*/
public static void seenVersion(String version) {
String[] v = Fields.commaList(version);
if ((v == null) || (v.length < 3))
return; // bad, but that will be discovered elsewhere
if (sameVersion(v)) {
int buildNo;
try {
buildNo = Integer.parseInt(v[3]);
} catch (NumberFormatException e) {
return;
}
if (buildNo > highestSeenBuild) {
if (logMINOR) {
Logger.minor(
Version.class,
"New highest seen build: " + buildNo);
}
highestSeenBuild = buildNo;
}
}
}
public static int getHighestSeenBuild() {
return highestSeenBuild;
}
/**
* @return true if the string describes the same node version as ours.
* Note that the build number may be different, and is ignored.
*/
public static boolean sameVersion(String[] v) {
return v[0].equals(nodeName)
&& v[1].equals(nodeVersion)
&& (v.length >= 4);
}
/**
* @return true if the string describes the same node version as an arbitrary one.
* Note that the build number may be different, and is ignored.
*/
public static boolean sameArbitraryVersion(String[] v, String[] lgv) {
return v[0].equals(lgv[0])
&& v[1].equals(lgv[1])
&& (v.length >= 4)
&& (lgv.length >= 4);
}
/**
* @return true if the string describes a stable node version
*/
private static boolean stableVersion(String[] v) {
return v[0].equals(nodeName)
&& v[1].equals(stableNodeVersion)
&& (v.length >= 4);
}
public static void main(String[] args) throws Throwable {
System.out.println(
"Freenet: "
+ nodeName
+ ' '
+ nodeVersion
+ " (protocol "
+ protocolVersion
+ ") build "
+ buildNumber
+ " (last good build: "
+ lastGoodBuild()
+ ')');
}
}