Core: Move router data structures, and the deprecated RouterAddress sorter, from core to router.

This will break Android and the i2pcontrol plugin but shouldn't affect anything else.
This commit is contained in:
zzz
2014-08-21 17:36:06 +00:00
parent 4f9e13d0f6
commit d7feab116f
95 changed files with 226 additions and 182 deletions

View File

@ -24,20 +24,15 @@ import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
@ -1420,58 +1415,6 @@ public class DataHelper {
out.write(data);
}
/**
* Sort based on the Hash of the DataStructure.
* Warning - relatively slow.
* WARNING - this sort order must be consistent network-wide, so while the order is arbitrary,
* it cannot be changed.
* Why? Just because it has to be consistent so signing will work.
* How to spec as returning the same type as the param?
* DEPRECATED - Only used by RouterInfo.
*
* @return a new list
*/
public static List<? extends DataStructure> sortStructures(Collection<? extends DataStructure> dataStructures) {
if (dataStructures == null) return Collections.emptyList();
// This used to use Hash.toString(), which is insane, since a change to toString()
// would break the whole network. Now use Hash.toBase64().
// Note that the Base64 sort order is NOT the same as the raw byte sort order,
// despite what you may read elsewhere.
//ArrayList<DataStructure> rv = new ArrayList(dataStructures.size());
//TreeMap<String, DataStructure> tm = new TreeMap();
//for (DataStructure struct : dataStructures) {
// tm.put(struct.calculateHash().toString(), struct);
//}
//for (DataStructure struct : tm.values()) {
// rv.add(struct);
//}
ArrayList<DataStructure> rv = new ArrayList<DataStructure>(dataStructures);
sortStructureList(rv);
return rv;
}
/**
* See above.
* DEPRECATED - Only used by RouterInfo.
*
* @since 0.9
*/
static void sortStructureList(List<? extends DataStructure> dataStructures) {
Collections.sort(dataStructures, new DataStructureComparator());
}
/**
* See sortStructures() comments.
* @since 0.8.3
*/
private static class DataStructureComparator implements Comparator<DataStructure>, Serializable {
public int compare(DataStructure l, DataStructure r) {
return l.calculateHash().toBase64().compareTo(r.calculateHash().toBase64());
}
}
/**
* NOTE: formatDuration2() recommended in most cases for readability
*/

View File

