Added some Record Tools
This commit is contained in:
2
pom.xml
2
pom.xml
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
<groupId>org.openautonomousconnection</groupId>
|
<groupId>org.openautonomousconnection</groupId>
|
||||||
<artifactId>protocol</artifactId>
|
<artifactId>protocol</artifactId>
|
||||||
<version>1.0.0-BETA.12</version>
|
<version>1.0.0-BETA.13</version>
|
||||||
<organization>
|
<organization>
|
||||||
<name>Open Autonomous Connection</name>
|
<name>Open Autonomous Connection</name>
|
||||||
<url>https://open-autonomous-connection.org/</url>
|
<url>https://open-autonomous-connection.org/</url>
|
||||||
|
|||||||
@@ -33,9 +33,9 @@ public final class INSServerListener extends EventListener {
|
|||||||
private ProtocolINSServer insServer;
|
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) {
|
public void setINSServer(ProtocolINSServer insServer) {
|
||||||
if (this.insServer != null) return;
|
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.
|
* Called when a new connection handler is attached to the INS network server.
|
||||||
* Adds the connected client to the ProtocolBridge's INS server client list.
|
* 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
|
@Listener
|
||||||
public void onConnect(ConnectionHandlerConnectedEvent event) {
|
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.
|
* Called when a connection handler disconnects from the INS server.
|
||||||
* Removes the disconnected client from the ProtocolBridge's INS server client list.
|
* 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
|
@Listener
|
||||||
public void onDisconnect(ConnectionHandlerDisconnectedEvent event) {
|
public void onDisconnect(ConnectionHandlerDisconnectedEvent event) {
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ public enum ProtocolVersion implements Serializable {
|
|||||||
/**
|
/**
|
||||||
* First Beta Version of OAC-Protocol
|
* 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.
|
* 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