package peergos.server.corenode; import peergos.server.mutable.*; import peergos.shared.corenode.CoreNode; import peergos.shared.corenode.CoreNodeUtils; import peergos.shared.corenode.UserPublicKeyLink; import java.net.*; import java.io.*; import java.util.*; import java.util.concurrent.*; import java.util.zip.*; import com.sun.net.httpserver.*; import peergos.shared.crypto.asymmetric.*; import peergos.shared.io.ipfs.api.*; import peergos.shared.mutable.*; import peergos.shared.util.Args; import peergos.shared.util.Serialize; public class HttpCoreNodeServer { private static final boolean LOGGING = true; private static final int CONNECTION_BACKLOG = 100; private static final int HANDLER_THREAD_COUNT = 100; public static final String CORE_URL = "core/"; public static final int PORT = 9999; public static class CoreNodeHandler implements HttpHandler { private final CoreNode coreNode; public CoreNodeHandler(CoreNode coreNode) { this.coreNode = coreNode; } public void handle(HttpExchange exchange) throws IOException { long t1 = System.currentTimeMillis(); DataInputStream din = new DataInputStream(exchange.getRequestBody()); ByteArrayOutputStream bout = new ByteArrayOutputStream(); DataOutputStream dout = new DataOutputStream(bout); String path = exchange.getRequestURI().getPath(); if (path.startsWith("/")) path = path.substring(1); String[] subComponents = path.substring(CORE_URL.length()).split("/"); String method = subComponents[0]; // System.out.println("core method "+ method +" from path "+ path); try { switch (method) { case "getChain": getChain(din, dout); break; case "updateChain": updateChain(din, dout); break; case "getPublicKey": getPublicKey(din, dout); break; case "getUsername": getUsername(din, dout); break; case "getUsernamesGzip": exchange.getResponseHeaders().set("Content-Encoding", "gzip"); exchange.getResponseHeaders().set("Content-Type", "application/json"); getAllUsernamesGzip(subComponents.length > 1 ? subComponents[1] : "", din, dout); break; case "followRequest": followRequest(din, dout); break; case "getFollowRequests": getFollowRequests(din, dout); break; case "removeFollowRequest": removeFollowRequest(din, dout); break; default: throw new IOException("Unknown method "+ method); } dout.flush(); dout.close(); byte[] b = bout.toByteArray(); exchange.sendResponseHeaders(200, b.length); exchange.getResponseBody().write(b); } catch (Exception e) { e.printStackTrace(); exchange.sendResponseHeaders(400, 0); } finally { exchange.close(); long t2 = System.currentTimeMillis(); if (LOGGING) System.out.println("Corenode server handled " + method + " request in: " + (t2 - t1) + " mS"); } } void getChain(DataInputStream din, DataOutputStream dout) throws Exception { String username = CoreNodeUtils.deserializeString(din); List<UserPublicKeyLink> chain = coreNode.getChain(username).get(); dout.writeInt(chain.size()); for (UserPublicKeyLink link : chain) { Serialize.serialize(link.owner.serialize(), dout); Serialize.serialize(link.toByteArray(), dout); } } void updateChain(DataInputStream din, DataOutputStream dout) throws Exception { String username = CoreNodeUtils.deserializeString(din); int count = din.readInt(); List<UserPublicKeyLink> res = new ArrayList<>(); for (int i=0; i < count; i++) { PublicSigningKey owner = PublicSigningKey.fromByteArray(Serialize.deserializeByteArray(din, PublicSigningKey.MAX_SIZE)); res.add(UserPublicKeyLink.fromByteArray(owner, Serialize.deserializeByteArray(din, UserPublicKeyLink.MAX_SIZE))); } boolean isAdded = coreNode.updateChain(username, res).get(); dout.writeBoolean(isAdded); } void getPublicKey(DataInputStream din, DataOutputStream dout) throws Exception { String username = CoreNodeUtils.deserializeString(din); Optional<PublicSigningKey> k = coreNode.getPublicKey(username).get(); dout.writeBoolean(k.isPresent()); if (!k.isPresent()) return; byte[] b = k.get().serialize(); dout.writeInt(b.length); dout.write(b); } void getUsername(DataInputStream din, DataOutputStream dout) throws Exception { byte[] publicKey = CoreNodeUtils.deserializeByteArray(din); String k = coreNode.getUsername(PublicSigningKey.fromByteArray(publicKey)).get(); if (k == null) k=""; Serialize.serialize(k, dout); } void getAllUsernamesGzip(String prefix, DataInputStream din, DataOutputStream dout) throws Exception { List<String> res = coreNode.getUsernames(prefix).get(); ByteArrayOutputStream bout = new ByteArrayOutputStream(); GZIPOutputStream gout = new GZIPOutputStream(bout); gout.write(JSONParser.toString(res).getBytes()); gout.flush(); gout.close(); dout.write(bout.toByteArray()); } void followRequest(DataInputStream din, DataOutputStream dout) throws Exception { byte[] encodedKey = Serialize.deserializeByteArray(din, PublicSigningKey.MAX_SIZE); PublicSigningKey target = PublicSigningKey.fromByteArray(encodedKey); byte[] encodedSharingPublicKey = CoreNodeUtils.deserializeByteArray(din); boolean followRequested = coreNode.followRequest(target, encodedSharingPublicKey).get(); dout.writeBoolean(followRequested); } void getFollowRequests(DataInputStream din, DataOutputStream dout) throws Exception { byte[] encodedKey = Serialize.deserializeByteArray(din, PublicSigningKey.MAX_SIZE); PublicSigningKey ownerPublicKey = PublicSigningKey.fromByteArray(encodedKey); byte[] res = coreNode.getFollowRequests(ownerPublicKey).get(); Serialize.serialize(res, dout); } void removeFollowRequest(DataInputStream din, DataOutputStream dout) throws Exception { byte[] encodedKey = Serialize.deserializeByteArray(din, PublicSigningKey.MAX_SIZE); PublicSigningKey owner = PublicSigningKey.fromByteArray(encodedKey); byte[] signedFollowRequest = CoreNodeUtils.deserializeByteArray(din); boolean isRemoved = coreNode.removeFollowRequest(owner, signedFollowRequest).get(); dout.writeBoolean(isRemoved); } public void close() throws IOException{ coreNode.close(); } } private final HttpServer server; private final InetSocketAddress address; private final CoreNodeHandler ch; public HttpCoreNodeServer(CoreNode coreNode, MutablePointers mutable, InetSocketAddress address) throws IOException { this.address = address; if (address.getHostName().contains("local")) server = HttpServer.create(address, CONNECTION_BACKLOG); else server = HttpServer.create(new InetSocketAddress(InetAddress.getLocalHost(), address.getPort()), CONNECTION_BACKLOG); ch = new CoreNodeHandler(coreNode); server.createContext("/" + CORE_URL, ch); server.createContext("/" + HttpMutablePointerServer.MUTABLE_POINTERS_URL, new HttpMutablePointerServer.MutationHandler(mutable)); server.setExecutor(Executors.newFixedThreadPool(HANDLER_THREAD_COUNT)); } public void start() throws IOException { server.start(); } public InetSocketAddress getAddress(){return address;} public void close() throws IOException { server.stop(5); ch.close(); } public static void createAndStart(String keyfile, char[] passphrase, int port, CoreNode coreNode, MutablePointers mutable, Args args) { // eventually will need our own keypair to sign traffic to other core nodes try { String hostname = args.getArg("domain", "localhost"); System.out.println("Starting core node server listening on: " + hostname+":"+port +" proxying to "+coreNode); InetSocketAddress address = new InetSocketAddress(hostname, port); HttpCoreNodeServer server = new HttpCoreNodeServer(coreNode, mutable, address); server.start(); } catch (Exception e) { e.printStackTrace(); System.out.println("Couldn't start Corenode server!"); } } }