package com.lbry.globe.util; import java.io.IOException; import java.net.DatagramSocket; import java.net.InetSocketAddress; import java.net.SocketException; import java.nio.ByteBuffer; import java.util.*; import java.util.concurrent.*; public class DHT{ public static byte[] NODE_ID = new byte[48]; private static final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); private static final TimeoutFutureManager futureManager = new TimeoutFutureManager<>(executor); private static final Map peers = new ConcurrentHashMap<>(); private static final DatagramSocket socket; static{ try{ socket = new DatagramSocket(); }catch(SocketException e){ throw new RuntimeException(e); } } public static TimeoutFutureManager getFutureManager(){ return DHT.futureManager; } public static DatagramSocket getSocket(){ return DHT.socket; } public static Map getPeers(){ return DHT.peers; } public static CompletableFuture ping(DatagramSocket socket,InetSocketAddress destination) throws IOException { byte[] rpcID = new byte[20]; new Random().nextBytes(rpcID); DHT.Message pingMessage = new DHT.Message<>(DHT.Message.TYPE_REQUEST,rpcID,DHT.NODE_ID,"ping",Collections.singletonList(Collections.singletonMap("protocolVersion",1))); return DHT.sendWithFuture(socket,destination,pingMessage); } public static CompletableFuture findNode(DatagramSocket socket,InetSocketAddress destination,byte[] key) throws IOException{ byte[] rpcID = new byte[20]; new Random().nextBytes(rpcID); DHT.Message findNodeMessage = new DHT.Message<>(DHT.Message.TYPE_REQUEST,rpcID,DHT.NODE_ID,"findNode",Arrays.asList(key,Collections.singletonMap("protocolVersion",1))); return DHT.sendWithFuture(socket,destination,findNodeMessage); } public static CompletableFuture findValue(DatagramSocket socket,InetSocketAddress destination,byte[] key) throws IOException{ byte[] rpcID = new byte[20]; new Random().nextBytes(rpcID); DHT.Message findNodeMessage = new DHT.Message<>(DHT.Message.TYPE_REQUEST,rpcID,DHT.NODE_ID,"findValue",Arrays.asList(key,Collections.singletonMap("protocolVersion",1))); return DHT.sendWithFuture(socket,destination,findNodeMessage); } protected static CompletableFuture sendWithFuture(DatagramSocket socket,InetSocketAddress destination, DHT.Message message) throws IOException{ UDP.send(socket,new UDP.Packet(destination,message.toBencode())); RPCID key = new RPCID(message); return DHT.futureManager.createFuture(key,1,TimeUnit.SECONDS); } public static class Message

{ public static final int TYPE_REQUEST = 0; public static final int TYPE_RESPONSE = 1; private int type; private byte[] rpcID; private byte[] nodeID; private P payload; private List arguments; private Message(){} public Message(int type,byte[] rpcID,byte[] nodeID){ this(type,rpcID,nodeID,null); } public Message(int type,byte[] rpcID,byte[] nodeID,P payload){ this(type,rpcID,nodeID,payload,null); } public Message(int type,byte[] rpcID,byte[] nodeID,P payload,List arguments){ this.type = type; this.rpcID = rpcID; this.nodeID = nodeID; this.payload = payload; this.arguments = arguments; } public int getType(){ return this.type; } public byte[] getRPCID(){ return this.rpcID; } public byte[] getNodeID(){ return this.nodeID; } public P getPayload(){ return this.payload; } public List getArguments(){ return this.arguments; } public Map toMap(){ Map dictionary = new HashMap<>(); dictionary.put("0",this.type); dictionary.put("1",this.rpcID); dictionary.put("2",this.nodeID); if(this.payload!=null){ dictionary.put("3",this.payload); } if(this.arguments!=null){ dictionary.put("4",this.arguments); } return dictionary; } public byte[] toBencode(){ return BencodeConverter.encode(this.toMap()); } private DHT.Message

setFromBencode(byte[] data){ Map dictionary = BencodeConverter.decode(data); this.type = ((Long) dictionary.get("0")).intValue(); this.rpcID = ((ByteBuffer) dictionary.get("1")).array(); this.nodeID = ((ByteBuffer) dictionary.get("2")).array(); this.payload = null; if(dictionary.containsKey("3")){ Object payload = dictionary.get("3"); this.payload = BencodeConverter.walkAndConvertByteBufferToByteArrayOrString(payload); } this.arguments = null; if(dictionary.containsKey("4")){ this.arguments = (List) dictionary.get("4"); } return this; } @Override public String toString() { return "Message{" + "type=" + type + ", rpcID=" + Hex.encode(rpcID) + ", nodeID=" + Hex.encode(nodeID) + ", payload=" + payload + ", arguments=" + arguments + '}'; } public static DHT.Message fromBencode(byte[] data){ return new Message<>().setFromBencode(data); } } public static class RPCID{ private final byte[] id; public RPCID(byte[] id){ this.id = id; } public RPCID(Message message){ this(message.rpcID); } @Override public boolean equals(Object obj){ if(obj instanceof RPCID){ RPCID other = (RPCID) obj; return Arrays.equals(this.id,other.id); } return super.equals(obj); } @Override public int hashCode(){ return -1; } @Override public String toString() { return "RPCID{" + "id=" + Hex.encode(id) + '}'; } } }