170 lines
6.0 KiB
Java
170 lines
6.0 KiB
Java
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.
|
||
* <p>
|
||
* This implementation expects a table schema similar to:
|
||
* <pre>
|
||
* 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
|
||
* );
|
||
* </pre>
|
||
*/
|
||
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.
|
||
* <p>
|
||
* 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<INSRecord> resolve(String tln, String name, String sub, INSRecordType type) {
|
||
List<INSRecord> 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.<tln>} without any sub-name.
|
||
* <p>
|
||
* 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;
|
||
}
|
||
}
|