diff --git a/app/src/main/java/io/xeres/app/xrs/service/RsService.java b/app/src/main/java/io/xeres/app/xrs/service/RsService.java
index 756d7f5c5..a376227be 100644
--- a/app/src/main/java/io/xeres/app/xrs/service/RsService.java
+++ b/app/src/main/java/io/xeres/app/xrs/service/RsService.java
@@ -117,6 +117,16 @@ public void init(NetworkReadyEvent event)
{
initialized = true;
initialize();
+ addSlavesIfNeeded();
+ }
+ }
+
+ private void addSlavesIfNeeded()
+ {
+ if (RsServiceMaster.class.isAssignableFrom(getClass()))
+ {
+ //noinspection rawtypes,unchecked
+ rsServiceRegistry.getSlaves(this).forEach(rsServiceSlave -> ((RsServiceMaster) this).addRsSlave(rsServiceSlave));
}
}
@@ -144,4 +154,10 @@ public int compareTo(RsService o)
{
return Integer.compare(getInitPriority().ordinal(), o.getInitPriority().ordinal());
}
+
+ @Override
+ public String toString()
+ {
+ return getServiceType().getName();
+ }
}
diff --git a/app/src/main/java/io/xeres/app/xrs/service/RsServiceMaster.java b/app/src/main/java/io/xeres/app/xrs/service/RsServiceMaster.java
new file mode 100644
index 000000000..38c7716e1
--- /dev/null
+++ b/app/src/main/java/io/xeres/app/xrs/service/RsServiceMaster.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2024 by David Gerber - https://zapek.com
+ *
+ * This file is part of Xeres.
+ *
+ * Xeres is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Xeres is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Xeres. If not, see .
+ */
+
+package io.xeres.app.xrs.service;
+
+/**
+ * This interface allows to implement dependencies between services, that is, one master service
+ * has a list of clients that it can handle. Each master service or client needs to implement this interface
+ * which can of course be extended.
+ *
+ * @see RsServiceSlave
+ */
+public interface RsServiceMaster
+{
+ /**
+ * Adds a slave service to a master service. The master service is responsible to handle them.
+ *
+ * @param slave the slave service to add to the master
+ */
+ void addRsSlave(T slave);
+}
diff --git a/app/src/main/java/io/xeres/app/xrs/service/RsServiceRegistry.java b/app/src/main/java/io/xeres/app/xrs/service/RsServiceRegistry.java
index 4ac2647e7..3e959612a 100644
--- a/app/src/main/java/io/xeres/app/xrs/service/RsServiceRegistry.java
+++ b/app/src/main/java/io/xeres/app/xrs/service/RsServiceRegistry.java
@@ -34,6 +34,8 @@
import java.lang.reflect.InvocationTargetException;
import java.util.*;
+import static org.apache.commons.collections4.ListUtils.emptyIfNull;
+
@Component
public class RsServiceRegistry
{
@@ -43,6 +45,7 @@ public class RsServiceRegistry
private final Set enabledServiceClasses = new HashSet<>();
private final Map services = new HashMap<>();
+ private final Map> masterServices = new HashMap<>();
private final Map>> itemClassesWaiting = new HashMap<>();
private final Map> itemClassesGxsWaiting = new HashMap<>();
@@ -102,7 +105,7 @@ else if (DynamicServiceType.class.isAssignableFrom(itemClass))
}
else
{
- itemClassesWaiting.computeIfAbsent(item.getServiceType(), k -> new HashMap<>()).put(item.getSubType(), itemClass);
+ itemClassesWaiting.computeIfAbsent(item.getServiceType(), v -> new HashMap<>()).put(item.getSubType(), itemClass);
}
}
catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException | ClassNotFoundException e)
@@ -140,6 +143,13 @@ public boolean registerService(RsService rsService)
services.put(serviceType, rsService);
+ if (RsServiceSlave.class.isAssignableFrom(rsService.getClass()))
+ {
+ var master = ((RsServiceSlave) rsService).isRsSlaveOf();
+
+ masterServices.computeIfAbsent(master.getServiceType().getType(), v -> new ArrayList<>()).add((RsServiceSlave) rsService);
+ }
+
if (GxsRsService.class.isAssignableFrom(rsService.getClass()))
{
itemClassesGxsWaiting.forEach((subType, itemClass) -> itemClasses.put(serviceType << 16 | subType, itemClass));
@@ -155,6 +165,15 @@ public boolean registerService(RsService rsService)
return true;
}
+ List getSlaves(RsService rsService)
+ {
+ if (!RsServiceMaster.class.isAssignableFrom(rsService.getClass()))
+ {
+ throw new IllegalArgumentException("Master service " + rsService + " doesn't implement RsServiceMaster interface");
+ }
+ return emptyIfNull(masterServices.get(rsService.getServiceType().getType()));
+ }
+
public Item buildIncomingItem(int version, int service, int subtype)
{
if (version == 2)
diff --git a/app/src/main/java/io/xeres/app/xrs/service/RsServiceSlave.java b/app/src/main/java/io/xeres/app/xrs/service/RsServiceSlave.java
new file mode 100644
index 000000000..3cfc331e6
--- /dev/null
+++ b/app/src/main/java/io/xeres/app/xrs/service/RsServiceSlave.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2024 by David Gerber - https://zapek.com
+ *
+ * This file is part of Xeres.
+ *
+ * Xeres is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Xeres is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Xeres. If not, see .
+ */
+
+package io.xeres.app.xrs.service;
+
+/**
+ * This interface allows to mark a service as a slave of some master.
+ *
+ * @see RsServiceMaster
+ */
+public interface RsServiceSlave
+{
+ /**
+ * Registers this service as a slave of another service.
+ *
+ * @return the master service this service is slave of
+ */
+ RsService isRsSlaveOf();
+}
diff --git a/app/src/main/java/io/xeres/app/xrs/service/filetransfer/FileTransferRsService.java b/app/src/main/java/io/xeres/app/xrs/service/filetransfer/FileTransferRsService.java
new file mode 100644
index 000000000..c1787f194
--- /dev/null
+++ b/app/src/main/java/io/xeres/app/xrs/service/filetransfer/FileTransferRsService.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) 2024 by David Gerber - https://zapek.com
+ *
+ * This file is part of Xeres.
+ *
+ * Xeres is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Xeres is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Xeres. If not, see .
+ */
+
+package io.xeres.app.xrs.service.filetransfer;
+
+import io.xeres.app.net.peer.PeerConnection;
+import io.xeres.app.service.file.FileService;
+import io.xeres.app.xrs.item.Item;
+import io.xeres.app.xrs.service.RsService;
+import io.xeres.app.xrs.service.RsServiceRegistry;
+import io.xeres.app.xrs.service.RsServiceType;
+import io.xeres.app.xrs.service.turtle.TurtleRsClient;
+import io.xeres.common.id.Sha1Sum;
+import org.springframework.stereotype.Component;
+
+import static io.xeres.app.xrs.service.RsServiceType.FILE_TRANSFER;
+
+@Component
+public class FileTransferRsService extends RsService implements TurtleRsClient
+{
+ private final FileService fileService;
+
+ public FileTransferRsService(RsServiceRegistry rsServiceRegistry, FileService fileService)
+ {
+ super(rsServiceRegistry);
+ this.fileService = fileService;
+ }
+
+ @Override
+ public RsServiceType getServiceType()
+ {
+ return FILE_TRANSFER;
+ }
+
+ @Override
+ public RsService isRsSlaveOf()
+ {
+ return this;
+ }
+
+ @Override
+ public void handleItem(PeerConnection sender, Item item)
+ {
+ // XXX
+ }
+
+ @Override
+ public boolean handleTunnelRequest(PeerConnection sender, Sha1Sum hash)
+ {
+ var file = fileService.findFile(hash);
+ if (file.isPresent())
+ {
+ // XXX: don't forget to handle encrypted hashes, files currently being swarmed and tons of other things
+ // XXX: sender might not necessarily be needed (it's for the permissions)
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void receiveTurtleData()
+ {
+
+ }
+
+ @Override
+ public boolean receiveSearchRequest()
+ {
+ return false;
+ }
+
+ @Override
+ public void receiveSearchResult()
+ {
+
+ }
+
+ @Override
+ public void addVirtualPeer()
+ {
+
+ }
+
+ @Override
+ public void removeVirtualPeer()
+ {
+
+ }
+}
diff --git a/app/src/main/java/io/xeres/app/xrs/service/turtle/TunnelProbability.java b/app/src/main/java/io/xeres/app/xrs/service/turtle/TunnelProbability.java
index 25f1325fe..7b2b6c5ec 100644
--- a/app/src/main/java/io/xeres/app/xrs/service/turtle/TunnelProbability.java
+++ b/app/src/main/java/io/xeres/app/xrs/service/turtle/TunnelProbability.java
@@ -86,4 +86,9 @@ private short incrementDepth(int id, short depth)
}
return depth;
}
+
+ public int getBias()
+ {
+ return bias;
+ }
}
diff --git a/app/src/main/java/io/xeres/app/xrs/service/turtle/TurtleRsClient.java b/app/src/main/java/io/xeres/app/xrs/service/turtle/TurtleRsClient.java
new file mode 100644
index 000000000..16cd53928
--- /dev/null
+++ b/app/src/main/java/io/xeres/app/xrs/service/turtle/TurtleRsClient.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2024 by David Gerber - https://zapek.com
+ *
+ * This file is part of Xeres.
+ *
+ * Xeres is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Xeres is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Xeres. If not, see .
+ */
+
+package io.xeres.app.xrs.service.turtle;
+
+import io.xeres.app.net.peer.PeerConnection;
+import io.xeres.app.xrs.service.RsServiceSlave;
+import io.xeres.common.id.Sha1Sum;
+
+public interface TurtleRsClient extends RsServiceSlave
+{
+ /**
+ * @param sender
+ * @param hash
+ * @return true if found
+ */
+ boolean handleTunnelRequest(PeerConnection sender, Sha1Sum hash);
+
+ void receiveTurtleData(); // XXX: args
+
+ boolean receiveSearchRequest(); // XXX: args
+
+ void receiveSearchResult(); // XXX: args
+
+ void addVirtualPeer(); // XXX: args
+
+ void removeVirtualPeer(); // XXX: args
+}
diff --git a/app/src/main/java/io/xeres/app/xrs/service/turtle/TurtleRsService.java b/app/src/main/java/io/xeres/app/xrs/service/turtle/TurtleRsService.java
index d0d842060..3822e7e39 100644
--- a/app/src/main/java/io/xeres/app/xrs/service/turtle/TurtleRsService.java
+++ b/app/src/main/java/io/xeres/app/xrs/service/turtle/TurtleRsService.java
@@ -21,11 +21,13 @@
import io.xeres.app.net.peer.PeerConnection;
import io.xeres.app.net.peer.PeerConnectionManager;
-import io.xeres.app.service.file.FileService;
import io.xeres.app.xrs.item.Item;
import io.xeres.app.xrs.service.RsService;
+import io.xeres.app.xrs.service.RsServiceMaster;
import io.xeres.app.xrs.service.RsServiceRegistry;
import io.xeres.app.xrs.service.RsServiceType;
+import io.xeres.app.xrs.service.identity.IdentityRsService;
+import io.xeres.app.xrs.service.identity.item.IdentityGroupItem;
import io.xeres.app.xrs.service.turtle.item.*;
import io.xeres.common.id.Sha1Sum;
import org.slf4j.Logger;
@@ -34,11 +36,13 @@
import java.time.Duration;
import java.time.Instant;
+import java.util.ArrayList;
+import java.util.List;
import static io.xeres.app.xrs.service.RsServiceType.TURTLE;
@Component
-public class TurtleRsService extends RsService
+public class TurtleRsService extends RsService implements RsServiceMaster
{
private static final Logger log = LoggerFactory.getLogger(TurtleRsService.class);
@@ -58,13 +62,15 @@ public class TurtleRsService extends RsService
private final PeerConnectionManager peerConnectionManager;
- private final FileService fileService;
+ private final List turtleClients = new ArrayList<>();
- protected TurtleRsService(RsServiceRegistry rsServiceRegistry, PeerConnectionManager peerConnectionManager, FileService fileService)
+ private final IdentityGroupItem ownIdentity;
+
+ protected TurtleRsService(RsServiceRegistry rsServiceRegistry, PeerConnectionManager peerConnectionManager, IdentityRsService identityRsService)
{
super(rsServiceRegistry);
this.peerConnectionManager = peerConnectionManager;
- this.fileService = fileService;
+ ownIdentity = identityRsService.getOwnIdentity();
}
@Override
@@ -73,6 +79,12 @@ public RsServiceType getServiceType()
return TURTLE;
}
+ @Override
+ public void addRsSlave(TurtleRsClient client)
+ {
+ turtleClients.add(client);
+ }
+
@Override
public void handleItem(PeerConnection sender, Item item)
{
@@ -117,11 +129,14 @@ private void handleTunnelRequest(PeerConnection sender, TurtleTunnelRequestItem
return;
}
- // XXX: if the request is not from us, perform a local search and send result back if found (otherwise forward)
- var file = fileService.findFile(item.getFileHash());
- if (file.isPresent())
+ var client = turtleClients.stream()
+ .filter(turtleRsClient -> turtleRsClient.handleTunnelRequest(sender, item.getFileHash()))
+ .findFirst();
+
+ if (client.isPresent())
{
- // XXX: return the file back!
+ var resultItem = new TurtleTunnelResultItem(item.getRequestId(), generateTunnelId(item, false));
+
return;
}
@@ -137,6 +152,29 @@ private void handleTunnelRequest(PeerConnection sender, TurtleTunnelRequestItem
}
}
+ private int generateTunnelId(TurtleTunnelRequestItem item, boolean symetrical)
+ {
+ // XXX: test this, compare with RS
+ var buf = item.getFileHash().toString() + ownIdentity.getGxsId().toString();
+ int result = tunnelProbability.getBias();
+ int decal = 0;
+
+ for (int i = 0; i < buf.length(); i++)
+ {
+ result += 7 * buf.charAt(i) + decal;
+
+ if (symetrical)
+ {
+ decal = decal * 44497 + 15641 + (result % 86243);
+ }
+ else
+ {
+ decal = decal * 86243 + 15649 + (result % 44497);
+ }
+ }
+ return item.getPartialTunnelId() ^ result;
+ }
+
private void handleSearchRequest(PeerConnection sender, TurtleSearchRequestItem item)
{
log.debug("Received search request from peer {}: {}", sender, item);
diff --git a/app/src/main/java/io/xeres/app/xrs/service/turtle/item/TurtleTunnelResultItem.java b/app/src/main/java/io/xeres/app/xrs/service/turtle/item/TurtleTunnelResultItem.java
index b4719f517..d0941db3c 100644
--- a/app/src/main/java/io/xeres/app/xrs/service/turtle/item/TurtleTunnelResultItem.java
+++ b/app/src/main/java/io/xeres/app/xrs/service/turtle/item/TurtleTunnelResultItem.java
@@ -31,6 +31,17 @@ public class TurtleTunnelResultItem extends Item
@RsSerialized
private int requestId;
+ public TurtleTunnelResultItem()
+ {
+ // Needed
+ }
+
+ public TurtleTunnelResultItem(int tunnelId, int requestId)
+ {
+ this.tunnelId = tunnelId;
+ this.requestId = requestId;
+ }
+
@Override
public int getServiceType()
{