Compare commits

...

20 Commits

Author SHA1 Message Date
crowetic
ce33abcade
Merge pull request #200 from jschulthess/reticulum
Reticulum Mesh Fixes, synchronized code with qortal-4.5.2-a02d1ce.
2024-08-20 11:13:46 -07:00
Jürg Schulthess
6c5fedd456 create identities directory if it does not exist 2024-08-14 19:49:48 +02:00
Jürg Schulthess
afc2884707 add starup options for netty 2024-08-14 19:48:47 +02:00
Jürg Schulthess
18f15d8122 update reticulum library, sync up with qortal version 4.5.2 2024-08-14 16:21:33 +02:00
Jürg Schulthess
9710d67cce Merge remote-tracking branch 'origin/master' into reticulum 2024-08-14 16:02:29 +02:00
Jürg Schulthess
b2ef503fa7
Merge branch 'Qortal:reticulum' into reticulum 2024-08-01 13:49:18 +02:00
Jürg Schulthess
a497edc488 fix dependencies for reticulum using nitrited db caching 2024-08-01 13:42:51 +02:00
Jürg Schulthess
185f3f515b a few fixes to the mesh functionality 2024-07-12 19:17:47 +02:00
Jürg Schulthess
a445fdc8f2 no pinging during pruning 2024-07-10 22:16:40 +02:00
Jürg Schulthess
61e57f9672 minimize packet timeout callback 2024-07-10 22:01:39 +02:00
Jürg Schulthess
fabfed552e add receipt timeout 2024-07-10 21:28:02 +02:00
Jürg Schulthess
c79a830f2e re-introduce pingRemote during pruning 2024-07-10 21:27:41 +02:00
Jürg Schulthess
1d9347ed23 fix pingRemote 2024-07-10 20:23:29 +02:00
crowetic
c4908678be
Merge pull request #199 from jschulthess/reticulum
Reticulum mesh and peer management implementation
2024-07-10 10:26:10 -07:00
Jürg Schulthess
706dc03b3e save dynamically created identity beck to file 2024-07-10 18:44:08 +02:00
Jürg Schulthess
f0d4c1e8de matching project object model 2024-07-09 13:59:17 +02:00
Jürg Schulthess
32460a1b45 initial mesh and peer management implementation 2024-07-09 13:57:32 +02:00
crowetic
4df05364f5
Merge pull request #186 from jschulthess/reticulum
Initial (almost) phase-1 Reticulum implementation
2024-03-25 09:18:55 -07:00
Jürg Schulthess
9f3c1f1cf1 fix typo in file name 2024-03-25 07:22:01 +01:00
Jürg Schulthess
0c8c722097 initial (almost) phase-1 reticulum implementation 2024-03-24 18:24:13 +01:00
12 changed files with 1362 additions and 22 deletions

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<metadata modelVersion="1.1.0">
<groupId>io.reticulum</groupId>
<artifactId>reticulum-network-stack</artifactId>
<version>1.0-SNAPSHOT</version>
<versioning>
<snapshot>
<localCopy>true</localCopy>
</snapshot>
<lastUpdated>20240814140435</lastUpdated>
<snapshotVersions>
<snapshotVersion>
<extension>jar</extension>
<value>1.0-SNAPSHOT</value>
<updated>20240814140435</updated>
</snapshotVersion>
<snapshotVersion>
<extension>pom</extension>
<value>1.0-SNAPSHOT</value>
<updated>20240324170649</updated>
</snapshotVersion>
</snapshotVersions>
</versioning>
</metadata>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<groupId>io.reticulum</groupId>
<artifactId>reticulum-network-stack</artifactId>
<version>1.0-SNAPSHOT</version>
<description>POM was created from install:install-file</description>
</project>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<metadata>
<groupId>io.reticulum</groupId>
<artifactId>reticulum-network-stack</artifactId>
<versioning>
<versions>
<version>1.0-SNAPSHOT</version>
</versions>
<lastUpdated>20240814140435</lastUpdated>
</versioning>
</metadata>

200
pom.xml
View File

