+ * Use the {@link Builder} class to construct instances with a fluent API. The configuration
+ * can also be serialized to/from Map for persistence or network transmission.
+ *
+ *
+ * @see NodeConfiguration
+ * @see Builder
*/
-@JsonPropertyOrder({"host4", "host6", "port", "privateKey", "dataPath", "storageURL", "bootstraps",
- "spamThrottling", "suspiciousNodeDetector", "developerMode", "metrics"})
public class DefaultNodeConfiguration implements NodeConfiguration {
/**
* The default port for the DHT node, chosen from the IANA unassigned range (38866-39062).
@@ -71,75 +75,56 @@ public class DefaultNodeConfiguration implements NodeConfiguration {
/**
* IPv4 address string for the DHT node. If null or empty, disables DHT on IPv4.
*/
- @JsonProperty("host4")
- @JsonInclude(JsonInclude.Include.NON_EMPTY)
- String host4;
+ private String host4;
/**
* IPv6 address string for the DHT node. If null or empty, disables DHT on IPv6.
*/
- @JsonProperty("host6")
- @JsonInclude(JsonInclude.Include.NON_EMPTY)
- String host6;
+ private String host6;
/**
* The port number for the DHT node.
*/
- @JsonProperty("port")
- @JsonInclude(JsonInclude.Include.NON_EMPTY)
- int port;
+ private int port;
/**
* The node's private key, encoded in Base58.
*/
- @JsonProperty("privateKey")
- @JsonInclude(JsonInclude.Include.NON_EMPTY)
- String privateKey;
+ private Signature.PrivateKey privateKey;
/**
- * Path to the directory for persistent DHT data storage. If null, disables persistence.
+ * Path to the directory for persistent DHT data storage. disables persistence if null.
*/
- private Path dataPath;
+ private Path dataDir;
/**
- * Optional external storage URL for the node.
+ * Optional external storage URI for the node.
*/
- @JsonProperty("storageURL")
- @JsonInclude(JsonInclude.Include.NON_EMPTY)
- private String storageURL;
+ private String storageURI;
/**
* Set of bootstrap nodes for joining the DHT network.
*/
- @JsonProperty("bootstraps")
- @JsonInclude(JsonInclude.Include.NON_EMPTY)
private final Set bootstraps;
/**
* Whether spam throttling is enabled for this node.
*/
- @JsonProperty("spamThrottling")
- @JsonInclude(JsonInclude.Include.NON_DEFAULT)
private boolean enableSpamThrottling;
/**
* Whether suspicious node detection is enabled for this node.
*/
- @JsonProperty("suspiciousNodeDetector")
- @JsonInclude(JsonInclude.Include.NON_DEFAULT)
private boolean enableSuspiciousNodeDetector;
/**
* Whether developer mode is enabled for this node.
*/
- @JsonProperty("developerMode")
- @JsonInclude(JsonInclude.Include.NON_DEFAULT)
private boolean enableDeveloperMode;
/**
- * Whether metrics collection is enabled for this node.
+ * Whether metrics is enabled for this node.
*/
- @JsonProperty("metrics")
private boolean enableMetrics;
/**
@@ -148,12 +133,14 @@ public class DefaultNodeConfiguration implements NodeConfiguration {
* developer mode and metrics are disabled, and no bootstraps are set.
*/
private DefaultNodeConfiguration() {
- this.bootstraps = new HashSet<>();
+ this.port = DEFAULT_DHT_PORT;
+ this.storageURI = "jdbc:sqlite:node.db";
this.enableSpamThrottling = true;
this.enableSuspiciousNodeDetector = true;
this.enableDeveloperMode = false;
this.enableMetrics = false;
- this.port = DEFAULT_DHT_PORT;
+
+ this.bootstraps = new HashSet<>();
}
/**
@@ -165,6 +152,19 @@ public Vertx vertx() {
return vertx;
}
+ /**
+ * Sets the Vert.x instance for this configuration.
+ *
+ * This method is typically used internally to inject the Vert.x instance after
+ * configuration construction.
+ *
+ *
+ * @param vertx the Vert.x instance to set
+ */
+ public void setVertx(Vertx vertx) {
+ this.vertx = vertx;
+ }
+
/**
* {@inheritDoc}
* @return the IPv4 address string for the DHT node, or null if disabled.
@@ -197,7 +197,7 @@ public int port() {
* @return the Base58-encoded private key string.
*/
@Override
- public String privateKey() {
+ public Signature.PrivateKey privateKey() {
return privateKey;
}
@@ -206,27 +206,8 @@ public String privateKey() {
* @return the path to the persistent data directory, or null if persistence is disabled.
*/
@Override
- public Path dataPath() {
- return dataPath;
- }
-
- /**
- * For Jackson serialization: gets the string representation of the data path.
- * @return the string path, or null if not set.
- */
- @JsonProperty("dataPath")
- @JsonInclude(JsonInclude.Include.NON_EMPTY)
- private String getDataPath() {
- return dataPath != null ? dataPath.toString() : null;
- }
-
- /**
- * For Jackson deserialization: sets the data path from a string.
- * @param dataPath the string path to set (may be null).
- */
- @JsonProperty("dataPath")
- private void setDataPath(String dataPath) {
- this.dataPath = normalizePath(dataPath != null ? Path.of(dataPath) : null);
+ public Path dataDir() {
+ return dataDir;
}
/**
@@ -234,8 +215,8 @@ private void setDataPath(String dataPath) {
* @return the external storage URL, or null if not set.
*/
@Override
- public String storageURL() {
- return storageURL;
+ public String storageURI() {
+ return storageURI;
}
/**
@@ -276,13 +257,137 @@ public boolean enableDeveloperMode() {
/**
* {@inheritDoc}
- * @return true if metrics collection is enabled.
+ * @return true if metrics is enabled.
*/
@Override
public boolean enableMetrics() {
return enableMetrics;
}
+ /**
+ * Creates a DefaultNodeConfiguration from a Map representation.
+ *
+ * This static factory method deserializes a configuration from a Map structure.
+ * The map should contain the following keys:
+ *
+ *
{@code host4} (String, optional) - IPv4 address
+ *
{@code host6} (String, optional) - IPv6 address (at least one of host4/host6 required)
+ *
{@code port} (Integer, optional) - DHT port (defaults to 39001)
+ *
{@code privateKey} (String, required) - Base58 or hex-encoded private key
+ *
{@code dataDir} (String, optional) - Path to persistent data directory
+ *
{@code storageURI} (String, required) - Storage URI (defaults to "jdbc:sqlite:node.db")
+ *
+ * @param map the map containing configuration data, must not be null or empty
+ * @return a new DefaultNodeConfiguration instance
+ * @throws NullPointerException if map is null
+ * @throws IllegalArgumentException if map is empty, required fields are missing, or values are invalid
+ */
+ public static DefaultNodeConfiguration fromMap(Map map) {
+ Objects.requireNonNull(map, "map");
+ if (map.isEmpty())
+ throw new IllegalArgumentException("Configuration is empty");
+
+ DefaultNodeConfiguration config = new DefaultNodeConfiguration();
+
+ ConfigMap m = new ConfigMap(map);
+
+ config.host4 = m.getString("host4", config.host4);
+ config.host6 = m.getString("host6", config.host6);
+ if (config.host4 == null || config.host4.isEmpty() && config.host6 == null || config.host6.isEmpty())
+ throw new IllegalArgumentException("Missing host4 or host6");
+
+ config.port = m.getPort("port", config.port);
+ String sk = m.getString("privateKey", null);
+ if (sk == null || sk.isEmpty())
+ throw new IllegalArgumentException("Missing privateKey");
+ try {
+ byte[] keyBytes = sk.startsWith("0x") ? Hex.decode(sk.substring(2)) : Base58.decode(sk);
+ config.privateKey = Signature.PrivateKey.fromBytes(keyBytes);
+ } catch (Exception e) {
+ throw new IllegalArgumentException("Invalid privateKey: " + config.privateKey);
+ }
+
+ String dir = m.getString("dataDir", null);
+ if (dir != null && !dir.isEmpty())
+ config.dataDir = Path.of(dir);
+
+ config.storageURI = m.getString("storageURI", config.storageURI);
+ if (config.storageURI == null || config.storageURI.isEmpty())
+ throw new IllegalArgumentException("Missing storageURI");
+
+ List> lst = m.getList("bootstraps");
+ if (lst != null && !lst.isEmpty()) {
+ lst.forEach(b -> {
+ if (b.size() != 3)
+ throw new IllegalArgumentException("Invalid bootstrap node: missing fields - " + b);
+
+ try {
+ Id id = Id.of((String) b.get(0));
+ String host = (String) b.get(1);
+ int port = (int) b.get(2);
+
+ config.bootstraps.add(new NodeInfo(id, host, port));
+ } catch (Exception e) {
+ throw new IllegalArgumentException("Invalid bootstrap node: " + b);
+ }
+ });
+ }
+
+ config.enableSpamThrottling = m.getBoolean("enableSpamThrottling", config.enableSpamThrottling);
+ config.enableSuspiciousNodeDetector = m.getBoolean("enableSuspiciousNodeDetector", config.enableSuspiciousNodeDetector);
+ config.enableDeveloperMode = m.getBoolean("enableDeveloperMode", config.enableDeveloperMode);
+ config.enableMetrics = m.getBoolean("enableMetrics", config.enableMetrics);
+
+ return config;
+ }
+
+ /**
+ * Serializes this configuration to a Map representation.
+ *
+ * The returned map contains all configured values and can be used for persistence,
+ * network transmission, or creating a new configuration via {@link #fromMap(Map)}.
+ * Null or empty values are excluded from the map.
+ *
+ *
+ * @return a Map containing the configuration data
+ */
+ public Map toMap() {
+ HashMap map = new HashMap<>();
+
+ if (host4 != null)
+ map.put("host4", host4);
+
+ if (host6 != null)
+ map.put("host6", host6);
+
+ map.put("port", port);
+ map.put("privateKey", privateKey);
+
+ if (dataDir != null)
+ map.put("dataDir", dataDir);
+
+ map.put("storageURI", storageURI);
+
+ if (!bootstraps.isEmpty()) {
+ List> lst = new ArrayList<>();
+ bootstraps.forEach(n -> lst.add(Arrays.asList(n.getId().toString(), n.getHost(), n.getPort())));
+ map.put("bootstraps", lst);
+ }
+
+ map.put("enableSpamThrottling", enableSpamThrottling);
+ map.put("enableSuspiciousNodeDetector", enableSuspiciousNodeDetector);
+ map.put("enableDeveloperMode", enableDeveloperMode);
+ map.put("enableMetrics", enableMetrics);
+
+ return map;
+ }
+
/**
* Builder helper class to create a {@link NodeConfiguration} object.
*
@@ -297,7 +402,39 @@ public static class Builder {
* Constructs a new Builder with default settings.
*/
protected Builder() {
- reset();
+ config = new DefaultNodeConfiguration();
+ }
+
+ /**
+ * Gets or lazily initializes the configuration instance.
+ *
+ * This private helper ensures the config field is initialized on first access.
+ *
+ *
+ * @return the configuration instance
+ */
+ private DefaultNodeConfiguration config() {
+ return config == null ? config = new DefaultNodeConfiguration() : config;
+ }
+
+ /**
+ * Initializes this builder from a template Map.
+ *
+ * This method loads a complete configuration from a Map, replacing any previously
+ * set values. The template map should follow the same structure as expected by
+ * {@link DefaultNodeConfiguration#fromMap(Map)}.
+ *
+ *
+ * @param template the template map containing configuration data, must not be null
+ * @return this Builder for chaining
+ * @throws NullPointerException if template is null
+ * @throws IllegalArgumentException if the template is invalid
+ * @see DefaultNodeConfiguration#fromMap(Map)
+ */
+ public Builder template(Map template) {
+ Objects.requireNonNull(template, "template");
+ this.config = DefaultNodeConfiguration.fromMap(template);
+ return this;
}
/**
@@ -308,7 +445,7 @@ protected Builder() {
*/
public Builder vertx(Vertx vertx) {
Objects.requireNonNull(vertx, "vertx");
- config.vertx = vertx;
+ config().vertx = vertx;
return this;
}
@@ -318,16 +455,11 @@ public Builder vertx(Vertx vertx) {
* @throws IllegalStateException if no suitable IPv4 address is found
*/
public Builder autoHost4() {
- InetAddress addr = AddressUtils.getAllAddresses()
- .filter(Inet4Address.class::isInstance)
- .filter(AddressUtils::isAnyUnicast)
- .distinct()
- .findFirst()
- .orElse(null);
+ InetAddress addr = AddressUtils.getDefaultRouteAddress(Inet6Address.class);
if (addr == null)
throw new IllegalStateException("No available IPv4 address");
- config.host4 = addr.getHostAddress();
+ config().host4 = addr.getHostAddress();
return this;
}
@@ -337,16 +469,11 @@ public Builder autoHost4() {
* @throws IllegalStateException if no suitable IPv6 address is found
*/
public Builder autoHost6() {
- InetAddress addr = AddressUtils.getAllAddresses()
- .filter(Inet6Address.class::isInstance)
- .filter(AddressUtils::isAnyUnicast)
- .distinct()
- .findFirst()
- .orElse(null);
+ InetAddress addr = AddressUtils.getDefaultRouteAddress(Inet6Address.class);
if (addr == null)
throw new IllegalStateException("No available IPv6 address");
- config.host6 = addr.getHostAddress();
+ config().host6 = addr.getHostAddress();
return this;
}
@@ -356,28 +483,28 @@ public Builder autoHost6() {
* @throws IllegalStateException if neither IPv4 nor IPv6 addresses are found
*/
public Builder autoHosts() {
- InetAddress addr4 = AddressUtils.getAllAddresses()
- .filter(Inet4Address.class::isInstance)
- .filter(AddressUtils::isAnyUnicast)
- .distinct()
- .findFirst()
- .orElse(null);
-
- InetAddress addr6 = AddressUtils.getAllAddresses()
- .filter(Inet6Address.class::isInstance)
- .filter(AddressUtils::isAnyUnicast)
- .distinct()
- .findFirst()
- .orElse(null);
+ InetAddress addr4;
+ try {
+ addr4 = AddressUtils.getDefaultRouteAddress(Inet4Address.class);
+ } catch (Exception e) {
+ addr4 = null;
+ }
+
+ InetAddress addr6;
+ try {
+ addr6 = AddressUtils.getDefaultRouteAddress(Inet6Address.class);
+ } catch (Exception e) {
+ addr6 = null;
+ }
if (addr4 == null && addr6 == null)
throw new IllegalStateException("No available IPv4/6 address");
if (addr4 != null)
- config.host4 = addr4.getHostAddress();
+ config().host4 = addr4.getHostAddress();
if (addr6 != null)
- config.host6 = addr6.getHostAddress();
+ config().host6 = addr6.getHostAddress();
return this;
}
@@ -387,7 +514,7 @@ public Builder autoHosts() {
* @param host the string host name or IPv4 address (must not be null)
* @return this Builder for chaining
* @throws IllegalArgumentException if the host is not a valid IPv4 address
- * @throws NullPointerException if host is null
+ * @throws NullPointerException if the host is null
*/
public Builder host4(String host) {
Objects.requireNonNull(host, "host");
@@ -412,7 +539,7 @@ public Builder address4(InetAddress addr) {
throw new IllegalArgumentException("Not any unicast address");
if (addr instanceof Inet4Address)
- config.host4 = addr.getHostAddress();
+ config().host4 = addr.getHostAddress();
else
throw new IllegalArgumentException("Invalid IPv4 address: " + addr);
@@ -424,7 +551,7 @@ public Builder address4(InetAddress addr) {
* @param host the string host name or IPv6 address (must not be null)
* @return this Builder for chaining
* @throws IllegalArgumentException if the host is not a valid IPv6 address
- * @throws NullPointerException if host is null
+ * @throws NullPointerException if the host is null
*/
public Builder host6(String host) {
Objects.requireNonNull(host, "host");
@@ -449,7 +576,7 @@ public Builder address6(InetAddress addr) {
throw new IllegalArgumentException("Not any unicast address");
if (addr instanceof Inet6Address)
- config.host6 = addr.getHostAddress();
+ config().host6 = addr.getHostAddress();
else
throw new IllegalArgumentException("Invalid IPv6 address: " + addr);
@@ -466,7 +593,7 @@ public Builder port(int port) {
if (port <= 0 || port > 65535)
throw new IllegalArgumentException("Invalid port: " + port);
- config.port = port;
+ config().port = port;
return this;
}
@@ -475,7 +602,7 @@ public Builder port(int port) {
* @return this Builder for chaining
*/
public Builder generatePrivateKey() {
- config.privateKey = Base58.encode(Signature.KeyPair.random().privateKey().bytes());
+ config().privateKey = Signature.KeyPair.random().privateKey();
return this;
}
@@ -486,28 +613,24 @@ public Builder generatePrivateKey() {
* @throws IllegalArgumentException if the key is not 64 bytes
*/
public Builder privateKey(byte[] privateKey) {
- if (privateKey == null || privateKey.length != 64)
- throw new IllegalArgumentException("Invalid private key");
-
- config.privateKey = Base58.encode(privateKey);
+ Objects.requireNonNull(privateKey, "privateKey");
+ config().privateKey = Signature.PrivateKey.fromBytes(privateKey);
return this;
}
/**
* Set the node's private key from a Base58-encoded string.
- * @param privateKey the Base58-encoded private key string (must not be null)
+ * @param privateKey the Base58-encoded or hex-encoded private key string (must not be null)
* @return this Builder for chaining
* @throws IllegalArgumentException if the key is not 64 bytes when decoded
* @throws NullPointerException if privateKey is null
*/
public Builder privateKey(String privateKey) {
Objects.requireNonNull(privateKey, "privateKey");
-
- byte[] key = Base58.decode(privateKey);
- if (key.length != 64)
- throw new IllegalArgumentException("Invalid private key");
-
- config.privateKey = privateKey;
+ byte[] key = privateKey.startsWith("0x") ?
+ Hex.decode(privateKey, 2, privateKey.length() - 2) :
+ Base58.decode(privateKey);
+ config().privateKey = Signature.PrivateKey.fromBytes(key);
return this;
}
@@ -516,35 +639,35 @@ public Builder privateKey(String privateKey) {
* @return true if a private key is set, false otherwise
*/
public boolean hasPrivateKey() {
- return config.privateKey != null;
+ return config().privateKey != null;
}
/**
* Set the storage path for DHT persistent data using a string path.
- * @param path the string path (may be null to disable persistence)
+ * @param dir the string path (maybe null to disable persistence)
* @return this Builder for chaining
*/
- public Builder dataPath(String path) {
- return dataPath(path != null ? Path.of(path) : null);
+ public Builder dataDir(String dir) {
+ return dataDir(dir != null ? Path.of(dir) : null);
}
/**
* Set the storage path for DHT persistent data using a File object.
- * @param path the File pointing to the storage directory (may be null to disable persistence)
+ * @param path the File pointing to the storage directory (maybe null to disable persistence)
* @return this Builder for chaining
*/
- public Builder dataPath(File path) {
- dataPath(path != null ? path.toPath() : null);
+ public Builder dataDir(File path) {
+ dataDir(path != null ? path.toPath() : null);
return this;
}
/**
* Set the storage path for DHT persistent data using a Path.
- * @param path the Path to the storage directory (may be null to disable persistence)
+ * @param path the Path to the storage directory (maybe null to disable persistence)
* @return this Builder for chaining
*/
- public Builder dataPath(Path path) {
- config.dataPath = normalizePath(path);
+ public Builder dataDir(Path path) {
+ config().dataDir = path;
return this;
}
@@ -552,27 +675,29 @@ public Builder dataPath(Path path) {
* Checks if a data path has been set.
* @return true if a data path is set, false otherwise
*/
- public boolean hasDataPath() {
- return config.dataPath != null;
+ public boolean hasDataDir() {
+ return config().dataDir != null;
}
/**
* Gets the current data path set in the builder.
* @return the Path to the storage directory, or null if not set
*/
- public Path dataPath() {
- return config.dataPath;
+ public Path dataDir() {
+ return config().dataDir;
}
/**
* Set the external storage URL for the node.
- * @param storageURL the storage URL (must not be null)
+ * @param storageURI the storage URL (must not be null)
* @return this Builder for chaining
- * @throws NullPointerException if storageURL is null
+ * @throws NullPointerException if storageURI is null
*/
- public Builder storageURL(String storageURL) {
- Objects.requireNonNull(storageURL, "storageURL");
- config.storageURL = storageURL;
+ public Builder storageURI(String storageURI) {
+ Objects.requireNonNull(storageURI, "storageURI");
+ if (!storageURI.startsWith("postgresql://") && !storageURI.startsWith("jdbc:sqlite:"))
+ throw new IllegalArgumentException("Unsupported storage URL: " + storageURI);
+ config().storageURI = storageURI;
return this;
}
@@ -585,7 +710,7 @@ public Builder storageURL(String storageURL) {
*/
public Builder addBootstrap(String id, String addr, int port) {
NodeInfo node = new NodeInfo(Id.of(id), addr, port);
- config.bootstraps.add(node);
+ config().bootstraps.add(node);
return this;
}
@@ -598,7 +723,7 @@ public Builder addBootstrap(String id, String addr, int port) {
*/
public Builder addBootstrap(Id id, InetAddress addr, int port) {
NodeInfo node = new NodeInfo(id, addr, port);
- config.bootstraps.add(node);
+ config().bootstraps.add(node);
return this;
}
@@ -610,7 +735,7 @@ public Builder addBootstrap(Id id, InetAddress addr, int port) {
*/
public Builder addBootstrap(Id id, InetSocketAddress addr) {
NodeInfo node = new NodeInfo(id, addr);
- config.bootstraps.add(node);
+ config().bootstraps.add(node);
return this;
}
@@ -618,11 +743,11 @@ public Builder addBootstrap(Id id, InetSocketAddress addr) {
* Add a new bootstrap node to the configuration.
* @param node the NodeInfo of the bootstrap node (must not be null)
* @return this Builder for chaining
- * @throws NullPointerException if node is null
+ * @throws NullPointerException if the node is null
*/
public Builder addBootstrap(NodeInfo node) {
Objects.requireNonNull(node, "node");
- config.bootstraps.add(node);
+ config().bootstraps.add(node);
return this;
}
@@ -630,11 +755,11 @@ public Builder addBootstrap(NodeInfo node) {
* Add multiple bootstrap nodes to the configuration.
* @param nodes the collection of NodeInfo bootstrap nodes (must not be null)
* @return this Builder for chaining
- * @throws NullPointerException if nodes is null
+ * @throws NullPointerException if the nodes parameter is null
*/
public Builder addBootstrap(Collection nodes) {
Objects.requireNonNull(nodes, "nodes");
- config.bootstraps.addAll(nodes);
+ config().bootstraps.addAll(nodes);
return this;
}
@@ -643,7 +768,7 @@ public Builder addBootstrap(Collection nodes) {
* @return this Builder for chaining
*/
public Builder enableSpamThrottling() {
- config.enableSpamThrottling = true;
+ config().enableSpamThrottling = true;
return this;
}
@@ -652,7 +777,7 @@ public Builder enableSpamThrottling() {
* @return this Builder for chaining
*/
public Builder disableSpamThrottling() {
- config.enableSpamThrottling = false;
+ config().enableSpamThrottling = false;
return this;
}
@@ -661,7 +786,7 @@ public Builder disableSpamThrottling() {
* @return this Builder for chaining
*/
public Builder enableSuspiciousNodeDetector() {
- config.enableSuspiciousNodeDetector = true;
+ config().enableSuspiciousNodeDetector = true;
return this;
}
@@ -670,7 +795,7 @@ public Builder enableSuspiciousNodeDetector() {
* @return this Builder for chaining
*/
public Builder disableSuspiciousNodeDetector() {
- config.enableSuspiciousNodeDetector = false;
+ config().enableSuspiciousNodeDetector = false;
return this;
}
@@ -679,7 +804,7 @@ public Builder disableSuspiciousNodeDetector() {
* @return this Builder for chaining
*/
public Builder enableDeveloperMode() {
- config.enableDeveloperMode = true;
+ config().enableDeveloperMode = true;
return this;
}
@@ -688,136 +813,20 @@ public Builder enableDeveloperMode() {
* @return this Builder for chaining
*/
public Builder disableDeveloperMode() {
- config.enableDeveloperMode = false;
- return this;
- }
-
- /**
- * Enables metrics collection for the node.
- * @return this Builder for chaining
- */
- public Builder enableMetrics() {
- config.enableMetrics = true;
- return this;
- }
-
- /**
- * Disables metrics collection for the node.
- * @return this Builder for chaining
- */
- public Builder disableMetrics() {
- config.enableMetrics = false;
+ config().enableDeveloperMode = false;
return this;
}
/**
- * Loads the configuration data from a JSON or YAML file.
- * The format is determined by the file extension (.json for JSON, otherwise YAML).
- * @param file the string file path to load (must not be null)
+ * Enables metrics for the node.
+ * @param enable true to enable metrics, false to disable
* @return this Builder for chaining
- * @throws IOException if I/O error occurs during loading
- * @throws IllegalArgumentException if the file does not exist or is a directory
*/
- public Builder load(String file) throws IOException {
- Objects.requireNonNull(file, "file");
- Path configFile = Path.of(file);
- return load(configFile);
- }
-
- /**
- * Loads the configuration data from a JSON or YAML file.
- * The format is determined by the file extension (.json for JSON, otherwise YAML).
- * @param file the File to load (must not be null)
- * @return this Builder for chaining
- * @throws IOException if I/O error occurs during loading
- * @throws IllegalArgumentException if the file does not exist or is a directory
- */
- public Builder load(File file) throws IOException {
- Objects.requireNonNull(file, "file");
- return load(file.toPath());
- }
-
- /**
- * Loads the configuration data from a JSON or YAML file.
- * The format is determined by the file extension (.json for JSON, otherwise YAML).
- * @param file the Path to the file to load (must not be null)
- * @return this Builder for chaining
- * @throws IOException if I/O error occurs during loading
- * @throws IllegalArgumentException if the file does not exist or is a directory
- */
- public Builder load(Path file) throws IOException {
- Objects.requireNonNull(file, "file");
- file = normalizePath(file);
- if (Files.notExists(file) || Files.isDirectory(file))
- throw new IllegalArgumentException("Invalid config file: " + file);
-
- try (InputStream in = Files.newInputStream(file)) {
- ObjectMapper mapper = file.getFileName().toString().endsWith(".json") ?
- Json.objectMapper() : Json.yamlMapper();
- config = mapper.readValue(in, DefaultNodeConfiguration.class);
- }
-
+ public Builder enableMetrics(boolean enable) {
+ config().enableMetrics = enable;
return this;
}
- /**
- * Saves the current configuration to a file in JSON or YAML format.
- * The format is determined by the file extension (.json for JSON, otherwise YAML).
- * @param file the string file path to save to (must not be null)
- * @return this Builder for chaining
- * @throws IOException if I/O error occurs during saving
- * @throws IllegalArgumentException if the file path is a directory
- */
- public Builder save(String file) throws IOException {
- Objects.requireNonNull(file, "file");
- return save(Path.of(file));
- }
-
- /**
- * Saves the current configuration to a file in JSON or YAML format.
- * The format is determined by the file extension (.json for JSON, otherwise YAML).
- * @param file the File to save to (must not be null)
- * @return this Builder for chaining
- * @throws IOException if I/O error occurs during saving
- * @throws IllegalArgumentException if the file path is a directory
- */
- public Builder save(File file) throws IOException {
- Objects.requireNonNull(file, "file");
- return save(file.toPath());
- }
-
- /**
- * Saves the current configuration to a file in JSON or YAML format.
- * The format is determined by the file extension (.json for JSON, otherwise YAML).
- * @param file the Path to save to (must not be null)
- * @return this Builder for chaining
- * @throws IOException if I/O error occurs during saving
- * @throws IllegalArgumentException if the file path is a directory
- */
- public Builder save(Path file) throws IOException {
- Objects.requireNonNull(file, "file");
- file = normalizePath(file);
- if (Files.exists(file) && Files.isDirectory(file))
- throw new IllegalArgumentException("Invalid config file: " + file);
-
- Files.createDirectories(file.getParent());
- try (OutputStream out = Files.newOutputStream(file)) {
- ObjectMapper mapper = file.getFileName().toString().endsWith(".json") ?
- Json.objectMapper() : Json.yamlMapper();
- mapper.writeValue(out, config);
- }
-
- return this;
- }
-
- /**
- * Resets the configuration builder object to the initial state,
- * clearing all existing settings.
- */
- private void reset() {
- config = new DefaultNodeConfiguration();
- }
-
/**
* Creates the {@link NodeConfiguration} instance with the current settings in this builder.
* After creating the new {@link NodeConfiguration} instance, the builder will be reset to the
@@ -825,31 +834,15 @@ private void reset() {
* @return the {@link NodeConfiguration} instance
*/
public NodeConfiguration build() {
- if (config.privateKey == null)
- config.privateKey = Base58.encode(Signature.KeyPair.random().privateKey().bytes());
+ if (config().host4() == null && config().host6() == null)
+ throw new IllegalArgumentException("Missing host4 or host6");
+
+ if (config().privateKey == null)
+ generatePrivateKey();
- DefaultNodeConfiguration c = config;
- reset();
+ DefaultNodeConfiguration c = config();
+ config = null;
return c;
}
}
-
- /**
- * Normalizes a filesystem path, expanding '~' to the user's home directory if present,
- * and converting to an absolute path.
- * @param path the path to normalize (may be null)
- * @return the normalized absolute path, or null if input is null
- */
- private static Path normalizePath(Path path) {
- if (path == null)
- return null;
-
- path = path.normalize();
- if (path.startsWith("~"))
- path = Path.of(System.getProperty("user.home")).resolve(path.subpath(1, path.getNameCount()));
- else
- path = path.toAbsolutePath();
-
- return path;
- }
}
\ No newline at end of file
diff --git a/api/src/main/java/io/bosonnetwork/NodeBlacklist.java b/api/src/main/java/io/bosonnetwork/NodeBlacklist.java
index de838d5..654422e 100644
--- a/api/src/main/java/io/bosonnetwork/NodeBlacklist.java
+++ b/api/src/main/java/io/bosonnetwork/NodeBlacklist.java
@@ -1,5 +1,31 @@
+/*
+ * Copyright (c) 2023 - bosonnetwork.io
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
package io.bosonnetwork;
+/**
+ * Provides an interface for managing a blacklist of nodes, allowing hosts and IDs
+ * to be banned and checked for their banned status.
+ */
public interface NodeBlacklist {
/**
* Checks if the specified host is banned.
diff --git a/api/src/main/java/io/bosonnetwork/NodeConfiguration.java b/api/src/main/java/io/bosonnetwork/NodeConfiguration.java
index 4eaa906..9cafe7e 100644
--- a/api/src/main/java/io/bosonnetwork/NodeConfiguration.java
+++ b/api/src/main/java/io/bosonnetwork/NodeConfiguration.java
@@ -29,6 +29,8 @@
import io.vertx.core.Vertx;
+import io.bosonnetwork.crypto.Signature;
+
/**
* Configuration interface for customizing the initialization and behavior of a Boson DHT node.
*
@@ -98,9 +100,9 @@ default int port() {
* If {@code null} is returned, the node will generate a random private key upon startup.
*
*
- * @return the private key as a string, or {@code null} if no key is set.
+ * @return the private key, or {@code null} if no key is set.
*/
- default String privateKey() {
+ default Signature.PrivateKey privateKey() {
return null;
}
@@ -113,7 +115,7 @@ default String privateKey() {
*
* @return the storage directory path, or {@code null} to disable persistence.
*/
- default Path dataPath() {
+ default Path dataDir() {
return null;
}
@@ -122,7 +124,7 @@ default Path dataPath() {
*
* @return the external storage URL as a string, or {@code null} if not configured.
*/
- default String storageURL() {
+ default String storageURI() {
return null;
}
diff --git a/api/src/main/java/io/bosonnetwork/database/Filter.java b/api/src/main/java/io/bosonnetwork/database/Filter.java
index 4c00ca6..851a51b 100644
--- a/api/src/main/java/io/bosonnetwork/database/Filter.java
+++ b/api/src/main/java/io/bosonnetwork/database/Filter.java
@@ -1,3 +1,25 @@
+/*
+ * Copyright (c) 2023 - bosonnetwork.io
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
package io.bosonnetwork.database;
import java.util.Arrays;
@@ -57,7 +79,7 @@ public static Filter eq(String column, Object value) {
}
/**
- * Creates a non-equality filter (column <> #{paramName}).
+ * Creates a non-equality filter (column <> #{paramName}).
*
* @param column the database column name
* @param paramName the parameter name to bind
@@ -72,7 +94,7 @@ public static Filter ne(String column, String paramName, Object value) {
}
/**
- * Creates a non-equality filter (column <> #{paramName}).
+ * Creates a non-equality filter (column <> #{paramName}).
*
* @param column the database column name
* @param value the value to bind
@@ -83,7 +105,7 @@ public static Filter ne(String column, Object value) {
}
/**
- * Creates a less-than filter (column < #{paramName}).
+ * Creates a less-than filter (column < #{paramName}).
*
* @param column the database column name
* @param paramName the parameter name to bind
@@ -98,7 +120,7 @@ public static Filter lt(String column, String paramName, Object value) {
}
/**
- * Creates a less-than filter (column < #{paramName}).
+ * Creates a less-than filter (column < #{paramName}).
*
* @param column the database column name
* @param value the value to bind
@@ -109,7 +131,7 @@ public static Filter lt(String column, Object value) {
}
/**
- * Creates a less-than-or-equal filter (column <= #{paramName}).
+ * Creates a less-than-or-equal filter (column <= #{paramName}).
*
* @param column the database column name
* @param paramName the parameter name to bind
@@ -124,7 +146,7 @@ public static Filter lte(String column, String paramName, Object value) {
}
/**
- * Creates a less-than-or-equal filter (column <= #{paramName}).
+ * Creates a less-than-or-equal filter (column <= #{paramName}).
*
* @param column the database column name
* @param value the value to bind
@@ -135,7 +157,7 @@ public static Filter lte(String column, Object value) {
}
/**
- * Creates a greater-than filter (column > #{paramName}).
+ * Creates a greater-than filter (column > #{paramName}).
*
* @param column the database column name
* @param paramName the parameter name to bind
@@ -150,7 +172,7 @@ public static Filter gt(String column, String paramName, Object value) {
}
/**
- * Creates a greater-than filter (column > #{paramName}).
+ * Creates a greater-than filter (column > #{paramName}).
*
* @param column the database column name
* @param value the value to bind
@@ -161,7 +183,7 @@ public static Filter gt(String column, Object value) {
}
/**
- * Creates a greater-than-or-equal filter (column >= #{paramName}).
+ * Creates a greater-than-or-equal filter (column >= #{paramName}).
*
* @param column the database column name
* @param paramName the parameter name to bind
@@ -176,7 +198,7 @@ public static Filter gte(String column, String paramName, Object value) {
}
/**
- * Creates a greater-than-or-equal filter (column >= #{paramName}).
+ * Creates a greater-than-or-equal filter (column >= #{paramName}).
*
* @param column the database column name
* @param value the value to bind
@@ -303,6 +325,11 @@ public boolean isEmpty() {
}
+ /**
+ * Returns the parameter bindings for this filter.
+ *
+ * @return a map of parameter names to their values
+ */
public Map getParams() {
return Map.of();
}
diff --git a/api/src/main/java/io/bosonnetwork/database/Ordering.java b/api/src/main/java/io/bosonnetwork/database/Ordering.java
index fc14bb1..9551d35 100644
--- a/api/src/main/java/io/bosonnetwork/database/Ordering.java
+++ b/api/src/main/java/io/bosonnetwork/database/Ordering.java
@@ -1,3 +1,25 @@
+/*
+ * Copyright (c) 2023 - bosonnetwork.io
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
package io.bosonnetwork.database;
import java.util.ArrayList;
@@ -11,11 +33,11 @@
* Supports multiple fields and prevents SQL injection by validating column names.
*
* Example:
+ *
* Ordering order = Ordering.by("name").asc()
* .then("created").desc();
- *
* String sql = order.toSql(); // " ORDER BY name ASC, created DESC"
- *
+ *
*/
public class Ordering {
/** Special instance representing no ordering. Method toSql() returns an empty string. */
@@ -33,9 +55,11 @@ public enum Direction {
/**
* Represents a column and its sorting direction in an SQL ORDER BY clause.
- * A Field is used to specify the sorting criteria for a query. Each Field contains:
- * - A column, representing the name of the database column to sort by.
- * - A direction, indicating whether the sorting should be ascending or descending.
+ *
+ * A Field is used to specify the sorting criteria for a query. Each Field contains
+ * a column name representing the database column to sort by, and a direction
+ * indicating whether the sorting should be ascending or descending.
+ *
*
* @param column representing the name of the database column to sort by.
* @param direction indicating whether the sorting should be ascending or descending.
@@ -43,6 +67,11 @@ public enum Direction {
public record Field(String column, Direction direction) {
}
+ /**
+ * Constructs an Ordering with the specified list of fields.
+ *
+ * @param fields the list of fields to order by
+ */
private Ordering(List fields) {
this.fields = Collections.unmodifiableList(fields);
}
@@ -92,7 +121,7 @@ public String toSql() {
* Generates a unique identifier string representing the ordering configuration
* based on the fields. If no fields are present, it returns "none".
*
- * @return a string in the format "orderBy___..." or "none" if no fields are defined
+ * @return a string in the format "orderBy_column_direction_..." or "none" if no fields are defined
*/
public String identifier() {
if (fields.isEmpty())
@@ -192,6 +221,16 @@ public Ordering build() {
}
}
+ /**
+ * Validates that the column name contains only safe characters.
+ *
+ * Only letters, digits, and underscores are allowed (safe for SQL identifiers).
+ * Optionally supports qualified names with a single dot (e.g., "table.column").
+ *
+ *
+ * @param column the column name to validate
+ * @throws IllegalArgumentException if the column name is invalid
+ */
private static void validateColumn(String column) {
// Only letters, digits, and underscore allowed (safe for SQL identifiers)
if (!column.matches("^[A-Za-z_][A-Za-z0-9_]*(?:\\.[A-Za-z_][A-Za-z0-9_]*)?$"))
diff --git a/api/src/main/java/io/bosonnetwork/database/Pagination.java b/api/src/main/java/io/bosonnetwork/database/Pagination.java
index 9ac25da..6490cc0 100644
--- a/api/src/main/java/io/bosonnetwork/database/Pagination.java
+++ b/api/src/main/java/io/bosonnetwork/database/Pagination.java
@@ -1,15 +1,39 @@
+/*
+ * Copyright (c) 2023 - bosonnetwork.io
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
package io.bosonnetwork.database;
import java.util.Map;
/**
* Helper class for building SQL LIMIT/OFFSET clauses safely.
- *
+ *
*/
public class Pagination {
+ /** Special instance representing no pagination. Method toSql() returns an empty string. */
public static final Pagination NONE = new Pagination(0, 0);
private final long offset;
diff --git a/api/src/main/java/io/bosonnetwork/metrics/Measured.java b/api/src/main/java/io/bosonnetwork/metrics/Measured.java
index b98c6c5..921ba8d 100644
--- a/api/src/main/java/io/bosonnetwork/metrics/Measured.java
+++ b/api/src/main/java/io/bosonnetwork/metrics/Measured.java
@@ -1,3 +1,25 @@
+/*
+ * Copyright (c) 2023 - bosonnetwork.io
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
package io.bosonnetwork.metrics;
/**
diff --git a/api/src/main/java/io/bosonnetwork/metrics/Metrics.java b/api/src/main/java/io/bosonnetwork/metrics/Metrics.java
index bb40889..b6a6868 100644
--- a/api/src/main/java/io/bosonnetwork/metrics/Metrics.java
+++ b/api/src/main/java/io/bosonnetwork/metrics/Metrics.java
@@ -1,3 +1,25 @@
+/*
+ * Copyright (c) 2023 - bosonnetwork.io
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
package io.bosonnetwork.metrics;
/**
diff --git a/api/src/main/java/io/bosonnetwork/service/BosonService.java b/api/src/main/java/io/bosonnetwork/service/BosonService.java
index ac0e84f..1072ed6 100644
--- a/api/src/main/java/io/bosonnetwork/service/BosonService.java
+++ b/api/src/main/java/io/bosonnetwork/service/BosonService.java
@@ -25,9 +25,11 @@
import java.util.concurrent.CompletableFuture;
+import io.bosonnetwork.Id;
+
/**
* Interface BosonService is the basic abstraction for the extensible service on top of
- * Boson super node. This interface describes the basic information about the service
+ * the Boson super node. This interface describes the basic information about the service
* itself and the life-cycle management methods. All super node services should implement
* this interface.
*/
@@ -46,6 +48,45 @@ public interface BosonService {
*/
String getName();
+ /**
+ * Retrieves the peer identifier associated with the service.
+ *
+ * @return an {@link Id} object representing the peer identifier.
+ */
+ Id getPeerId();
+
+ /**
+ * Retrieves the host associated with the service.
+ *
+ * @return a string representing the host's address.
+ */
+ String getHost();
+
+ /**
+ * Retrieves the port number associated with the service.
+ *
+ * @return an integer representing the port number.
+ */
+ int getPort();
+
+ /**
+ * Retrieves an alternative endpoint for the service.
+ *
+ * @return a string representing the alternative endpoint.
+ */
+ default String getAlternativeEndpoint() {
+ return null;
+ }
+
+ /**
+ * Checks whether the federation feature is enabled for the service.
+ *
+ * @return true if federation is enabled, false otherwise.
+ */
+ default boolean isFederationEnabled() {
+ return false;
+ }
+
/**
* Get the running status
*
diff --git a/api/src/main/java/io/bosonnetwork/service/ClientAuthenticator.java b/api/src/main/java/io/bosonnetwork/service/ClientAuthenticator.java
index d684b4e..ba70987 100644
--- a/api/src/main/java/io/bosonnetwork/service/ClientAuthenticator.java
+++ b/api/src/main/java/io/bosonnetwork/service/ClientAuthenticator.java
@@ -1,11 +1,125 @@
+/*
+ * Copyright (c) 2023 - bosonnetwork.io
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
package io.bosonnetwork.service;
+import java.util.List;
+import java.util.Map;
import java.util.concurrent.CompletableFuture;
import io.bosonnetwork.Id;
+/**
+ * Interface for authenticating clients (users and devices).
+ *
+ * Implementations of this interface provide mechanisms to verify the identity of users and devices
+ * using cryptographic signatures and other credentials.
+ */
public interface ClientAuthenticator {
+
+ /**
+ * Authenticates a user based on their ID and a cryptographic signature.
+ *
+ * @param userId the unique identifier of the user
+ * @param nonce the random challenge data (nonce) used for authentication
+ * @param signature the digital signature of the nonce, generated using the user's private key
+ * @return a {@link CompletableFuture} that completes with {@code true} if the user is successfully authenticated,
+ * or {@code false} otherwise
+ */
CompletableFuture authenticateUser(Id userId, byte[] nonce, byte[] signature);
+ /**
+ * Authenticates a specific device belonging to a user.
+ *
+ * @param userId the unique identifier of the user who owns the device
+ * @param deviceId the unique identifier of the device attempting to authenticate
+ * @param nonce the random challenge data (nonce) used for authentication
+ * @param signature the digital signature of the nonce, generated using the device's private key
+ * @param address the network address (e.g., IP address) from which the device is connecting
+ * @return a {@link CompletableFuture} that completes with {@code true} if the device is successfully authenticated,
+ * or {@code false} otherwise
+ */
CompletableFuture authenticateDevice(Id userId, Id deviceId, byte[] nonce, byte[] signature, String address);
+
+ /**
+ * Returns a `ClientAuthenticator` instance that allows all authentication attempts.
+ * The returned authenticator verifies the provided signature against the corresponding
+ * signature key derived from the user or device ID, enabling universal authentication
+ * acceptance when the signature is valid.
+ *
+ * @return a `ClientAuthenticator` instance that performs signature validation for user
+ * and device authentication and always allows access if the signature is valid
+ */
+ static ClientAuthenticator allowAll() {
+ return new ClientAuthenticator() {
+ @Override
+ public CompletableFuture authenticateUser(Id userId, byte[] nonce, byte[] signature) {
+ return userId.toSignatureKey().verify(nonce, signature) ?
+ CompletableFuture.completedFuture(true) :
+ CompletableFuture.completedFuture(false);
+ }
+
+ @Override
+ public CompletableFuture authenticateDevice(Id userId, Id deviceId, byte[] nonce, byte[] signature, String address) {
+ return deviceId.toSignatureKey().verify(nonce, signature) ?
+ CompletableFuture.completedFuture(true) :
+ CompletableFuture.completedFuture(false);
+ }
+ };
+ }
+
+ /**
+ * Creates a `ClientAuthenticator` instance that validates authentication
+ * attempts based on a provided mapping of users and their associated devices.
+ * The returned authenticator verifies the provided signature and ensures
+ * that the user ID and device ID exist in the given map, granting access
+ * if all checks are satisfied.
+ *
+ * @param userDeviceMap a mapping where the keys represent user IDs and the values
+ * are lists of device IDs associated with each user
+ * @return a `ClientAuthenticator` instance that performs authentication based
+ * on the provided user-device mapping and cryptographic signature verification
+ */
+ static ClientAuthenticator allow(Map> userDeviceMap) {
+ return new ClientAuthenticator() {
+ @Override
+ public CompletableFuture authenticateUser(Id userId, byte[] nonce, byte[] signature) {
+ if (!userDeviceMap.containsKey(userId))
+ return CompletableFuture.completedFuture(false);
+
+ return userId.toSignatureKey().verify(nonce, signature) ?
+ CompletableFuture.completedFuture(true) :
+ CompletableFuture.completedFuture(false);
+ }
+
+ @Override
+ public CompletableFuture authenticateDevice(Id userId, Id deviceId, byte[] nonce, byte[] signature, String address) {
+ if (!userDeviceMap.containsKey(userId) || !userDeviceMap.get(userId).contains(deviceId))
+ return CompletableFuture.completedFuture(false);
+
+ return deviceId.toSignatureKey().verify(nonce, signature) ?
+ CompletableFuture.completedFuture(true) :
+ CompletableFuture.completedFuture(false);
+ }
+ };
+ }
}
\ No newline at end of file
diff --git a/api/src/main/java/io/bosonnetwork/service/ClientAuthorizer.java b/api/src/main/java/io/bosonnetwork/service/ClientAuthorizer.java
new file mode 100644
index 0000000..5603a42
--- /dev/null
+++ b/api/src/main/java/io/bosonnetwork/service/ClientAuthorizer.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2023 - bosonnetwork.io
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package io.bosonnetwork.service;
+
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+
+import io.bosonnetwork.Id;
+
+/**
+ * Interface for authorizing client requests to access specific services.
+ *
+ * Implementations of this interface define the logic to determine if a user on a specific device
+ * is granted permission to use the requested service.
+ */
+public interface ClientAuthorizer {
+ /**
+ * Authorizes the specified user and device for the given service.
+ *
+ * @param userId the unique identifier of the user requesting access
+ * @param deviceId the unique identifier of the device used for the request
+ * @param serviceId the identifier of the service to be accessed
+ * @return a {@link CompletableFuture} that completes with a map containing authorization details
+ * (e.g., tokens, permissions) if successful, or completes exceptionally if authorization fails
+ */
+ CompletableFuture