@ -1,353 +0,0 @@
package net.i2p.data;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Collections;
import java.util.Date;
import java.util.Map;
import java.util.Properties;
import net.i2p.util.Addresses;
import net.i2p.util.OrderedProperties;
/**
* Defines a method of communicating with a router
*
* For efficiency, the options methods and structures here are unsynchronized.
* Initialize the structure with readBytes(), or call the setOptions().
* Don't change it after that.
*
* To ensure integrity of the RouterInfo, methods that change an element of the
* RouterInfo will throw an IllegalStateException after the RouterInfo is signed.
*
* As of 0.9.3, expiration MUST be all zeros as it is ignored on
* readin and the signature will fail.
* If we implement expiration, or other use for the field, we must allow
* several releases for the change to propagate as it is backwards-incompatible.
* Restored as of 0.9.12.
*
* @author jrandom
*/
public class RouterAddress extends DataStructureImpl {
private short _cost;
private long _expiration;
private String _transportStyle;
private final Properties _options;
// cached values
private byte[] _ip;
private int _port;
public static final String PROP_HOST = "host";
public static final String PROP_PORT = "port";
public RouterAddress() {
_options = new OrderedProperties();
}
/**
* For efficiency when created by a Transport.
* @param options not copied; do not reuse or modify
* @param cost 0-255
* @since IPv6
*/
public RouterAddress(String style, OrderedProperties options, int cost) {
_transportStyle = style;
_options = options;
if (cost < 0 || cost > 255)
throw new IllegalArgumentException();
_cost = (short) cost;
}
/**
* Retrieve the weighted cost of this address, relative to other methods of
* contacting this router. The value 0 means free and 255 means really expensive.
* No value above 255 is allowed.
*
* Unused before 0.7.12
* @return 0-255
*/
public int getCost() {
return _cost;
}
/**
* Configure the weighted cost of using the address.
* No value negative or above 255 is allowed.
*
* WARNING - do not change cost on a published address or it will break the RI sig.
* There is no check here.
* Rarely used, use 3-arg constructor.
*
* NTCP is set to 10 and SSU to 5 by default, unused before 0.7.12
*/
public void setCost(int cost) {
if (cost < 0 || cost > 255)
throw new IllegalArgumentException();
_cost = (short) cost;
}
/**
* Retrieve the date after which the address should not be used. If this
* is null, then the address never expires.
* As of 0.9.3, expiration MUST be all zeros as it is ignored on
* readin and the signature will fail.
* Restored as of 0.9.12.
*
* @deprecated unused for now
* @return null for never, or a Date
*/
public Date getExpiration() {
//return _expiration;
if (_expiration > 0)
return new Date(_expiration);
return null;
}
/**
* Retrieve the date after which the address should not be used. If this
* is zero, then the address never expires.
*
* @deprecated unused for now
* @return 0 for never
* @since 0.9.12
*/
public long getExpirationTime() {
return _expiration;
}
/**
* Configure the expiration date of the address (null for no expiration)
* As of 0.9.3, expiration MUST be all zeros as it is ignored on
* readin and the signature will fail.
* Restored as of 0.9.12, wait several more releases before using.
* TODO: Use for introducers
*
* Unused for now, always null
* @deprecated unused for now
*/
public void setExpiration(Date expiration) {
_expiration = expiration.getDate();
}
/**
* Retrieve the type of transport that must be used to communicate on this address.
*
*/
public String getTransportStyle() {
return _transportStyle;
}
/**
* Configure the type of transport that must be used to communicate on this address
*
* @throws IllegalStateException if was already set
* @deprecated unused, use 3-arg constructor
*/
public void setTransportStyle(String transportStyle) {
if (_transportStyle != null)
throw new IllegalStateException();
_transportStyle = transportStyle;
}
/**
* Retrieve the transport specific options necessary for communication
*
* @deprecated use getOptionsMap()
* @return sorted, non-null, NOT a copy, do not modify
*/
public Properties getOptions() {
return _options;
}
/**
* Retrieve the transport specific options necessary for communication
*
* @return an unmodifiable view, non-null, sorted
* @since 0.8.13
*/
public Map<Object, Object> getOptionsMap() {
return Collections.unmodifiableMap(_options);
}
/**
* @since 0.8.13
*/
public String getOption(String opt) {
return _options.getProperty(opt);
}
/**
* Specify the transport specific options necessary for communication.
* Makes a copy.
* @param options non-null
* @throws IllegalStateException if was already set
* @deprecated unused, use 3-arg constructor
*/
public void setOptions(Properties options) {
if (!_options.isEmpty())
throw new IllegalStateException();
_options.putAll(options);
}
/**
* Caching version of InetAddress.getByName(getOption("host")).getAddress(), which is slow.
* Caches numeric host names only.
* Will resolve but not cache resolution of DNS host names.
*
* @return IP or null
* @since 0.9.3
*/
public byte[] getIP() {
if (_ip != null)
return _ip;
byte[] rv = null;
String host = getHost();
if (host != null) {
rv = Addresses.getIP(host);
if (rv != null &&
(host.replaceAll("[0-9\\.]", "").length() == 0 ||
host.replaceAll("[0-9a-fA-F:]", "").length() == 0)) {
_ip = rv;
}
}
return rv;
}
/**
* Convenience, same as getOption("host").
* Does no parsing, so faster than getIP().
*
* @return host string or null
* @since IPv6
*/
public String getHost() {
return _options.getProperty(PROP_HOST);
}
/**
* Caching version of Integer.parseInt(getOption("port"))
* Caches valid ports 1-65535 only.
*
* @return 1-65535 or 0 if invalid
* @since 0.9.3
*/
public int getPort() {
if (_port != 0)
return _port;
String port = _options.getProperty(PROP_PORT);
if (port != null) {
try {
int rv = Integer.parseInt(port);
if (rv > 0 && rv <= 65535)
_port = rv;
} catch (NumberFormatException nfe) {}
}
return _port;
}
/**
* As of 0.9.3, expiration MUST be all zeros as it is ignored on
* readin and the signature will fail.
* Restored as of 0.9.12, wait several more releases before using.
* @throws IllegalStateException if was already read in
*/
public void readBytes(InputStream in) throws DataFormatException, IOException {
if (_transportStyle != null)
throw new IllegalStateException();
_cost = (short) DataHelper.readLong(in, 1);
_expiration = DataHelper.readLong(in, 8);
_transportStyle = DataHelper.readString(in);
// reduce Object proliferation
if (_transportStyle.equals("SSU"))
_transportStyle = "SSU";
else if (_transportStyle.equals("NTCP"))
_transportStyle = "NTCP";
DataHelper.readProperties(in, _options);
}
/**
* As of 0.9.3, expiration MUST be all zeros as it is ignored on
* readin and the signature will fail.
*/
public void writeBytes(OutputStream out) throws DataFormatException, IOException {
if (_transportStyle == null)
throw new DataFormatException("uninitialized");
DataHelper.writeLong(out, 1, _cost);
DataHelper.writeLong(out, 8, _expiration);
DataHelper.writeString(out, _transportStyle);
DataHelper.writeProperties(out, _options);
}
/**
* Transport, host, and port only.
* Never look at cost or other properties.
*/
@Override
public boolean equals(Object object) {
if (object == this) return true;
if ((object == null) || !(object instanceof RouterAddress)) return false;
RouterAddress addr = (RouterAddress) object;
return
getPort() == addr.getPort() &&
DataHelper.eq(getHost(), addr.getHost()) &&
DataHelper.eq(_transportStyle, addr._transportStyle);
//DataHelper.eq(_options, addr._options) &&
//DataHelper.eq(_expiration, addr._expiration);
}
/**
* Everything, including Transport, host, port, options, and cost
* @param addr may be null
* @since IPv6
*/
public boolean deepEquals(RouterAddress addr) {
return
equals(addr) &&
_cost == addr._cost &&
_options.equals(addr._options);
}
/**
* Just use a few items for speed (expiration is always null).
* Never look at cost or other properties.
*/
@Override
public int hashCode() {
return DataHelper.hashCode(_transportStyle) ^
DataHelper.hashCode(getIP()) ^
getPort();
}
/**
* This is used on peers.jsp so sort options so it looks better.
* We don't just use OrderedProperties for _options because DataHelper.writeProperties()
* sorts also.
*/
@Override
public String toString() {
StringBuilder buf = new StringBuilder(128);
buf.append("[RouterAddress: ");
buf.append("\n\tType: ").append(_transportStyle);
buf.append("\n\tCost: ").append(_cost);
if (_expiration > 0)
buf.append("\n\tExpiration: ").append(new Date(_expiration));
buf.append("\n\tOptions (").append(_options.size()).append("):");
for (Map.Entry<Object, Object> e : _options.entrySet()) {
String key = (String) e.getKey();
String val = (String) e.getValue();
buf.append("\n\t\t[").append(key).append("] = [").append(val).append("]");
}
buf.append("]");
return buf.toString();
}
}

