Open Autonomous Connection
https://open-autonomous-connection.org/
diff --git a/src/main/java/org/openautonomousconnection/protocol/listeners/INSServerListener.java b/src/main/java/org/openautonomousconnection/protocol/listeners/INSServerListener.java
index 08180c0..51d3677 100644
--- a/src/main/java/org/openautonomousconnection/protocol/listeners/INSServerListener.java
+++ b/src/main/java/org/openautonomousconnection/protocol/listeners/INSServerListener.java
@@ -33,9 +33,9 @@ public final class INSServerListener extends EventListener {
private ProtocolINSServer insServer;
/**
- * Sets the insServer variable
+ * Injects the INS server instance once.
*
- * @param insServer The Instance of the INSServer
+ * @param insServer The INS server implementation.
*/
public void setINSServer(ProtocolINSServer insServer) {
if (this.insServer != null) return;
@@ -43,10 +43,10 @@ public final class INSServerListener extends EventListener {
}
/**
- * Handles the event when a connection handler connects to the INS server.
- * Adds the connected client to the ProtocolBridge's INS server client list.
+ * Called when a new connection handler is attached to the INS network server.
+ * Wraps the connection in a {@link ConnectedProtocolClient} and keeps track of it.
*
- * @param event The connection handler connected event.
+ * @param event The connection event fired by the network layer.
*/
@Listener
public void onConnect(ConnectionHandlerConnectedEvent event) {
@@ -54,10 +54,10 @@ public final class INSServerListener extends EventListener {
}
/**
- * Handles the event when a connection handler disconnects from the INS server.
- * Removes the disconnected client from the ProtocolBridge's INS server client list.
+ * Called when a connection handler disconnects from the INS server.
+ * Removes the associated {@link ConnectedProtocolClient} from the list.
*
- * @param event The connection handler disconnected event.
+ * @param event The disconnection event fired by the network layer.
*/
@Listener
public void onDisconnect(ConnectionHandlerDisconnectedEvent event) {
diff --git a/src/main/java/org/openautonomousconnection/protocol/versions/ProtocolVersion.java b/src/main/java/org/openautonomousconnection/protocol/versions/ProtocolVersion.java
index baff8d2..df82886 100644
--- a/src/main/java/org/openautonomousconnection/protocol/versions/ProtocolVersion.java
+++ b/src/main/java/org/openautonomousconnection/protocol/versions/ProtocolVersion.java
@@ -19,7 +19,7 @@ public enum ProtocolVersion implements Serializable {
/**
* First Beta Version of OAC-Protocol
*/
- PV_1_0_0_BETA("1.0.0", ProtocolType.BETA, ProtocolSide.ALL, List.of(Protocol.OAC));
+ PV_1_0_0_BETA("1.0.0", ProtocolType.BETA, ProtocolSide.ALL, List.of(Protocol.OAC), PV_1_0_0_CLASSIC),;
/**
* The version string of the protocol version.
diff --git a/src/main/java/org/openautonomousconnection/protocol/versions/v1_0_0/beta/INSRecordTools.java b/src/main/java/org/openautonomousconnection/protocol/versions/v1_0_0/beta/INSRecordTools.java
new file mode 100644
index 0000000..3f2281e
--- /dev/null
+++ b/src/main/java/org/openautonomousconnection/protocol/versions/v1_0_0/beta/INSRecordTools.java
@@ -0,0 +1,181 @@
+package org.openautonomousconnection.protocol.versions.v1_0_0.beta;
+
+import org.openautonomousconnection.protocol.side.ins.ProtocolINSServer;
+import org.openautonomousconnection.protocol.versions.v1_0_0.beta.INSRecord;
+import org.openautonomousconnection.protocol.versions.v1_0_0.beta.INSRecordType;
+
+import java.util.*;
+
+/**
+ * Utility methods for working with {@link INSRecord} instances.
+ *
+ * Features:
+ *
+ * - Recursive CNAME resolution with loop protection
+ * - Basic record validation helpers
+ *
+ */
+public final class INSRecordTools {
+
+ /**
+ * Maximum number of CNAME hops before resolution is aborted.
+ */
+ public static final int MAX_CNAME_DEPTH = 10;
+
+ /**
+ * Resolves records for the given name and type, following CNAME chains if necessary.
+ *
+ * Resolution strategy:
+ *
+ * - Try to resolve the requested {@code type} directly via {@link ProtocolINSServer#resolve}
+ * - If no records are found:
+ *
+ * - Resolve {@link INSRecordType#CNAME} for the same (tln, name, sub)
+ * - For each CNAME target, follow the chain up to {@link #MAX_CNAME_DEPTH}
+ *
+ *
+ * - Return the first non-empty resolution result
+ *
+ *
+ * @param server The INS server implementation to use for lookups.
+ * @param tln Top-level-name (e.g. {@code "net"}).
+ * @param name InfoName (e.g. {@code "example"}).
+ * @param sub Optional sub-name (e.g. {@code "www"}), may be {@code null}.
+ * @param type Desired record type (e.g. A, AAAA, TXT, MX, ...).
+ *
+ * @return A list of resolved records. May be empty if nothing could be resolved.
+ */
+ public static List resolveWithCNAME(ProtocolINSServer server,
+ String tln,
+ String name,
+ String sub,
+ INSRecordType type) {
+
+ // If we explicitly ask for CNAME, just return raw CNAME records
+ if (type == INSRecordType.CNAME) return server.resolve(tln, name, sub, INSRecordType.CNAME);
+
+ // Try direct resolution first
+ List direct = server.resolve(tln, name, sub, type);
+ if (!direct.isEmpty()) return direct;
+
+ // Otherwise follow CNAME chain
+ Set visited = new HashSet<>();
+ return followCNAME(server, tln, name, sub, type, 0, visited);
+ }
+
+ private static List followCNAME(ProtocolINSServer server, String tln, String name, String sub, INSRecordType targetType, int depth, Set visited) {
+
+ if (depth > MAX_CNAME_DEPTH) {
+ server.getProtocolBridge().getLogger().warn("Max CNAME depth exceeded for " + fqdn(tln, name, sub));
+ return Collections.emptyList();
+ }
+
+ String key = fqdn(tln, name, sub);
+ if (!visited.add(key)) {
+ // Loop detected
+ server.getProtocolBridge().getLogger().warn("CNAME loop detected for " + key);
+ return Collections.emptyList();
+ }
+
+ List cnames = server.resolve(tln, name, sub, INSRecordType.CNAME);
+ if (cnames.isEmpty()) return Collections.emptyList();
+
+ for (INSRecord cname : cnames) {
+ if (!isValidRecord(cname)) continue;
+
+ // Target might be "host.tln" or full "sub.name.tln"
+ ParsedName target = parseTargetName(cname.value);
+ if (target == null) continue;
+
+ List resolved = server.resolve(target.tln, target.name,target.sub, targetType);
+
+ if (!resolved.isEmpty()) return resolved;
+
+ // Try next CNAME hop
+ List deeper = followCNAME(server, target.tln, target.name, target.sub, targetType, depth + 1, visited);
+
+ if (!deeper.isEmpty()) return deeper;
+ }
+
+ return Collections.emptyList();
+ }
+
+ private static String fqdn(String tln, String name, String sub) {
+ if (sub == null || sub.isEmpty()) return name + "." + tln;
+ return sub + "." + name + "." + tln;
+ }
+
+ /**
+ * Parses a CNAME target into (tln, name, sub) assuming the last label is the TLN
+ * and the one before that is the InfoName.
+ *
+ * @param target FQDN string.
+ *
+ * @return ParsedName or {@code null} if it cannot be parsed.
+ */
+ private static ParsedName parseTargetName(String target) {
+ if (target == null) return null;
+
+ String hostname = target.trim();
+ if (hostname.endsWith(".")) hostname = hostname.substring(0, hostname.length() - 1);
+
+ String[] parts = hostname.split("\\.");
+ if (parts.length < 2) return null;
+
+ String tln = parts[parts.length - 1];
+ String name = parts[parts.length - 2];
+ String sub = null;
+
+ if (parts.length > 2) sub = String.join(".", Arrays.copyOfRange(parts, 0, parts.length - 2));
+ return new ParsedName(tln, name, sub);
+ }
+
+ private record ParsedName(String tln, String name, String sub) {}
+
+ // -------------------------------------------------------------------------
+ // Validation helpers
+ // -------------------------------------------------------------------------
+
+ /**
+ * Performs basic validation on a single {@link INSRecord}.
+ *
+ * Checks:
+ *
+ * - type is non-null
+ * - value is non-null and non-empty
+ * - port is in range 0–65535
+ * - ttl is non-negative
+ *
+ *
+ * @param record The record to validate.
+ *
+ * @return {@code true} if the record passes all checks, {@code false} otherwise.
+ */
+ public static boolean isValidRecord(INSRecord record) {
+ if (record == null) return false;
+ if (record.type == null) return false;
+ if (record.value == null || record.value.isEmpty()) return false;
+
+ if (record.port < 0 || record.port > 65535) return false;
+ if (record.ttl < 0) return false;
+
+ return true;
+ }
+
+ /**
+ * Validates a collection of records by applying {@link #isValidRecord(INSRecord)}
+ * to each element.
+ *
+ * @param records Records to validate.
+ *
+ * @return A new list containing only valid records.
+ */
+ public static List filterValid(List records) {
+ if (records == null || records.isEmpty()) return Collections.emptyList();
+ List out = new ArrayList<>(records.size());
+ for (INSRecord r : records) {
+ if (isValidRecord(r)) out.add(r);
+ }
+ return out;
+ }
+}