@ -8,7 +8,6 @@
<properties> <properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<skipTests>true</skipTests> <skipTests>true</skipTests>
<altcoinj.version>7dc8c6f</altcoinj.version> <altcoinj.version>7dc8c6f</altcoinj.version>
<bitcoinj.version>0.15.10</bitcoinj.version> <bitcoinj.version>0.15.10</bitcoinj.version>
<bouncycastle.version>1.70</bouncycastle.version> <bouncycastle.version>1.70</bouncycastle.version>
@ -45,7 +44,8 @@
<maven-dependency-plugin.version>3.6.1</maven-dependency-plugin.version> <maven-dependency-plugin.version>3.6.1</maven-dependency-plugin.version>
<maven-jar-plugin.version>3.4.2</maven-jar-plugin.version> <maven-jar-plugin.version>3.4.2</maven-jar-plugin.version>
<maven-package-info-plugin.version>1.1.0</maven-package-info-plugin.version> <maven-package-info-plugin.version>1.1.0</maven-package-info-plugin.version>
<maven-plugin.version>2.16.2</maven-plugin.version> <!--<maven-plugin.version>2.16.2</maven-plugin.version>-->
<maven-plugin.version>3.12.1</maven-plugin.version>
<maven-reproducible-build-plugin.version>0.16</maven-reproducible-build-plugin.version> <maven-reproducible-build-plugin.version>0.16</maven-reproducible-build-plugin.version>
<maven-resources-plugin.version>3.3.1</maven-resources-plugin.version> <maven-resources-plugin.version>3.3.1</maven-resources-plugin.version>
<maven-shade-plugin.version>3.6.0</maven-shade-plugin.version> <maven-shade-plugin.version>3.6.0</maven-shade-plugin.version>
@ -53,11 +53,15 @@
<protobuf.version>3.25.3</protobuf.version> <protobuf.version>3.25.3</protobuf.version>
<replacer.version>1.5.3</replacer.version> <replacer.version>1.5.3</replacer.version>
<simplemagic.version>1.17</simplemagic.version> <simplemagic.version>1.17</simplemagic.version>
<slf4j.version>1.7.36</slf4j.version>
<swagger-api.version>2.0.10</swagger-api.version> <swagger-api.version>2.0.10</swagger-api.version>
<swagger-ui.version>5.17.14</swagger-ui.version> <swagger-ui.version>5.17.14</swagger-ui.version>
<upnp.version>1.2</upnp.version> <upnp.version>1.2</upnp.version>
<xz.version>1.9</xz.version> <xz.version>1.9</xz.version>
<lombok.version>1.18.30</lombok.version>
<jackson.version>2.16.1</jackson.version>
<slf4j.version>2.0.12</slf4j.version>
<nitrite.version>4.3.0</nitrite.version>
<junit.version>5.9.2</junit.version>
</properties> </properties>
<build> <build>
<sourceDirectory>src/main/java</sourceDirectory> <sourceDirectory>src/main/java</sourceDirectory>
@ -426,13 +430,41 @@
<id>project.local</id> <id>project.local</id>
<name>project</name> <name>project</name>
<url>file:${project.basedir}/lib</url> <url>file:${project.basedir}/lib</url>
<snapshots>
<enabled>true</enabled>
<updatePolicy>always</updatePolicy>
</snapshots>
</repository> </repository>
<!-- jitpack for build-on-demand of altcoinj --> <!-- jitpack for build-on-demand of altcoinj -->
<repository> <repository>
<id>jitpack.io</id> <id>jitpack.io</id>
<url>https://jitpack.io</url> <url>https://jitpack.io</url>
<snapshots>
<enabled>true</enabled>
<updatePolicy>always</updatePolicy>
</snapshots>
</repository> </repository>
</repositories> </repositories>
<!--
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.dizitart</groupId>
<artifactId>nitrite-bom</artifactId>
<version>${nitrite.version}</version>
<scope>import</scope>
<type>pom</type>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-bom</artifactId>
<version>${log4j.version}</version>
<scope>import</scope>
<type>pom</type>
</dependency>
</dependencies>
</dependencyManagement>
-->
<dependencies> <dependencies>
<!-- https://mvnrepository.com/artifact/org.codehaus.mojo/build-helper-maven-plugin --> <!-- https://mvnrepository.com/artifact/org.codehaus.mojo/build-helper-maven-plugin -->
<dependency> <dependency>
@ -558,7 +590,17 @@
<artifactId>guava</artifactId> <artifactId>guava</artifactId>
<version>${guava.version}</version> <version>${guava.version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<!-- Logging: log4j2 --> <!-- Logging: log4j2 -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j2-impl</artifactId>
<version>${log4j.version}</version>
</dependency>
<dependency> <dependency>
<groupId>org.apache.logging.log4j</groupId> <groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId> <artifactId>log4j-core</artifactId>
@ -569,24 +611,11 @@
<artifactId>log4j-api</artifactId> <artifactId>log4j-api</artifactId>
<version>${log4j.version}</version> <version>${log4j.version}</version>
</dependency> </dependency>
<!-- redirect slf4j to log4j2 --> <dependency>
<dependency> <groupId>org.apache.logging.log4j</groupId>
<groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-jul</artifactId>
<artifactId>log4j-slf4j-impl</artifactId> <version>${log4j.version}</version>
<version>${log4j.version}</version> </dependency>
</dependency>
<!-- redirect java.utils.logging to log4j2 -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-jul</artifactId>
<version>${log4j.version}</version>
</dependency>
<!-- Logging: slf4j used by Jetty/Jersey -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<!-- Servlet related --> <!-- Servlet related -->
<dependency> <dependency>
<groupId>javax.servlet</groupId> <groupId>javax.servlet</groupId>
@ -728,6 +757,11 @@
<groupId>org.bouncycastle</groupId> <groupId>org.bouncycastle</groupId>
<artifactId>bctls-jdk15on</artifactId> <artifactId>bctls-jdk15on</artifactId>
<version>${bouncycastle.version}</version> <version>${bouncycastle.version}</version>
</dependency><!-- https://mvnrepository.com/artifact/org.bouncycastle/bcprov-jdk15to18 -->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15to18</artifactId>
<version>${bouncycastle.version}</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.jsoup</groupId> <groupId>org.jsoup</groupId>
@ -770,5 +804,129 @@
<artifactId>jaxb-runtime</artifactId> <artifactId>jaxb-runtime</artifactId>
<version>${jaxb-runtime.version}</version> <version>${jaxb-runtime.version}</version>
</dependency> </dependency>
<!-- reticulum_network_stack -->
<dependency>
<groupId>io.reticulum</groupId>
<artifactId>reticulum-network-stack</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-codec/commons-codec -->
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.16.1</version>
</dependency>
<!-- already declared earlier
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.14.0</version>
</dependency>
-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.4</version>
</dependency>
<!-- already declared earlier
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-compress</artifactId>
<version>1.26.0</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
-->
<!-- note: covered ? -->
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-yaml</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>2.2</version>
</dependency>
<dependency>
<groupId>org.msgpack</groupId>
<artifactId>jackson-dataformat-msgpack</artifactId>
<version>0.9.8</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId>
<version>1.70</version>
</dependency>
<dependency>
<groupId>com.macasaet.fernet</groupId>
<artifactId>fernet-java8</artifactId>
<version>1.5.0</version>
</dependency>
<dependency>
<groupId>com.igormaznitsa</groupId>
<artifactId>jbbp</artifactId>
<version>2.0.6</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<!--<version>4.1.106.Final</version>-->
<version>5.0.0.Alpha2</version>
</dependency>
<dependency>
<groupId>com.github.seancfoley</groupId>
<artifactId>ipaddress</artifactId>
<version>5.4.2</version>
</dependency>
<!-- Nitrite Modules -->
<dependency>
<groupId>org.dizitart</groupId>
<artifactId>nitrite</artifactId>
<version>${nitrite.version}</version>
</dependency>
<dependency>
<groupId>org.dizitart</groupId>
<artifactId>nitrite-mvstore-adapter</artifactId>
<version>${nitrite.version}</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>2.3.230</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2-mvstore</artifactId>
<version>2.3.230</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>5.10.0</version>
<scope>test</scope>
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

@ -31,6 +31,7 @@ import org.qortal.globalization.Translator;
import org.qortal.gui.Gui; import org.qortal.gui.Gui;
import org.qortal.gui.SysTray; import org.qortal.gui.SysTray;
import org.qortal.network.Network; import org.qortal.network.Network;
import org.qortal.network.RNSNetwork;
import org.qortal.network.Peer; import org.qortal.network.Peer;
import org.qortal.network.message.*; import org.qortal.network.message.*;
import org.qortal.repository.*; import org.qortal.repository.*;
@ -115,6 +116,7 @@ public class Controller extends Thread {
private long repositoryCheckpointTimestamp = startTime; // ms private long repositoryCheckpointTimestamp = startTime; // ms
private long prunePeersTimestamp = startTime; // ms private long prunePeersTimestamp = startTime; // ms
private long ntpCheckTimestamp = startTime; // ms private long ntpCheckTimestamp = startTime; // ms
private long pruneRNSPeersTimestamp = startTime; // ms
private long deleteExpiredTimestamp = startTime + DELETE_EXPIRED_INTERVAL; // ms private long deleteExpiredTimestamp = startTime + DELETE_EXPIRED_INTERVAL; // ms
/** Whether we can mint new blocks, as reported by BlockMinter. */ /** Whether we can mint new blocks, as reported by BlockMinter. */
@ -481,6 +483,15 @@ public class Controller extends Thread {
return; // Not System.exit() so that GUI can display error return; // Not System.exit() so that GUI can display error
} }
LOGGER.info("Starting Reticulum");
try {
RNSNetwork rns = RNSNetwork.getInstance();
rns.start();
LOGGER.debug("Reticulum instance: {}", rns.toString());
} catch (IOException | DataException e) {
LOGGER.error("Unable to start Reticulum", e);
}
Runtime.getRuntime().addShutdownHook(new Thread() { Runtime.getRuntime().addShutdownHook(new Thread() {
@Override @Override
public void run() { public void run() {
@ -609,6 +620,8 @@ public class Controller extends Thread {
final long repositoryCheckpointInterval = Settings.getInstance().getRepositoryCheckpointInterval(); final long repositoryCheckpointInterval = Settings.getInstance().getRepositoryCheckpointInterval();
long repositoryMaintenanceInterval = getRandomRepositoryMaintenanceInterval(); long repositoryMaintenanceInterval = getRandomRepositoryMaintenanceInterval();
final long prunePeersInterval = 5 * 60 * 1000L; // Every 5 minutes final long prunePeersInterval = 5 * 60 * 1000L; // Every 5 minutes
//final long pruneRNSPeersInterval = 5 * 60 * 1000L; // Every 5 minutes
final long pruneRNSPeersInterval = 1 * 60 * 1000L; // Every 1 minute (during development)
// Start executor service for trimming or pruning // Start executor service for trimming or pruning
PruneManager.getInstance().start(); PruneManager.getInstance().start();
@ -717,6 +730,18 @@ public class Controller extends Thread {
} }
} }
// Q: Do we need global pruning?
if (now >= pruneRNSPeersTimestamp + pruneRNSPeersInterval) {
pruneRNSPeersTimestamp = now + pruneRNSPeersInterval;
try {
LOGGER.debug("Pruning Reticulum peers...");
RNSNetwork.getInstance().prunePeers();
} catch (DataException e) {
LOGGER.warn(String.format("Repository issue when trying to prune Reticulum peers: %s", e.getMessage()));
}
}
// Delete expired transactions // Delete expired transactions
if (now >= deleteExpiredTimestamp) { if (now >= deleteExpiredTimestamp) {
deleteExpiredTimestamp = now + DELETE_EXPIRED_INTERVAL; deleteExpiredTimestamp = now + DELETE_EXPIRED_INTERVAL;
@ -1015,6 +1040,9 @@ public class Controller extends Thread {
LOGGER.info("Shutting down networking"); LOGGER.info("Shutting down networking");
Network.getInstance().shutdown(); Network.getInstance().shutdown();
LOGGER.info("Shutting down Reticulum");
RNSNetwork.getInstance().shutdown();
LOGGER.info("Shutting down controller"); LOGGER.info("Shutting down controller");
this.interrupt(); this.interrupt();
try { try {

View File

@ -0,0 +1,23 @@
package org.qortal.network;
public class RNSCommon {
/**
* Destination application name
*/
public static String APP_NAME = "qortal";
/**
* Configuration path relative to the Qortal launch directory
*/
public static String defaultRNSConfigPath = new String(".reticulum");
///**
// * Qortal RNS Destinations
// */
//public enum RNSDestinations {
// BASE,
// QDN;
//}
}

View File

@ -0,0 +1,637 @@
package org.qortal.network;
import io.reticulum.Reticulum;
import io.reticulum.Transport;
import io.reticulum.interfaces.ConnectionInterface;
import io.reticulum.destination.Destination;
import io.reticulum.destination.DestinationType;
import io.reticulum.destination.Direction;
import io.reticulum.destination.ProofStrategy;
import io.reticulum.identity.Identity;
import io.reticulum.link.Link;
import io.reticulum.link.LinkStatus;
//import io.reticulum.constant.LinkConstant;
import io.reticulum.packet.Packet;
import io.reticulum.packet.PacketReceipt;
import io.reticulum.packet.PacketReceiptStatus;
import io.reticulum.transport.AnnounceHandler;
import static io.reticulum.link.TeardownSession.DESTINATION_CLOSED;
import static io.reticulum.link.TeardownSession.INITIATOR_CLOSED;
import static io.reticulum.link.TeardownSession.TIMEOUT;
import static io.reticulum.link.LinkStatus.ACTIVE;
import static io.reticulum.link.LinkStatus.STALE;
import static io.reticulum.link.LinkStatus.PENDING;
import static io.reticulum.link.LinkStatus.HANDSHAKE;
//import static io.reticulum.packet.PacketContextType.LINKCLOSE;
import static io.reticulum.identity.IdentityKnownDestination.recall;
import static io.reticulum.utils.IdentityUtils.concatArrays;
//import static io.reticulum.constant.ReticulumConstant.TRUNCATED_HASHLENGTH;
import static io.reticulum.constant.ReticulumConstant.CONFIG_FILE_NAME;
import lombok.Data;
//import lombok.Setter;
//import lombok.Getter;
import lombok.Synchronized;
import org.qortal.repository.DataException;
import org.qortal.settings.Settings;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.StandardCopyOption;
import static java.nio.file.StandardOpenOption.CREATE;
import static java.nio.file.StandardOpenOption.WRITE;
import java.nio.file.Files;
import java.nio.file.Path;
import static java.nio.charset.StandardCharsets.UTF_8;
//import static java.util.Objects.isNull;
//import static java.util.Objects.isNull;
import static java.util.Objects.nonNull;
//import static org.apache.commons.lang3.BooleanUtils.isFalse;
import java.io.File;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
//import java.util.Random;
import java.util.Scanner;
import java.util.concurrent.TimeUnit;
import org.apache.commons.codec.binary.Hex;
// logging
import lombok.extern.slf4j.Slf4j;
//import org.slf4j.Logger;
//import org.slf4j.LoggerFactory;
@Data
@Slf4j
public class RNSNetwork {
Reticulum reticulum;
//private static final String APP_NAME = "qortal";
static final String APP_NAME = RNSCommon.APP_NAME;
static final String defaultConfigPath = new String(".reticulum"); // if empty will look in Reticulums default paths
//static final String defaultConfigPath = RNSCommon.defaultRNSConfigPath;
//private final String defaultConfigPath = Settings.getInstance().getDefaultRNSConfigPathForReticulum();
private static Integer MAX_PEERS = 12;
//private final Integer MAX_PEERS = Settings.getInstance().getMaxReticulumPeers();
private static Integer MIN_DESIRED_PEERS = 3;
//private final Integer MIN_DESIRED_PEERS = Settings.getInstance().getMinDesiredPeers();
Identity serverIdentity;
public Destination baseDestination;
private volatile boolean isShuttingDown = false;
private final List<RNSPeer> linkedPeers = Collections.synchronizedList(new ArrayList<>());
private final List<Link> incomingLinks = Collections.synchronizedList(new ArrayList<>());
////private final ExecuteProduceConsume rnsNetworkEPC;
//private static final long NETWORK_EPC_KEEPALIVE = 1000L; // 1 second
//private volatile boolean isShuttingDown = false;
//private int totalThreadCount = 0;
//// TODO: settings - MaxReticulumPeers, MaxRNSNetworkThreadPoolSize (if needed)
//private static final Logger logger = LoggerFactory.getLogger(RNSNetwork.class);
// Constructor
private RNSNetwork () {
log.info("RNSNetwork constructor");
try {
//String configPath = new java.io.File(defaultConfigPath).getCanonicalPath();
log.info("creating config from {}", defaultConfigPath);
initConfig(defaultConfigPath);
//reticulum = new Reticulum(configPath);
reticulum = new Reticulum(defaultConfigPath);
var identitiesPath = reticulum.getStoragePath().resolve("identities");
if (Files.notExists(identitiesPath)) {
Files.createDirectories(identitiesPath);
}
} catch (IOException e) {
log.error("unable to create Reticulum network", e);
}
log.info("reticulum instance created");
log.info("reticulum instance created: {}", reticulum);
// Settings.getInstance().getMaxRNSNetworkThreadPoolSize(), // statically set to 5 below
//ExecutorService RNSNetworkExecutor = new ThreadPoolExecutor(1,
// 5,
// NETWORK_EPC_KEEPALIVE, TimeUnit.SECONDS,
// new SynchronousQueue<Runnable>(),
// new NamedThreadFactory("RNSNetwork-EPC"));
//rnsNetworkEPC = new RNSNetworkProcessor(RNSNetworkExecutor);
}
// Note: potentially create persistent serverIdentity (utility rnid) and load it from file
public void start() throws IOException, DataException {
// create identity either from file or new (creating new keys)
var serverIdentityPath = reticulum.getStoragePath().resolve("identities/"+APP_NAME);
if (Files.isReadable(serverIdentityPath)) {
serverIdentity = Identity.fromFile(serverIdentityPath);
log.info("server identity loaded from file {}", serverIdentityPath.toString());
} else {
serverIdentity = new Identity();
log.info("APP_NAME: {}, storage path: {}", APP_NAME, serverIdentityPath);
log.info("new server identity created dynamically.");
// save it back to file by default for next start (possibly add setting to override)
try {
Files.write(serverIdentityPath, serverIdentity.getPrivateKey(), CREATE, WRITE);
log.info("serverIdentity written back to file");
} catch (IOException e) {
log.error("Error while saving serverIdentity to {}", serverIdentityPath, e);
}
}
log.debug("Server Identity: {}", serverIdentity.toString());
// show the ifac_size of the configured interfaces (debug code)
for (ConnectionInterface i: Transport.getInstance().getInterfaces() ) {
log.info("interface {}, length: {}", i.getInterfaceName(), i.getIfacSize());
}
baseDestination = new Destination(
serverIdentity,
Direction.IN,
DestinationType.SINGLE,
APP_NAME,
"core"
);
//// idea for other entry point
//dataDestination = new Destination(
// serverIdentity,
// Direction.IN,
// DestinationType.SINGLE,
// APP_NAME,
// "core",
// "qdn"
//);
log.info("Destination "+Hex.encodeHexString(baseDestination.getHash())+" "+baseDestination.getName()+" running.");
baseDestination.setProofStrategy(ProofStrategy.PROVE_ALL);
baseDestination.setAcceptLinkRequests(true);
baseDestination.setLinkEstablishedCallback(this::clientConnected);
Transport.getInstance().registerAnnounceHandler(new QAnnounceHandler());
log.debug("announceHandlers: {}", Transport.getInstance().getAnnounceHandlers());
// do a first announce
baseDestination.announce();
log.debug("Sent initial announce from {} ({})", Hex.encodeHexString(baseDestination.getHash()), baseDestination.getName());
// Start up first networking thread (the "server loop")
//rnsNetworkEPC.start();
}
private void initConfig(String configDir) throws IOException {
File configDir1 = new File(defaultConfigPath);
if (!configDir1.exists()) {
configDir1.mkdir();
}
var configPath = Path.of(configDir1.getAbsolutePath());
Path configFile = configPath.resolve(CONFIG_FILE_NAME);
if (Files.notExists(configFile)) {
var defaultConfig = this.getClass().getClassLoader().getResourceAsStream("reticulum_default_config.yml");
Files.copy(defaultConfig, configFile, StandardCopyOption.REPLACE_EXISTING);
}
}
public void shutdown() {
isShuttingDown = true;
log.info("shutting down Reticulum");
// Stop processing threads (the "server loop")
//try {
// if (!this.rnsNetworkEPC.shutdown(5000)) {
// logger.warn("RNSNetwork threads failed to terminate");
// }
//} catch (InterruptedException e) {
// logger.warn("Interrupted while waiting for RNS networking threads to terminate");
//}
// Disconnect peers gracefully and terminate Reticulum
for (RNSPeer p: linkedPeers) {
log.info("shutting down peer: {}", Hex.encodeHexString(p.getDestinationHash()));
log.debug("peer: {}", p);
p.shutdown();
try {
TimeUnit.SECONDS.sleep(1); // allow for peers to disconnect gracefully
} catch (InterruptedException e) {
log.error("exception: {}", e);
}
}
// gracefully close links of peers that point to us
for (Link l: incomingLinks) {
sendCloseToRemote(l);
}
// Note: we still need to get the packet timeout callback to work...
reticulum.exitHandler();
}
public void sendCloseToRemote(Link link) {
if (nonNull(link)) {
var data = concatArrays("close::".getBytes(UTF_8),link.getDestination().getHash());
Packet closePacket = new Packet(link, data);
var packetReceipt = closePacket.send();
packetReceipt.setDeliveryCallback(this::closePacketDelivered);
packetReceipt.setTimeoutCallback(this::packetTimedOut);
} else {
log.debug("can't send to null link");
}
}
public void closePacketDelivered(PacketReceipt receipt) {
var rttString = new String("");
if (receipt.getStatus() == PacketReceiptStatus.DELIVERED) {
var rtt = receipt.getRtt(); // rtt (Java) is in miliseconds
//log.info("qqp - packetDelivered - rtt: {}", rtt);
if (rtt >= 1000) {
rtt = Math.round(rtt / 1000);
rttString = String.format("%d seconds", rtt);
} else {
rttString = String.format("%d miliseconds", rtt);
}
log.info("Shutdown packet confirmation received from {}, round-trip time is {}",
Hex.encodeHexString(receipt.getDestination().getHash()), rttString);
}
}
public void packetTimedOut(PacketReceipt receipt) {
log.info("packet timed out, receipt status: {}", receipt.getStatus());
}
public void clientConnected(Link link) {
link.setLinkClosedCallback(this::clientDisconnected);
link.setPacketCallback(this::serverPacketReceived);
var peer = findPeerByLink(link);
if (nonNull(peer)) {
log.info("initiator peer {} opened link (link lookup: {}), link destination hash: {}",
Hex.encodeHexString(peer.getDestinationHash()), link, Hex.encodeHexString(link.getDestination().getHash()));
// make sure the peerLink is actvive.
peer.getOrInitPeerLink();
} else {
log.info("non-initiator closed link (link lookup: {}), link destination hash (initiator): {}",
peer, link, Hex.encodeHexString(link.getDestination().getHash()));
}
incomingLinks.add(link);
log.info("***> Client connected, link: {}", link);
}
public void clientDisconnected(Link link) {
var peer = findPeerByLink(link);
if (nonNull(peer)) {
log.info("initiator peer {} closed link (link lookup: {}), link destination hash: {}",
Hex.encodeHexString(peer.getDestinationHash()), link, Hex.encodeHexString(link.getDestination().getHash()));
} else {
log.info("non-initiator closed link (link lookup: {}), link destination hash (initiator): {}",
peer, link, Hex.encodeHexString(link.getDestination().getHash()));
}
// if we have a peer pointing to that destination, we can close and remove it
peer = findPeerByDestinationHash(link.getDestination().getHash());
if (nonNull(peer)) {
// Note: no shutdown as the remobe peer could be only rebooting.
// keep it to reopen link later if possible.
peer.getPeerLink().teardown();
}
incomingLinks.remove(link);
log.info("***> Client disconnected");
}
public void serverPacketReceived(byte[] message, Packet packet) {
var msgText = new String(message, StandardCharsets.UTF_8);
log.info("Received data on link - message: {}, destinationHash: {}", msgText, Hex.encodeHexString(packet.getDestinationHash()));
//var peer = findPeerByDestinationHash(packet.getDestinationHash());
//if (msgText.equals("ping")) {
// log.info("received ping");
// //if (nonNull(peer)) {
// // String replyText = "pong";
// // byte[] replyData = replyText.getBytes(StandardCharsets.UTF_8);
// // Packet reply = new Packet(peer.getPeerLink(), replyData);
// //}
//}
//if (msgText.equals("shutdown")) {
// log.info("shutdown packet received");
// var link = recall(packet.getDestinationHash());
// log.info("recalled destinationHash: {}", link);
// //...
//}
// TODO: process packet....
}
//public void announceBaseDestination () {
// getBaseDestination().announce();
//}
//@Slf4j
private class QAnnounceHandler implements AnnounceHandler {
@Override
public String getAspectFilter() {
// handle all announces
return null;
}
@Override
@Synchronized
public void receivedAnnounce(byte[] destinationHash, Identity announcedIdentity, byte[] appData) {
var peerExists = false;
var activePeerCount = 0;
log.info("Received an announce from {}", Hex.encodeHexString(destinationHash));
if (nonNull(appData)) {
log.debug("The announce contained the following app data: {}", new String(appData, UTF_8));
}
// add to peer list if we can use more peers
//synchronized (this) {
var lps = RNSNetwork.getInstance().getLinkedPeers();
for (RNSPeer p: lps) {
var pl = p.getPeerLink();
if ((nonNull(pl) && (pl.getStatus() == ACTIVE))) {
activePeerCount = activePeerCount + 1;
}
}
if (activePeerCount < MAX_PEERS) {
//if (!peerExists) {
//var peer = findPeerByDestinationHash(destinationHash);
for (RNSPeer p: lps) {
if (Arrays.equals(p.getDestinationHash(), destinationHash)) {
log.info("QAnnounceHandler - peer exists - found peer matching destinationHash");
if (nonNull(p.getPeerLink())) {
log.info("peer link: {}, status: {}", p.getPeerLink(), p.getPeerLink().getStatus());
}
peerExists = true;
if (p.getPeerLink().getStatus() != ACTIVE) {
p.getOrInitPeerLink();
}
break;
} else {
if (nonNull(p.getPeerLink())) {
log.info("QAnnounceHandler - other peer - link: {}, status: {}", p.getPeerLink(), p.getPeerLink().getStatus());
} else {
log.info("QAnnounceHandler - null link peer - link: {}", p.getPeerLink());
}
}
}
if (!peerExists) {
RNSPeer newPeer = new RNSPeer(destinationHash);
newPeer.setServerIdentity(announcedIdentity);
newPeer.setIsInitiator(true);
lps.add(newPeer);
log.info("added new RNSPeer, destinationHash: {}", Hex.encodeHexString(destinationHash));
}
}
//}
}
}
// Main thread
//class RNSNetworkProcessor extends ExecuteProduceConsume {
//
// //private final Logger logger = LoggerFactory.getLogger(RNSNetworkProcessor.class);
//
// private final AtomicLong nextConnectTaskTimestamp = new AtomicLong(0L); // ms - try first connect once NTP syncs
// private final AtomicLong nextBroadcastTimestamp = new AtomicLong(0L); // ms - try first broadcast once NTP syncs
//
// private Iterator<SelectionKey> channelIterator = null;
//
// RNSNetworkProcessor(ExecutorService executor) {
// super(executor);
// }
//
// @Override
// protected void onSpawnFailure() {
// // For debugging:
// // ExecutorDumper.dump(this.executor, 3, ExecuteProduceConsume.class);
// }
//
// @Override
// protected Task produceTask(boolean canBlock) throws InterruptedException {
// Task task;
//
// //task = maybeProducePeerMessageTask();
// //if (task != null) {
// // return task;
// //}
// //
// //final Long now = NTP.getTime();
// //
// //task = maybeProducePeerPingTask(now);
// //if (task != null) {
// // return task;
// //}
// //
// //task = maybeProduceConnectPeerTask(now);
// //if (task != null) {
// // return task;
// //}
// //
// //task = maybeProduceBroadcastTask(now);
// //if (task != null) {
// // return task;
// //}
// //
// // Only this method can block to reduce CPU spin
// //return maybeProduceChannelTask(canBlock);
//
// // TODO: flesh out the tasks handled by Reticulum
// return null;
// }
// //...TODO: implement abstract methods...
//}
// getter / setter
private static class SingletonContainer {
private static final RNSNetwork INSTANCE = new RNSNetwork();
}
public static RNSNetwork getInstance() {
return SingletonContainer.INSTANCE;
}
public Identity getServerIdentity() {
return this.serverIdentity;
}
public Reticulum getReticulum() {
return this.reticulum;
}
public List<RNSPeer> getLinkedPeers() {
synchronized(this.linkedPeers) {
//return new ArrayList<>(this.linkedPeers);
return this.linkedPeers;
}
}
public Integer getTotalPeers() {
synchronized (this) {
return linkedPeers.size();
}
}
public Destination getBaseDestination() {
return baseDestination;
}
// maintenance
//public void removePeer(RNSPeer peer) {
// synchronized(this) {
// List<RNSPeer> peerList = this.linkedPeers;
// log.info("removing peer {} on peer shutdown", peer);
// peerList.remove(peer);
// }
//}
//public void pingPeer(RNSPeer peer) {
// if (nonNull(peer)) {
// peer.pingRemote();
// } else {
// log.error("peer argument is null");
// }
//}
@Synchronized
public void prunePeers() throws DataException {
// run periodically (by the Controller)
//List<Link> linkList = getLinkedPeers();
var peerList = getLinkedPeers();
log.info("number of links (linkedPeers) before prunig: {}", peerList.size());
Link pLink;
LinkStatus lStatus;
for (RNSPeer p: peerList) {
pLink = p.getPeerLink();
log.info("prunePeers - pLink: {}, destinationHash: {}",
pLink, Hex.encodeHexString(p.getDestinationHash()));
log.debug("peer: {}", p);
if (nonNull(pLink)) {
if (p.getPeerTimedOut()) {
// close peer link for now
pLink.teardown();
}
lStatus = pLink.getStatus();
log.info("Link {} status: {}", pLink, lStatus);
// lStatus in: PENDING, HANDSHAKE, ACTIVE, STALE, CLOSED
if ((lStatus == STALE) || (pLink.getTeardownReason() == TIMEOUT) || (p.getDeleteMe())) {
p.shutdown();
peerList.remove(p);
} else if (lStatus == HANDSHAKE) {
// stuck in handshake state (do we need to shutdown/remove it?)
log.info("peer status HANDSHAKE");
p.shutdown();
peerList.remove(p);
}
} else {
peerList.remove(p);
}
}
//removeExpiredPeers(this.linkedPeers);
log.info("number of links (linkedPeers) after prunig: {}", peerList.size());
//log.info("we have {} non-initiator links, list: {}", incomingLinks.size(), incomingLinks);
var activePeerCount = 0;
var lps = RNSNetwork.getInstance().getLinkedPeers();
for (RNSPeer p: lps) {
pLink = p.getPeerLink();
p.pingRemote();
try {
TimeUnit.SECONDS.sleep(2); // allow for peers to disconnect gracefully
} catch (InterruptedException e) {
log.error("exception: {}", e);
}
if ((nonNull(pLink) && (pLink.getStatus() == ACTIVE))) {
activePeerCount = activePeerCount + 1;
}
}
log.info("we have {} active peers", activePeerCount);
maybeAnnounce(getBaseDestination());
}
//public void removeExpiredPeers(List<RNSPeer> peerList) {
// //List<RNSPeer> peerList = this.linkedPeers;
// for (RNSPeer p: peerList) {
// if (p.getPeerLink() == null) {
// peerList.remove(p);
// } else if (p.getPeerLink().getStatus() == STALE) {
// peerList.remove(p);
// }
// }
//}
public void maybeAnnounce(Destination d) {
if (getLinkedPeers().size() < MIN_DESIRED_PEERS) {
d.announce();
}
}
/**
* Helper methods
*/
//@Synchronized
//public RNSPeer getPeerIfExists(RNSPeer peer) {
// List<RNSPeer> lps = RNSNetwork.getInstance().getLinkedPeers();
// RNSPeer result = null;
// for (RNSPeer p: lps) {
// if (nonNull(p.getDestinationHash()) && Arrays.equals(p.getDestinationHash(), peer.getDestinationHash())) {
// log.info("found match by destinationHash");
// result = p;
// //break;
// }
// if (nonNull(p.getPeerDestinationHash()) && Arrays.equals(p.getPeerDestinationHash(), peer.getPeerDestinationHash())) {
// log.info("found match by peerDestinationHash");
// result = p;
// //break;
// }
// if (nonNull(p.getPeerBaseDestinationHash()) && Arrays.equals(p.getPeerBaseDestinationHash(), peer.getPeerBaseDestinationHash())) {
// log.info("found match by peerBaseDestinationHash");
// result = p;
// //break;
// }
// if (nonNull(p.getRemoteTestHash()) && Arrays.equals(p.getRemoteTestHash(), peer.getRemoteTestHash())) {
// log.info("found match by remoteTestHash");
// result = p;
// //break;
// }
// }
// return result;
//}
public RNSPeer findPeerByLink(Link link) {
List<RNSPeer> lps = RNSNetwork.getInstance().getLinkedPeers();
RNSPeer peer = null;
for (RNSPeer p : lps) {
var pLink = p.getPeerLink();
if (nonNull(pLink)) {
if (Arrays.equals(pLink.getDestination().getHash(),link.getDestination().getHash())) {
log.info("found peer matching destinationHash: {}", Hex.encodeHexString(link.getDestination().getHash()));
peer = p;
break;
}
}
}
return peer;
}
public RNSPeer findPeerByDestinationHash(byte[] dhash) {
List<RNSPeer> lps = RNSNetwork.getInstance().getLinkedPeers();
RNSPeer peer = null;
for (RNSPeer p : lps) {
if (Arrays.equals(p.getDestinationHash(), dhash)) {
log.info("found peer matching destinationHash: {}", Hex.encodeHexString(dhash));
peer = p;
break;
}
}
return peer;
}
public void removePeer(RNSPeer peer) {
List<RNSPeer> peerList = this.linkedPeers;
if (nonNull(peer)) {
peerList.remove(peer);
}
}
}

View File

@ -0,0 +1,284 @@
package org.qortal.network;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static java.util.Objects.isNull;
import static java.util.Objects.nonNull;
import java.io.IOException;
import java.time.Instant;
import java.util.Arrays;
import io.reticulum.Reticulum;
import org.qortal.network.RNSNetwork;
import io.reticulum.link.Link;
import io.reticulum.link.RequestReceipt;
import io.reticulum.packet.PacketReceiptStatus;
import io.reticulum.packet.Packet;
import io.reticulum.packet.PacketReceipt;
import io.reticulum.identity.Identity;
import io.reticulum.channel.Channel;
import io.reticulum.destination.Destination;
import io.reticulum.destination.DestinationType;
import io.reticulum.destination.Direction;
import io.reticulum.destination.ProofStrategy;
import io.reticulum.resource.Resource;
import static io.reticulum.link.TeardownSession.INITIATOR_CLOSED;
import static io.reticulum.link.TeardownSession.DESTINATION_CLOSED;
import static io.reticulum.link.TeardownSession.TIMEOUT;
import static io.reticulum.link.LinkStatus.ACTIVE;
import static io.reticulum.link.LinkStatus.CLOSED;
import static io.reticulum.identity.IdentityKnownDestination.recall;
//import static io.reticulum.identity.IdentityKnownDestination.recallAppData;
import java.nio.charset.StandardCharsets;
import static java.nio.charset.StandardCharsets.UTF_8;
import org.apache.commons.codec.binary.Hex;
import static org.apache.commons.lang3.ArrayUtils.subarray;
import lombok.extern.slf4j.Slf4j;
import lombok.Setter;
import lombok.Data;
import lombok.AccessLevel;
@Data
@Slf4j
public class RNSPeer {
//static final String APP_NAME = "qortal";
static final String APP_NAME = RNSCommon.APP_NAME;
//static final String defaultConfigPath = new String(".reticulum");
static final String defaultConfigPath = RNSCommon.defaultRNSConfigPath;
private byte[] destinationHash; // remote destination hash
Destination peerDestination; // OUT destination created for this
private Identity serverIdentity;
@Setter(AccessLevel.PACKAGE) private Instant creationTimestamp;
private Instant lastAccessTimestamp;
Link peerLink;
private Boolean isInitiator;
private Boolean deleteMe = false;
private Double requestResponseProgress;
@Setter(AccessLevel.PACKAGE) private Boolean peerTimedOut = false;
public RNSPeer(byte[] dhash) {
destinationHash = dhash;
serverIdentity = recall(dhash);
initPeerLink();
//setCreationTimestamp(System.currentTimeMillis());
creationTimestamp = Instant.now();
}
public void initPeerLink() {
peerDestination = new Destination(
this.serverIdentity,
Direction.OUT,
DestinationType.SINGLE,
RNSNetwork.APP_NAME,
"core"
);
peerDestination.setProofStrategy(ProofStrategy.PROVE_ALL);
lastAccessTimestamp = Instant.now();
isInitiator = true;
peerLink = new Link(peerDestination);
this.peerLink.setLinkEstablishedCallback(this::linkEstablished);
this.peerLink.setLinkClosedCallback(this::linkClosed);
this.peerLink.setPacketCallback(this::linkPacketReceived);
}
public Link getOrInitPeerLink() {
if (this.peerLink.getStatus() == ACTIVE) {
lastAccessTimestamp = Instant.now();
return this.peerLink;
} else {
initPeerLink();
}
return this.peerLink;
}
public void shutdown() {
if (nonNull(peerLink)) {
log.info("shutdown - peerLink: {}, status: {}", peerLink, peerLink.getStatus());
if (peerLink.getStatus() == ACTIVE) {
peerLink.teardown();
}
this.peerLink = null;
}
this.deleteMe = true;
}
public Channel getChannel() {
if (isNull(getPeerLink())) {
log.warn("link is null.");
return null;
}
setLastAccessTimestamp(Instant.now());
return getPeerLink().getChannel();
}
/** Link callbacks */
public void linkEstablished(Link link) {
link.setLinkClosedCallback(this::linkClosed);
log.info("peerLink {} established (link: {}) with peer: hash - {}, link destination hash: {}",
peerLink, link, Hex.encodeHexString(destinationHash),
Hex.encodeHexString(link.getDestination().getHash()));
}
public void linkClosed(Link link) {
if (link.getTeardownReason() == TIMEOUT) {
log.info("The link timed out");
this.peerTimedOut = true;
} else if (link.getTeardownReason() == INITIATOR_CLOSED) {
log.info("Link closed callback: The initiator closed the link");
log.info("peerLink {} closed (link: {}), link destination hash: {}",
peerLink, link, Hex.encodeHexString(link.getDestination().getHash()));
} else if (link.getTeardownReason() == DESTINATION_CLOSED) {
log.info("Link closed callback: The link was closed by the peer, removing peer");
log.info("peerLink {} closed (link: {}), link destination hash: {}",
peerLink, link, Hex.encodeHexString(link.getDestination().getHash()));
} else {
log.info("Link closed callback");
}
}
public void linkPacketReceived(byte[] message, Packet packet) {
var msgText = new String(message, StandardCharsets.UTF_8);
if (msgText.equals("ping")) {
log.info("received ping on link");
} else if (msgText.startsWith("close::")) {
var targetPeerHash = subarray(message, 7, message.length);
log.info("peer dest hash: {}, target hash: {}",
Hex.encodeHexString(destinationHash),
Hex.encodeHexString(targetPeerHash));
if (Arrays.equals(destinationHash, targetPeerHash)) {
log.info("closing link: {}", peerLink.getDestination().getHexHash());
peerLink.teardown();
}
} else if (msgText.startsWith("open::")) {
var targetPeerHash = subarray(message, 7, message.length);
log.info("peer dest hash: {}, target hash: {}",
Hex.encodeHexString(destinationHash),
Hex.encodeHexString(targetPeerHash));
if (Arrays.equals(destinationHash, targetPeerHash)) {
log.info("closing link: {}", peerLink.getDestination().getHexHash());
getOrInitPeerLink();
}
}
// TODO: process incoming packet....
}
/** PacketReceipt callbacks */
public void packetDelivered(PacketReceipt receipt) {
var rttString = new String("");
//log.info("packet delivered callback, receipt: {}", receipt);
if (receipt.getStatus() == PacketReceiptStatus.DELIVERED) {
var rtt = receipt.getRtt(); // rtt (Java) is in miliseconds
//log.info("qqp - packetDelivered - rtt: {}", rtt);
if (rtt >= 1000) {
rtt = Math.round(rtt / 1000);
rttString = String.format("%d seconds", rtt);
} else {
rttString = String.format("%d miliseconds", rtt);
}
log.info("Valid reply received from {}, round-trip time is {}",
Hex.encodeHexString(receipt.getDestination().getHash()), rttString);
}
}
public void packetTimedOut(PacketReceipt receipt) {
log.info("packet timed out, receipt status: {}", receipt.getStatus());
if (receipt.getStatus() == PacketReceiptStatus.FAILED) {
this.peerTimedOut = true;
this.peerLink.teardown();
}
}
/** Link Request callbacks */
public void linkRequestResponseReceived(RequestReceipt rr) {
log.info("Response received");
}
public void linkRequestResponseProgress(RequestReceipt rr) {
this.requestResponseProgress = rr.getProgress();
log.debug("Response progress set");
}
public void linkRequestFailed(RequestReceipt rr) {
log.error("Request failed");
}
/** Link Resource callbacks */
// Resource: allow arbitrary amounts of data to be passed over a link with
// sequencing, compression, coordination and checksumming handled automatically
//public Boolean linkResourceAdvertised(Resource resource) {
// log.debug("Resource advertised");
//}
public void linkResourceTransferStarted(Resource resource) {
log.debug("Resource transfer started");
}
public void linkResourceTransferComcluded(Resource resource) {
log.debug("Resource transfer complete");
}
/** Utility methods */
public void pingRemote() {
var link = this.peerLink;
if (nonNull(link)) {
if (peerLink.getStatus() == ACTIVE) {
log.info("pinging remote: {}", link);
var data = "ping".getBytes(UTF_8);
link.setPacketCallback(this::linkPacketReceived);
Packet pingPacket = new Packet(link, data);
PacketReceipt packetReceipt = pingPacket.send();
// Note: don't setTimeout, we want it to timeout with FAIL if not deliverable
//packetReceipt.setTimeout(5000L);
packetReceipt.setTimeoutCallback(this::packetTimedOut);
packetReceipt.setDeliveryCallback(this::packetDelivered);
} else {
log.info("can't send ping to a peer {} with (link) status: {}",
Hex.encodeHexString(peerLink.getDestination().getHash()), peerLink.getStatus());
}
}
}
//public void shutdownLink(Link link) {
// var data = "shutdown".getBytes(UTF_8);
// Packet shutdownPacket = new Packet(link, data);
// PacketReceipt packetReceipt = shutdownPacket.send();
// packetReceipt.setTimeout(2000L);
// packetReceipt.setTimeoutCallback(this::packetTimedOut);
// packetReceipt.setDeliveryCallback(this::shutdownPacketDelivered);
//}
///** check if a link is available (ACTIVE)
// * link: a certain peer link, or null (default link == link to Qortal node RNS baseDestination)
// */
//public Boolean peerLinkIsAlive(Link link) {
// var result = false;
// if (isNull(link)) {
// // default link
// var defaultLink = getLink();
// if (nonNull(defaultLink) && defaultLink.getStatus() == ACTIVE) {
// result = true;
// log.info("Default link is available");
// } else {
// log.info("Default link {} is not available, status: {}", defaultLink, defaultLink.getStatus());
// }
// } else {
// // other link (future where we have multiple destinations...)
// if (link.getStatus() == ACTIVE) {
// result = true;
// log.info("Link {} is available (status: {})", link, link.getStatus());
// } else {
// log.info("Link {} is not available, status: {}", link, link.getStatus());
// }
// }
// return result;
//}
}

View File

@ -0,0 +1,93 @@
---
# You should probably edit it to include any additional,
# interfaces and settings you might need.
# Only the most basic options are included in this default
# configuration. To see a more verbose, and much longer,
# configuration example, you can run the command:
# rnsd --exampleconfig
reticulum:
# If you enable Transport, your system will route traffic
# for other peers, pass announces and serve path requests.
# This should only be done for systems that are suited to
# act as transport nodes, ie. if they are stationary and
# always-on. This directive is optional and can be removed
# for brevity.
enable_transport: false
# By default, the first program to launch the Reticulum
# Network Stack will create a shared instance, that other
# programs can communicate with. Only the shared instance
# opens all the configured interfaces directly, and other
# local programs communicate with the shared instance over
# a local socket. This is completely transparent to the
# user, and should generally be turned on. This directive
# is optional and can be removed for brevity.
share_instance: false
# If you want to run multiple *different* shared instances
# on the same system, you will need to specify different
# shared instance ports for each. The defaults are given
# below, and again, these options can be left out if you
# don't need them.
#shared_instance_port: 37428
#instance_control_port: 37429
shared_instance_port: 37438
instance_control_port: 37439
# You can configure Reticulum to panic and forcibly close
# if an unrecoverable interface error occurs, such as the
# hardware device for an interface disappearing. This is
# an optional directive, and can be left out for brevity.
# This behaviour is disabled by default.
panic_on_interface_error: false
# The interfaces section defines the physical and virtual
# interfaces Reticulum will use to communicate on. This
# section will contain examples for a variety of interface
# types. You can modify these or use them as a basis for
# your own config, or simply remove the unused ones.
interfaces:
# This interface enables communication with other
# link-local Reticulum nodes over UDP. It does not
# need any functional IP infrastructure like routers
# or DHCP servers, but will require that at least link-
# local IPv6 is enabled in your operating system, which
# should be enabled by default in almost any OS. See
# the Reticulum Manual for more configuration options.
#"Default Interface":
# type: AutoInterface
# enabled: true
# This interface enables communication with a "backbone"
# server over TCP.
# Note: others may be added for redundancy
"TCP Client Interface mobilefabrik":
type: TCPClientInterface
enabled: true
target_host: phantom.mobilefabrik.com
target_port: 4242
#network_name: qortal
# This interface turns this Reticulum instance into a
# server other clients can connect to over TCP.
# To enable this instance to route traffic the above
# setting "enable_transport" needs to be set (to true).
# Note: this interface type is not yet supported by
# reticulum-network-stack.
#"TCP Server Interface":
# type: TCPServerInterface
# enabled: true
# listen_ip: 0.0.0.0
# listen_port: 3434
# #network_name: qortal

View File

@ -0,0 +1,70 @@
package org.qortal.test.network;
import org.apache.commons.lang3.StringUtils;
//import org.junit.Before;
//import org.junit.Ignore;
import org.junit.Test;
//import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
//import java.util.Arrays;
import static io.reticulum.constant.ReticulumConstant.ETC_DIR;
import static org.apache.commons.lang3.SystemUtils.USER_HOME;
//import static org.junit.Assert.assertNotNull;
class ReticulumTest {
//@Test
//void t() throws DecoderException {
// System.out.println(Arrays.toString(Hex.decodeHex("adf54d882c9a9b80771eb4995d702d4a3e733391b2a0f53f416d9f907e55cff8")));
// System.out.println(2 + 1 + (128 / 8) * 2);
//}
@Test
void path() {
System.out.println(initConfig(null));
}
//@Test
//void testConfigYamlParse() throws IOException {
// var config = ConfigObj.initConfig(Path.of(getSystemClassLoader().getResource("reticulum.default.yml").getPath()));
// assertNotNull(config);
//}
//@Test
//void testHKDF() {
// var ifac_netname = "name";
// var ifac_netkey = "password";
// var ifacOrigin = new byte[]{};
// ifacOrigin = ArrayUtils.addAll(ifacOrigin, getSha256Digest().digest(ifac_netname.getBytes(UTF_8)));
// ifacOrigin = ArrayUtils.addAll(ifacOrigin, getSha256Digest().digest(ifac_netkey.getBytes(UTF_8)));
//
// var ifacOriginHash = getSha256Digest().digest(ifacOrigin);
//
// var HKDF = new HKDFBytesGenerator(new SHA256Digest());
// HKDF.init(new HKDFParameters(ifacOriginHash, IFAC_SALT, new byte[0]));
// var result = new byte[64];
// var len = HKDF.generateBytes(result, 0, result.length);
//
// assertNotNull(Hex.encodeHexString(result));
//}
private String initConfig(String configDir) {
if (StringUtils.isNotBlank(configDir)) {
return configDir;
} else {
if (Files.isDirectory(Path.of(ETC_DIR)) && Files.exists(Path.of(ETC_DIR, "config"))) {
return ETC_DIR;
} else if (
Files.isDirectory(Path.of(USER_HOME, ".config", "reticulum"))
&& Files.exists(Path.of(USER_HOME, ".config", "reticulum", "config"))
) {
return Path.of(USER_HOME, ".config", "reticulum").toString();
} else {
return Path.of(USER_HOME, ".reticulum").toString();
}
}
}
}

View File

@ -34,7 +34,7 @@ fi
# Comment out for bigger systems, e.g. non-routers # Comment out for bigger systems, e.g. non-routers
# or when API documentation is enabled # or when API documentation is enabled
# Uncomment (remove '#' sign) line below if your system has less than 12GB of RAM for optimal RAM defaults # Uncomment (remove '#' sign) line below if your system has less than 12GB of RAM for optimal RAM defaults
JVM_MEMORY_ARGS="-Xss256m -XX:+UseSerialGC" #JVM_MEMORY_ARGS="-Xss256m -XX:+UseSerialGC"
# Although java.net.preferIPv4Stack is supposed to be false # Although java.net.preferIPv4Stack is supposed to be false
# by default in Java 11, on some platforms (e.g. FreeBSD 12), # by default in Java 11, on some platforms (e.g. FreeBSD 12),
@ -43,6 +43,9 @@ JVM_MEMORY_ARGS="-Xss256m -XX:+UseSerialGC"
nohup nice -n 20 java \ nohup nice -n 20 java \
-Djava.net.preferIPv4Stack=false \ -Djava.net.preferIPv4Stack=false \
${JVM_MEMORY_ARGS} \ ${JVM_MEMORY_ARGS} \
--add-opens=java.base/java.lang=ALL-UNNAMED \
--add-opens=java.base/java.net=ALL-UNNAMED \
--illegal-access=warn \
-jar qortal.jar \ -jar qortal.jar \
1>run.log 2>&1 & 1>run.log 2>&1 &