package org.openautonomousconnection.ins; import dev.unlegitdqrk.unlegitlibrary.file.ConfigurationManager; import org.openautonomousconnection.protocol.ProtocolBridge; 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.io.File; import java.io.IOException; import java.security.cert.CertificateException; import java.sql.*; import java.util.ArrayList; import java.util.List; /** * Concrete INS server implementation backed by a SQL database. *

* This implementation expects a table schema similar to: *

 * CREATE TABLE ins_records (
 *   id        BIGINT PRIMARY KEY AUTO_INCREMENT,
 *   tln       VARCHAR(64)  NOT NULL,
 *   name      VARCHAR(255) NOT NULL,
 *   sub       VARCHAR(255) NULL,
 *   type      VARCHAR(16)  NOT NULL, -- matches {@link INSRecordType#name()}
 *   value     VARCHAR(1024) NOT NULL,
 *   priority  INT NOT NULL,
 *   weight    INT NOT NULL,
 *   port      INT NOT NULL,
 *   ttl       INT NOT NULL
 * );
 *
 * CREATE TABLE ins_tln_info (
 *   tln       VARCHAR(64) PRIMARY KEY,
 *   host      VARCHAR(255) NOT NULL,
 *   port      INT NOT NULL
 * );
 * 
*/ public final class DatabaseINSServer extends ProtocolINSServer { private final String jdbcUrl; private final String jdbcUser; private final String jdbcPassword; private final ConfigurationManager configurationManager; /** * Creates a new database-backed INS server. * * @throws IOException If the base server initialization fails. * @throws CertificateException If required certificate files are missing or invalid. */ public DatabaseINSServer() throws IOException, CertificateException { super(new File("config.properties")); configurationManager = new ConfigurationManager(new File("config.properties")); configurationManager.loadProperties(); if (!configurationManager.isSet("db.url")) { configurationManager.set("db.url", "jdbc:mysql://localhost:3306/DB_NAME?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC\n"); configurationManager.saveProperties(); } if (!configurationManager.isSet("db.user")) { configurationManager.set("db.user", "username"); configurationManager.saveProperties(); } if (!configurationManager.isSet("db.password")) { configurationManager.set("db.password", "password"); configurationManager.saveProperties(); } jdbcUrl = configurationManager.getString("db.url"); jdbcUser = configurationManager.getString("db.user"); jdbcPassword = configurationManager.getString("db.password"); } private Connection openConnection() throws SQLException { return DriverManager.getConnection(jdbcUrl, jdbcUser, jdbcPassword); } /** * Resolves records for the given INS name from the database. *

* This method does a single lookup and returns exactly the records that * match the given query (no CNAME recursion here – that is handled on a * higher level, e.g. via {@code INSRecordTools.resolveWithCNAME(...)}). * * @param tln Top-level-name. * @param name InfoName. * @param sub Optional sub-name. May be {@code null}. * @param type Desired record type. * * @return List of matching {@link INSRecord}s, or an empty list if none exist. */ @Override public List resolve(String tln, String name, String sub, INSRecordType type) { List result = new ArrayList<>(); String sql = "SELECT type, value, priority, weight, port, ttl " + "FROM ins_records " + "WHERE tln = ? AND name = ? AND type = ? " + "AND ( (sub IS NULL AND ? IS NULL) OR sub = ? )"; try (Connection conn = openConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { ps.setString(1, tln); ps.setString(2, name); ps.setString(3, type.name()); ps.setString(4, sub); ps.setString(5, sub); try (ResultSet rs = ps.executeQuery()) { while (rs.next()) { INSRecordType dbType = INSRecordType.valueOf(rs.getString("type")); String value = rs.getString("value"); int priority = rs.getInt("priority"); int weight = rs.getInt("weight"); int port = rs.getInt("port"); int ttl = rs.getInt("ttl"); result.add(new INSRecord(dbType, value, priority, weight, port, ttl)); } } } catch (SQLException ex) { getProtocolBridge().getLogger().exception("Failed to resolve INS record from database", ex); } return result; } /** * Resolves the TLN info site, * which is used when a client queries {@code info.} without any sub-name. *

* The result must be returned as {@code "host:port"}. * * @param tln The TLN the client is asking about. * * @return A {@code "host:port"} string or {@code null} if no TLN info site is configured. */ @Override public String resolveTLNInfoSite(String tln) { String sql = "SELECT host, port FROM ins_tln_info WHERE tln = ?"; try (Connection conn = openConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { ps.setString(1, tln); try (ResultSet rs = ps.executeQuery()) { if (rs.next()) { String host = rs.getString("host"); int port = rs.getInt("port"); return host + ":" + port; } } } catch (SQLException ex) { getProtocolBridge().getLogger().exception("Failed to resolve TLN info site for tln=" + tln, ex); } return null; } }