View File

@ -1,37 +0,0 @@
package net.i2p.data;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
/**
* Defines the unique identifier of a router, including any certificate or
* public key.
*
* As of 0.9.9 this data structure is immutable after the two keys and the certificate
* are set; attempts to change them will throw an IllegalStateException.
*
* @author jrandom
*/
public class RouterIdentity extends KeysAndCert {
/**
* This router specified that they should not be used as a part of a tunnel,
* nor queried for the netDb, and that disclosure of their contact information
* should be limited.
*
*/
public boolean isHidden() {
return (_certificate != null) && (_certificate.getCertificateType() == Certificate.CERTIFICATE_TYPE_HIDDEN);
}
@Override
public boolean equals(Object o) {
return super.equals(o) && (o instanceof RouterIdentity);
}
}

View File

@ -1,681 +0,0 @@
package net.i2p.data;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.DigestInputStream;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import net.i2p.I2PAppContext;
import net.i2p.crypto.DSAEngine;
import net.i2p.crypto.SHA1;
import net.i2p.crypto.SHA1Hash;
import net.i2p.crypto.SHA256Generator;
import net.i2p.util.Clock;
import net.i2p.util.Log;
import net.i2p.util.OrderedProperties;
import net.i2p.util.SystemVersion;
/**
* Defines the data that a router either publishes to the global routing table or
* provides to trusted peers.
*
* For efficiency, the methods and structures here are now unsynchronized.
* Initialize the RI with readBytes(), or call the setters and then sign() in a single thread.
* Don't change it after that.
*
* To ensure integrity of the RouterInfo, methods that change an element of the
* RouterInfo will throw an IllegalStateException after the RouterInfo is signed.
*
* @author jrandom
*/
public class RouterInfo extends DatabaseEntry {
private RouterIdentity _identity;
private volatile long _published;
/**
* Addresses must be sorted by SHA256.
* When an RI is created, they are sorted in setAddresses().
* Save addresses in the order received so we need not resort.
*/
private final List<RouterAddress> _addresses;
/** may be null to save memory, no longer final */
private Set<Hash> _peers;
private final Properties _options;
private volatile boolean _validated;
private volatile boolean _isValid;
//private volatile String _stringified;
private volatile byte _byteified[];
private volatile int _hashCode;
private volatile boolean _hashCodeInitialized;
/** should we cache the byte and string versions _byteified ? **/
private boolean _shouldCache;
/** maybe we should check if we are floodfill? */
private static final boolean CACHE_ALL = SystemVersion.getMaxMemory() > 128*1024*1024l;
public static final String PROP_NETWORK_ID = "netId";
public static final String PROP_CAPABILITIES = "caps";
public static final char CAPABILITY_HIDDEN = 'H';
// Public string of chars which serve as bandwidth capacity markers
// NOTE: individual chars defined in Router.java
public static final String BW_CAPABILITY_CHARS = "KLMNO";
public RouterInfo() {
_addresses = new ArrayList<RouterAddress>(2);
_options = new OrderedProperties();
}
/**
* Used only by Router and PublishLocalRouterInfoJob.
* Copies ONLY the identity and peers.
* Does not copy published, addresses, options, or signature.
*/
public RouterInfo(RouterInfo old) {
this();
setIdentity(old.getIdentity());
//setPublished(old.getPublished());
//setAddresses(old.getAddresses());
setPeers(old.getPeers());
//setOptions(old.getOptions());
//setSignature(old.getSignature());
// copy over _byteified?
}
public long getDate() {
return _published;
}
protected KeysAndCert getKeysAndCert() {
return _identity;
}
public int getType() {
return KEY_TYPE_ROUTERINFO;
}
/**
* Retrieve the identity of the router represented
*
*/
public RouterIdentity getIdentity() {
return _identity;
}
/**
* Configure the identity of the router represented
*
* @throws IllegalStateException if RouterInfo is already signed
*/
public void setIdentity(RouterIdentity ident) {
if (_signature != null)
throw new IllegalStateException();
_identity = ident;
// We only want to cache the bytes for our own RI, which is frequently written.
// To cache for all RIs doubles the RI memory usage.
// setIdentity() is only called when we are creating our own RI.
// Otherwise, the data is populated with readBytes().
_shouldCache = true;
}
/**
* Retrieve the approximate date on which the info was published
* (essentially a version number for the routerInfo structure, except that
* it also contains freshness information - whether or not the router is
* currently publishing its information). This should be used to help expire
* old routerInfo structures
*
*/
public long getPublished() {
return _published;
}
/**
* Date on which it was published, in milliseconds since Midnight GMT on Jan 01, 1970
*
* @throws IllegalStateException if RouterInfo is already signed
*/
public void setPublished(long published) {
if (_signature != null)
throw new IllegalStateException();
_published = published;
}
/**
* Retrieve the set of RouterAddress structures at which this
* router can be contacted.
*
* @return unmodifiable view, non-null
*/
public Collection<RouterAddress> getAddresses() {
return Collections.unmodifiableCollection(_addresses);
}
/**
* Specify a set of RouterAddress structures at which this router
* can be contacted.
*
* Warning - Sorts the addresses here. Do not modify any address
* after calling this, as the sort order is based on the
* hash of the entire address structure.
*
* @param addresses may be null
* @throws IllegalStateException if RouterInfo is already signed or addresses previously set
*/
public void setAddresses(Collection<RouterAddress> addresses) {
if (_signature != null || !_addresses.isEmpty())
throw new IllegalStateException();
if (addresses != null) {
_addresses.addAll(addresses);
if (_addresses.size() > 1) {
// WARNING this sort algorithm cannot be changed, as it must be consistent
// network-wide. The signature is not checked at readin time, but only
// later, and the addresses are stored in a Set, not a List.
DataHelper.sortStructureList(_addresses);
}
}
}
/**
* Retrieve a set of SHA-256 hashes of RouterIdentities from routers
* this router can be reached through.
*
* @deprecated Implemented here but unused elsewhere
*/
public Set<Hash> getPeers() {
if (_peers == null)
return Collections.emptySet();
return _peers;
}
/**
* Specify a set of SHA-256 hashes of RouterIdentities from routers
* this router can be reached through.
*
* @deprecated Implemented here but unused elsewhere
* @throws IllegalStateException if RouterInfo is already signed
*/
public void setPeers(Set<Hash> peers) {
if (_signature != null)
throw new IllegalStateException();
if (peers == null || peers.isEmpty()) {
_peers = null;
return;
}
if (_peers == null)
_peers = new HashSet<Hash>(2);
synchronized (_peers) {
_peers.clear();
_peers.addAll(peers);
}
}
/**
* Retrieve a set of options or statistics that the router can expose.
*
* @deprecated use getOptionsMap()
* @return sorted, non-null, NOT a copy, do not modify!!!
*/
public Properties getOptions() {
return _options;
}
/**
* Retrieve a set of options or statistics that the router can expose.
*
* @return an unmodifiable view, non-null, sorted
* @since 0.8.13
*/
public Map<Object, Object> getOptionsMap() {
return Collections.unmodifiableMap(_options);
}
public String getOption(String opt) {
return _options.getProperty(opt);
}
/**
* Configure a set of options or statistics that the router can expose.
* Makes a copy.
*
* @param options if null, clears current options
* @throws IllegalStateException if RouterInfo is already signed
*/
public void setOptions(Properties options) {
if (_signature != null)
throw new IllegalStateException();
_options.clear();
if (options != null)
_options.putAll(options);
}
/**
* Write out the raw payload of the routerInfo, excluding the signature. This
* caches the data in memory if possible.
*
* @throws DataFormatException if the data is somehow b0rked (missing props, etc)
*/
protected byte[] getBytes() throws DataFormatException {
if (_byteified != null) return _byteified;
if (_identity == null) throw new DataFormatException("Router identity isn't set? wtf!");
//long before = Clock.getInstance().now();
ByteArrayOutputStream out = new ByteArrayOutputStream(2*1024);
try {
_identity.writeBytes(out);
// avoid thrashing objects
//DataHelper.writeDate(out, new Date(_published));
DataHelper.writeLong(out, 8, _published);
int sz = _addresses.size();
if (sz <= 0 || isHidden()) {
// Do not send IP address to peers in hidden mode
DataHelper.writeLong(out, 1, 0);
} else {
DataHelper.writeLong(out, 1, sz);
for (RouterAddress addr : _addresses) {
addr.writeBytes(out);
}
}
// XXX: what about peers?
// answer: they're always empty... they're a placeholder for one particular
// method of trusted links, which isn't implemented in the router
// at the moment, and may not be later.
int psz = _peers == null ? 0 : _peers.size();
DataHelper.writeLong(out, 1, psz);
if (psz > 0) {
Collection<Hash> peers = _peers;
if (psz > 1)
// WARNING this sort algorithm cannot be changed, as it must be consistent
// network-wide. The signature is not checked at readin time, but only
// later, and the hashes are stored in a Set, not a List.
peers = (Collection<Hash>) DataHelper.sortStructures(peers);
for (Hash peerHash : peers) {
peerHash.writeBytes(out);
}
}
DataHelper.writeProperties(out, _options);
} catch (IOException ioe) {
throw new DataFormatException("IO Error getting bytes", ioe);
}
byte data[] = out.toByteArray();
//if (_log.shouldLog(Log.DEBUG)) {
// long after = Clock.getInstance().now();
// _log.debug("getBytes() took " + (after - before) + "ms");
//}
if (CACHE_ALL || _shouldCache)
_byteified = data;
return data;
}
/**
* Determine whether this router info is authorized with a valid signature
*
*/
public boolean isValid() {
if (!_validated) doValidate();
return _isValid;
}
/**
* Same as isValid()
* @since 0.9
*/
@Override
public boolean verifySignature() {
return isValid();
}
/**
* which network is this routerInfo a part of. configured through the property
* PROP_NETWORK_ID
* @return -1 if unknown
*/
public int getNetworkId() {
String id = _options.getProperty(PROP_NETWORK_ID);
if (id != null) {
try {
return Integer.parseInt(id);
} catch (NumberFormatException nfe) {}
}
return -1;
}
/**
* what special capabilities this router offers
* @return non-null, empty string if none
*/
public String getCapabilities() {
String capabilities = _options.getProperty(PROP_CAPABILITIES);
if (capabilities != null)
return capabilities;
else
return "";
}
/**
* Is this a hidden node?
*/
public boolean isHidden() {
return (getCapabilities().indexOf(CAPABILITY_HIDDEN) != -1);
}
/**
* Return a string representation of this node's bandwidth tier,
* or "Unknown"
*/
public String getBandwidthTier() {
String bwTiers = BW_CAPABILITY_CHARS;
String bwTier = "Unknown";
String capabilities = getCapabilities();
// Iterate through capabilities, searching for known bandwidth tier
for (int i = 0; i < capabilities.length(); i++) {
if (bwTiers.indexOf(String.valueOf(capabilities.charAt(i))) != -1) {
bwTier = String.valueOf(capabilities.charAt(i));
break;
}
}
return (bwTier);
}
/**
* @throws IllegalStateException if RouterInfo is already signed
*/
public void addCapability(char cap) {
if (_signature != null)
throw new IllegalStateException();
String caps = _options.getProperty(PROP_CAPABILITIES);
if (caps == null)
_options.setProperty(PROP_CAPABILITIES, ""+cap);
else if (caps.indexOf(cap) == -1)
_options.setProperty(PROP_CAPABILITIES, caps + cap);
}
/**
* @throws IllegalStateException if RouterInfo is already signed
*/
public void delCapability(char cap) {
if (_signature != null)
throw new IllegalStateException();
String caps = _options.getProperty(PROP_CAPABILITIES);
int idx;
if (caps == null) {
return;
} else if ((idx = caps.indexOf(cap)) == -1) {
return;
} else {
StringBuilder buf = new StringBuilder(caps);
while ( (idx = buf.indexOf(""+cap)) != -1)
buf.deleteCharAt(idx);
_options.setProperty(PROP_CAPABILITIES, buf.toString());
}
}
/**
* Determine whether the router was published recently (within the given age milliseconds).
* The age should be large enough to take into consideration any clock fudge factor, so
* values such as 1 or 2 hours are probably reasonable.
*
* @param maxAgeMs milliseconds between the current time and publish date to check
* @return true if it was published recently, false otherwise
*/
public boolean isCurrent(long maxAgeMs) {
long earliestExpire = Clock.getInstance().now() - maxAgeMs;
if (_published < earliestExpire)
return false;
return true;
}
/**
* Pull the first workable target address for the given transport.
* Use to check for any address. For all addresses, use getTargetAddresses(),
* which you probably want if you care about IPv6.
*/
public RouterAddress getTargetAddress(String transportStyle) {
for (RouterAddress addr : _addresses) {
if (addr.getTransportStyle().equals(transportStyle))
return addr;
}
return null;
}
/**
* For multiple addresses per-transport (IPv4 or IPv6)
* @return non-null
* @since 0.7.11
*/
public List<RouterAddress> getTargetAddresses(String transportStyle) {
List<RouterAddress> ret = new ArrayList<RouterAddress>(_addresses.size());
for (RouterAddress addr : _addresses) {
if(addr.getTransportStyle().equals(transportStyle))
ret.add(addr);
}
return ret;
}
/**
* Actually validate the signature
*/
private void doValidate() {
_isValid = super.verifySignature();
_validated = true;
if (!_isValid) {
Log log = I2PAppContext.getGlobalContext().logManager().getLog(RouterInfo.class);
byte data[] = null;
try {
data = getBytes();
} catch (DataFormatException dfe) {
log.error("Error validating", dfe);
return;
}
log.error("Invalid [" + SHA256Generator.getInstance().calculateHash(data).toBase64()
+ (log.shouldLog(Log.WARN) ? ("]\n" + toString()) : ""),
new Exception("Signature failed"));
}
}
/**
* This does NOT validate the signature
*
* @throws IllegalStateException if RouterInfo was already read in
*/
public void readBytes(InputStream in) throws DataFormatException, IOException {
readBytes(in, false);
}
/**
* If verifySig is true,
* this validates the signature while reading in,
* and throws a DataFormatException if the sig is invalid.
* This is faster than reserializing to validate later.
*
* @throws IllegalStateException if RouterInfo was already read in
* @since 0.9
*/
public void readBytes(InputStream in, boolean verifySig) throws DataFormatException, IOException {
if (_signature != null)
throw new IllegalStateException();
InputStream din;
MessageDigest digest;
if (verifySig) {
digest = SHA1.getInstance();
din = new DigestInputStream(in, digest);
} else {
digest = null;
din = in;
}
_identity = new RouterIdentity();
_identity.readBytes(din);
// avoid thrashing objects
//Date when = DataHelper.readDate(in);
//if (when == null)
// _published = 0;
//else
// _published = when.getTime();
_published = DataHelper.readLong(din, 8);
int numAddresses = (int) DataHelper.readLong(din, 1);
for (int i = 0; i < numAddresses; i++) {
RouterAddress address = new RouterAddress();
address.readBytes(din);
_addresses.add(address);
}
int numPeers = (int) DataHelper.readLong(din, 1);
if (numPeers == 0) {
_peers = null;
} else {
_peers = new HashSet<Hash>(numPeers);
for (int i = 0; i < numPeers; i++) {
Hash peerIdentityHash = new Hash();
peerIdentityHash.readBytes(din);
_peers.add(peerIdentityHash);
}
}
DataHelper.readProperties(din, _options);
_signature = new Signature(_identity.getSigningPublicKey().getType());
_signature.readBytes(in);
if (verifySig) {
SHA1Hash hash = new SHA1Hash(digest.digest());
_isValid = DSAEngine.getInstance().verifySignature(_signature, hash, _identity.getSigningPublicKey());
_validated = true;
if (!_isValid) {
throw new DataFormatException("Bad sig");
}
}
//_log.debug("Read routerInfo: " + toString());
}
/**
* This does NOT validate the signature
*/
public void writeBytes(OutputStream out) throws DataFormatException, IOException {
if (_identity == null) throw new DataFormatException("Missing identity");
if (_published < 0) throw new DataFormatException("Invalid published date: " + _published);
if (_signature == null) throw new DataFormatException("Signature is null");
//if (!isValid())
// throw new DataFormatException("Data is not valid");
ByteArrayOutputStream baos = new ByteArrayOutputStream(2048);
baos.write(getBytes());
_signature.writeBytes(baos);
byte data[] = baos.toByteArray();
//_log.debug("Writing routerInfo [len=" + data.length + "]: " + toString());
out.write(data);
}
@Override
public boolean equals(Object object) {
if (object == this) return true;
if ((object == null) || !(object instanceof RouterInfo)) return false;
RouterInfo info = (RouterInfo) object;
return
_published == info.getPublished()
&& DataHelper.eq(_signature, info.getSignature())
&& DataHelper.eq(_identity, info.getIdentity());
// Let's speed up the NetDB
//&& DataHelper.eq(_addresses, info.getAddresses())
//&& DataHelper.eq(_options, info.getOptions())
//&& DataHelper.eq(getPeers(), info.getPeers());
}
@Override
public int hashCode() {
if (!_hashCodeInitialized) {
_hashCode = DataHelper.hashCode(_identity) + (int) _published;
_hashCodeInitialized = true;
}
return _hashCode;
}
@Override
public String toString() {
//if (_stringified != null) return _stringified;
StringBuilder buf = new StringBuilder(1024);
buf.append("[RouterInfo: ");
buf.append("\n\tIdentity: ").append(_identity);
buf.append("\n\tSignature: ").append(_signature);
buf.append("\n\tPublished: ").append(new Date(_published));
if (_peers != null) {
buf.append("\n\tPeers (").append(_peers.size()).append("):");
for (Hash hash : _peers) {
buf.append("\n\t\tPeer hash: ").append(hash);
}
}
buf.append("\n\tOptions (").append(_options.size()).append("):");
for (Map.Entry<Object, Object> e : _options.entrySet()) {
String key = (String) e.getKey();
String val = (String) e.getValue();
buf.append("\n\t\t[").append(key).append("] = [").append(val).append("]");
}
if (!_addresses.isEmpty()) {
buf.append("\n\tAddresses (").append(_addresses.size()).append("):");
for (RouterAddress addr : _addresses) {
buf.append("\n\t").append(addr);
}
}
buf.append("]");
String rv = buf.toString();
//_stringified = rv;
return rv;
}
/**
* Print out routerinfos from files specified on the command line.
* Exits 1 if any RI is invalid, fails signature, etc.
* @since 0.8
*/
public static void main(String[] args) {
if (args.length <= 0) {
System.err.println("Usage: RouterInfo file ...");
System.exit(1);
}
boolean fail = false;
for (int i = 0; i < args.length; i++) {
RouterInfo ri = new RouterInfo();
InputStream is = null;
try {
is = new java.io.FileInputStream(args[i]);
ri.readBytes(is);
if (ri.isValid()) {
System.out.println(ri.toString());
} else {
System.err.println("Router info " + args[i] + " is invalid");
fail = true;
}
} catch (Exception e) {
System.err.println("Error reading " + args[i] + ": " + e);
fail = true;
} finally {
if (is != null) {
try { is.close(); } catch (IOException ioe) {}
}
}
}
if (fail)
System.exit(1);
}
}