diff --git a/src/main/java/network/crypta/node/NodeControlMessageHandler.java b/src/main/java/network/crypta/node/NodeControlMessageHandler.java index 6cad3fd222..e7d0384dd0 100644 --- a/src/main/java/network/crypta/node/NodeControlMessageHandler.java +++ b/src/main/java/network/crypta/node/NodeControlMessageHandler.java @@ -8,7 +8,6 @@ import network.crypta.io.comm.MessageType; import network.crypta.io.comm.NotConnectedException; import network.crypta.io.comm.Peer; -import network.crypta.node.updater.NodeUpdateManager; import network.crypta.support.Fields; import network.crypta.support.ShortBuffer; import org.slf4j.Logger; @@ -49,7 +48,7 @@ final class NodeControlMessageHandler { private static final Logger LOG = LoggerFactory.getLogger(NodeControlMessageHandler.class); /** - * Owning node used to access network, messaging, and updater subsystems. + * The owning node used to access network, messaging, and updater subsystems. * *
This reference is stable for the lifetime of the handler and is not expected to be {@code * null}. It provides the necessary entry points for side effects triggered by control messages. @@ -144,7 +143,7 @@ private boolean handleDetectedAddress(Message m, PeerNode source, MessageType sp } /** - * Handles darknet visibility messages, if the source supports them. + * Handles darknet visibility messages if the source supports them. * * @param m message carrying visibility payload; must not be null * @param source peer that sent the message; must not be null @@ -229,23 +228,6 @@ private boolean handleUomMessages(Message m, PeerNode source, MessageType spec) .getUpdateOverMandatory() .handleSendingRevocation(m, source); } - if (Objects.equals(spec, DMT.CryptadUOMRequestMainJar) - && NodeUpdateManager.SUPPORTS_JAR_UOM - && source.isRealConnection()) { - node.services().nodeUpdater().getUpdateOverMandatory().handleRequestJar(m, source); - return true; - } - if (Objects.equals(spec, DMT.CryptadUOMSendingMainJar) - && NodeUpdateManager.SUPPORTS_JAR_UOM - && source.isRealConnection()) { - return node.services().nodeUpdater().getUpdateOverMandatory().handleSendingMain(m, source); - } - if (Objects.equals(spec, DMT.CryptadUOMFetchDependency) - && NodeUpdateManager.SUPPORTS_JAR_UOM - && source.isRealConnection()) { - node.services().nodeUpdater().getUpdateOverMandatory().handleFetchDependency(m, source); - return true; - } return false; } @@ -359,13 +341,14 @@ private void handleDisconnect(final Message m, final PeerNode source) { /** * Performs the actual disconnect workflow and processes parting metadata. * - * @param m original disconnect message containing flags and node-to-node data; must not be null + * @param m the original disconnect message containing flags and node-to-node data; must not be + * null * @param source peer being disconnected; must not be null */ private void finishDisconnect(final Message m, final PeerNode source) { source.disconnected(true, true); - // If true, remove from active routing table, likely to be down for a while. - // Otherwise, just dump all current connection state and keep trying to connect. + // If true, remove from the active routing table, likely to be down for a while. + // Otherwise, just dump all current connection states and keep trying to connect. boolean remove = m.getBoolean(DMT.REMOVE); if (remove) { node.network().peers().messenger().disconnectAndRemove(source, false, false, false); @@ -376,7 +359,7 @@ private void finishDisconnect(final Message m, final PeerNode source) { peerNode.getName()); } // If true, purge all references to this node. Otherwise, we can keep the node - // around in secondary tables etc. in order to more easily reconnect later. + // around in secondary tables etc. to more easily reconnect later. // (Mostly used on opennet) boolean purge = m.getBoolean(DMT.PURGE); if (purge) { diff --git a/src/main/java/network/crypta/node/updater/NodeUpdateManager.java b/src/main/java/network/crypta/node/updater/NodeUpdateManager.java index 61f65aac7d..a1d47ae6d2 100644 --- a/src/main/java/network/crypta/node/updater/NodeUpdateManager.java +++ b/src/main/java/network/crypta/node/updater/NodeUpdateManager.java @@ -35,6 +35,7 @@ import network.crypta.support.HTMLNode; import network.crypta.support.api.BooleanCallback; import network.crypta.support.api.Bucket; +import network.crypta.support.api.IntCallback; import network.crypta.support.api.StringCallback; import network.crypta.support.io.BucketTools; import network.crypta.support.io.FileUtil; @@ -83,6 +84,18 @@ public final class NodeUpdateManager { // L10n parameter keys and repeated URL query parts private static final String L10N_PARAM_ERROR = "error"; private static final String QUERY_TEXT_PLAIN = "?type=text/plain"; + private static final String URI_TYPE_SEPARATOR = "@"; + private static final String URI_PATH_SEPARATOR = "/"; + private static final String UPDATE_URI_PREFIX = "USK@"; + private static final String UPDATE_URI_DOC_NAME = "info"; + private static final String LEGACY_UPDATE_URI_DOC_NAME = "jar"; + private static final String REVOCATION_URI_PREFIX = "SSK@"; + private static final String REVOCATION_URI_DOC_NAME = "revoked"; + private static final String LAST_KNOWN_GOOD_FETCHED_EDITION_OPTION = + "lastKnownGoodFetchedEdition"; + private static final String LAST_KNOWN_GOOD_FETCHED_EDITION_KEY_OPTION = + "lastKnownGoodFetchedEditionKey"; + private static final String REVOCATION_URI_OPTION = "revocationURI"; /** * The last build on the previous key with Java 7 support. Older nodes can update to this point @@ -90,14 +103,13 @@ public final class NodeUpdateManager { */ public static final int TRANSITION_VERSION = 1481; - /** The URI for post-TRANSITION_VERSION builds' freenet.jar. */ + /** Public key material for post-TRANSITION_VERSION update URIs. */ public static final String UPDATE_URI = - "USK@uQnFwn0aEFSAZihnSDduEHUd3GUmGg68ATn5R95MKJo,mcNiZqosfZ1F~PkZY8v1TuDKsY6noda-hGRXvu7uUFc,AQACAAE/jar/" - + Version.currentBuildNumber(); + "uQnFwn0aEFSAZihnSDduEHUd3GUmGg68ATn5R95MKJo,mcNiZqosfZ1F~PkZY8v1TuDKsY6noda-hGRXvu7uUFc,AQACAAE"; - /** Default USK/SSK pointing to revocation content that disables auto‑update. */ + /** Public key material used to derive the revocation key URI. */ public static final String REVOCATION_URI = - "SSK@TAnVLWtrGguuIi3fXkf8OmT5Pmy2Hduai18FUCP0uAU,tMg8t4kLktzmz~uFC6jk~-CUNv1mQ-C573sjLeg0alU,AQACAAE/revoked"; + "TAnVLWtrGguuIi3fXkf8OmT5Pmy2Hduai18FUCP0uAU,tMg8t4kLktzmz~uFC6jk~-CUNv1mQ-C573sjLeg0alU,AQACAAE"; // These are necessary to prevent DoS. /** Maximum allowed decoded byte length of a revocation document. */ @@ -109,9 +121,6 @@ public final class NodeUpdateManager { /** Maximum on‑disk blob length in bytes for a persisted revocation document. */ public static final long MAX_REVOCATION_KEY_BLOB_LENGTH = 128L * 1024L; - /** Maximum allowed size in bytes for the historical main JAR (legacy paths only). */ - public static final long MAX_MAIN_JAR_LENGTH = 48L * 1024L * 1024L; // 48MiB - /** Maximum allowed size in bytes for the IPv4‐to‐country database. */ public static final long MAX_IP_TO_COUNTRY_LENGTH = 24L * 1024L * 1024L; @@ -121,19 +130,15 @@ public final class NodeUpdateManager { /** Remaining time for a legacy final-check timer. */ public static final long TIME_REMAINING_ON_CHECK = 0L; - /** Legacy timestamp for when normal main-jar fetching started. */ - static final long STARTED_FETCHING_NEXT_MAIN_JAR_TIMESTAMP = 0L; - /** Whether dependency checks are currently considered broken. */ public static final boolean BROKEN_DEPENDENCIES = false; - /** Whether legacy main-jar Update-over-Mandatory flows are enabled. */ - public static final boolean SUPPORTS_JAR_UOM = false; - // Installer/seednodes length caps removed with deprecated auto-fetch paths private FreenetURI updateURI; private FreenetURI revocationURI; + private volatile int lastKnownGoodFetchedEdition; + private volatile String lastKnownGoodFetchedEditionKey; // Legacy MainJarUpdater removed; core package updater is used instead. // Package-based core updater (Kotlin) @@ -230,14 +235,15 @@ public NodeUpdateManager(Node node, Config config) throws InvalidConfigValueExce 3, true, true, "NodeUpdateManager.updateURI", "NodeUpdateManager.updateURILong"), new UpdateURICallback()); + String configuredUpdateUriValue = updaterConfig.getString("URI"); try { - updateURI = new FreenetURI(updaterConfig.getString("URI")); + updateURI = parseConfiguredUpdateURI(configuredUpdateUriValue); } catch (MalformedURLException e) { throw new InvalidConfigValueException( l10n("invalidUpdateURI", L10N_PARAM_ERROR, e.getLocalizedMessage())); } + migrateLegacyUpdateUriValueIfNeeded(updaterConfig, configuredUpdateUriValue); - updateURI = updateURI.setSuggestedEdition(Version.currentBuildNumber()); if (updateURI.hasMetaStrings()) { throw new InvalidConfigValueException(l10n("updateURIMustHaveNoMetaStrings")); } @@ -246,7 +252,7 @@ public NodeUpdateManager(Node node, Config config) throws InvalidConfigValueExce } updaterConfig.register( - "revocationURI", + REVOCATION_URI_OPTION, REVOCATION_URI, new Option.Meta( 4, @@ -256,12 +262,43 @@ public NodeUpdateManager(Node node, Config config) throws InvalidConfigValueExce "NodeUpdateManager.revocationURILong"), new UpdateRevocationURICallback()); + String configuredRevocationUriValue = updaterConfig.getString(REVOCATION_URI_OPTION); try { - revocationURI = new FreenetURI(updaterConfig.getString("revocationURI")); + revocationURI = parseConfiguredRevocationURI(configuredRevocationUriValue); } catch (MalformedURLException e) { throw new InvalidConfigValueException( l10n("invalidRevocationURI", L10N_PARAM_ERROR, e.getLocalizedMessage())); } + migrateLegacyRevocationUriValueIfNeeded(updaterConfig, configuredRevocationUriValue); + + updaterConfig.register( + LAST_KNOWN_GOOD_FETCHED_EDITION_OPTION, + -1, + new Option.Meta( + 5, + true, + false, + "NodeUpdateManager.lastKnownGoodFetchedEdition", + "NodeUpdateManager.lastKnownGoodFetchedEditionLong"), + new LastKnownGoodFetchedEditionCallback(), + false); + lastKnownGoodFetchedEdition = + sanitizeFetchedEdition(updaterConfig.getInt(LAST_KNOWN_GOOD_FETCHED_EDITION_OPTION)); + + updaterConfig.register( + LAST_KNOWN_GOOD_FETCHED_EDITION_KEY_OPTION, + UPDATE_URI, + new Option.Meta( + 6, + true, + false, + "NodeUpdateManager.lastKnownGoodFetchedEditionKey", + "NodeUpdateManager.lastKnownGoodFetchedEditionKeyLong"), + new LastKnownGoodFetchedEditionKeyCallback()); + lastKnownGoodFetchedEditionKey = + sanitizeFetchedEditionScope( + updaterConfig.getString(LAST_KNOWN_GOOD_FETCHED_EDITION_KEY_OPTION)); + alignLastKnownGoodFetchedEditionToCurrentUpdateScope(); // Deprecated UI option: updateSeednodes (no longer shown on the Auto-update page). // Keep internal default as false; accept but ignore legacy config values. @@ -747,18 +784,64 @@ public synchronized void addChangelogLinks(long version, HTMLNode node) { */ public synchronized void setURI(FreenetURI uri) { NodeUpdater updater; + int subscribeEditionSeed; synchronized (this) { if (updateURI.equals(uri)) { return; } + String oldUpdateScope = normalizeFetchedEditionScope(updateURI); updateURI = uri; updateURI = updateURI.setSuggestedEdition(Version.currentBuildNumber()); + String newUpdateScope = normalizeFetchedEditionScope(updateURI); + if (!newUpdateScope.equals(oldUpdateScope)) { + resetLastKnownGoodFetchedEditionLocked(newUpdateScope); + } + subscribeEditionSeed = computeCoreUpdaterSubscribeEditionSeedLocked(newUpdateScope); updater = coreUpdater; if (updater == null) { return; } } - updater.onChangeURI(uri); + updater.onChangeURI(uri, subscribeEditionSeed); + } + + /** + * Records a successfully fetched core-info edition for startup seeding. + * + *
The hint is scoped to the normalized update URI: editions fetched from a stale or different + * update scope are ignored. + */ + void recordSuccessfulCoreInfoFetch(FreenetURI fetchedUri, int fetchedEdition) { + if (fetchedEdition < 0 || fetchedUri == null) { + return; + } + synchronized (this) { + String fetchedScope = normalizeFetchedEditionScope(fetchedUri); + String currentScope = normalizeFetchedEditionScope(updateURI); + if (!currentScope.equals(fetchedScope)) { + if (LOG.isDebugEnabled()) { + LOG.debug( + "Ignoring fetched edition {} for stale scope {}; current scope {}", + fetchedEdition, + fetchedScope, + currentScope); + } + return; + } + if (!currentScope.equals(lastKnownGoodFetchedEditionKey)) { + lastKnownGoodFetchedEditionKey = currentScope; + lastKnownGoodFetchedEdition = -1; + } + if (fetchedEdition > lastKnownGoodFetchedEdition) { + lastKnownGoodFetchedEdition = fetchedEdition; + if (LOG.isDebugEnabled()) { + LOG.debug( + "Recorded last known good fetched edition {} for scope {}", + lastKnownGoodFetchedEdition, + currentScope); + } + } + } } /** @@ -1081,18 +1164,45 @@ public void set(Boolean val) throws InvalidConfigValueException { } } + class LastKnownGoodFetchedEditionCallback extends IntCallback { + + @Override + public Integer get() { + return lastKnownGoodFetchedEdition; + } + + @Override + public void set(Integer val) { + lastKnownGoodFetchedEdition = sanitizeFetchedEdition(val); + } + } + + class LastKnownGoodFetchedEditionKeyCallback extends StringCallback { + + @Override + public String get() { + return lastKnownGoodFetchedEditionKey; + } + + @Override + public void set(String val) { + lastKnownGoodFetchedEditionKey = sanitizeFetchedEditionScope(val); + alignLastKnownGoodFetchedEditionToCurrentUpdateScope(); + } + } + class UpdateURICallback extends StringCallback { @Override public String get() { - return getURI().toString(false, false); + return toConfigUpdateUriValue(getURI()); } @Override public void set(String val) throws InvalidConfigValueException { FreenetURI uri; try { - uri = new FreenetURI(val); + uri = parseConfiguredUpdateURI(val); } catch (MalformedURLException e) { throw new InvalidConfigValueException( l10n("invalidUpdateURI", L10N_PARAM_ERROR, e.getLocalizedMessage())); @@ -1116,14 +1226,14 @@ public UpdateRevocationURICallback() { @Override public String get() { - return getRevocationURI().toString(false, false); + return toConfigRevocationUriValue(getRevocationURI()); } @Override public void set(String val) throws InvalidConfigValueException { FreenetURI uri; try { - uri = new FreenetURI(val); + uri = parseConfiguredRevocationURI(val); } catch (MalformedURLException e) { throw new InvalidConfigValueException( l10n("invalidRevocationURI", L10N_PARAM_ERROR, e.getLocalizedMessage())); @@ -1132,6 +1242,273 @@ public void set(String val) throws InvalidConfigValueException { } } + private static FreenetURI parseConfiguredUpdateURI(String configuredValue) + throws MalformedURLException { + String normalizedLegacyKey = extractLegacyUpdatePublicKeyMaterial(configuredValue); + if (normalizedLegacyKey != null) { + FreenetURI parsed = new FreenetURI(expandUpdateUriFromPublicKey(normalizedLegacyKey)); + return parsed.setSuggestedEdition(Version.currentBuildNumber()); + } + + FreenetURI parsed = new FreenetURI(trimConfigValue(configuredValue)); + return parsed.setSuggestedEdition(Version.currentBuildNumber()); + } + + private static FreenetURI parseConfiguredRevocationURI(String configuredValue) + throws MalformedURLException { + String normalizedLegacyKey = extractLegacyRevocationPublicKeyMaterial(configuredValue); + if (normalizedLegacyKey != null) { + return new FreenetURI(expandRevocationUriFromPublicKey(normalizedLegacyKey)); + } + return new FreenetURI(trimConfigValue(configuredValue)); + } + + private static String extractLegacyUpdatePublicKeyMaterial(String configuredValue) + throws MalformedURLException { + String trimmed = trimConfigValue(configuredValue); + if (isBarePublicKey(trimmed)) { + return trimmed; + } + + FreenetURI parsed = new FreenetURI(trimmed); + if (!parsed.isUSK() || parsed.hasMetaStrings()) { + return null; + } + String docName = parsed.getDocName(); + if (!UPDATE_URI_DOC_NAME.equals(docName) && !LEGACY_UPDATE_URI_DOC_NAME.equals(docName)) { + return null; + } + return extractPublicKeyMaterial(parsed); + } + + private static String extractLegacyRevocationPublicKeyMaterial(String configuredValue) + throws MalformedURLException { + String trimmed = trimConfigValue(configuredValue); + if (isBarePublicKey(trimmed)) { + return trimmed; + } + + FreenetURI parsed = new FreenetURI(trimmed); + if (!parsed.isSSK() || parsed.hasMetaStrings()) { + return null; + } + if (!REVOCATION_URI_DOC_NAME.equals(parsed.getDocName())) { + return null; + } + return extractPublicKeyMaterial(parsed); + } + + private static String expandUpdateUriFromPublicKey(String keyMaterial) { + return UPDATE_URI_PREFIX + + keyMaterial + + URI_PATH_SEPARATOR + + UPDATE_URI_DOC_NAME + + URI_PATH_SEPARATOR + + Version.currentBuildNumber(); + } + + private static String expandRevocationUriFromPublicKey(String keyMaterial) { + return REVOCATION_URI_PREFIX + keyMaterial + URI_PATH_SEPARATOR + REVOCATION_URI_DOC_NAME; + } + + private static String toConfigUpdateUriValue(FreenetURI uri) { + if (uri.isUSK() + && !uri.hasMetaStrings() + && (UPDATE_URI_DOC_NAME.equals(uri.getDocName()) + || LEGACY_UPDATE_URI_DOC_NAME.equals(uri.getDocName()))) { + return extractPublicKeyMaterial(uri); + } + return uri.toString(false, false); + } + + private static String toConfigRevocationUriValue(FreenetURI uri) { + if (uri.isSSK() && !uri.hasMetaStrings() && REVOCATION_URI_DOC_NAME.equals(uri.getDocName())) { + return extractPublicKeyMaterial(uri); + } + return uri.toString(false, false); + } + + private void migrateLegacyUpdateUriValueIfNeeded(SubConfig updaterConfig, String configuredValue) + throws InvalidConfigValueException { + migrateLegacyOptionValueIfNeeded( + updaterConfig, + "URI", + configuredValue, + NodeUpdateManager::extractLegacyUpdatePublicKeyMaterial); + } + + private void migrateLegacyRevocationUriValueIfNeeded( + SubConfig updaterConfig, String configuredValue) throws InvalidConfigValueException { + migrateLegacyOptionValueIfNeeded( + updaterConfig, + REVOCATION_URI_OPTION, + configuredValue, + NodeUpdateManager::extractLegacyRevocationPublicKeyMaterial); + } + + private static void migrateLegacyOptionValueIfNeeded( + SubConfig updaterConfig, + String optionName, + String configuredValue, + LegacyKeyExtractor extractor) + throws InvalidConfigValueException { + if (isBarePublicKey(trimConfigValue(configuredValue))) { + return; + } + + String extracted; + try { + extracted = extractor.extract(configuredValue); + } catch (MalformedURLException e) { + throw new InvalidConfigValueException(e.getLocalizedMessage()); + } + if (extracted == null) { + return; + } + + Option> option = updaterConfig.getOption(optionName); + if (option == null) { + return; + } + option.setInitialValue(extracted); + } + + private static String extractPublicKeyMaterial(FreenetURI uri) { + String fullUri = uri.toString(false, false); + if (fullUri == null || fullUri.isEmpty()) { + return ""; + } + int typeSeparator = fullUri.indexOf(URI_TYPE_SEPARATOR); + if (typeSeparator < 0) { + return fullUri; + } + int pathSeparator = fullUri.indexOf(URI_PATH_SEPARATOR, typeSeparator + 1); + if (pathSeparator < 0) { + return fullUri.substring(typeSeparator + 1); + } + return fullUri.substring(typeSeparator + 1, pathSeparator); + } + + private static int sanitizeFetchedEdition(Integer edition) { + if (edition == null) { + return -1; + } + return Math.max(-1, edition); + } + + private static String sanitizeFetchedEditionScope(String value) { + String trimmed = trimConfigValue(value); + if (trimmed == null || trimmed.isEmpty()) { + return ""; + } + if (isBarePublicKey(trimmed)) { + return trimmed; + } + try { + FreenetURI parsed = new FreenetURI(trimmed); + if (!parsed.isUSK() || parsed.hasMetaStrings()) { + return ""; + } + return normalizeFetchedEditionScope(parsed); + } catch (MalformedURLException _) { + return ""; + } + } + + private synchronized void alignLastKnownGoodFetchedEditionToCurrentUpdateScope() { + String currentUpdateScope = normalizeFetchedEditionScope(updateURI); + if (alignLegacyBareFetchedEditionScope(currentUpdateScope)) { + return; + } + if (!currentUpdateScope.equals(lastKnownGoodFetchedEditionKey)) { + if (LOG.isDebugEnabled()) { + LOG.debug( + "Resetting persisted fetched edition {} due to scope mismatch: persisted={}," + + " current={}", + lastKnownGoodFetchedEdition, + lastKnownGoodFetchedEditionKey, + currentUpdateScope); + } + resetLastKnownGoodFetchedEditionLocked(currentUpdateScope); + return; + } + lastKnownGoodFetchedEdition = sanitizeFetchedEdition(lastKnownGoodFetchedEdition); + } + + private boolean alignLegacyBareFetchedEditionScope(String currentUpdateScope) { + if (!isBarePublicKey(lastKnownGoodFetchedEditionKey)) { + return false; + } + if (!extractPublicKeyMaterial(updateURI).equals(lastKnownGoodFetchedEditionKey)) { + if (LOG.isDebugEnabled()) { + LOG.debug( + "Resetting persisted fetched edition {} due to legacy key mismatch: persisted={}," + + " current={}", + lastKnownGoodFetchedEdition, + lastKnownGoodFetchedEditionKey, + currentUpdateScope); + } + resetLastKnownGoodFetchedEditionLocked(currentUpdateScope); + return true; + } + String legacyInfoScope = + normalizeFetchedEditionScope(updateURI.setDocName(UPDATE_URI_DOC_NAME)); + if (!legacyInfoScope.equals(currentUpdateScope)) { + if (LOG.isDebugEnabled()) { + LOG.debug( + "Resetting persisted fetched edition {} while migrating legacy key {} to custom" + + " scope {}", + lastKnownGoodFetchedEdition, + lastKnownGoodFetchedEditionKey, + currentUpdateScope); + } + resetLastKnownGoodFetchedEditionLocked(currentUpdateScope); + return true; + } + lastKnownGoodFetchedEditionKey = currentUpdateScope; + return false; + } + + private int computeCoreUpdaterSubscribeEditionSeedLocked(String currentUpdateScope) { + if (!currentUpdateScope.equals(lastKnownGoodFetchedEditionKey)) { + return Version.currentBuildNumber(); + } + int highestKnownEdition = sanitizeFetchedEdition(lastKnownGoodFetchedEdition); + if (highestKnownEdition > Version.currentBuildNumber()) { + return highestKnownEdition - 1; + } + return Version.currentBuildNumber(); + } + + private void resetLastKnownGoodFetchedEditionLocked(String currentUpdateScope) { + lastKnownGoodFetchedEdition = -1; + lastKnownGoodFetchedEditionKey = currentUpdateScope; + } + + private static String normalizeFetchedEditionScope(FreenetURI uri) { + FreenetURI normalized = uri; + if (normalized.isSSK() && normalized.isSSKForUSK()) { + normalized = normalized.uskForSSK(); + } + return normalized.setSuggestedEdition(0).toString(false, false); + } + + private static boolean isBarePublicKey(String value) { + if (value == null || value.isEmpty()) { + return false; + } + return !value.contains(URI_TYPE_SEPARATOR) && !value.contains(URI_PATH_SEPARATOR); + } + + private static String trimConfigValue(String value) { + return value == null ? null : value.trim(); + } + + @FunctionalInterface + private interface LegacyKeyExtractor { + String extract(String configuredValue) throws MalformedURLException; + } + /** * Called when a peer indicates in its UOMAnnounce that it has fetched the revocation key (or * failed to do so in a way suggesting that somebody knows the key). @@ -1228,21 +1605,6 @@ public void renderProgress(HTMLNode alertNode) { if (cu != null) cu.renderProperties(alertNode); } - /** Callback invoked when beginning a legacy UoM fetch; no‑op in package‑based mode. */ - public void onStartFetchingUOM() { - /* no-op */ - } - - /** - * Returns the legacy blob file for the current version. - * - * @return always {@code null}; serving the core JAR via UoM is disabled - */ - public synchronized File getCurrentVersionBlobFile() { - // Serving the main.jar over UOM is disabled in package-based updater. - return null; - } - // getMainUpdater() removed; jar updates are disabled. /** @@ -1286,14 +1648,17 @@ public ByteCounter getByteCounter() { /** Create and wire the package‑based {@link CoreUpdater} if not already present. */ public synchronized void startCoreUpdater() { if (coreUpdater != null) return; + int subscribeEditionSeed = + computeCoreUpdaterSubscribeEditionSeedLocked(normalizeFetchedEditionScope(updateURI)); NodeUpdaterParams params = new NodeUpdaterParams( this, - getCoreInfoURI(), + getURI(), Version.currentBuildNumber(), -1, Integer.MAX_VALUE, - "core-info-"); + "core-info-", + subscribeEditionSeed); coreUpdater = new CoreUpdater(params); } diff --git a/src/main/java/network/crypta/node/updater/NodeUpdater.java b/src/main/java/network/crypta/node/updater/NodeUpdater.java index 7c4d391b8b..99155f71c2 100644 --- a/src/main/java/network/crypta/node/updater/NodeUpdater.java +++ b/src/main/java/network/crypta/node/updater/NodeUpdater.java @@ -28,7 +28,6 @@ import network.crypta.node.NodeClientCore; import network.crypta.node.RequestClient; import network.crypta.node.RequestStarter; -import network.crypta.node.Version; import network.crypta.support.Ticker; import network.crypta.support.api.Bucket; import network.crypta.support.api.RandomAccessBucket; @@ -139,7 +138,7 @@ public abstract class NodeUpdater implements ClientGetCallback, USKCallback, Req // Debug gating derives from LOG.isDebugEnabled() where needed this.manager = params.manager(); this.node = manager.getNode(); - this.uri = params.updateUri().setSuggestedEdition(((long) Version.currentBuildNumber()) + 1); + this.uri = params.updateUri().setSuggestedEdition(params.subscribeEditionSeed()); this.ticker = node.network().ticker(); this.core = node.services().clientCore(); this.currentVersion = params.current(); @@ -163,12 +162,11 @@ void start() { private void subscribe(Runnable onError) { try { - // because of UoM, this version is actually worth having as well FreenetURI localUri; synchronized (this) { localUri = this.uri; } - USK myUsk = USK.create(localUri.setSuggestedEdition(currentVersion)); + USK myUsk = USK.create(localUri); core.getUskManager().subscribe(myUsk, this, true, getRequestClient()); } catch (MalformedURLException _) { LOG.error("The auto-update URI isn't valid and can't be used"); @@ -357,14 +355,15 @@ RandomAccessBucket getBlobBucket(int availableVersion) { public void onSuccess(FetchResult result, ClientGetter state) { File localTempBlobFile; int localFetchingVersion; + FreenetURI fetchedUri = state != null ? state.getURI() : null; synchronized (this) { localTempBlobFile = tempBlobFile; localFetchingVersion = fetchingVersion; } - onSuccess(result, localTempBlobFile, localFetchingVersion); + onSuccess(result, localTempBlobFile, localFetchingVersion, fetchedUri); } - void onSuccess(FetchResult result, File tempBlobFile, int fetchedVersion) { + void onSuccess(FetchResult result, File tempBlobFile, int fetchedVersion, FreenetURI fetchedUri) { // Debug gating derives from LOG.isDebugEnabled() where needed File blobFile; synchronized (this) { @@ -393,6 +392,8 @@ void onSuccess(FetchResult result, File tempBlobFile, int fetchedVersion) { this.cg = null; } processSuccess(fetchedVersion, result, blobFile); + manager.recordSuccessfulCoreInfoFetch( + fetchedUri != null ? fetchedUri : getUpdateKey(), fetchedVersion); } private boolean shouldSkipAlreadyFetched(int fetchedVersion) { @@ -755,8 +756,9 @@ public synchronized boolean canUpdateNow() { * Called when the fetch URI has changed. The caller holds no major locks. * * @param newUri the new update key; its doc name is preserved when the argument omits one + * @param subscribeEditionSeed edition to use when subscribing to the new update key */ - public void onChangeURI(FreenetURI newUri) { + public void onChangeURI(FreenetURI newUri, int subscribeEditionSeed) { String previousDocName; synchronized (this) { previousDocName = (this.uri != null) ? this.uri.getDocName() : null; @@ -767,7 +769,7 @@ public void onChangeURI(FreenetURI newUri) { ? newUri.setDocName(previousDocName) : newUri; synchronized (this) { - this.uri = nextUri.setSuggestedEdition(((long) Version.currentBuildNumber()) + 1); + this.uri = nextUri.setSuggestedEdition(subscribeEditionSeed); availableVersion = -1; realAvailableVersion = -1; fetchingVersion = -1; diff --git a/src/main/java/network/crypta/node/updater/NodeUpdaterParams.java b/src/main/java/network/crypta/node/updater/NodeUpdaterParams.java index 12cb398645..a47e719e96 100644 --- a/src/main/java/network/crypta/node/updater/NodeUpdaterParams.java +++ b/src/main/java/network/crypta/node/updater/NodeUpdaterParams.java @@ -28,6 +28,7 @@ * @param min minimum acceptable deployment build number for this node. * @param max maximum acceptable deployment build number for this node. * @param blobFilenamePrefix prefix for update blob filenames stored on disk. + * @param subscribeEditionSeed initial USK edition used when subscribing for update discovery. * @see NodeUpdateManager * @see NodeUpdater */ @@ -37,4 +38,5 @@ public record NodeUpdaterParams( int current, int min, int max, - String blobFilenamePrefix) {} + String blobFilenamePrefix, + int subscribeEditionSeed) {} diff --git a/src/main/java/network/crypta/node/updater/UpdateOverMandatoryManager.java b/src/main/java/network/crypta/node/updater/UpdateOverMandatoryManager.java index 3bd139331a..f8da24e541 100644 --- a/src/main/java/network/crypta/node/updater/UpdateOverMandatoryManager.java +++ b/src/main/java/network/crypta/node/updater/UpdateOverMandatoryManager.java @@ -1,6 +1,5 @@ package network.crypta.node.updater; -import java.io.BufferedInputStream; import java.io.DataInputStream; import java.io.EOFException; import java.io.File; @@ -18,7 +17,6 @@ import java.util.HashSet; import java.util.List; import java.util.Set; -import java.util.WeakHashMap; import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -61,23 +59,19 @@ import network.crypta.node.useralerts.AbstractUserAlert; import network.crypta.node.useralerts.UserAlert; import network.crypta.support.HTMLNode; -import network.crypta.support.HexUtil; import network.crypta.support.ShortBuffer; import network.crypta.support.SizeUtil; -import network.crypta.support.TimeUtil; import network.crypta.support.WeakHashSet; import network.crypta.support.api.Bucket; import network.crypta.support.api.RandomAccessBucket; import network.crypta.support.api.RandomAccessBuffer; import network.crypta.support.io.ArrayBucket; -import network.crypta.support.io.ByteArrayRandomAccessBuffer; import network.crypta.support.io.FileBucket; import network.crypta.support.io.FileRandomAccessBuffer; import network.crypta.support.io.FileUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import static java.util.concurrent.TimeUnit.HOURS; import static java.util.concurrent.TimeUnit.SECONDS; /** @@ -85,26 +79,20 @@ * *
UoM is a fallback update path used when peers are too far apart in protocol/build versions to * route requests normally. It piggybacks small control messages and bulk binary transfers so a node - * can receive critical information such as revocation certificates and, when enabled, a new core - * jar. The {@link network.crypta.node.NodeDispatcher} forwards UoM messages to this class, which - * decides whether and how to respond (accept, delay, or ignore) based on local policy and current - * update state managed by {@link NodeUpdateManager}. + * can receive critical information such as revocation certificates. The {@link + * network.crypta.node.NodeDispatcher} forwards UoM messages to this class, which decides whether + * and how to respond (accept, delay, or ignore) based on local policy and current update state + * managed by {@link NodeUpdateManager}. * - *
Behavior differs depending on the update mode. In the current package‑based updater flow - * (where {@link NodeUpdateManager#SUPPORTS_JAR_UOM} is {@code false}), UoM is only used for - * revocation handling; main‑jar exchange is disabled, and any received main‑jar offers are ignored - * after being logged for diagnostics. In legacy jar‑based mode, this manager may fetch a new jar - * directly from peers after a configurable grace period. + *
In the current package‑based updater flow, UoM is only used for revocation handling. Main‑jar + * exchange is disabled. * *
Concurrency and state: this class is designed to be called from network/async threads; it uses - * internal synchronization around its shared sets and maps to maintain consistency. At most {@link - * #MAX_NODES_SENDING_JAR} concurrent main‑jar transfers are allowed to reduce waste. A grace window - * ({@link #GRACE_TIME}) gives the normal updater time to succeed before UoM takes over. All state + * internal synchronization around its shared sets and maps to maintain consistency. All state * changes are defensive: inconsistent or malicious inputs are ignored and logged. * *
When a peer offers a newer main jar, the normal updater is given this much time before UoM
- * initiates a peer‑to‑peer fetch. The value is expressed in milliseconds and currently equals
- * three hours. Implementations should treat this as a read ‑only configuration; callers must not
- * modify it.
- */
- public static final long GRACE_TIME = HOURS.toMillis(3);
-
private static final String BUILD_NUM_PREFIX = " (build #";
private static final String NODE_PREFIX = "Node ";
private static final String PEER_PREFIX = "Peer ";
@@ -181,13 +144,7 @@ public class UpdateOverMandatoryManager implements RequestClient {
private static final Pattern revocationTempBuildNumberPattern =
Pattern.compile("^revocation(?:-jar)?-(\\d+-)?(\\d+)\\.fblob\\.tmp*$");
- // The main jar insert policy via UOM is handled elsewhere; no random insert here.
-
- private boolean fetchingUOM;
-
- private final HashMap The message may advertise a revocation certificate, a main‑jar offer (when jar UoM is
- * enabled), or both. Revocation announcements are processed first and may short‑circuit further
- * handling. For main‑jar offers, this method validates the advertised version and file length and
- * either fetches immediately (when outdated or past the grace time) or remembers the offer for a
- * later takeover.
+ * Announcements are used for revocation signaling and as a source of candidate peers for
+ * dependency fetchers. Revocation announcements are processed first and may short-circuit further
+ * handling.
*
- * @param m UOM announcement message to handle. Expected to contain keys such as {@code
- * MAIN_JAR_KEY}, {@code MAIN_JAR_VERSION}, and revocation fields; the map must be well‑formed
- * for correct processing.
+ * @param m UOM announcement message to handle.
* @param source The peer that sent the announcement. Must be a currently known {@link PeerNode};
* its connection status influences later scheduling.
* @return Always {@code true}. Returning a value allows symmetry with other handlers and aids
@@ -235,14 +182,11 @@ public UpdateOverMandatoryManager(NodeUpdateManager manager) {
*/
public boolean handleAnnounce(Message m, final PeerNode source) {
- String mainJarKey = m.getString(DMT.MAIN_JAR_KEY);
String revocationKey = m.getString(DMT.REVOCATION_KEY);
boolean haveRevocationKey = m.getBoolean(DMT.HAVE_REVOCATION_KEY);
- int mainJarVersion = m.getInt(DMT.MAIN_JAR_VERSION);
long revocationKeyLastTried = m.getLong(DMT.REVOCATION_KEY_TIME_LAST_TRIED);
int revocationKeyDNFs = m.getInt(DMT.REVOCATION_KEY_DNF_COUNT);
long revocationKeyFileLength = m.getLong(DMT.REVOCATION_KEY_FILE_LENGTH);
- long mainJarFileLength = m.getLong(DMT.MAIN_JAR_FILE_LENGTH);
int pingTime = m.getInt(DMT.PING_TIME);
int delayTime = m.getInt(DMT.BWLIMIT_DELAY_TIME);
@@ -253,8 +197,6 @@ public boolean handleAnnounce(Message m, final PeerNode source) {
"Update Over Mandatory offer from node {} : {}:",
source.getPeer(),
source.userToString());
- LOG.debug(
- "Main jar key: {} version={} length={}", mainJarKey, mainJarVersion, mainJarFileLength);
LOG.debug(
"Revocation key: {} found={} length={} last had 3 DNFs {} ms ago, {} DNFs so far",
revocationKey,
@@ -278,13 +220,10 @@ public boolean handleAnnounce(Message m, final PeerNode source) {
if (!stopProcessing) {
tellFetchers(source);
- // In package-based updater mode there is no main-jar UOM; only revocation is handled.
- if (!updateManager.isBlown()
- && updateManager.isEnabled()
- && NodeUpdateManager.SUPPORTS_JAR_UOM) {
- long now = System.currentTimeMillis();
- handleMainJarOffer(now, mainJarFileLength, mainJarVersion, source, mainJarKey);
+ synchronized (this) {
+ allNodesOfferedMainJar.add(source);
}
+ startSomeDependencyFetchers();
}
return true;
@@ -459,362 +398,6 @@ public void sent() {
}
- private void handleMainJarOffer(
- long now, long mainJarFileLength, int mainJarVersion, PeerNode source, String jarKey) {
-
- long started = NodeUpdateManager.STARTED_FETCHING_NEXT_MAIN_JAR_TIMESTAMP;
- // Legacy main-jar updater start time is not tracked in CoreUpdater mode; use the current offer
- // time.
- long whenToTakeOverTheNormalUpdater = now + GRACE_TIME;
- boolean isOutdated = updateManager.getNode().isOutdated();
- // if the new build is self-mandatory, or if the "normal" updater has been trying to update for
- // more than one hour
- if (LOG.isInfoEnabled()) {
- String takeoverDelay = TimeUtil.formatTime(whenToTakeOverTheNormalUpdater - now);
- LOG.info(
- "We received a valid UOMAnnouncement (main) : (isOutdated={} version={}"
- + " whenToTakeOverTheNormalUpdater={}) file length {} updateManager version {}",
- isOutdated,
- mainJarVersion,
- takeoverDelay,
- mainJarFileLength,
- updateManager.newMainJarVersion());
- }
-
- boolean offerIsValid =
- mainJarVersion > Version.currentBuildNumber()
- && mainJarFileLength > 0
- && mainJarVersion > updateManager.newMainJarVersion();
-
- if (offerIsValid) {
- source.setMainJarOfferedVersion(mainJarVersion);
- if (LOG.isDebugEnabled()) LOG.debug("Offer is valid");
- onValidMainJarOffer(
- now, started, isOutdated, mainJarVersion, source, jarKey, whenToTakeOverTheNormalUpdater);
- } else {
- // We may want the dependencies.
- // These may be similar even if his url is different, so add unconditionally.
- synchronized (this) {
- allNodesOfferedMainJar.add(source);
- }
- }
- startSomeDependencyFetchers();
- }
-
- private void onValidMainJarOffer(
- long now,
- long started,
- boolean isOutdated,
- int mainJarVersion,
- PeerNode source,
- String jarKey,
- long whenToTakeOverTheNormalUpdater) {
- if (isOutdated || whenToTakeOverTheNormalUpdater < now) {
- fetchMainJarNow(now, started, isOutdated, mainJarVersion, source, jarKey);
- } else {
- scheduleMainJarTakeover(whenToTakeOverTheNormalUpdater, now, source);
- }
- }
-
- private void fetchMainJarNow(
- long now,
- long started,
- boolean isOutdated,
- int mainJarVersion,
- PeerNode source,
- String jarKey) {
- // Take up the offer, subject to limits on the number of simultaneous downloads.
- // If we have fetches running already, then sendUOMRequestMainJar() will add the offer to
- // nodesOfferedMainJar, so that if all our fetches fail, we can fetch from this node.
- if (!isOutdated) {
- String howLong = TimeUtil.formatTime(now - started);
- LOG.error(
- "The update process seems to have been stuck for {}; let's switch to UoM! SHOULD NOT"
- + " HAPPEN! (1)",
- howLong);
- } else if (LOG.isDebugEnabled()) {
- LOG.debug("Fetching via UOM as our build is deprecated");
- }
- try {
- FreenetURI mainJarURI = new FreenetURI(jarKey).setSuggestedEdition(mainJarVersion);
- if (mainJarURI.equals(updateManager.getURI().setSuggestedEdition(mainJarVersion))) {
- sendUOMRequest(source, true);
- } else {
- // Transitional version differences may be expected; logging retained for diagnostics.
- if (LOG.isWarnEnabled()) {
- LOG.warn(
- "{}{} offered us a new main jar (version {}) but key differs. our key: {} his key:{}",
- NODE_PREFIX,
- source.userToString(),
- mainJarVersion,
- updateManager.getURI(),
- mainJarURI);
- }
- }
- } catch (MalformedURLException e) {
- // Should maybe be a useralert?
- LOG.error(
- "Node {} sent us a UOMAnnouncement claiming to have a new ext jar, but it had an invalid"
- + " URI: {}",
- source,
- jarKey,
- e);
- }
- synchronized (this) {
- allNodesOfferedMainJar.add(source);
- }
- }
-
- private void scheduleMainJarTakeover(
- long whenToTakeOverTheNormalUpdater, long now, final PeerNode source) {
- // Don't take up the offer. Add to nodesOfferedMainJar so that we know where to fetch it
- // from when we need it.
- synchronized (this) {
- nodesOfferedMainJar.add(source);
- allNodesOfferedMainJar.add(source);
- }
- updateManager
- .getNode()
- .network()
- .ticker()
- .queueTimedJob(
- () -> {
- if (updateManager.isBlown()) return;
- if (!updateManager.isEnabled()) return;
- if (updateManager.hasNewMainJar()) return;
- if (!updateManager.getNode().isOutdated()) {
- LOG.error(
- "The update process seems to have been stuck for too long; let's switch to UoM!"
- + " SHOULD NOT HAPPEN! (2) (ext)");
- }
- maybeRequestMainJar();
- },
- whenToTakeOverTheNormalUpdater - now);
- }
-
- private void sendUOMRequest(final PeerNode source, boolean addOnFail) {
- final String name = "Main";
- String lname = "main";
- if (LOG.isDebugEnabled()) LOG.debug("sendUOMRequest {} ({},{})", name, source, addOnFail);
- if (!source.isConnected() || source.isSeed()) {
- if (LOG.isDebugEnabled())
- LOG.debug("Not sending UOM {} request to {} (disconnected or seednode)", lname, source);
- return;
- }
- final Set Respects {@link #MAX_NODES_SENDING_JAR}. If no capacity is available or no offers remain,
- * the method returns immediately. This method is idempotent with respect to already contacted
- * peers.
- */
- protected void maybeRequestMainJar() {
- PeerNode[] offers;
- synchronized (this) {
- if (nodesAskedSendMainJar.size() + nodesSendingMainJar.size() >= MAX_NODES_SENDING_JAR)
- return;
- if (nodesOfferedMainJar.isEmpty()) return;
- offers = nodesOfferedMainJar.toArray(new PeerNode[0]);
- }
- for (PeerNode offer : offers) {
- boolean shouldSkip;
- synchronized (this) {
- if (nodesAskedSendMainJar.size() + nodesSendingMainJar.size() >= MAX_NODES_SENDING_JAR)
- return;
- shouldSkip = nodesSendingMainJar.contains(offer) || nodesAskedSendMainJar.contains(offer);
- }
- if (!offer.isConnected() || shouldSkip) continue;
- sendUOMRequest(offer, false);
- }
- }
-
private void alertUser() {
synchronized (this) {
if (alert != null) return;
@@ -1624,13 +1207,6 @@ private void cancelSend(PeerNode source, long uid) {
}
}
- private void removeAskedAndCancel(PeerNode source, long uid) {
- cancelSend(source, uid);
- synchronized (this) {
- this.nodesAskedSendMainJar.remove(source);
- }
- }
-
/**
* Unregisters and clears the current “peers say key blown” alert, if any.
*
@@ -1645,602 +1221,106 @@ public void killAlert() {
}
}
+ // Removed an unused method maybeInsertMainJar: insertion is coordinated via updater flows.
+
/**
- * Handles a peer request to send the current main jar binary blob.
- *
- * Validates policy (e.g., opennet restrictions and minimum peer version), locates the local
- * jar, and initiates a bulk transfer back to the requester. When requirements are not met or the
- * jar is unavailable, the method logs and returns without transferring.
+ * Deletes obsolete persistent temporary files related to UoM transfers.
*
- * @param m Request message with a unique {@code UID} to correlate the bulk transfer.
- * @param source The requesting peer; connection state and version determine eligibility.
+ * The method scans the persistent temp directory for known UoM patterns (revocation and
+ * main‑jar blobs and their temporary variants). It removes files that are clearly safe to delete,
+ * including old build‑number‑scoped files below the minimum acceptable build. Errors are logged
+ * but otherwise ignored.
*/
- public void handleRequestJar(Message m, final PeerNode source) {
- final String name = "main";
-
- Message msg;
-
- if (source.isOpennet() && updateManager.dontAllowUOM()) {
- LOG.info(
- "Peer {} asked us for the blob file for {}; We are a seednode, so we ignore it!",
- source,
- name);
+ protected void removeOldTempFiles() {
+ File oldTempFilesPeerDir =
+ updateManager.getNode().services().clientCore().getPersistentTempDir();
+ if (!oldTempFilesPeerDir.exists()) return;
+ if (!oldTempFilesPeerDir.isDirectory()) {
+ LOG.error(
+ "Persistent temporary files location is not a directory: {}",
+ oldTempFilesPeerDir.getPath());
return;
}
- // Do we have the data?
- File data;
- int version;
- FreenetURI uri;
- // Legacy support removed - only serve the current version
- if (!Version.isBuildAtLeast(
- source.getNodeName(), source.getBuildNumber(), NodeUpdateManager.TRANSITION_VERSION)) {
- // Don't serve updates to very old nodes
- LOG.info(
- "Peer {} is too old (version < {}), not serving update",
- source,
- NodeUpdateManager.TRANSITION_VERSION);
+ // Best-effort cleanup; failures are only logged.
+ File[] oldTempFiles =
+ oldTempFilesPeerDir.listFiles(file -> shouldDeleteTempFile(file.getName()));
+ if (oldTempFiles == null) {
+ LOG.warn("Could not list temporary persistent files in {}", oldTempFilesPeerDir);
return;
}
- data = updateManager.getCurrentVersionBlobFile();
- version = Version.currentBuildNumber();
- uri = updateManager.getURI();
- if (data == null) {
- LOG.info(
- "UOM main jar request: peer {} requested {} jar but it is missing locally", source, name);
- // Probably a race condition on reconnection, hopefully we'll be asked again
- return;
+ for (File fileToDelete : oldTempFiles) {
+ String fileToDeleteName = fileToDelete.getName();
+ try {
+ Files.delete(fileToDelete.toPath());
+ } catch (NoSuchFileException _) {
+ LOG.info("Temporary persistent file does not exist when deleting: {}", fileToDeleteName);
+ } catch (IOException _) {
+ LOG.error(
+ "Cannot delete temporary persistent file {} even though it exists: must be TOO"
+ + " persistent :)",
+ fileToDeleteName);
+ }
}
- final long uid = m.getLong(DMT.UID);
-
- if (!source.sendingUOMJar(false)) {
- LOG.error("Peer {} asked for UOM main jar twice", source);
- return;
- }
+ // Caller doesn't use the result; nothing to return.
+ }
- final long length = data.length();
- msg = DMT.createUOMSendingMainJar(uid, length, uri.toString(), version);
+ private boolean shouldDeleteTempFile(String fileName) {
+ if (fileName.startsWith("revocation-") && fileName.endsWith(FBLOB_TMP_SUFFIX)) return true;
- final Runnable r = buildMainJarSender(source, data, uid, length);
+ Matcher mainBuildNumberMatcher = mainBuildNumberPattern.matcher(fileName);
+ Matcher mainTempBuildNumberMatcher = mainTempBuildNumberPattern.matcher(fileName);
+ Matcher revocationTempBuildNumberMatcher = revocationTempBuildNumberPattern.matcher(fileName);
- try {
- source
- .transport()
- .sendAsync(
- msg,
- new AsyncMessageCallback() {
+ if (mainBuildNumberMatcher.matches()) {
+ try {
+ String buildNumberStr = mainBuildNumberMatcher.group(1);
+ int buildNumber = Integer.parseInt(buildNumberStr);
+ int lastGoodMainBuildNumber = Version.MIN_ACCEPTABLE_CRYPTAD_BUILD_NUMBER;
+ return buildNumber < lastGoodMainBuildNumber;
+ } catch (NumberFormatException _) {
+ LOG.error("Wierd file in persistent temp: {}", fileName);
+ return false;
+ }
+ }
+ return mainTempBuildNumberMatcher.matches() || revocationTempBuildNumberMatcher.matches();
+ }
- @Override
- public void acknowledged() {
- if (LOG.isDebugEnabled()) LOG.debug("UOM main jar send: starting data transfer");
- // Send the data
+ /** {@inheritDoc} */
+ @Override
+ public boolean persistent() {
+ return false;
+ }
- updateManager
- .getNode()
- .network()
- .executor()
- .execute(r, name + " jar send for " + uid + " to " + source.userToString());
- }
-
- @Override
- public void disconnected() {
- // Argh
- LOG.error(
- "UOM main jar send aborted: peer {} disconnected before UOMSendingMainJar for"
- + " {} jar",
- source,
- name);
- source.finishedSendingUOMJar(false);
- }
-
- @Override
- public void fatalError() {
- // Argh
- LOG.error(
- "UOM main jar send failed: fatal error before UOMSendingMainJar from peer {}"
- + " for {} jar",
- source,
- name);
- source.finishedSendingUOMJar(false);
- }
-
- @Override
- public void sent() {
- if (LOG.isDebugEnabled())
- LOG.debug("UOM main jar send: message sent, data follows");
- }
-
- @Override
- public String toString() {
- return super.toString() + "(" + uid + ":" + source.getPeer() + ")";
- }
- },
- updateManager.getByteCounter());
- } catch (NotConnectedException e) {
- LOG.error(
- "UOM main jar send failed: peer {} disconnected while sending UOMSendingMainJar for {}"
- + " jar",
- source,
- name,
- e);
- } catch (RuntimeException e) {
- source.finishedSendingUOMJar(false);
- throw e;
- }
- }
-
- private Runnable buildMainJarSender(
- final PeerNode source, final File data, final long uid, final long length) {
- return () -> {
- try (FileRandomAccessBuffer rafLocal = new FileRandomAccessBuffer(data, true)) {
- PartiallyReceivedBulk prb =
- new PartiallyReceivedBulk(
- updateManager.getNode().network().usm(), length, Node.PACKET_SIZE, rafLocal, true);
- BulkTransmitter bt =
- new BulkTransmitter(prb, source, uid, false, updateManager.getByteCounter(), true);
- if (!bt.send()) {
- if (LOG.isErrorEnabled()) {
- LOG.error(
- "Failed to send {} jar blob to {} : {}",
- "main",
- source.userToString(),
- bt.getCancelReason());
- }
- } else {
- if (LOG.isInfoEnabled()) {
- LOG.info("Sent {} jar blob to {}", "main", source.userToString());
- }
- }
- } catch (FileNotFoundException e) {
- LOG.error("UOM main jar send failed: missing file after check for {} jar", "main", e);
- } catch (IOException e) {
- LOG.error("UOM main jar send failed: disk I/O reading {} jar after download", "main", e);
- } catch (DisconnectedException e) {
- LOG.error(
- "UOM main jar send failed: peer {} disconnected during bulk send for {} jar",
- source,
- "main",
- e);
- } finally {
- source.finishedSendingUOMJar(false);
- }
- };
- }
+ /**
+ * Clears UoM state associated with a disconnected peer.
+ *
+ * Removes the peer from all tracking sets (offers, active transfers, and revocation reports)
+ * and re‑evaluates whether the revocation condition still plausibly holds.
+ *
+ * @param pn The peer that disconnected.
+ */
+ public void disconnected(PeerNode pn) {
+ synchronized (this) {
+ nodesSayKeyRevoked.remove(pn);
+ nodesSayKeyRevokedFailedTransfer.remove(pn);
+ nodesSayKeyRevokedTransferring.remove(pn);
+ allNodesOfferedMainJar.remove(pn);
+ }
+ maybeNotRevoked();
+ }
/**
- * Handles a peer announcement that it is sending the main jar to us.
+ * Reports whether at least two UoM transfers are in progress.
*
- * Validates the advertised {@code URI}, version, and length, checks local acceptance rules,
- * and, if acceptable, schedules a bulk receiving to a temporary file followed by verification and
- * cleanup. If the offer is rejected, the method cancels the transfer.
+ * Main-jar UoM is disabled, so this always returns {@code false}.
*
- * @param m Message describing the transfer, including {@code UID}, {@code FILE_LENGTH}, {@code
- * MAIN_JAR_KEY}, and {@code MAIN_JAR_VERSION}.
- * @param source The peer that will transmit the jar.
- * @return {@code true} when the message was handled; {@code false} is not used.
- */
- public boolean handleSendingMain(Message m, final PeerNode source) {
- final long uid = m.getLong(DMT.UID);
- final long length = m.getLong(DMT.FILE_LENGTH);
- final String key = m.getString(DMT.MAIN_JAR_KEY);
- final int version = m.getInt(DMT.MAIN_JAR_VERSION);
- processSendingMain(uid, length, key, version, source);
- return true;
- }
-
- private void processSendingMain(long uid, long length, String key, int version, PeerNode source) {
- final FreenetURI jarURI;
- try {
- jarURI = new FreenetURI(key).setSuggestedEdition(version);
- } catch (MalformedURLException e) {
- LOG.error(
- "Failed receiving main jar {} because URI not parsable: {} for {}", version, e, key);
- removeAskedAndCancel(source, uid);
- return;
- }
-
- if (!isUriAccepted(jarURI, version, source, uid)) return;
- if (!canReceiveMainJar(source, uid)) return;
- if (!isMainJarLengthAccepted(length, version, source, uid)) return;
-
- if (LOG.isInfoEnabled()) {
- LOG.info("Receiving main jar {}{}{}", version, FROM_LITERAL, source.userToString());
- }
-
- final File temp = prepareTempMainJarFile(uid, source);
- if (temp == null) return;
-
- FileRandomAccessBuffer raf = prepareMainJarRaf(temp, length, source);
- if (raf == null) return;
-
- PartiallyReceivedBulk prb =
- new PartiallyReceivedBulk(
- updateManager.getNode().network().usm(), length, Node.PACKET_SIZE, raf, false);
-
- final BulkReceiver br = new BulkReceiver(prb, source, uid, updateManager.getByteCounter());
- final FreenetURI jarUriForLambda = jarURI;
- updateManager
- .getNode()
- .network()
- .executor()
- .execute(
- () -> {
- boolean success = false;
- try {
- synchronized (UpdateOverMandatoryManager.class) {
- nodesAskedSendMainJar.remove(source);
- nodesSendingMainJar.add(source);
- }
- success = br.receive();
- if (success) processMainJarBlob(temp, source, version, jarUriForLambda);
- else {
- LOG.error("Failed to transfer main jar {}{}{}", version, FROM_LITERAL, source);
- try {
- Files.delete(temp.toPath());
- } catch (IOException ex) {
- LOG.warn(FAILED_DELETE_TMP, temp, ex);
- }
- }
- } finally {
- synchronized (UpdateOverMandatoryManager.class) {
- nodesSendingMainJar.remove(source);
- if (success) nodesSentMainJar.add(source);
- }
- }
- },
- "Main jar ("
- + version
- + ") receive"
- + FOR_LITERAL
- + uid
- + FROM_LITERAL
- + source.userToString());
- }
-
- private boolean isUriAccepted(FreenetURI jarURI, int version, PeerNode source, long uid) {
- if (!jarURI.equals(updateManager.getURI().setSuggestedEdition(version))) {
- if (LOG.isWarnEnabled()) {
- LOG.warn(
- """
- Node sending us a main jar update ({}) from the wrong URI:
- Node: {}
- Our URI: {}
- Their URI: {}
- """,
- version,
- source.userToString(),
- updateManager.getURI(),
- jarURI);
- }
- removeAskedAndCancel(source, uid);
- return false;
- }
- return true;
- }
-
- private boolean canReceiveMainJar(PeerNode source, long uid) {
- if (updateManager.isBlown()) {
- LOG.debug("Key blown, so not receiving main jar from {}({})", source, uid);
- removeAskedAndCancel(source, uid);
- return false;
- }
- return true;
- }
-
- private boolean isMainJarLengthAccepted(long length, int version, PeerNode source, long uid) {
- if (length > NodeUpdateManager.MAX_MAIN_JAR_LENGTH) {
- if (LOG.isErrorEnabled()) {
- LOG.error(
- "{}{} offered us a main jar ({}) {} long. This is unacceptably long so we have refused"
- + " the transfer.",
- NODE_PREFIX,
- source.userToString(),
- version,
- SizeUtil.formatSize(length));
- LOG.error(
- "Node {} offered us a main jar ({}) {} long. This is unacceptably long so we have"
- + " refused the transfer.",
- source.userToString(),
- version,
- SizeUtil.formatSize(length));
- }
- // If the transfer fails, we don't try again.
- removeAskedAndCancel(source, uid);
- return false;
- }
- return true;
- }
-
- private File prepareTempMainJarFile(long uid, PeerNode source) {
- try {
- File temp =
- File.createTempFile(
- "main-",
- FBLOB_TMP_SUFFIX,
- updateManager.getNode().services().clientCore().getPersistentTempDir());
- temp.deleteOnExit();
- return temp;
- } catch (IOException e) {
- LOG.error("Cannot save new main jar to disk and therefore cannot fetch it from our peer!", e);
- removeAskedAndCancel(source, uid);
- return null;
- }
- }
-
- private FileRandomAccessBuffer prepareMainJarRaf(File temp, long length, PeerNode source) {
- try {
- return new FileRandomAccessBuffer(temp, length, false);
- } catch (IOException e) {
- LOG.error(
- "Peer {} sending us a main jar binary blob, but we {}{} : {}",
- source,
- (e instanceof FileNotFoundException)
- ? "lost the temp file "
- : "cannot read the temp file ",
- temp,
- e,
- e);
- synchronized (this) {
- this.nodesAskedSendMainJar.remove(source);
- }
- return null;
- }
- }
-
- /**
- * Verifies and processes a received main‑jar binary blob.
- *
- * Reads the blob, reconstructs a temporary fetch context using its blocks, and fetches the jar
- * via the local store using the supplied {@code uri}. On success, the cleaned blob is freed, and
- * the temporary file is deleted; failures are logged and the temp file is removed.
- *
- * @param temp Temporary file containing the binary blob as received over UoM; must exist.
- * @param source Peer that sent the blob; used for logs, may be {@code null} for local testing.
- * @param version Suggested edition to fetch; must be positive.
- * @param uri Expected URI of the jar to fetch from the store for validation and assembly.
- */
- protected void processMainJarBlob(
- final File temp, final PeerNode source, final long version, FreenetURI uri) {
- SimpleBlockSet blocks = new SimpleBlockSet();
- final String toString = source == null ? "(local)" : source.userToString();
-
- if (!readMainJarBlob(temp, version, toString, blocks)) return;
-
- // Fetch the jar from the datastore plus the binary blob
-
- FetchContext seedContext =
- updateManager
- .getNode()
- .services()
- .clientCore()
- .makeClient((short) 0, true, false)
- .getFetchContext();
- FetchContext tempContext =
- new FetchContext(seedContext, FetchContext.IDENTICAL_MASK, true, blocks);
- tempContext.setLocalRequestOnly(true);
-
- final ArrayBucket cleanedBlob = new ArrayBucket();
-
- ClientGetCallback myCallback = buildMainJarCallback(temp, version, toString, cleanedBlob);
-
- ClientGetter cg =
- new ClientGetter(
- myCallback, uri, tempContext, (short) 0, null, new BinaryBlobWriter(cleanedBlob), null);
-
- try {
- updateManager.getNode().services().clientCore().getClientContext().start(cg);
- } catch (FetchException e1) {
- myCallback.onFailure(e1);
- } catch (PersistenceDisabledException _) {
- // Impossible
- }
- }
-
- private boolean readMainJarBlob(File temp, long version, String toString, SimpleBlockSet blocks) {
- try (DataInputStream dis =
- new DataInputStream(new BufferedInputStream(new FileInputStream(temp)))) {
- BinaryBlob.readBinaryBlob(dis, blocks, true);
- return true;
- } catch (FileNotFoundException _) {
- LOG.error(
- "{}{} ? We lost the main jar ({}) from {}!",
- SOMEONE_DELETED_PREFIX,
- temp,
- version,
- toString);
- return false;
- } catch (IOException _) {
- LOG.error(
- "Could not read main jar ({}) from temp file {} from node {} !", version, temp, toString);
- return false;
- } catch (BinaryBlobFormatException e) {
- LOG.error("Peer {} sent us an invalid main jar ({})!", toString, version, e);
- return false;
- }
- }
-
- // No file-backed cleaned blob is required for UOM; we buffer in memory.
-
- private ClientGetCallback buildMainJarCallback(
- final File temp, final long version, final String toString, final Bucket cleanedBlob) {
- return new ClientGetCallback() {
-
- @Override
- public void onFailure(FetchException e) {
- handleMainJarFetchFailure(e, temp, version, toString, cleanedBlob);
- }
-
- @Override
- public void onSuccess(FetchResult result, ClientGetter state) {
- LOG.info("Got main jar version {}{}{}", version, FROM_LITERAL, toString);
- if (result.size() == 0) {
- LOG.warn("Ignoring main jar because 0 bytes long");
- return;
- }
-
- if (!NodeUpdateManager.SUPPORTS_JAR_UOM) {
- LOG.info("Ignoring UOM main jar because jar updates are disabled.");
- try {
- Files.delete(temp.toPath());
- } catch (IOException ex) {
- LOG.warn(FAILED_DELETE_TMP, temp, ex);
- }
- if (cleanedBlob != null) cleanedBlob.free();
- return;
- }
- if (cleanedBlob != null) cleanedBlob.free();
- }
-
- @Override
- public void onResume(ClientContext context) {
- // Not persistent.
- }
-
- @Override
- public RequestClient getRequestClient() {
- return UpdateOverMandatoryManager.this;
- }
- };
- }
-
- private void handleMainJarFetchFailure(
- FetchException e, File temp, long version, String toString, Bucket cleanedBlob) {
- if (e.mode == FetchExceptionMode.CANCELLED) {
- LOG.error("Cancelled fetch from store/blob of main jar ({}) from {}", version, toString);
- } else if (e.newURI != null) {
- try {
- Files.delete(temp.toPath());
- } catch (IOException ex) {
- LOG.warn(FAILED_DELETE_TMP, temp, ex);
- }
- LOG.error("URI changed fetching main jar {} from {}", version, toString);
- } else if (e.isFatal()) {
- try {
- Files.delete(temp.toPath());
- } catch (IOException ex) {
- LOG.warn(FAILED_DELETE_TMP, temp, ex);
- }
- LOG.error(
- "Failed to fetch main jar {} from {} : fatal error (update was probably inserted badly):",
- version,
- toString,
- e);
- } else {
- LOG.error("Failed to fetch main jar {} from blob from {}", version, toString);
- }
- if (cleanedBlob != null) cleanedBlob.free();
- }
-
- // Removed an unused method maybeInsertMainJar: insertion is coordinated via updater flows.
-
- /**
- * Deletes obsolete persistent temporary files related to UoM transfers.
- *
- * The method scans the persistent temp directory for known UoM patterns (revocation and
- * main‑jar blobs and their temporary variants). It removes files that are clearly safe to delete,
- * including old build‑number‑scoped files below the minimum acceptable build. Errors are logged
- * but otherwise ignored.
- */
- protected void removeOldTempFiles() {
- File oldTempFilesPeerDir =
- updateManager.getNode().services().clientCore().getPersistentTempDir();
- if (!oldTempFilesPeerDir.exists()) return;
- if (!oldTempFilesPeerDir.isDirectory()) {
- LOG.error(
- "Persistent temporary files location is not a directory: {}",
- oldTempFilesPeerDir.getPath());
- return;
- }
-
- // Best-effort cleanup; failures are only logged.
- File[] oldTempFiles =
- oldTempFilesPeerDir.listFiles(file -> shouldDeleteTempFile(file.getName()));
- if (oldTempFiles == null) {
- LOG.warn("Could not list temporary persistent files in {}", oldTempFilesPeerDir);
- return;
- }
-
- for (File fileToDelete : oldTempFiles) {
- String fileToDeleteName = fileToDelete.getName();
- try {
- Files.delete(fileToDelete.toPath());
- } catch (NoSuchFileException _) {
- LOG.info("Temporary persistent file does not exist when deleting: {}", fileToDeleteName);
- } catch (IOException _) {
- LOG.error(
- "Cannot delete temporary persistent file {} even though it exists: must be TOO"
- + " persistent :)",
- fileToDeleteName);
- }
- }
-
- // Caller doesn't use the result; nothing to return.
- }
-
- private boolean shouldDeleteTempFile(String fileName) {
- if (fileName.startsWith("revocation-") && fileName.endsWith(FBLOB_TMP_SUFFIX)) return true;
-
- Matcher mainBuildNumberMatcher = mainBuildNumberPattern.matcher(fileName);
- Matcher mainTempBuildNumberMatcher = mainTempBuildNumberPattern.matcher(fileName);
- Matcher revocationTempBuildNumberMatcher = revocationTempBuildNumberPattern.matcher(fileName);
-
- if (mainBuildNumberMatcher.matches()) {
- try {
- String buildNumberStr = mainBuildNumberMatcher.group(1);
- int buildNumber = Integer.parseInt(buildNumberStr);
- int lastGoodMainBuildNumber = Version.MIN_ACCEPTABLE_CRYPTAD_BUILD_NUMBER;
- return buildNumber < lastGoodMainBuildNumber;
- } catch (NumberFormatException _) {
- LOG.error("Wierd file in persistent temp: {}", fileName);
- return false;
- }
- }
- return mainTempBuildNumberMatcher.matches() || revocationTempBuildNumberMatcher.matches();
- }
-
- /** {@inheritDoc} */
- @Override
- public boolean persistent() {
- return false;
- }
-
- /**
- * Clears UoM state associated with a disconnected peer.
- *
- * Removes the peer from all tracking sets (offers, active transfers, and revocation reports)
- * and re‑evaluates whether the revocation condition still plausibly holds.
- *
- * @param pn The peer that disconnected.
- */
- public void disconnected(PeerNode pn) {
- synchronized (this) {
- nodesSayKeyRevoked.remove(pn);
- nodesSayKeyRevokedFailedTransfer.remove(pn);
- nodesSayKeyRevokedTransferring.remove(pn);
- nodesOfferedMainJar.remove(pn);
- allNodesOfferedMainJar.remove(pn);
- nodesSentMainJar.remove(pn);
- nodesAskedSendMainJar.remove(pn);
- nodesSendingMainJar.remove(pn);
- }
- maybeNotRevoked();
- }
-
- /**
- * Reports whether two concurrent main‑jar transfers are in progress.
- *
- * This reflects the internal cap enforced by {@link #MAX_NODES_SENDING_JAR} for reliability
- * and bandwidth conservation.
- *
- * @return {@code true} if at least two peers are currently sending the main jar; otherwise {@code
- * false}.
+ * @return {@code false}
*/
public boolean fetchingFromTwo() {
- synchronized (this) {
- return this.nodesSendingMainJar.size() >= 2;
- }
+ return false;
}
/** {@inheritDoc} */
@@ -2250,190 +1330,14 @@ public boolean realTimeFlag() {
}
/**
- * Indicates whether a main‑jar transfer is currently active.
+ * Indicates whether a legacy main-jar UoM transfer is active.
*
- * @return {@code true} if at least one peer is sending the main jar; {@code false} otherwise.
- */
- public boolean isFetchingMain() {
- synchronized (this) {
- return !nodesSendingMainJar.isEmpty();
- }
- }
-
- /**
- * Advertises a locally available dependency that can be served to peers by hash.
+ * Main-jar UoM is disabled, so this always returns {@code false}.
*
- * The file is indexed by its {@code SHA‑256} hash and may later be read and transferred on
- * demand. Only register files that currently exist and are readable; absence at transfer time is
- * treated as a transient failure.
- *
- * @param expectedHash The exact SHA‑256 of the file content as a byte array; must not be null.
- * @param filename The on‑disk file to serve when requested; the path is not copied.
+ * @return {@code false}
*/
- @SuppressWarnings("unused")
- public void addDependency(byte[] expectedHash, File filename) {
- if (LOG.isDebugEnabled())
- LOG.debug("Add dependency: {} for {}", filename, HexUtil.bytesToHex(expectedHash));
- synchronized (dependencies) {
- dependencies.put(new ShortBuffer(expectedHash), filename);
- }
- }
-
- static final int MAX_TRANSFERS_PER_PEER = 2;
-
- /**
- * Handles a peer request to fetch a registered dependency by its hash.
- *
- * Validates the request, enforces per‑peer transfer caps, and streams the file using the bulk
- * transfer protocol when available. If the file is unavailable or the request exceeds limits, a
- * cancellation is sent instead.
- *
- * @param m The request message containing {@code EXPECTED_HASH}, {@code FILE_LENGTH}, and {@code
- * UID} fields.
- * @param source The requesting peer.
- */
- public void handleFetchDependency(Message m, final PeerNode source) {
- File data;
- final ShortBuffer buf = (ShortBuffer) m.getObject(DMT.EXPECTED_HASH);
- long length = m.getLong(DMT.FILE_LENGTH);
- long uid = m.getLong(DMT.UID);
- synchronized (dependencies) {
- data = dependencies.get(buf);
- }
- boolean fail = !incrementDependencies(source);
- FileRandomAccessBuffer raf;
- final BulkTransmitter bt;
-
- DepOpen dep = openDependency(data, buf, source);
- raf = dep.raf;
- fail = fail || dep.fail;
-
- PrbDecision prbDecision = buildDependencyPrb(raf, length);
- PartiallyReceivedBulk prb = prbDecision.prb();
- fail = fail || prbDecision.sizeMismatch();
-
- try {
- bt = new BulkTransmitter(prb, source, uid, false, updateManager.getByteCounter(), true);
- } catch (DisconnectedException e) {
- LOG.error(
- "Peer {} asked us for the dependency with hash {} jar then disconnected",
- source,
- HexUtil.bytesToHex(buf.getData()),
- e);
- if (raf != null) {
- raf.close();
- }
- decrementDependencies(source);
- return;
- }
-
- if (fail) {
- cancelSend(source, uid);
- decrementDependencies(source);
- } else {
- final FileRandomAccessBuffer r = raf;
- updateManager
- .getNode()
- .network()
- .executor()
- .execute(
- () -> {
- source.incrementUOMSends();
- try {
- bt.send();
- } catch (DisconnectedException _) {
- LOG.info(
- "Disconnected while sending dependency with hash {} to {}",
- HexUtil.bytesToHex(buf.getData()),
- source);
- } finally {
- source.decrementUOMSends();
- decrementDependencies(source);
- if (r != null) {
- r.close();
- }
- }
- });
- }
- }
-
- private record PrbDecision(PartiallyReceivedBulk prb, boolean sizeMismatch) {}
-
- private PrbDecision buildDependencyPrb(FileRandomAccessBuffer raf, long expectedLength) {
- if (raf != null) {
- long thisLength = raf.size();
- PartiallyReceivedBulk prb =
- new PartiallyReceivedBulk(
- updateManager.getNode().network().usm(), thisLength, Node.PACKET_SIZE, raf, true);
- return new PrbDecision(prb, expectedLength != thisLength);
- }
- PartiallyReceivedBulk prb =
- new PartiallyReceivedBulk(
- updateManager.getNode().network().usm(),
- 0,
- Node.PACKET_SIZE,
- new ByteArrayRandomAccessBuffer(new byte[0]),
- true);
- return new PrbDecision(prb, false);
- }
-
- private void decrementDependencies(PeerNode source) {
- synchronized (peersFetchingDependencies) {
- Integer x = peersFetchingDependencies.get(source);
- if (x == null) {
- LOG.error("Inconsistent dependency counting? Should not be null for {}", source);
- } else if (x == 1) {
- peersFetchingDependencies.remove(source);
- } else if (x <= 0) {
- LOG.error("Inconsistent dependency counting? Counter is {} for {}", x, source);
- peersFetchingDependencies.remove(source);
- } else {
- peersFetchingDependencies.put(source, x - 1);
- }
- }
- }
-
- private record DepOpen(FileRandomAccessBuffer raf, boolean fail) {}
-
- private DepOpen openDependency(File data, ShortBuffer buf, PeerNode source) {
- try {
- if (data != null) return new DepOpen(new FileRandomAccessBuffer(data, true), false);
- if (LOG.isErrorEnabled()) {
- LOG.error("Dependency with hash {} not found!", HexUtil.bytesToHex(buf.getData()));
- }
- return new DepOpen(null, true);
- } catch (IOException e) {
- LOG.error(
- "Peer {} asked us for the dependency with hash {} jar, we have downloaded it but {} even"
- + " though we did have it when we checked!: {}",
- source,
- HexUtil.bytesToHex(buf.getData()),
- e instanceof FileNotFoundException ? "don't have the file" : "can't read the file",
- e,
- e);
- return new DepOpen(null, true);
- }
- }
-
- /**
- * @return False if we cannot accept any more transfers from this node. True to accept the
- * transfer.
- */
- private boolean incrementDependencies(PeerNode source) {
- synchronized (peersFetchingDependencies) {
- Integer x = peersFetchingDependencies.get(source);
- if (x == null) x = 0;
- x++;
- if (x > MAX_TRANSFERS_PER_PEER) {
- LOG.info("Too many dependency transfers for peer {} - rejecting", source);
- return false;
- } else peersFetchingDependencies.put(source, x);
- return true;
- }
- }
-
- boolean fetchingUOM() {
- return fetchingUOM;
+ public boolean isFetchingMain() {
+ return false;
}
/** Callback notified when a dependency fetch completes successfully. */
@@ -2570,20 +1474,10 @@ private PeerNode findPeerWithFallback() {
boolean tryEverything = false;
while (true) {
HashSet