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() {