Added some Record Tools
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 0–65535</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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user