From 95dd4676346160883d0aa916b14ddd46fdfeefe6 Mon Sep 17 00:00:00 2001 From: Finn Date: Thu, 11 Dec 2025 11:32:52 +0100 Subject: [PATCH] Added some Record Tools --- pom.xml | 2 +- .../protocol/listeners/INSServerListener.java | 16 +- .../protocol/versions/ProtocolVersion.java | 2 +- .../versions/v1_0_0/beta/INSRecordTools.java | 181 ++++++++++++++++++ 4 files changed, 191 insertions(+), 10 deletions(-) create mode 100644 src/main/java/org/openautonomousconnection/protocol/versions/v1_0_0/beta/INSRecordTools.java diff --git a/pom.xml b/pom.xml index 40531bb..540128e 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ org.openautonomousconnection protocol - 1.0.0-BETA.12 + 1.0.0-BETA.13 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: + *

+ */ +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: + *

    + *
  1. Try to resolve the requested {@code type} directly via {@link ProtocolINSServer#resolve}
  2. + *
  3. 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}
    • + *
    + *
  4. + *
  5. Return the first non-empty resolution result
  6. + *
+ * + * @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; + } +}