Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ out/jreleaser
build-logic/build/
build-logic/bin/
tmp*
/changelogfull.md
/changelogfull.txt

# WiX/jpackage debug artifacts (decompiled MSI and relink outputs)
/Crypta-*.wxs
/.vscode
Expand Down
12 changes: 12 additions & 0 deletions src/main/java/network/crypta/client/async/ChosenBlock.java
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,18 @@ private record ConstructionState(
*/
public abstract short getPriority();

/**
* Returns an optional external identifier used for diagnostics correlation.
*
* <p>The default implementation returns {@code null}. Implementations that can map a chosen block
* to a client-visible request identifier should override this method.
*
* @return external identifier string, or {@code null} if unavailable
*/
public String getExternalRequestIdentifier() {
return null;
}

private boolean sendIsBlocking;

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,15 @@ public short getPriority() {
return request.getPriorityClass();
}

@Override
public String getExternalRequestIdentifier() {
ClientRequester clientRequester = request.getClientRequest();
if (clientRequester == null) {
return null;
}
return clientRequester.getExternalRequestIdentifier();
}

/**
* Obtains the sender capable of executing this block under the provided context. The returned
* sender defines whether sending blocks and performs the actual network/storage interaction.
Expand Down
37 changes: 37 additions & 0 deletions src/main/java/network/crypta/client/async/ClientRequester.java
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,14 @@ public abstract void onTransition(
*/
protected transient RequestClient client;

/**
* Optional external identifier used to correlate scheduler activity with client-visible requests.
*
* <p>Typical values include an FCP request identifier prefix such as {@code fcp:...}. The value
* is intentionally transient so persistence format remains unchanged.
*/
private transient volatile String externalRequestIdentifier;

/**
* Returns the current scheduling priority class for this request.
*
Expand Down Expand Up @@ -571,6 +579,35 @@ public RequestClient getClient() {
return client;
}

/**
* Assigns an external correlation identifier for diagnostics.
*
* <p>This does not affect routing or scheduling; it only enriches logs when low-level stalls are
* reported.
*
* @param externalRequestIdentifier external identifier string, or {@code null} to clear
*/
public final void setExternalRequestIdentifier(String externalRequestIdentifier) {
this.externalRequestIdentifier = normalizeExternalRequestIdentifier(externalRequestIdentifier);
}

/**
* Returns the optional external correlation identifier used for diagnostics.
*
* @return external identifier, or {@code null} if none has been assigned
*/
public final String getExternalRequestIdentifier() {
return externalRequestIdentifier;
}

private static String normalizeExternalRequestIdentifier(String externalRequestIdentifier) {
if (externalRequestIdentifier == null) {
return null;
}
String normalized = externalRequestIdentifier.trim();
return normalized.isEmpty() ? null : normalized;
}

/**
* Changes the scheduling priority class of this request and re-registers all sub-requests.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -758,7 +758,8 @@ private void putToStore(NodeClientCore core, ChosenBlock req, KeyBlock b, Client
req.forkOnCacheable,
Node.PREFER_INSERT_DEFAULT,
Node.IGNORE_LOW_BACKOFF_DEFAULT,
req.realTimeFlag);
req.realTimeFlag,
req.getExternalRequestIdentifier());
}

private boolean handleCollision(
Expand Down
32 changes: 26 additions & 6 deletions src/main/java/network/crypta/client/async/SplitFileFetcher.java
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,27 @@ public final class SplitFileFetcher
/** Simple holder for completion-via-truncation configuration. */
private record TruncationConfig(FileGetCompletionCallback callback, File tempFile) {}

/**
* Ensures a restored RAF is closed if constructor resume fails before ownership is transferred to
* this fetcher instance.
*/
private static final class ResumeRafCloseGuard implements AutoCloseable {
private LockableRandomAccessBuffer raf;

void track(LockableRandomAccessBuffer raf) {
this.raf = raf;
}

void release() {
raf = null;
}

@Override
public void close() {
IOUtils.closeQuietly(raf);
}
}

/**
* Stores the progress of the download, including the actual data, in a separate file. Created in
* onResume() or in the constructor, so must be volatile.
Expand Down Expand Up @@ -792,9 +813,8 @@ public boolean writeTrivialProgress(DataOutputStream dos) throws IOException {
public SplitFileFetcher(ClientGetter getter, DataInputStream dis, ClientContext context)
throws StorageFormatException, ResumeFailedException, IOException {
LOG.info("Resuming splitfile download for {}", this);
LockableRandomAccessBuffer restored = null;
boolean success = false;
try {
LockableRandomAccessBuffer restored;
try (ResumeRafCloseGuard closeGuard = new ResumeRafCloseGuard()) {
boolean completeViaTruncation = dis.readBoolean();
if (completeViaTruncation) {
fileCompleteViaTruncation = new File(dis.readUTF());
Expand All @@ -808,6 +828,7 @@ public SplitFileFetcher(ClientGetter getter, DataInputStream dis, ClientContext
// Note: Could verify against finalLength to finish immediately if it matches.
restored =
new PooledFileRandomAccessBuffer(fileCompleteViaTruncation, false, rafSize, -1, true);
closeGuard.track(restored);
} else {
restored =
BucketTools.restoreRAFFrom(
Expand All @@ -817,6 +838,7 @@ public SplitFileFetcher(ClientGetter getter, DataInputStream dis, ClientContext
context.getPersistentMasterSecret());
fileCompleteViaTruncation = null;
callbackCompleteViaTruncation = null;
closeGuard.track(restored);
}
this.raf = restored;
this.parent = getter;
Expand All @@ -830,9 +852,7 @@ public SplitFileFetcher(ClientGetter getter, DataInputStream dis, ClientContext
// onResume() will do the rest.
LOG.info("Resumed splitfile download for {}", this);
lastNotifiedStoreFetch = System.currentTimeMillis();
success = true;
} finally {
if (!success) IOUtils.closeQuietly(restored);
closeGuard.release();
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,8 @@ public boolean send(
request.forkOnCacheable,
Node.PREFER_INSERT_DEFAULT,
Node.IGNORE_LOW_BACKOFF_DEFAULT,
request.realTimeFlag);
request.realTimeFlag,
request.getExternalRequestIdentifier());
}
request.onInsertSuccess(key, context);
} catch (final LowLevelPutException e) {
Expand Down
3 changes: 3 additions & 0 deletions src/main/java/network/crypta/clients/fcp/ClientGet.java
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,7 @@ record ClientGetSetup(
this.extensionCheck = returnSetup.extension();
this.initialMetadata = setup.initialMetadata();
getter = makeGetter(setup.core(), returnSetup.bucket());
applyDiagnosticIdentifier(getter);
initHelpers();
}

Expand All @@ -290,6 +291,7 @@ record ClientGetSetup(
this.extensionCheck = returnSetup.extension();
this.initialMetadata = setup.initialMetadata();
getter = makeGetter(setup.core(), returnSetup.bucket());
applyDiagnosticIdentifier(getter);
initHelpers();
}

Expand Down Expand Up @@ -1183,6 +1185,7 @@ public void getClientDetail(DataOutputStream dos, ChecksumChecker checker) throw
restoredGetter = makeGetterForPersistence(makeBucket(false));
}
getter = restoredGetter;
applyDiagnosticIdentifier(getter);
initHelpers();
}

Expand Down
3 changes: 3 additions & 0 deletions src/main/java/network/crypta/clients/fcp/ClientPut.java
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ public ClientPut(
options.overrideSplitfileCryptoKey(),
-1);
putter = ClientPutPutterFactory.create(putterRequest, putterOptions);
applyDiagnosticIdentifier(putter);
}

/**
Expand Down Expand Up @@ -291,6 +292,7 @@ public ClientPut(FCPConnectionHandler handler, ClientPutMessage message, FCPServ
message.overrideSplitfileCryptoKey,
message.metadataThreshold);
putter = ClientPutPutterFactory.create(putterRequest, putterOptions);
applyDiagnosticIdentifier(putter);
}

/**
Expand Down Expand Up @@ -839,6 +841,7 @@ RequestStatus getStatus() {
*/
@Override
public void innerResume(ClientContext context) throws ResumeFailedException {
applyDiagnosticIdentifier(putter);
if (data != null) data.onResume(context);
}

Expand Down
22 changes: 19 additions & 3 deletions src/main/java/network/crypta/clients/fcp/ClientPutMessage.java
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ public ClientPutMessage(SimpleFieldSet fs) throws MessageInvalidException {

fileHash = fs.get(ClientPutBase.FILE_HASH);

UploadConfig uploadConfig = parseUploadSource(fs, identifier, global, filenameHint);
UploadConfig uploadConfig = parseUploadSource(fs, identifier, global, filenameHint, uri);
payloadLength = uploadConfig.length();
uploadFromType = uploadConfig.type();
origFilename = uploadConfig.originalFile();
Expand Down Expand Up @@ -299,7 +299,11 @@ private static short parsePriorityClass(String priorityString, String identifier
}

private UploadConfig parseUploadSource(
SimpleFieldSet fs, String identifier, boolean global, String filenameHint)
SimpleFieldSet fs,
String identifier,
boolean global,
String filenameHint,
FreenetURI parsedUri)
throws MessageInvalidException {
String uploadFrom = fs.get(FIELD_UPLOAD_FROM);
if (uploadFrom == null || uploadFrom.equalsIgnoreCase("direct")) {
Expand All @@ -316,7 +320,10 @@ private UploadConfig parseUploadSource(
throw new MessageInvalidException(
ProtocolErrorMessage.FILE_NOT_FOUND, null, identifier, global);
bucket = new FileBucket(f, true, false, false, false);
String resolvedName = filenameHint != null ? filenameHint : f.getName();
String resolvedName = filenameHint;
if (resolvedName == null && shouldInferDiskFilenameHint(parsedUri)) {
resolvedName = f.getName();
}
return new UploadConfig(f.length(), UploadFrom.DISK, f, null, resolvedName);
}
if (uploadFrom.equalsIgnoreCase("redirect")) {
Expand Down Expand Up @@ -405,6 +412,15 @@ private static String parseCompressorDescriptor(String codecs, String identifier
}
}

private static boolean shouldInferDiskFilenameHint(FreenetURI uri) {
// For bare CHK inserts we do not infer a target filename from the local disk path.
// Preserving a null filename keeps semantics aligned with direct payload uploads and avoids
// forcing a metadata wrapper when the client did not explicitly request one.
return !(uri.getRoutingKey() == null
&& uri.getDocName() == null
&& "CHK".equals(uri.getKeyType()));
}

private record UriParseResult(FreenetURI uri, String filenameHint) {}

private record UploadConfig(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import java.io.File;
import network.crypta.client.DefaultMIMETypes;
import network.crypta.client.async.BinaryBlob;
import network.crypta.keys.FreenetURI;

/**
* Resolves and validates MIME types for {@link ClientPut} requests.
Expand Down Expand Up @@ -88,6 +89,24 @@ static String resolve(
identifier,
global);
}
if (shouldSuppressMimeForDiskBareChk(message, targetFilename)) {
// Disk uploads for bare CHK@ without an explicit TargetFilename should follow direct-mode
// semantics and avoid forcing a two-block metadata wrapper solely for MIME information.
mimeType = null;
}
return mimeType;
}

private static boolean shouldSuppressMimeForDiskBareChk(
ClientPutMessage message, String targetFilename) {
return message.uploadFromType == ClientPutBase.UploadFrom.DISK
&& targetFilename == null
&& isBareChkInsertUri(message.uri);
}

private static boolean isBareChkInsertUri(FreenetURI uri) {
return uri.getRoutingKey() == null
&& uri.getDocName() == null
&& "CHK".equals(uri.getKeyType());
}
}
26 changes: 26 additions & 0 deletions src/main/java/network/crypta/clients/fcp/ClientRequest.java
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,32 @@ public String getIdentifier() {
return identifier;
}

/**
* Builds the diagnostics correlation identifier for this request.
*
* <p>The identifier is prefixed to make it explicit that the value originates from FCP.
*
* @return prefixed identifier string, or {@code null} when no identifier is available
*/
protected final String diagnosticIdentifier() {
if (identifier == null) {
return null;
}
return "fcp:" + identifier;
}

/**
* Assigns this request's diagnostics identifier to a low-level requester.
*
* @param requester requester that should carry the diagnostics identifier
*/
protected final void applyDiagnosticIdentifier(ClientRequester requester) {
if (requester == null) {
return;
}
requester.setExternalRequestIdentifier(diagnosticIdentifier());
}

/**
* Returns the underlying {@link ClientRequester} that performs the actual network work.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -579,17 +579,30 @@ public static LockableRandomAccessBuffer create(
if (type == null) throw new StorageFormatException("Unknown EncryptedRandomAccessBufferType");
LockableRandomAccessBuffer underlying =
BucketTools.restoreRAFFrom(dis, fg, persistentFileTracker, masterKey);
boolean success = false;
try {
class UnderlyingCloseGuard implements AutoCloseable {
private LockableRandomAccessBuffer buffer;

private UnderlyingCloseGuard(LockableRandomAccessBuffer buffer) {
this.buffer = buffer;
}

private void release() {
buffer = null;
}

@Override
public void close() {
IOUtils.closeQuietly(buffer);
}
}
try (UnderlyingCloseGuard closeGuard = new UnderlyingCloseGuard(underlying)) {
EncryptedRandomAccessBuffer restored =
new EncryptedRandomAccessBuffer(type, underlying, masterKey, false);
success = true;
closeGuard.release();
return restored;
} catch (GeneralSecurityException e) {
throw new ResumeFailedException(
new GeneralSecurityException("Crypto error resuming EncryptedRandomAccessBuffer", e));
} finally {
if (!success) IOUtils.closeQuietly(underlying);
}
}

Expand Down
Loading
Loading