diff --git a/.gitignore b/.gitignore index 08026dbb..fd094857 100644 --- a/.gitignore +++ b/.gitignore @@ -37,3 +37,5 @@ html /InsideOutsideTest.svg.png /Alexes_Bad.svg.png /*.jks +/file.txt +/file2.txt diff --git a/build.gradle b/build.gradle index 6fbf6e77..3955cb70 100644 --- a/build.gradle +++ b/build.gradle @@ -118,6 +118,10 @@ dependencies { implementation 'com.aparapi:aparapi:3.0.2' + //SSL for server +implementation 'org.bouncycastle:bcprov-jdk18on:1.80' +implementation 'org.bouncycastle:bcpkix-jdk18on:1.80' + } Date buildTimeAndDate = new Date() diff --git a/src/main/java/eu/mihosoft/vrl/v3d/CSG.java b/src/main/java/eu/mihosoft/vrl/v3d/CSG.java index d88081bc..95feab5e 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/CSG.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/CSG.java @@ -124,6 +124,7 @@ @SuppressWarnings("restriction") public class CSG implements IuserAPI, Serializable { + private static int MinPolygonsForOffloading = 200; private static final long serialVersionUID = 4071874097772427063L; private static IDebug3dProvider providerOf3d = null; private static int numFacesInOffset = 15; @@ -147,11 +148,11 @@ public class CSG implements IuserAPI, Serializable { private static String defaultcolor = "#007956"; /** The color. */ - //private Color color = getDefaultColor(); - private double r= getDefaultColor().getRed(); - private double g=getDefaultColor().getGreen(); - private double b= getDefaultColor().getBlue(); - private double o=getDefaultColor().getOpacity(); + // private Color color = getDefaultColor(); + private double r = getDefaultColor().getRed(); + private double g = getDefaultColor().getGreen(); + private double b = getDefaultColor().getBlue(); + private double o = getDefaultColor().getOpacity(); /** The manipulator. */ private Affine manipulator; private Bounds bounds; @@ -228,10 +229,10 @@ public Color getColor() { * @param color the new color */ public CSG setColor(Color color) { - r=color.getRed(); - g=color.getGreen(); - b=color.getBlue(); - o=color.getOpacity(); + r = color.getRed(); + g = color.getGreen(); + b = color.getBlue(); + o = color.getOpacity(); for (Polygon p : polygons) p.setColor(color); return this; @@ -800,15 +801,16 @@ public CSG optimization(OptType type) { * @return union of this csg and the specified csg */ public CSG union(CSG csg) { - if(CSGClient.isRunning()) { - ArrayList go=new ArrayList(Arrays.asList(this)); - try { - return CSGClient.getClient().union(go).get(0); - } catch (Exception e) { - // TODO Auto-generated catch block - e.printStackTrace(); + if (this.polygons.size() > getMinPolygonsForOffloading() || csg.polygons.size() > getMinPolygonsForOffloading()) + if (CSGClient.isRunning()) { + ArrayList go = new ArrayList(Arrays.asList(this)); + try { + return CSGClient.getClient().union(go).get(0); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } } - } // triangulate(); // csg.triangulate(); switch (getOptType()) { @@ -956,19 +958,28 @@ public static CSG unionAll(CSG... csgs) { } public static CSG unionAll(List csgs) { - if(CSGClient.isRunning()) { - List back; - try { - back = CSGClient.getClient().union(csgs); - return back.get(0); - } catch (Exception e) { - // TODO Auto-generated catch block - e.printStackTrace(); + + if (CSGClient.isRunning()) { + boolean offload = false; + for (int i = 0; i < csgs.size(); i++) + if (csgs.get(i).polygons.size() > getMinPolygonsForOffloading()) { + offload = true; + break; + } + if (offload) { + List back; + try { + back = CSGClient.getClient().union(csgs); + return back.get(0); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } } } CSG first = csgs.get(0); return first.union(csgs.stream().skip(1).collect(Collectors.toList())); - + } public static CSG hullAll(CSG... csgs) { @@ -1157,7 +1168,15 @@ private CSG _unionNoOpt(CSG csg) { * @return difference of this csg and the specified csgs */ public CSG difference(List csgs) { - + if (CSGClient.isRunning()) { + ArrayList go = new ArrayList(csgs); + try { + return CSGClient.getClient().difference(go).get(0); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } if (csgs.isEmpty()) { return this.clone(); } @@ -1233,15 +1252,16 @@ public CSG difference(CSG... csgs) { * @return difference of this csg and the specified csg */ public CSG difference(CSG csg) { - if(CSGClient.isRunning()) { - ArrayList go=new ArrayList(Arrays.asList(this,csg)); - try { - return CSGClient.getClient().difference(go).get(0); - } catch (Exception e) { - // TODO Auto-generated catch block - e.printStackTrace(); + if (this.polygons.size() > getMinPolygonsForOffloading() || csg.polygons.size() > getMinPolygonsForOffloading()) + if (CSGClient.isRunning()) { + ArrayList go = new ArrayList(Arrays.asList(this, csg)); + try { + return CSGClient.getClient().difference(go).get(0); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } } - } // triangulate(); // csg.triangulate(); try { @@ -1392,15 +1412,16 @@ private CSG _differenceNoOpt(CSG csg) { * @return intersection of this csg and the specified csg */ public CSG intersect(CSG csg) { - if(CSGClient.isRunning()) { - ArrayList go=new ArrayList(Arrays.asList(this,csg)); - try { - return CSGClient.getClient().intersect(go).get(0); - } catch (Exception e) { - // TODO Auto-generated catch block - e.printStackTrace(); + if (this.polygons.size() > getMinPolygonsForOffloading() || csg.polygons.size() > getMinPolygonsForOffloading()) + if (CSGClient.isRunning()) { + ArrayList go = new ArrayList(Arrays.asList(this, csg)); + try { + return CSGClient.getClient().intersect(go).get(0); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } } - } // triangulate(); // csg.triangulate(); Node a = new Node(this.clone().getPolygons()); @@ -1447,7 +1468,15 @@ public CSG intersect(CSG csg) { * @return intersection of this csg and the specified csgs */ public CSG intersect(List csgs) { - + if (CSGClient.isRunning()) { + ArrayList go = new ArrayList(csgs); + try { + return CSGClient.getClient().intersect(go).get(0); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } if (csgs.isEmpty()) { return this.clone(); } @@ -1544,27 +1573,28 @@ public CSG triangulate(boolean fix) { triangulated = false; if (triangulated) return this; - if(CSGClient.isRunning()) { - ArrayList go=new ArrayList(Arrays.asList(this)); - try { - return CSGClient.getClient().triangulate(go).get(0); - } catch (Exception e) { - // TODO Auto-generated catch block - e.printStackTrace(); + if (this.polygons.size() > getMinPolygonsForOffloading()) + if (CSGClient.isRunning()) { + ArrayList go = new ArrayList(Arrays.asList(this)); + try { + return CSGClient.getClient().triangulate(go).get(0); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } } - } if (providerOf3d == null && Debug3dProvider.provider != null) providerOf3d = Debug3dProvider.provider; IDebug3dProvider start = Debug3dProvider.provider; Debug3dProvider.setProvider(null); - //performTriangulation(); + // performTriangulation(); if (preventNonManifoldTriangles) { - //for (int i = 0; i < 1; i++) - if (isUseGPU()) { - runGPUMakeManifold(); - } else { - runCPUMakeManifold(); - } + // for (int i = 0; i < 1; i++) + if (isUseGPU()) { + runGPUMakeManifold(); + } else { + runCPUMakeManifold(); + } } performTriangulation(); // now all polygons are definantly triangles @@ -1576,14 +1606,14 @@ public CSG triangulate(boolean fix) { private void performTriangulation() { ArrayList toAdd = new ArrayList(); int failedPolys = 0; - for(int i=0;i0) - System.out.println("Pruned "+failedPolys+" polygons from CSG "+getName()); + if (failedPolys > 0) + System.out.println("Pruned " + failedPolys + " polygons from CSG " + getName()); if (toAdd.size() > 0) { setPolygons(toAdd); } @@ -1591,7 +1621,8 @@ private void performTriangulation() { private void runCPUMakeManifold() { long start = System.currentTimeMillis(); - //System.err.println("Cleaning up the mesh by adding coincident points to the polygons they touch"); + // System.err.println("Cleaning up the mesh by adding coincident points to the + // polygons they touch"); int totalAdded = 0; double tOL = 1.0e-11; @@ -1653,7 +1684,7 @@ private void runCPUMakeManifold() { } totalAdded += 32; threads.clear(); - if (threadIndex/32 % 50 == 0 || j == polygons.size() - 1) { + if (threadIndex / 32 % 50 == 0 || j == polygons.size() - 1) { progressMoniter.progressUpdate(j, polygons.size(), "STL Processing Polygons for Manifold Vertex, #" + totalAdded + " added so far", this); } @@ -1669,7 +1700,8 @@ private void runCPUMakeManifold() { // Auto-generated catch block e.printStackTrace(); } - //progressMoniter.progressUpdate(polygons.size(),polygons.size(),"Manifold fix took " + (System.currentTimeMillis() - start),this); + // progressMoniter.progressUpdate(polygons.size(),polygons.size(),"Manifold fix + // took " + (System.currentTimeMillis() - start),this); } private void runGPUMakeManifold() { @@ -1742,19 +1774,18 @@ private CSG updatePolygons(ArrayList toAdd, Polygon p) { if (p == null) return this; - if (p.getVertices().size() == 3) { toAdd.add(p); } else { try { - if(!p.areAllPointsCollinear()) { + if (!p.areAllPointsCollinear()) { List triangles = PolygonUtil.concaveToConvex(p); for (Polygon poly : triangles) { toAdd.add(poly); } - }else { - System.err.println("Polygon is colinear, removing "+p); + } else { + System.err.println("Polygon is colinear, removing " + p); return null; } } catch (Throwable ex) { @@ -2214,6 +2245,15 @@ public ArrayList mink(CSG travelingShape) { * @return */ public ArrayList minkowskiHullShape(CSG travelingShape) { + if (CSGClient.isRunning()) { + ArrayList go = new ArrayList(Arrays.asList(this,travelingShape)); + try { + return CSGClient.getClient().minkowskiHullShape(go); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } ArrayList bits = new ArrayList<>(); for (Polygon p : this.getPolygons()) { List plist = new ArrayList<>(); @@ -2404,7 +2444,7 @@ public CSG historySync(CSG dyingCSG) { } if (getName().length() == 0) setName(dyingCSG.getName()); - setColor( dyingCSG.getColor()); + setColor(dyingCSG.getColor()); return this; } @@ -2533,8 +2573,9 @@ public CSG setRegenerate(IRegenerate function) { regenerate = function; return this; } + public IRegenerate getRegenerate() { - return regenerate ; + return regenerate; } public CSG regenerate() { @@ -3113,7 +3154,7 @@ public boolean isHole() { public CSG syncProperties(CSG dying) { getStorage().syncProperties(dying.getStorage()); - regenerate=dying.regenerate; + regenerate = dying.regenerate; return this; } @@ -3403,4 +3444,12 @@ public boolean isBoundsTouching(CSG incoming) { return getBounds().isBoundsTouching(incoming.getBounds()); } + public static int getMinPolygonsForOffloading() { + return MinPolygonsForOffloading; + } + + public static void setMinPolygonsForOffloading(int minPolygonsForOffloading) { + MinPolygonsForOffloading = minPolygonsForOffloading; + } + } diff --git a/src/main/java/eu/mihosoft/vrl/v3d/CSGClient.java b/src/main/java/eu/mihosoft/vrl/v3d/CSGClient.java index d80cfb84..875bbec5 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/CSGClient.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/CSGClient.java @@ -1,10 +1,8 @@ package eu.mihosoft.vrl.v3d; -import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; import java.net.Socket; import java.net.UnknownHostException; +import java.nio.file.Files; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -22,19 +20,19 @@ class CSGClient { private String hostname; private int port; - public CSGClient(String hostname, int port) { + private String key = null; + + private static boolean serverCall = false; + + public CSGClient(String hostname, int port, File f) throws Exception { this.hostname = hostname; this.port = port; - try { - Socket socket = new Socket(hostname, port); - socket.close(); - } catch (UnknownHostException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (IOException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } + if (f != null) + if(f.exists()) + key = Files.readAllLines(f.toPath()).toArray(new String[0])[0]; + + Socket socket = new Socket(hostname, port); + socket.close(); } @@ -74,6 +72,18 @@ public ArrayList intersect(ArrayList csgList) throws Exception { return performOperation(csgList, CSGRemoteOperation.INTERSECT); } + /** + * Perform minkowskiHullShape operations on consecutive CSG pairs + * + * @param csgList List of CSG objects to perform minkowskiHullShape on + * @return List of intersection results + * @throws IOException if communication error occurs + * @throws CSGOperationException if server returns an error + */ + public ArrayList minkowskiHullShape(ArrayList csgList) throws Exception { + return performOperation(csgList, CSGRemoteOperation.minkowskiHullShape); + } + /** * Perform triangulation on each CSG object * @@ -110,15 +120,19 @@ public void checkServerTrusted(X509Certificate[] certs, String authType) { try (SSLSocket socket = (SSLSocket) factory.createSocket(hostname, port); ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream()); ObjectInputStream ois = new ObjectInputStream(socket.getInputStream())) { - + System.out.println("Running Operation on server: " + hostname + " " + operation); // Create and send request CSGRequest request = new CSGRequest(csgList, operation); + if (key != null) + request.setAPIKEY(key); oos.writeObject(request); oos.flush(); // Receive response - CSGRequest response = (CSGRequest) ois.readObject(); + CSGResponse response = (CSGResponse) ois.readObject(); socket.close(); + if (response.getState() != ServerActionState.SUCCESS) + throw new RuntimeException(response.getMessage()); // Return results as ArrayList return new ArrayList<>(response.getCsgList()); @@ -137,10 +151,10 @@ public String getServerInfo() { return hostname + ":" + port + " (connected: " + ")"; } - public static boolean start(String hostname, int port) throws IOException { + public static boolean start(String hostname, int port, File f) throws Exception { if (getClient() != null) return false; - setClient(new CSGClient(hostname, port)); + setClient(new CSGClient(hostname, port, f)); return true; } @@ -150,6 +164,8 @@ public static void close() { } public static boolean isRunning() { + if(isServerCall()) + return false; if (getClient() == null) return false; return true; @@ -162,9 +178,12 @@ public static void main(String[] args) { // Create client with try-with-resources for automatic cleanup try { - CSGClient.start(hostname, port); + File f = new File("file.txt"); + CSGClient.start(hostname, port, f); + // Set a low number to ensure the Server is used. this defaults to 200 + CSG.setMinPolygonsForOffloading(4); // Connect to server - System.out.println("Client info: " + getClient().getServerInfo()); + System.out.println("Client info: " + CSGClient.getClient().getServerInfo()); CSG a = new Cube(20).toCSG(); CSG b = new Cube(20, 30, 5).toCSG(); @@ -172,8 +191,9 @@ public static void main(String[] args) { CSG u = CSG.unionAll(a, b, c); CSG d = a.difference(b); CSG t = d.triangulate(true); + ArrayList m = a.minkowskiHullShape(b); - } catch (IOException e) { + } catch (Exception e) { System.err.println("Communication error: " + e.getMessage()); e.printStackTrace(); } @@ -188,4 +208,12 @@ public static CSGClient getClient() { private static void setClient(CSGClient client) { CSGClient.client = client; } + + public static boolean isServerCall() { + return serverCall; + } + + public static void setServerCall(boolean serverCall) { + CSGClient.serverCall = serverCall; + } } diff --git a/src/main/java/eu/mihosoft/vrl/v3d/CSGClientHandler.java b/src/main/java/eu/mihosoft/vrl/v3d/CSGClientHandler.java deleted file mode 100644 index 5d7b6424..00000000 --- a/src/main/java/eu/mihosoft/vrl/v3d/CSGClientHandler.java +++ /dev/null @@ -1,82 +0,0 @@ -package eu.mihosoft.vrl.v3d; - -import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.net.Socket; -import java.util.ArrayList; - -import javax.net.ssl.SSLSocket; - -class CSGClientHandler implements Runnable { - private SSLSocket clientSocket; - - public CSGClientHandler(SSLSocket socket) { - this.clientSocket = socket; - } - - @Override - public void run() { - try (ObjectInputStream ois = new ObjectInputStream(clientSocket.getInputStream()); - ObjectOutputStream oos = new ObjectOutputStream(clientSocket.getOutputStream())) { - - System.out.println("Client connected: " + clientSocket.getRemoteSocketAddress()); - - // Read the CSG request - CSGRequest request = (CSGRequest) ois.readObject(); - System.out.println("Received request: " + request.getOperation()); - - // Process the request - CSGRequest response = processCSGRequest(request); - - // Send back the response - oos.writeObject(response); - oos.flush(); - - System.out.println("Sent response: " + response); - - } catch (IOException | ClassNotFoundException e) { - System.err.println("client disconnected: "); - } finally { - close(); - } - } - - private void close() { - System.out.println("Closing Handler socket"); - try { - if (!clientSocket.isClosed()) { - clientSocket.close(); - } - } catch (IOException e) { - System.err.println("Error closing client socket: " + e.getMessage()); - } - } - - private CSGRequest processCSGRequest(CSGRequest request) { - ArrayList back = new ArrayList(); - switch(request.getOperation()) { - case DIFFERENCE: - CSG first = request.getCsgList().remove(0); - back.add(first.difference(request.getCsgList())); - break; - case INTERSECT: - CSG f = request.getCsgList().remove(0); - back.add(f.intersect(request.getCsgList())); - break; - case TRIANGULATE: - CSG.setPreventNonManifoldTriangles(true); - for(CSG c:request.getCsgList()) - back.add(c.triangulate(true)); - break; - case UNION: - back.add(CSG.unionAll(request.getCsgList())); - break; - default: - break; - - } - return new CSGRequest(back, request.getOperation()); - } - -} diff --git a/src/main/java/eu/mihosoft/vrl/v3d/CSGRemoteOperation.java b/src/main/java/eu/mihosoft/vrl/v3d/CSGRemoteOperation.java index 24503eb7..17a00de1 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/CSGRemoteOperation.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/CSGRemoteOperation.java @@ -4,5 +4,5 @@ public enum CSGRemoteOperation { UNION, DIFFERENCE, INTERSECT, - TRIANGULATE + TRIANGULATE, minkowskiHullShape } diff --git a/src/main/java/eu/mihosoft/vrl/v3d/CSGRequest.java b/src/main/java/eu/mihosoft/vrl/v3d/CSGRequest.java index b6d2edec..141590a5 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/CSGRequest.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/CSGRequest.java @@ -8,6 +8,7 @@ class CSGRequest implements Serializable { private static final long serialVersionUID = 1L; private List csgList; private CSGRemoteOperation operation; + private String APIKEY; public CSGRequest() { this.csgList = new ArrayList<>(); @@ -39,4 +40,12 @@ public void setOperation(CSGRemoteOperation operation) { public String toString() { return "CSGRequest{operation=" + operation + ", csgCount=" + csgList.size() + "}"; } + + public String getAPIKey() { + return APIKEY; + } + + public void setAPIKEY(String aPIKEY) { + APIKEY = aPIKEY; + } } \ No newline at end of file diff --git a/src/main/java/eu/mihosoft/vrl/v3d/CSGResponse.java b/src/main/java/eu/mihosoft/vrl/v3d/CSGResponse.java new file mode 100644 index 00000000..81af7c5b --- /dev/null +++ b/src/main/java/eu/mihosoft/vrl/v3d/CSGResponse.java @@ -0,0 +1,69 @@ +package eu.mihosoft.vrl.v3d; + +import java.io.PrintWriter; +import java.io.Serializable; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.List; + +class CSGResponse implements Serializable { + private static final long serialVersionUID = 1L; + private List csgList; + private CSGRemoteOperation operation; + private ServerActionState state=ServerActionState.SUCCESS; + private String message=null; + + public CSGResponse() { + this.csgList = new ArrayList<>(); + this.operation = CSGRemoteOperation.UNION; + } + + public CSGResponse(List csgList, CSGRemoteOperation operation) { + this.csgList = csgList != null ? new ArrayList<>(csgList) : new ArrayList<>(); + this.operation = operation != null ? operation : CSGRemoteOperation.UNION; + } + + public List getCsgList() { + return csgList; + } + + public void setCsgList(List csgList) { + this.csgList = csgList != null ? new ArrayList<>(csgList) : new ArrayList<>(); + } + + public CSGRemoteOperation getOperation() { + return operation; + } + + public void setOperation(CSGRemoteOperation operation) { + this.operation = operation != null ? operation : CSGRemoteOperation.UNION; + } + + @Override + public String toString() { + return "CSGRequest{operation=" + operation + ", csgCount=" + csgList.size() + "}"; + } + + public ServerActionState getState() { + return state; + } + + public void setState(ServerActionState state) { + this.state = state; + if( message==null) + message=state.toString(); + } + + public String getMessage() { + return message; + } + public void setMessage(Throwable t) { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + t.printStackTrace(pw); + this.message = sw.toString(); + } + public void setMessage(String message) { + this.message = message; + } +} \ No newline at end of file diff --git a/src/main/java/eu/mihosoft/vrl/v3d/CSGServer.java b/src/main/java/eu/mihosoft/vrl/v3d/CSGServer.java index 813a8345..7d086564 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/CSGServer.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/CSGServer.java @@ -3,7 +3,8 @@ import java.io.IOException; import java.math.BigInteger; import java.net.ServerSocket; -import java.net.Socket; +import java.nio.file.Files; +import java.nio.file.Paths; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; @@ -13,22 +14,44 @@ import java.security.KeyPairGenerator; import java.security.KeyStore; import java.security.SecureRandom; -import java.net.Socket; import java.security.cert.Certificate; import java.security.cert.X509Certificate; -import java.security.spec.RSAKeyGenParameterSpec; import java.util.Date; -import java.time.LocalDateTime; -import java.time.ZoneId; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.io.FileOutputStream; +import java.security.PrivateKey; +import java.security.PublicKey; +import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; +import org.bouncycastle.asn1.x509.Time; +import org.bouncycastle.cert.X509CertificateHolder; +import org.bouncycastle.cert.X509v3CertificateBuilder; +import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; +import org.bouncycastle.operator.ContentSigner; +import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; //Main TCP Server class public class CSGServer { private final int port; private final ExecutorService threadPool; - private ServerSocket serverSocket; private volatile boolean running = false; - private static final String KEYSTORE_PATH = "serverCredentials.jks"; - private static final String KEYSTORE_PASSWORD = "password"; + private static String KEYSTORE_PATH = "servername"; + private static File directory = new File(System.getProperty("java.io.tmpdir")); + private static final String KEYSTORE_NAME = "CSGSelfSign"; + private String[] lines = null; + private SSLServerSocket serverSocket2; + + public CSGServer(int port, File APIKEYS) throws IOException { + this.port = port; + this.threadPool = Executors.newCachedThreadPool(); + if (APIKEYS != null) { + if(APIKEYS.exists()) + lines = Files.readAllLines(APIKEYS.toPath()).toArray(new String[0]); + } + if(lines!=null) { + System.out.println("Starting server with "+lines.length+" keys"); + } + } public static void ensureKeystoreExists(String keystorePath, String keystorePassword, String alias, String commonName) { @@ -37,17 +60,11 @@ public static void ensureKeystoreExists(String keystorePath, String keystorePass if (!keystoreFile.exists()) { System.out.println("Keystore not found. Generating new keystore: " + keystorePath); try { - if (isKeytoolAvailable()) { - generateKeystoreWithKeytool(keystorePath, keystorePassword, alias, commonName); - System.out.println("Keystore generated successfully using keytool."); - } else { - throw new RuntimeException( - "keytool command not found. Please install JDK or create keystore manually using: keytool -genkeypair -alias " - + alias + " -keyalg RSA -keysize 2048 -keystore " + keystorePath + " -storepass " - + keystorePassword + " -keypass " + keystorePassword + " -dname \"CN=" + commonName - + ",OU=Auto,O=Dev,C=US\" -validity 365"); - } + generateKeystoreWithBouncyCastle(keystorePath, keystorePassword, alias, commonName); + System.out.println("Keystore generated successfully using keytool."); + } catch (Exception e) { + e.printStackTrace(); throw new RuntimeException("Failed to generate keystore: " + e.getMessage(), e); } } else { @@ -55,61 +72,77 @@ public static void ensureKeystoreExists(String keystorePath, String keystorePass } } - private static void generateKeystoreWithKeytool(String keystorePath, String keystorePassword, String alias, +// Alternative implementation using Bouncy Castle (if available) + private static void generateKeystoreWithBouncyCastle(String keystorePath, String keystorePassword, String alias, String commonName) throws Exception { - System.out.println("Generating keystore using keytool command..."); + System.out.println("Generating keystore using Bouncy Castle..."); + // Generate RSA key pair + KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); + keyPairGenerator.initialize(2048, new SecureRandom()); + KeyPair keyPair = keyPairGenerator.generateKeyPair(); - String[] command = { "keytool", "-genkeypair", "-alias", alias, "-keyalg", "RSA", "-keysize", "2048", - "-keystore", keystorePath, "-storepass", keystorePassword, "-keypass", keystorePassword, "-dname", - "CN=" + commonName + ",OU=Auto-Generated,O=Development,L=Unknown,ST=Unknown,C=US", "-validity", "365" }; + // Create self-signed certificate + X509Certificate certificate = createSelfSignedCertificateBC(keyPair, commonName); - ProcessBuilder pb = new ProcessBuilder(command); - pb.redirectErrorStream(true); - Process process = pb.start(); + // Create keystore and add the key pair with certificate + KeyStore keyStore = KeyStore.getInstance("JKS"); + keyStore.load(null, null); // Initialize empty keystore -// Read output - StringBuilder output = new StringBuilder(); - try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) { - String line; - while ((line = reader.readLine()) != null) { - output.append(line).append("\n"); - System.out.println("keytool: " + line); - } - } + Certificate[] certificateChain = { certificate }; + keyStore.setKeyEntry(alias, keyPair.getPrivate(), keystorePassword.toCharArray(), certificateChain); - int exitCode = process.waitFor(); - if (exitCode != 0) { - throw new RuntimeException("keytool failed with exit code " + exitCode + ". Output: " + output.toString()); + // Save keystore to file + try (FileOutputStream fos = new FileOutputStream(keystorePath)) { + keyStore.store(fos, keystorePassword.toCharArray()); } + + System.out.println("Keystore generated successfully at: " + keystorePath); } - /** - * Check if keytool is available on the system - */ - public static boolean isKeytoolAvailable() { - try { - ProcessBuilder pb = new ProcessBuilder("keytool", "-help"); - pb.redirectErrorStream(true); - Process process = pb.start(); - -// Consume output to prevent blocking - try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) { - while (reader.readLine() != null) { -// Just consume the output - } - } + private static X509Certificate createSelfSignedCertificateBC(KeyPair keyPair, String commonName) throws Exception { - int exitCode = process.waitFor(); - return exitCode == 0; - } catch (Exception e) { - return false; - } - } + PrivateKey privateKey = keyPair.getPrivate(); + PublicKey publicKey = keyPair.getPublic(); - public CSGServer(int port) { - this.port = port; - this.threadPool = Executors.newCachedThreadPool(); + // Certificate validity period (365 days) + Instant now = Instant.now(); + Date notBefore = Date.from(now); + Date notAfter = Date.from(now.plus(365, ChronoUnit.DAYS)); + + // Create X.500 distinguished name + String distinguishedName = String.format("CN=%s,OU=Auto-Generated,O=Development,L=Unknown,ST=Unknown,C=US", + commonName); + org.bouncycastle.asn1.x500.X500Name x500Name = new org.bouncycastle.asn1.x500.X500Name(distinguishedName); + + // Generate serial number + BigInteger serialNumber = new BigInteger(64, new SecureRandom()); + + // Create certificate builder using BC 1.80 constructor + SubjectPublicKeyInfo subjectPublicKeyInfo = SubjectPublicKeyInfo.getInstance(publicKey.getEncoded()); + + // Convert dates to Time objects for BC 1.80 + Time notBeforeTime = new Time(notBefore); + Time notAfterTime = new Time(notAfter); + + X509v3CertificateBuilder certBuilder = new X509v3CertificateBuilder(x500Name, // issuer (X500Name) + (BigInteger) serialNumber, // serial (BigInteger) + (Time) notBeforeTime, // notBefore (Time) + (Time) notAfterTime, // notAfter (Time) + x500Name, // subject (X500Name) - same as issuer for self-signed + (SubjectPublicKeyInfo) subjectPublicKeyInfo // publicKeyInfo (SubjectPublicKeyInfo) + ); + + // Create content signer + ContentSigner contentSigner = new JcaContentSignerBuilder("SHA256withRSA").build(privateKey); + + // Build and sign certificate + X509CertificateHolder certHolder = certBuilder.build(contentSigner); + + // Convert to X509Certificate + JcaX509CertificateConverter certConverter = new JcaX509CertificateConverter(); + + return certConverter.getCertificate(certHolder); } public void start() throws Exception { @@ -122,12 +155,13 @@ public void start() throws Exception { })); // Load the keystore KeyStore keyStore = KeyStore.getInstance("JKS"); - ensureKeystoreExists(KEYSTORE_PATH, KEYSTORE_PASSWORD, "server", "localhost"); - keyStore.load(new FileInputStream(KEYSTORE_PATH), KEYSTORE_PASSWORD.toCharArray()); + String path = getDirectory().getAbsolutePath()+"/"+KEYSTORE_PATH; + ensureKeystoreExists(path, KEYSTORE_NAME, "server", "localhost"); + keyStore.load(new FileInputStream(path), KEYSTORE_NAME.toCharArray()); // Create KeyManagerFactory KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); - kmf.init(keyStore, KEYSTORE_PASSWORD.toCharArray()); + kmf.init(keyStore, KEYSTORE_NAME.toCharArray()); // Create SSLContext SSLContext sslContext = SSLContext.getInstance("TLS"); @@ -135,20 +169,20 @@ public void start() throws Exception { // Create SSL server socket SSLServerSocketFactory factory = sslContext.getServerSocketFactory(); - SSLServerSocket serverSocket = (SSLServerSocket) factory.createServerSocket(port); + serverSocket2 = (SSLServerSocket) factory.createServerSocket(port); // serverSocket = new ServerSocket(port); - running = true; + setRunning(true); System.out.println("CSG TCP Server started on port " + port); System.out.println("Waiting for clients..."); - while (running) { + while (isRunning()) { try { - SSLSocket clientSocket = (SSLSocket) serverSocket.accept(); - threadPool.execute(new CSGClientHandler(clientSocket)); + SSLSocket clientSocket = (SSLSocket) serverSocket2.accept(); + threadPool.execute(new CSGServerHandler(clientSocket,lines)); } catch (IOException e) { - if (running) { + if (isRunning()) { System.err.println("Error accepting client connection: " + e.getMessage()); } } @@ -156,9 +190,9 @@ public void start() throws Exception { } public void stop() throws IOException { - running = false; - if (serverSocket != null && !serverSocket.isClosed()) { - serverSocket.close(); + setRunning(false); + if (serverSocket2 != null && !serverSocket2.isClosed()) { + serverSocket2.close(); } threadPool.shutdown(); try { @@ -183,8 +217,27 @@ public static void main(String[] args) throws Exception { System.err.println("Invalid port number. Using default port 8080"); } } - - CSGServer server = new CSGServer(port); + File f = new File("file.txt"); + if (!f.exists()) { + f.createNewFile(); + } + CSGServer server = new CSGServer(port, f); server.start(); } + + public boolean isRunning() { + return running; + } + + public void setRunning(boolean running) { + this.running = running; + } + + public static File getDirectory() { + return directory; + } + + public static void setDirectory(File directory) { + CSGServer.directory = directory; + } } \ No newline at end of file diff --git a/src/main/java/eu/mihosoft/vrl/v3d/CSGServerHandler.java b/src/main/java/eu/mihosoft/vrl/v3d/CSGServerHandler.java new file mode 100644 index 00000000..d8514552 --- /dev/null +++ b/src/main/java/eu/mihosoft/vrl/v3d/CSGServerHandler.java @@ -0,0 +1,127 @@ +package eu.mihosoft.vrl.v3d; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.net.Socket; +import java.util.ArrayList; + +import javax.net.ssl.SSLSocket; + +class CSGServerHandler implements Runnable { + private SSLSocket clientSocket; + + private String[] APIKEY = null; + + public CSGServerHandler(SSLSocket socket, String[] lines) { + this.clientSocket = socket; + APIKEY = lines; + } + + @Override + public void run() { + try (ObjectInputStream ois = new ObjectInputStream(clientSocket.getInputStream()); + ObjectOutputStream oos = new ObjectOutputStream(clientSocket.getOutputStream())) { + + System.out.println("Client connected: " + clientSocket.getRemoteSocketAddress()); + + // Read the CSG request + CSGRequest request = (CSGRequest) ois.readObject(); + System.out.println("Received request: " + request.getOperation()); + boolean APIPass = true; + if (getAPIKEYs() != null) { + APIPass = false; + for (int i = 0; i < getAPIKEYs().length; i++) + if (request.getAPIKey().contentEquals(getAPIKEYs()[i])) { + APIPass = true; + System.out.println("API Key Match"); + break; + } + } + + // Process the request + CSGResponse response = null; + if (APIPass) { + try { + response = processCSGRequest(request); + } catch (Throwable t) { + t.printStackTrace(); + response = new CSGResponse(); + response.setState(ServerActionState.ERROR); + response.setMessage(t); + } + } else { + response = new CSGResponse(); + response.setState(ServerActionState.BADAPIKEY); + response.setMessage("Your API key " + request.getAPIKey() + " Does not match server's key"); + } + // Send back the response + oos.writeObject(response); + oos.flush(); + + System.out.println("Sent response: " + response); + + } catch (IOException | ClassNotFoundException e) { + System.err.println("client disconnected: "); + } finally { + close(); + } + } + + private void close() { + System.out.println("Closing Handler socket"); + try { + if (!clientSocket.isClosed()) { + clientSocket.close(); + } + } catch (IOException e) { + System.err.println("Error closing client socket: " + e.getMessage()); + } + } + + private CSGResponse processCSGRequest(CSGRequest request) { + ArrayList back = new ArrayList(); + CSGClient.setServerCall(true); + try { + switch (request.getOperation()) { + case DIFFERENCE: + CSG first = request.getCsgList().remove(0); + back.add(first.difference(request.getCsgList())); + break; + case INTERSECT: + CSG f = request.getCsgList().remove(0); + back.add(f.intersect(request.getCsgList())); + break; + case TRIANGULATE: + CSG.setPreventNonManifoldTriangles(true); + for (CSG c : request.getCsgList()) + back.add(c.triangulate(true)); + break; + case UNION: + back.add(CSG.unionAll(request.getCsgList())); + break; + case minkowskiHullShape: + CSG m1 = request.getCsgList().remove(0); + CSG t = request.getCsgList().remove(0); + back.addAll(m1.minkowskiHullShape(t)); + break; + default: + throw new RuntimeException("No Such Operation " + request.getOperation()); + } + } catch (Throwable t) { + CSGClient.setServerCall(false); + throw t; + } + CSGClient.setServerCall(false); + return new CSGResponse(back, request.getOperation()); + } + + public String[] getAPIKEYs() { + return APIKEY; + } + + public void setAPIKEYs(String[] aPIKEY) { + APIKEY = aPIKEY; + } + +} diff --git a/src/main/java/eu/mihosoft/vrl/v3d/Polygon.java b/src/main/java/eu/mihosoft/vrl/v3d/Polygon.java index 6e95e062..c9c4c993 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/Polygon.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/Polygon.java @@ -742,10 +742,14 @@ public ArrayList edges() { } public Polygon setColor(Color color) { - r=color.getRed(); - g=color.getGreen(); - b=color.getBlue(); - o=color.getOpacity(); + if(color!=null) { + r=color.getRed(); + g=color.getGreen(); + b=color.getBlue(); + o=color.getOpacity(); + }else { + setColor(CSG.getDefaultColor()); + } return this; } public Color getColor() { diff --git a/src/main/java/eu/mihosoft/vrl/v3d/ServerActionState.java b/src/main/java/eu/mihosoft/vrl/v3d/ServerActionState.java new file mode 100644 index 00000000..906fda07 --- /dev/null +++ b/src/main/java/eu/mihosoft/vrl/v3d/ServerActionState.java @@ -0,0 +1,5 @@ +package eu.mihosoft.vrl.v3d; + +public enum ServerActionState { +ERROR, SUCCESS, BADAPIKEY +} diff --git a/src/main/java/eu/mihosoft/vrl/v3d/svg/SVGLoad.java b/src/main/java/eu/mihosoft/vrl/v3d/svg/SVGLoad.java index d6986869..c1d3e750 100644 --- a/src/main/java/eu/mihosoft/vrl/v3d/svg/SVGLoad.java +++ b/src/main/java/eu/mihosoft/vrl/v3d/svg/SVGLoad.java @@ -649,9 +649,10 @@ private void loadSingle(String code, double resolution, Transform startingFrame, poly = Polygon.fromPoints(Extrude.toCCW(poly.getPoints())); poly.setHole(hole); - if (c != null) + if (c != null) { colors.put(poly, c); - poly.setColor(c); + poly.setColor(c); + } list.add(poly); } diff --git a/src/test/java/eu/mihosoft/vrl/v3d/ServerClientTest.java b/src/test/java/eu/mihosoft/vrl/v3d/ServerClientTest.java new file mode 100644 index 00000000..ec2567f0 --- /dev/null +++ b/src/test/java/eu/mihosoft/vrl/v3d/ServerClientTest.java @@ -0,0 +1,62 @@ +package eu.mihosoft.vrl.v3d; + +import static org.junit.Assert.*; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; + +import org.junit.Test; + +public class ServerClientTest { + + @Test + public void test() throws Exception { + int port = 3742; + + + File f = new File("file.txt"); + CSGServer server = new CSGServer(port, f); + + Thread serverThread = new Thread(()->{ + try { + server.start(); + } catch (Exception e) { + fail(); + } + }); + serverThread.start(); + while(!server.isRunning()) { + Thread.sleep(500); + System.out.println("Waiting for server to start..."); + } + + String hostname = "localhost"; + // Create client with try-with-resources for automatic cleanup + try { + CSGClient.start(hostname, port, f); + // Set a low number to ensure the Server is used. this defaults to 200 + CSG.setMinPolygonsForOffloading(4); + // Connect to server + System.out.println("Client info: " + CSGClient.getClient().getServerInfo()); + + CSG a = new Cube(20).toCSG(); + CSG b = new Cube(20, 30, 5).toCSG(); + CSG c = new Cube(10, 10, 10).toCSG(); + CSG u = CSG.unionAll(a, b, c); + CSG d = a.difference(b); + CSG t = d.triangulate(true); + ArrayList m = a.minkowskiHullShape(b); + + } catch (Exception e) { + System.err.println("Communication error: " + e.getMessage()); + e.printStackTrace(); + } + + server.stop(); + serverThread.interrupt(); + serverThread.join(); + System.out.println("\nClient example completed."); + } + +} diff --git a/src/test/java/eu/mihosoft/vrl/v3d/textTest.java b/src/test/java/eu/mihosoft/vrl/v3d/textTest.java index 08823592..997514d9 100644 --- a/src/test/java/eu/mihosoft/vrl/v3d/textTest.java +++ b/src/test/java/eu/mihosoft/vrl/v3d/textTest.java @@ -20,11 +20,11 @@ public class textTest { public void test() throws IOException { //TextExtrude.text(10.0, "Hello", new Font("Helvedica", 18)); //TextExtrude.text(10.0, "Hello World!", new Font("Times New Roman", 18)); - - CSG text = CSG.text("Hello world",10, 10,"Serif Regular"); - text=new Cube(180,40,10).toCSG().toZMin().toXMin().toYMin().movey(-5).difference(text); - FileUtil.write(Paths.get("exampleText.stl"), - text.toStlString()); +// +// CSG text = CSG.text("Hello world",10, 10,"Serif Regular"); +// text=new Cube(180,40,10).toCSG().toZMin().toXMin().toYMin().movey(-5).difference(text); +// FileUtil.write(Paths.get("exampleText.stl"), +// text.toStlString()); }