Added some Record Tools

This commit is contained in:
Finn
2025-12-11 11:32:52 +01:00
parent 8c64f67538
commit 95dd467634
4 changed files with 191 additions and 10 deletions

View File

@@ -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) {

View File

@@ -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.

View File

@@ -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.
* <p>
* Features:
* <ul>
* <li>Recursive CNAME resolution with loop protection</li>
* <li>Basic record validation helpers</li>
* </ul>
*/
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.
* <p>
* Resolution strategy:
* <ol>
* <li>Try to resolve the requested {@code type} directly via {@link ProtocolINSServer#resolve}</li>
* <li>If no records are found:
* <ul>
* <li>Resolve {@link INSRecordType#CNAME} for the same (tln, name, sub)</li>
* <li>For each CNAME target, follow the chain up to {@link #MAX_CNAME_DEPTH}</li>
* </ul>
* </li>
* <li>Return the first non-empty resolution result</li>
* </ol>
*
* @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<INSRecord> 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<INSRecord> direct = server.resolve(tln, name, sub, type);
if (!direct.isEmpty()) return direct;
// Otherwise follow CNAME chain
Set<String> visited = new HashSet<>();
return followCNAME(server, tln, name, sub, type, 0, visited);
}
private static List<INSRecord> followCNAME(ProtocolINSServer server, String tln, String name, String sub, INSRecordType targetType, int depth, Set<String> 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<INSRecord> 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<INSRecord> resolved = server.resolve(target.tln, target.name,target.sub, targetType);
if (!resolved.isEmpty()) return resolved;
// Try next CNAME hop
List<INSRecord> 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}.
* <p>
* Checks:
* <ul>
* <li>type is non-null</li>
* <li>value is non-null and non-empty</li>
* <li>port is in range 065535</li>
* <li>ttl is non-negative</li>
* </ul>
*
* @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<INSRecord> filterValid(List<INSRecord> records) {
if (records == null || records.isEmpty()) return Collections.emptyList();
List<INSRecord> out = new ArrayList<>(records.size());
for (INSRecord r : records) {
if (isValidRecord(r)) out.add(r);
}
return out;
}
}