mirror of
https://github.com/Quad4-Software/Reticulum-Go
synced 2026-08-29 23:48:44 -04:00
179 lines
7.7 KiB
Java
179 lines
7.7 KiB
Java
// SPDX-License-Identifier: Apache-2.0
|
|
// Copyright (c) 2024-2026 Quad4.io
|
|
//
|
|
// NomadNet-style pageserver over Java librns bindings.
|
|
// Usage: java Main -c config [-i identity] [-a announce_sec] [-p page_file]
|
|
|
|
import io.quad4.rns.Destination;
|
|
import io.quad4.rns.Event;
|
|
import io.quad4.rns.Identity;
|
|
import io.quad4.rns.Link;
|
|
import io.quad4.rns.Node;
|
|
import io.quad4.rns.Rns;
|
|
import io.quad4.rns.RnsException;
|
|
import java.io.IOException;
|
|
import java.nio.charset.StandardCharsets;
|
|
import java.nio.file.Files;
|
|
import java.nio.file.Paths;
|
|
import java.util.Arrays;
|
|
|
|
public final class Main {
|
|
private static final int DEFAULT_ANNOUNCE_SEC = 900;
|
|
private static final String DEFAULT_PAGE_PATH = "/page/index.mu";
|
|
private static final String DEFAULT_FILE_PATH = "/file/test.txt";
|
|
private static final String DEFAULT_PAGE_FILE = "pages/index.mu";
|
|
private static final String DEFAULT_FILE_FILE = "files/test.txt";
|
|
private static final String DEFAULT_IDENTITY_PATH = "identity";
|
|
private static final int REQ_DATA_CAP = 64 * 1024;
|
|
private static final String FALLBACK_PAGE =
|
|
"> Java pageserver\n\nlibrns via Reticulum-Go\n\nFallback page (file not found).\n\n";
|
|
private static final String FALLBACK_FILE = "Test file from Reticulum-Go node!\n";
|
|
|
|
public static void main(String[] args) throws Exception {
|
|
String configPath = null;
|
|
String identityPath = DEFAULT_IDENTITY_PATH;
|
|
int announceSec = DEFAULT_ANNOUNCE_SEC;
|
|
String pageFile = DEFAULT_PAGE_FILE;
|
|
String fileFile = DEFAULT_FILE_FILE;
|
|
String requestPath = DEFAULT_PAGE_PATH;
|
|
for (int i = 0; i < args.length; i++) {
|
|
if ("-c".equals(args[i]) && i + 1 < args.length) {
|
|
configPath = args[++i];
|
|
} else if ("-i".equals(args[i]) && i + 1 < args.length) {
|
|
identityPath = args[++i];
|
|
} else if ("-a".equals(args[i]) && i + 1 < args.length) {
|
|
announceSec = Integer.parseInt(args[++i]);
|
|
} else if ("-p".equals(args[i]) && i + 1 < args.length) {
|
|
pageFile = args[++i];
|
|
} else if ("-f".equals(args[i]) && i + 1 < args.length) {
|
|
fileFile = args[++i];
|
|
} else if ("-P".equals(args[i]) && i + 1 < args.length) {
|
|
requestPath = args[++i];
|
|
} else {
|
|
die("unexpected argument: " + args[i]);
|
|
}
|
|
}
|
|
if (configPath == null || announceSec < 0) {
|
|
die("usage: java Main -c config [options]");
|
|
}
|
|
if (!Rns.API_VERSION.equals(Rns.version())) {
|
|
die("librns version mismatch: got " + Rns.version());
|
|
}
|
|
|
|
byte[] pageBody = loadBytes(pageFile, FALLBACK_PAGE);
|
|
byte[] fileBody = loadBytes(fileFile, FALLBACK_FILE);
|
|
|
|
try (Node node = Node.create(configPath);
|
|
Identity identity = loadOrCreateIdentity(identityPath);
|
|
Destination dest =
|
|
Destination.create(node, null, "nomadnetwork", Arrays.asList("node"), true)) {
|
|
node.setIdentity(identity);
|
|
node.start();
|
|
dest.registerRequestHandler(requestPath);
|
|
dest.registerRequestHandler(DEFAULT_FILE_PATH);
|
|
|
|
String destHex = Rns.hashToHex(dest.hash());
|
|
System.out.println("DEST_HASH=" + destHex);
|
|
System.out.println("REQUEST_PATH=" + requestPath);
|
|
System.out.println("FILE_PATH=" + DEFAULT_FILE_PATH);
|
|
System.err.println("librns " + Rns.version() + " pageserver listening as nomadnetwork.node");
|
|
|
|
byte[] appData = "librns-java-pageserver".getBytes(StandardCharsets.UTF_8);
|
|
try {
|
|
dest.announce(appData);
|
|
System.err.println("announce sent");
|
|
} catch (RnsException e) {
|
|
printLastError("destination.announce failed");
|
|
}
|
|
|
|
long lastAnnounce = System.currentTimeMillis();
|
|
while (true) {
|
|
if (announceSec > 0 && System.currentTimeMillis() - lastAnnounce >= announceSec * 1000L) {
|
|
try {
|
|
dest.announce(appData);
|
|
System.err.println("announce refreshed");
|
|
} catch (RnsException ignored) {
|
|
// continue
|
|
}
|
|
lastAnnounce = System.currentTimeMillis();
|
|
}
|
|
|
|
try {
|
|
Event ev = Event.poll(node, 200, REQ_DATA_CAP);
|
|
if (ev.kind() == Event.LINK_ESTABLISHED) {
|
|
System.err.println("inbound link established");
|
|
} else if (ev.kind() == Event.LINK_CLOSED) {
|
|
System.err.println("link closed");
|
|
} else if (ev.kind() == Event.REQUEST_INCOMING) {
|
|
String path = ev.path();
|
|
System.err.println("request incoming path=" + path);
|
|
byte[] reqId = ev.requestId();
|
|
if (requestPath.equals(path)) {
|
|
try {
|
|
Link.requestRespond(node, reqId, pageBody);
|
|
System.err.println("served " + requestPath + " (" + pageBody.length + " bytes)");
|
|
} catch (RnsException e) {
|
|
printLastError("request_respond failed");
|
|
}
|
|
} else if (DEFAULT_FILE_PATH.equals(path)) {
|
|
try {
|
|
Link.requestRespondFile(node, reqId, "test.txt", fileBody);
|
|
System.err.println(
|
|
"served " + DEFAULT_FILE_PATH + " (" + fileBody.length + " bytes)");
|
|
} catch (RnsException e) {
|
|
printLastError("request_respond_file failed");
|
|
}
|
|
} else {
|
|
try {
|
|
Link.requestRespond(node, reqId, "page not found\n".getBytes(StandardCharsets.UTF_8));
|
|
} catch (RnsException e) {
|
|
printLastError("request_respond failed");
|
|
}
|
|
}
|
|
}
|
|
} catch (RnsException e) {
|
|
if (e.getCode() != RnsException.TIMEOUT) {
|
|
printLastError("Event.poll failed");
|
|
System.exit(1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private static byte[] loadBytes(String path, String fallback) {
|
|
try {
|
|
return Files.readAllBytes(Paths.get(path));
|
|
} catch (IOException e) {
|
|
System.err.println("warning: could not read " + path + ", using built-in content");
|
|
return fallback.getBytes(StandardCharsets.UTF_8);
|
|
}
|
|
}
|
|
|
|
private static Identity loadOrCreateIdentity(String path) {
|
|
try {
|
|
Identity identity = Identity.load(path);
|
|
System.err.println("loaded identity from " + path);
|
|
return identity;
|
|
} catch (RnsException e) {
|
|
Identity identity = Identity.generate();
|
|
identity.save(path);
|
|
System.err.println("created and saved identity to " + path);
|
|
return identity;
|
|
}
|
|
}
|
|
|
|
private static void die(String msg) {
|
|
System.err.println(msg);
|
|
System.exit(1);
|
|
}
|
|
|
|
private static void printLastError(String what) {
|
|
String msg = Rns.lastError();
|
|
if (msg.isEmpty()) {
|
|
System.err.println(what);
|
|
} else {
|
|
System.err.println(what + ": " + msg);
|
|
}
|
|
}
|
|
}
|