} without any sub-name.
*
- * The value is read from {@code tln.info} and must be of the form
- * {@code "host:port"}.
+ * The value is read from {@code tln.info} and must be of the form {@code "host:port"}.
+ *
+ * @param tln The TLN name.
+ * @return The configured info target ("host:port") or {@code null} if not present.
*/
@Override
public String resolveTLNInfoSite(String tln) {
@@ -103,7 +122,7 @@ public final class DatabaseINSServer extends ProtocolINSServer {
ps.setString(1, tln);
try (ResultSet rs = ps.executeQuery()) {
- if (rs.next()) return rs.getString("info"); // expected "host:port"
+ if (rs.next()) return rs.getString("info");
}
} catch (SQLException ex) {
getProtocolBridge().getLogger().exception("Failed to resolve TLN info site for tln=" + tln, ex);
@@ -118,18 +137,35 @@ public final class DatabaseINSServer extends ProtocolINSServer {
}
/**
- * Recursive resolver with CNAME handling and depth limit.
+ * Internal recursive resolver with CNAME handling.
+ *
+ * @param depth Current recursion depth.
+ * @param visited Loop detection set of canonical names (sub.name.tln).
*/
- private List resolveInternal(String tln, String name, String sub, INSRecordType requestedType, int depth) throws SQLException {
- final int MAX_CNAME_DEPTH = 8;
+ private List resolveInternal(
+ String tln,
+ String name,
+ String sub,
+ INSRecordType requestedType,
+ int depth,
+ Set visited
+ ) throws SQLException {
+
+ final int MAX_CNAME_DEPTH = 16;
+
+ String canonical = formatName(tln, name, sub).toLowerCase(Locale.ROOT);
+ if (!visited.add(canonical)) {
+ // loop detected
+ getProtocolBridge().getLogger().warn("CNAME loop detected for " + canonical + " type=" + requestedType);
+ return new ArrayList<>();
+ }
if (depth > MAX_CNAME_DEPTH) {
- getProtocolBridge().getLogger().warn("CNAME recursion limit exceeded for " + formatName(tln, name, sub) + " type=" + requestedType);
+ getProtocolBridge().getLogger().warn("CNAME recursion limit exceeded for " + canonical + " type=" + requestedType);
return new ArrayList<>();
}
try (Connection conn = openConnection()) {
- // Get IDs
Integer tlnId = findTLNId(conn, tln);
if (tlnId == null) return new ArrayList<>();
@@ -138,43 +174,93 @@ public final class DatabaseINSServer extends ProtocolINSServer {
Integer subNameId = findSubNameId(conn, infoNameId, sub);
- // Get records
+ // 1) direct records
List direct = loadRecords(conn, infoNameId, subNameId, requestedType);
+ direct.sort(recordComparator(requestedType));
- // No more recursion when CNAME received
+ // If the requested type is CNAME, do not recurse.
if (requestedType == INSRecordType.CNAME) return direct;
if (!direct.isEmpty()) return direct;
- // CNAME lookup
+ // 2) fallback to CNAME if no direct records exist
List cnames = loadRecords(conn, infoNameId, subNameId, INSRecordType.CNAME);
- if (cnames.isEmpty()) return direct; // empty
+ cnames.sort(recordComparator(INSRecordType.CNAME));
+
+ if (cnames.isEmpty()) return new ArrayList<>();
List aggregated = new ArrayList<>();
+
for (INSRecord cname : cnames) {
TargetName target = parseCnameTarget(cname.value);
-
if (target == null) {
- getProtocolBridge().getLogger().warn("Invalid CNAME target '" + cname.value + "' on " + formatName(tln, name, sub));
+ getProtocolBridge().getLogger().warn("Invalid CNAME target '" + cname.value + "' on " + canonical);
continue;
}
- // Rekursiv den Zielnamen auflösen
- aggregated.addAll(resolveInternal(target.tln, target.name, target.sub, requestedType, depth + 1));
+ // recurse on the target name to fetch the original requested type
+ List resolvedTarget = resolveInternal(
+ target.tln(),
+ target.name(),
+ target.sub(),
+ requestedType,
+ depth + 1,
+ visited
+ );
+ aggregated.addAll(resolvedTarget);
}
+ aggregated.sort(recordComparator(requestedType));
return aggregated;
+ } finally {
+ // important: visited is shared across the recursion chain on purpose
+ // (do not remove canonical here; loop detection should stay for the whole chain)
}
}
+ /**
+ * Deterministic ordering for returned records. The goal is stable ranking so callers can pick index 0.
+ *
+ * Rules:
+ *
+ * - priority ASC (smaller is better)
+ * - weight DESC (larger is better)
+ * - port ASC (smaller is better)
+ * - ttl DESC (larger is better)
+ * - value ASC (case-insensitive) as final tie-breaker
+ *
+ *
+ * @param requestedType The type being requested (kept for future type-specific tuning).
+ * @return Comparator for {@link INSRecord}.
+ */
+ private Comparator recordComparator(INSRecordType requestedType) {
+ return Comparator
+ .comparingInt((INSRecord r) -> safeInt(r.priority))
+ .thenComparingInt(r -> -safeInt(r.weight))
+ .thenComparingInt(r -> safeInt(r.port))
+ .thenComparingInt(r -> -safeInt(r.ttl))
+ .thenComparing(r -> safeString(r.value), String.CASE_INSENSITIVE_ORDER);
+ }
+
+ private static int safeInt(int v) {
+ return v;
+ }
+
+ private static String safeString(String s) {
+ return s == null ? "" : s;
+ }
+
/**
* Loads all records of a given type for (infoname_id, subname_id).
*
* @param type May be {@code null} to load all types.
*/
private List loadRecords(Connection conn, int infonameId, Integer subnameId, INSRecordType type) throws SQLException {
-
- StringBuilder sql = new StringBuilder("SELECT type, value, ttl, priority, port, weight FROM records WHERE infoname_id = ? ");
+ StringBuilder sql = new StringBuilder(
+ "SELECT type, value, ttl, priority, port, weight " +
+ "FROM records " +
+ "WHERE infoname_id = ? "
+ );
if (subnameId == null) sql.append("AND subname_id IS NULL ");
else sql.append("AND subname_id = ? ");
@@ -213,7 +299,6 @@ public final class DatabaseINSServer extends ProtocolINSServer {
try (PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setString(1, tln);
-
try (ResultSet rs = ps.executeQuery()) {
if (rs.next()) return rs.getInt("id");
}
@@ -227,12 +312,10 @@ public final class DatabaseINSServer extends ProtocolINSServer {
try (PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setInt(1, tlnId);
ps.setString(2, infoName);
-
try (ResultSet rs = ps.executeQuery()) {
if (rs.next()) return rs.getInt("id");
}
}
-
return null;
}
@@ -248,16 +331,14 @@ public final class DatabaseINSServer extends ProtocolINSServer {
if (rs.next()) return rs.getInt("id");
}
}
-
return null;
}
/**
* Parses a CNAME target string into TLN, InfoName and optional subname.
- *
*
- * @param value Raw CNAME value from the {@code records} table.
- * @return Parsed {@link TargetName} or {@code null} if the value is invalid.
+ * @param value Raw CNAME value (e.g. "sub.app.example" or "example.net").
+ * @return Parsed {@link TargetName} or {@code null} if invalid.
*/
private TargetName parseCnameTarget(String value) {
if (value == null) return null;
@@ -265,7 +346,7 @@ public final class DatabaseINSServer extends ProtocolINSServer {
if (trimmed.isEmpty()) return null;
String[] parts = trimmed.split("\\.");
- if (parts.length < 2) return null; // at least "."
+ if (parts.length < 2) return null;
String tln = parts[parts.length - 1];
String name = parts[parts.length - 2];
@@ -273,15 +354,13 @@ public final class DatabaseINSServer extends ProtocolINSServer {
if (parts.length > 2) {
StringBuilder sb = new StringBuilder();
-
for (int i = 0; i < parts.length - 2; i++) {
if (i > 0) sb.append('.');
sb.append(parts[i]);
}
-
sub = sb.toString();
}
return new TargetName(tln, name, sub);
}
-}
\ No newline at end of file
+}
diff --git a/src/main/java/org/openautonomousconnection/insserver/Main.java b/src/main/java/org/openautonomousconnection/insserver/Main.java
index 037eca7..b9ed53f 100644
--- a/src/main/java/org/openautonomousconnection/insserver/Main.java
+++ b/src/main/java/org/openautonomousconnection/insserver/Main.java
@@ -9,6 +9,7 @@ import lombok.Getter;
import org.openautonomousconnection.protocol.ProtocolBridge;
import org.openautonomousconnection.protocol.ProtocolSettings;
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
+import org.openautonomousconnection.protocol.versions.v1_0_0.classic.handlers.builtin.INSClassic;
import java.io.File;
import java.util.Scanner;
@@ -31,7 +32,11 @@ public class Main {
settings.eventManager.registerListener(Listener.class);
settings.port = 1023;
- protocolBridge = new ProtocolBridge(new DatabaseINSServer(), settings, ProtocolVersion.PV_1_0_0_BETA, new File("logs"));
+ DatabaseINSServer server = new DatabaseINSServer();
+ protocolBridge = new ProtocolBridge(server, settings, ProtocolVersion.PV_1_0_0_BETA, new File("logs"));
+ protocolBridge.setClassicHandlerINSServer(new INSClassic(protocolBridge));
+ server.getPipelineServer().start();
+
commandManager = new CommandManager(protocolBridge.getProtocolSettings().eventManager);
Scanner scanner = new Scanner(System.in);
diff --git a/src/main/java/org/openautonomousconnection/insserver/utils/ClassicHelper.java b/src/main/java/org/openautonomousconnection/insserver/utils/ClassicHelper.java
deleted file mode 100644
index 42027ca..0000000
--- a/src/main/java/org/openautonomousconnection/insserver/utils/ClassicHelper.java
+++ /dev/null
@@ -1,38 +0,0 @@
-package org.openautonomousconnection.insserver.utils;
-
-import org.openautonomousconnection.insserver.Main;
-import org.openautonomousconnection.protocol.versions.v1_0_0.beta.INSRecord;
-import org.openautonomousconnection.protocol.versions.v1_0_0.classic.objects.Classic_Domain;
-import org.openautonomousconnection.protocol.versions.v1_0_0.classic.objects.Classic_RequestDomain;
-
-public class ClassicHelper {
- public static Classic_Domain buildClassicDomain(ParsedDomain req, INSRecord rec) {
- Classic_Domain info = new Classic_Domain(req.name, req.tln, req.sub, req.path, Main.getProtocolBridge());
-
- String host = rec.value;
- int port = rec.port > 0 ? rec.port : 80;
-
- return new Classic_Domain(
- req.name, req.tln,
- host.contains(":") ? host : host + ":" + port,
- req.path, Main.getProtocolBridge());
- }
-
- public static ParsedDomain parseDomain(Classic_RequestDomain req) {
- String tln = req.topLevelDomain; // example: "net"
- String full = req.name; // example: "api.v1.example"
-
- String[] parts = full.split("\\.");
-
- if (parts.length == 1) {
- return new ParsedDomain(tln, full, null, req.path);
- }
-
- String name = parts[parts.length - 1];
- String sub = parts.length > 1
- ? String.join(".", java.util.Arrays.copyOfRange(parts, 0, parts.length - 1))
- : null;
-
- return new ParsedDomain(tln, name, sub, req.path);
- }
-}
diff --git a/src/main/java/org/openautonomousconnection/insserver/utils/ParsedDomain.java b/src/main/java/org/openautonomousconnection/insserver/utils/ParsedDomain.java
deleted file mode 100644
index 5a51f38..0000000
--- a/src/main/java/org/openautonomousconnection/insserver/utils/ParsedDomain.java
+++ /dev/null
@@ -1,15 +0,0 @@
-package org.openautonomousconnection.insserver.utils;
-
-public final class ParsedDomain {
- public final String tln;
- public final String name;
- public final String sub;
- public final String path;
-
- public ParsedDomain(String tln, String name, String sub, String path) {
- this.tln = tln;
- this.name = name;
- this.sub = sub;
- this.path = path;
- }
-}
\ No newline at end of file
diff --git a/src/main/java/org/openautonomousconnection/insserver/utils/TargetName.java b/src/main/java/org/openautonomousconnection/insserver/utils/TargetName.java
deleted file mode 100644
index e6f0d47..0000000
--- a/src/main/java/org/openautonomousconnection/insserver/utils/TargetName.java
+++ /dev/null
@@ -1,16 +0,0 @@
-package org.openautonomousconnection.insserver.utils;
-
-/**
- * Represents a parsed CNAME target in (tln, name, sub) form.
- */
-public final class TargetName {
- public final String tln;
- public final String name;
- public final String sub;
-
- public TargetName(String tln, String name, String sub) {
- this.tln = tln;
- this.name = name;
- this.sub = sub;
- }
-}