diff --git a/.gitignore b/.gitignore index dbf9e4c3..56f130ee 100644 --- a/.gitignore +++ b/.gitignore @@ -55,3 +55,6 @@ coverage.xml netns/setup.py daemon/setup.py ns3/setup.py + +# ignore corefx build +corefx/target diff --git a/corefx/pom.xml b/corefx/pom.xml new file mode 100644 index 00000000..149d4ac1 --- /dev/null +++ b/corefx/pom.xml @@ -0,0 +1,156 @@ + + + 4.0.0 + + com.core + corefx + 1.0-SNAPSHOT + + + UTF-8 + 1.8 + 1.8 + 2.1.1 + 2.9.6 + 1.20.0 + + + + + net.sf.jung + jung-api + ${jung.version} + + + net.sf.jung + jung-graph-impl + ${jung.version} + + + net.sf.jung + jung-algorithms + ${jung.version} + + + net.sf.jung + jung-io + ${jung.version} + + + net.sf.jung + jung-visualization + ${jung.version} + + + com.fasterxml.jackson.core + jackson-core + ${jackson.version} + + + com.fasterxml.jackson.core + jackson-databind + ${jackson.version} + + + com.fasterxml.jackson.core + jackson-annotations + ${jackson.version} + + + com.squareup.okhttp3 + okhttp + 3.11.0 + + + org.apache.logging.log4j + log4j-api + 2.9.0 + + + org.apache.logging.log4j + log4j-core + 2.9.0 + + + io.socket + socket.io-client + 0.8.3 + + + org.projectlombok + lombok + 1.18.0 + provided + + + commons-net + commons-net + 3.6 + + + com.jfoenix + jfoenix + 8.0.7 + + + io.grpc + grpc-netty-shaded + ${grpc.version} + + + io.grpc + grpc-protobuf + ${grpc.version} + + + io.grpc + grpc-stub + ${grpc.version} + + + com.google.guava + guava + 20.0 + + + + + + + kr.motd.maven + os-maven-plugin + 1.5.0.Final + + + + + com.zenjava + javafx-maven-plugin + 8.8.3 + + com.core.Main + + + + org.xolstice.maven.plugins + protobuf-maven-plugin + 0.5.1 + + com.google.protobuf:protoc:3.7.1:exe:${os.detected.classifier} + grpc-java + io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier} + + + + + compile + compile-custom + + + + + + + \ No newline at end of file diff --git a/corefx/src/main/java/com/core/Controller.java b/corefx/src/main/java/com/core/Controller.java new file mode 100644 index 00000000..bfff6bf2 --- /dev/null +++ b/corefx/src/main/java/com/core/Controller.java @@ -0,0 +1,543 @@ +package com.core; + +import com.core.client.ICoreClient; +import com.core.client.grpc.CoreGrpcClient; +import com.core.data.*; +import com.core.graph.NetworkGraph; +import com.core.ui.*; +import com.core.ui.dialogs.*; +import com.core.utils.ConfigUtils; +import com.core.utils.Configuration; +import com.core.utils.NodeTypeConfig; +import com.core.websocket.CoreWebSocket; +import com.jfoenix.controls.JFXDecorator; +import com.jfoenix.controls.JFXProgressBar; +import javafx.application.Application; +import javafx.application.Platform; +import javafx.concurrent.Task; +import javafx.embed.swing.SwingNode; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.fxml.Initializable; +import javafx.scene.control.CheckMenuItem; +import javafx.scene.control.MenuItem; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.StackPane; +import javafx.scene.layout.VBox; +import javafx.stage.FileChooser; +import javafx.stage.Stage; +import lombok.Data; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.awt.event.ItemEvent; +import java.io.File; +import java.io.IOException; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.*; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.stream.Collectors; + +@Data +public class Controller implements Initializable { + private static final Logger logger = LogManager.getLogger(); + @FXML private StackPane stackPane; + @FXML private BorderPane borderPane; + @FXML private VBox top; + @FXML private VBox bottom; + @FXML private SwingNode swingNode; + @FXML private MenuItem saveXmlMenuItem; + @FXML private JFXProgressBar progressBar; + @FXML private CheckMenuItem throughputMenuItem; + + private final ExecutorService executorService = Executors.newSingleThreadExecutor(); + private final Map mobilityScripts = new HashMap<>(); + private final Map mobilityPlayerDialogs = new HashMap<>(); + private Application application; + private JFXDecorator decorator; + private Stage window; + private Configuration configuration; + private Map> defaultServices = new HashMap<>(); + + // core client utilities + private ICoreClient coreClient = new CoreGrpcClient(); + private CoreWebSocket coreWebSocket; + + // ui elements + private NetworkGraph networkGraph = new NetworkGraph(this); + private AnnotationToolbar annotationToolbar = new AnnotationToolbar(networkGraph); + private NodeDetails nodeDetails = new NodeDetails(this); + private LinkDetails linkDetails = new LinkDetails(this); + private GraphToolbar graphToolbar = new GraphToolbar(this); + + // dialogs + private SessionsDialog sessionsDialog = new SessionsDialog(this); + private ServiceDialog serviceDialog = new ServiceDialog(this); + private NodeServicesDialog nodeServicesDialog = new NodeServicesDialog(this); + private NodeEmaneDialog nodeEmaneDialog = new NodeEmaneDialog(this); + private NodeWlanDialog nodeWlanDialog = new NodeWlanDialog(this); + private ConfigDialog configDialog = new ConfigDialog(this); + private HooksDialog hooksDialog = new HooksDialog(this); + private MobilityDialog mobilityDialog = new MobilityDialog(this); + private ChartDialog chartDialog = new ChartDialog(this); + private NodeTypesDialog nodeTypesDialog = new NodeTypesDialog(this); + private BackgroundDialog backgroundDialog = new BackgroundDialog(this); + private LocationDialog locationDialog = new LocationDialog(this); + private GeoDialog geoDialog = new GeoDialog(this); + private ConnectDialog connectDialog = new ConnectDialog(this); + private GuiPreferencesDialog guiPreferencesDialog = new GuiPreferencesDialog(this); + private NodeTypeCreateDialog nodeTypeCreateDialog = new NodeTypeCreateDialog(this); + + public void connectToCore(String address, int port) { + coreWebSocket.stop(); + + ExecutorService executorService = Executors.newSingleThreadExecutor(); + executorService.submit(() -> { + try { + coreWebSocket.start(address, port); + coreClient.setConnection(address, port); + initialJoin(); + } catch (IOException | URISyntaxException ex) { + Toast.error(String.format("Connection failure: %s", ex.getMessage()), ex); + Platform.runLater(() -> connectDialog.showDialog()); + } + }); + } + + private void initialJoin() throws IOException { + Map> serviceGroups = coreClient.getServices(); + logger.info("core services: {}", serviceGroups); + nodeServicesDialog.setServices(serviceGroups); + nodeTypeCreateDialog.setServices(serviceGroups); + + logger.info("initial core session join"); + List sessions = coreClient.getSessions(); + + logger.info("existing sessions: {}", sessions); + Integer sessionId; + if (sessions.isEmpty()) { + logger.info("creating initial session"); + SessionOverview sessionOverview = coreClient.createSession(); + sessionId = sessionOverview.getId(); + Toast.info(String.format("Created Session %s", sessionId)); + } else { + SessionOverview sessionOverview = sessions.get(0); + sessionId = sessionOverview.getId(); + Toast.info(String.format("Joined Session %s", sessionId)); + } + + joinSession(sessionId); + + // set emane models + List emaneModels = coreClient.getEmaneModels(); + nodeEmaneDialog.setModels(emaneModels); + } + + public void joinSession(Integer sessionId) throws IOException { + // clear graph + networkGraph.reset(); + + // clear out any previously set information + mobilityPlayerDialogs.clear(); + mobilityScripts.clear(); + mobilityDialog.setNode(null); + Platform.runLater(() -> borderPane.setRight(null)); + + // get session to join + Session session = coreClient.getSession(sessionId); + SessionState sessionState = SessionState.get(session.getState()); + + // update client to use this session + coreClient.updateSession(sessionId); + coreClient.updateState(sessionState); + + // display all nodes + logger.info("joining core session({}) state({}): {}", sessionId, sessionState, session); + for (CoreNode node : session.getNodes()) { + NodeType nodeType = NodeType.find(node.getType(), node.getModel()); + if (nodeType == null) { + logger.info(String.format("failed to find node type(%s) model(%s): %s", + node.getType(), node.getModel(), node.getName())); + continue; + } + + node.setNodeType(nodeType); + networkGraph.addNode(node); + } + + // display all links + for (CoreLink link : session.getLinks()) { + if (link.getInterfaceOne() != null || link.getInterfaceTwo() != null) { + link.setType(LinkTypes.WIRED.getValue()); + } + + networkGraph.addLink(link); + } + + // refresh graph + networkGraph.getGraphViewer().repaint(); + + // update other components for new session + graphToolbar.setRunButton(coreClient.isRunning()); + hooksDialog.updateHooks(); + + // update session default services + setCoreDefaultServices(); + + // retrieve current mobility script configurations and show dialogs + Map mobilityConfigMap = coreClient.getMobilityConfigs(); + mobilityScripts.putAll(mobilityConfigMap); + showMobilityScriptDialogs(); + + Platform.runLater(() -> decorator.setTitle(String.format("CORE (Session %s)", sessionId))); + } + + public boolean startSession() throws IOException { + // force nodes to get latest positions + networkGraph.updatePositions(); + + // retrieve items for creation/start + Collection nodes = networkGraph.getGraph().getVertices(); + Collection links = networkGraph.getGraph().getEdges(); + List hooks = hooksDialog.getHooks(); + + // start/create session + progressBar.setVisible(true); + boolean result = coreClient.start(nodes, links, hooks); + progressBar.setVisible(false); + if (result) { + showMobilityScriptDialogs(); + saveXmlMenuItem.setDisable(false); + } + return result; + } + + public boolean stopSession() throws IOException { + // clear out any drawn wireless links + List wirelessLinks = networkGraph.getGraph().getEdges().stream() + .filter(CoreLink::isWireless) + .collect(Collectors.toList()); + wirelessLinks.forEach(networkGraph::removeWirelessLink); + networkGraph.getGraphViewer().repaint(); + + // stop session + progressBar.setVisible(true); + boolean result = coreClient.stop(); + progressBar.setVisible(false); + if (result) { + saveXmlMenuItem.setDisable(true); + } + return result; + } + + public void handleThroughputs(Throughputs throughputs) { + for (InterfaceThroughput interfaceThroughput : throughputs.getInterfaces()) { + int nodeId = interfaceThroughput.getNode(); + CoreNode node = networkGraph.getVertex(nodeId); + Collection links = networkGraph.getGraph().getIncidentEdges(node); + int interfaceId = interfaceThroughput.getNodeInterface(); + for (CoreLink link : links) { + if (nodeId == link.getNodeOne()) { + if (interfaceId == link.getInterfaceOne().getId()) { + link.setThroughput(interfaceThroughput.getThroughput()); + } + } else { + if (interfaceId == link.getInterfaceTwo().getId()) { + link.setThroughput(interfaceThroughput.getThroughput()); + } + } + } + } + networkGraph.getGraphViewer().repaint(); + } + + private void setCoreDefaultServices() { + try { + coreClient.setDefaultServices(defaultServices); + } catch (IOException ex) { + Toast.error("Error updating core default services", ex); + } + } + + public void updateNodeTypes() { + graphToolbar.setupNodeTypes(); + setCoreDefaultServices(); + try { + ConfigUtils.save(configuration); + } catch (IOException ex) { + Toast.error("Error saving configuration", ex); + } + } + + public void deleteNode(CoreNode node) { + networkGraph.removeNode(node); + CoreNode mobilityNode = mobilityDialog.getNode(); + if (mobilityNode != null && mobilityNode.getId().equals(node.getId())) { + mobilityDialog.setNode(null); + } + } + + void setWindow(Stage window) { + this.window = window; + sessionsDialog.setOwner(window); + hooksDialog.setOwner(window); + nodeServicesDialog.setOwner(window); + serviceDialog.setOwner(window); + nodeWlanDialog.setOwner(window); + nodeEmaneDialog.setOwner(window); + configDialog.setOwner(window); + mobilityDialog.setOwner(window); + nodeTypesDialog.setOwner(window); + backgroundDialog.setOwner(window); + locationDialog.setOwner(window); + connectDialog.setOwner(window); + guiPreferencesDialog.setOwner(window); + nodeTypeCreateDialog.setOwner(window); + } + + private void showMobilityScriptDialogs() { + for (Map.Entry entry : mobilityScripts.entrySet()) { + Integer nodeId = entry.getKey(); + CoreNode node = networkGraph.getVertex(nodeId); + MobilityConfig mobilityConfig = entry.getValue(); + Platform.runLater(() -> { + MobilityPlayerDialog mobilityPlayerDialog = new MobilityPlayerDialog(this, node); + mobilityPlayerDialog.setOwner(window); + mobilityPlayerDialogs.put(nodeId, mobilityPlayerDialog); + mobilityPlayerDialog.showDialog(mobilityConfig); + }); + } + } + + @FXML + private void onCoreMenuConnect(ActionEvent event) { + logger.info("showing connect!"); + connectDialog.showDialog(); + } + + @FXML + private void onOptionsMenuNodeTypes(ActionEvent event) { + nodeTypesDialog.showDialog(); + } + + @FXML + private void onOptionsMenuBackground(ActionEvent event) { + backgroundDialog.showDialog(); + } + + @FXML + private void onOptionsMenuLocation(ActionEvent event) { + locationDialog.showDialog(); + } + + @FXML + private void onOptionsMenuPreferences(ActionEvent event) { + guiPreferencesDialog.showDialog(); + } + + @FXML + private void onHelpMenuWebsite(ActionEvent event) { + application.getHostServices().showDocument("https://github.com/coreemu/core"); + } + + @FXML + private void onHelpMenuDocumentation(ActionEvent event) { + application.getHostServices().showDocument("http://coreemu.github.io/core/"); + } + + @FXML + private void onHelpMenuMailingList(ActionEvent event) { + application.getHostServices().showDocument("https://publists.nrl.navy.mil/mailman/listinfo/core-users"); + } + + @FXML + private void onOpenXmlAction() { + FileChooser fileChooser = new FileChooser(); + fileChooser.setTitle("Open Session"); + fileChooser.setInitialDirectory(new File(configuration.getXmlPath())); + fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("XML", "*.xml")); + try { + File file = fileChooser.showOpenDialog(window); + if (file != null) { + openXml(file); + } + } catch (IllegalArgumentException ex) { + Toast.error(String.format("Invalid XML directory: %s", configuration.getXmlPath())); + } + } + + private void openXml(File file) { + logger.info("opening session xml: {}", file.getPath()); + try { + SessionOverview sessionOverview = coreClient.openSession(file); + Integer sessionId = sessionOverview.getId(); + joinSession(sessionId); + Toast.info(String.format("Joined Session %s", sessionId)); + } catch (IOException ex) { + Toast.error("Error opening session xml", ex); + } + } + + @FXML + private void onSaveXmlAction() { + FileChooser fileChooser = new FileChooser(); + fileChooser.setTitle("Save Session"); + fileChooser.setInitialFileName("session.xml"); + fileChooser.setInitialDirectory(new File(configuration.getXmlPath())); + fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("XML", "*.xml")); + File file = fileChooser.showSaveDialog(window); + if (file != null) { + logger.info("saving session xml: {}", file.getPath()); + try { + coreClient.saveSession(file); + } catch (IOException ex) { + Toast.error("Error saving session xml", ex); + } + } + } + + @FXML + private void onSessionMenu(ActionEvent event) { + logger.info("sessions menu clicked"); + try { + sessionsDialog.showDialog(); + } catch (IOException ex) { + Toast.error("Error retrieving sessions", ex); + } + } + + @FXML + private void onSessionNodesMenu(ActionEvent event) { + + } + + @FXML + private void onSessionHooksMenu(ActionEvent event) { + hooksDialog.showDialog(); + } + + @FXML + private void onSessionOptionsMenu(ActionEvent event) { + try { + List configGroups = coreClient.getSessionConfig(); + configDialog.showDialog("Session Options", configGroups, () -> { + List options = configDialog.getOptions(); + try { + boolean result = coreClient.setSessionConfig(options); + if (result) { + Toast.info("Session options saved"); + } else { + Toast.error("Failure to set session config"); + } + } catch (IOException ex) { + logger.error("error getting session config"); + } + }); + } catch (IOException ex) { + logger.error("error getting session config"); + } + } + + @FXML + private void onTestMenuCharts(ActionEvent event) { + chartDialog.show(); + } + + @FXML + private void onTestMenuGeo(ActionEvent event) { + geoDialog.showDialog(); + } + + @Override + public void initialize(URL location, ResourceBundle resources) { + coreWebSocket = new CoreWebSocket(this); + configuration = ConfigUtils.load(); + String address = configuration.getCoreAddress(); + int port = configuration.getCorePort(); + logger.info("core connection: {}:{}", address, port); + connectDialog.setAddress(address); + connectDialog.setPort(port); + connectToCore(address, port); + + logger.info("controller initialize"); + swingNode.setContent(networkGraph.getGraphViewer()); + + // update graph preferences + networkGraph.updatePreferences(configuration); + + // set node types / default services + graphToolbar.setupNodeTypes(); + defaultServices = configuration.getNodeTypeConfigs().stream() + .collect(Collectors.toMap(NodeTypeConfig::getModel, NodeTypeConfig::getServices)); + + // set graph toolbar + borderPane.setLeft(graphToolbar); + + // setup snackbar + Toast.setSnackbarRoot(stackPane); + + // setup throughput menu item + throughputMenuItem.setOnAction(event -> executorService.submit(new ChangeThroughputTask())); + + // node details + networkGraph.getGraphViewer().getPickedVertexState().addItemListener(event -> { + CoreNode node = (CoreNode) event.getItem(); + logger.info("picked: {}", node.getName()); + if (event.getStateChange() == ItemEvent.SELECTED) { + Platform.runLater(() -> { + nodeDetails.setNode(node); + borderPane.setRight(nodeDetails); + }); + } else { + Platform.runLater(() -> borderPane.setRight(null)); + } + }); + + // edge details + networkGraph.getGraphViewer().getPickedEdgeState().addItemListener(event -> { + CoreLink link = (CoreLink) event.getItem(); + logger.info("picked: {} - {}", link.getNodeOne(), link.getNodeTwo()); + if (event.getStateChange() == ItemEvent.SELECTED) { + Platform.runLater(() -> { + linkDetails.setLink(link); + borderPane.setRight(linkDetails); + }); + } else { + Platform.runLater(() -> borderPane.setRight(null)); + } + }); + } + + private class ChangeThroughputTask extends Task { + @Override + protected Boolean call() throws Exception { + if (throughputMenuItem.isSelected()) { + return coreClient.startThroughput(); + } else { + return coreClient.stopThroughput(); + } + } + + @Override + protected void succeeded() { + if (getValue()) { + if (throughputMenuItem.isSelected()) { + networkGraph.setShowThroughput(true); + } else { + networkGraph.setShowThroughput(false); + networkGraph.getGraph().getEdges().forEach(edge -> edge.setThroughput(0)); + networkGraph.getGraphViewer().repaint(); + } + } else { + Toast.error("Failure changing throughput"); + } + } + + @Override + protected void failed() { + Toast.error("Error changing throughput", new RuntimeException(getException())); + } + } +} diff --git a/corefx/src/main/java/com/core/Main.java b/corefx/src/main/java/com/core/Main.java new file mode 100644 index 00000000..4834278f --- /dev/null +++ b/corefx/src/main/java/com/core/Main.java @@ -0,0 +1,72 @@ +package com.core; + +import com.core.utils.ConfigUtils; +import com.jfoenix.controls.JFXDecorator; +import com.jfoenix.svg.SVGGlyphLoader; +import javafx.application.Application; +import javafx.application.Platform; +import javafx.fxml.FXMLLoader; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.text.Font; +import javafx.stage.Stage; + +import java.nio.file.Path; +import java.nio.file.Paths; + +public class Main extends Application { + private static final Path LOG_FILE = Paths.get(System.getProperty("user.home"), ".core", "core.log"); + + @Override + public void start(Stage window) throws Exception { + // set core dir property for logging + System.setProperty("core_log", LOG_FILE.toString()); + + // check for and create gui home directory + ConfigUtils.checkHomeDirectory(); + + // load svg icons + SVGGlyphLoader.loadGlyphsFont(getClass().getResourceAsStream("/icons/icomoon_material.svg"), + "icomoon.svg"); + + // load font + Font.loadFont(getClass().getResourceAsStream("/font/roboto/Roboto-Regular.ttf"), 10); + + // load main fxml + FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/main.fxml")); + Parent root = loader.load(); + + // window decorator + JFXDecorator decorator = new JFXDecorator(window, root); + decorator.setCustomMaximize(true); + decorator.setMaximized(true); + decorator.setTitle("CORE"); + Image coreIcon = new Image(getClass().getResourceAsStream("/core-icon.png")); + decorator.setGraphic(new ImageView(coreIcon)); + window.getIcons().add(coreIcon); + + // create scene and set as current scene within window + Scene scene = new Scene(decorator); + scene.getStylesheets().add(getClass().getResource("/css/main.css").toExternalForm()); + window.setScene(scene); + + // update controller + Controller controller = loader.getController(); + controller.setApplication(this); + controller.setWindow(window); + controller.setDecorator(decorator); + + // configure window + window.setOnCloseRequest(event -> { + Platform.exit(); + System.exit(0); + }); + window.show(); + } + + public static void main(String[] args) { + launch(args); + } +} diff --git a/corefx/src/main/java/com/core/client/ICoreClient.java b/corefx/src/main/java/com/core/client/ICoreClient.java new file mode 100644 index 00000000..fa06395d --- /dev/null +++ b/corefx/src/main/java/com/core/client/ICoreClient.java @@ -0,0 +1,118 @@ +package com.core.client; + +import com.core.client.rest.ServiceFile; +import com.core.client.rest.WlanConfig; +import com.core.data.*; + +import java.io.File; +import java.io.IOException; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public interface ICoreClient { + void setConnection(String address, int port); + + boolean isLocalConnection(); + + Integer currentSession(); + + boolean startThroughput() throws IOException; + + boolean stopThroughput() throws IOException; + + void updateSession(Integer sessionId); + + void updateState(SessionState state); + + SessionOverview createSession() throws IOException; + + boolean deleteSession(Integer sessionId) throws IOException; + + List getSessions() throws IOException; + + Session getSession(Integer sessionId) throws IOException; + + boolean start(Collection nodes, Collection links, List hooks) throws IOException; + + boolean stop() throws IOException; + + boolean setState(SessionState state) throws IOException; + + Map> getServices() throws IOException; + + Map> getDefaultServices() throws IOException; + + boolean setDefaultServices(Map> defaults) throws IOException; + + CoreService getService(CoreNode node, String serviceName) throws IOException; + + boolean setService(CoreNode node, String serviceName, CoreService service) throws IOException; + + String getServiceFile(CoreNode node, String serviceName, String fileName) throws IOException; + + boolean startService(CoreNode node, String serviceName) throws IOException; + + boolean stopService(CoreNode node, String serviceName) throws IOException; + + boolean restartService(CoreNode node, String serviceName) throws IOException; + + boolean validateService(CoreNode node, String serviceName) throws IOException; + + boolean setServiceFile(CoreNode node, String serviceName, ServiceFile serviceFile) throws IOException; + + List getEmaneConfig(CoreNode node) throws IOException; + + List getEmaneModels() throws IOException; + + boolean setEmaneConfig(CoreNode node, List options) throws IOException; + + List getEmaneModelConfig(Integer id, String model) throws IOException; + + boolean setEmaneModelConfig(Integer id, String model, List options) throws IOException; + + boolean isRunning(); + + void saveSession(File file) throws IOException; + + SessionOverview openSession(File file) throws IOException; + + List getSessionConfig() throws IOException; + + boolean setSessionConfig(List configOptions) throws IOException; + + boolean createNode(CoreNode node) throws IOException; + + String nodeCommand(CoreNode node, String command) throws IOException; + + boolean editNode(CoreNode node) throws IOException; + + boolean deleteNode(CoreNode node) throws IOException; + + boolean createLink(CoreLink link) throws IOException; + + boolean editLink(CoreLink link) throws IOException; + + boolean createHook(Hook hook) throws IOException; + + List getHooks() throws IOException; + + WlanConfig getWlanConfig(CoreNode node) throws IOException; + + boolean setWlanConfig(CoreNode node, WlanConfig config) throws IOException; + + String getTerminalCommand(CoreNode node) throws IOException; + + Map getMobilityConfigs() throws IOException; + + boolean setMobilityConfig(CoreNode node, MobilityConfig config) throws IOException; + + MobilityConfig getMobilityConfig(CoreNode node) throws IOException; + + boolean mobilityAction(CoreNode node, String action) throws IOException; + + LocationConfig getLocationConfig() throws IOException; + + boolean setLocationConfig(LocationConfig config) throws IOException; +} diff --git a/corefx/src/main/java/com/core/client/grpc/CoreGrpcClient.java b/corefx/src/main/java/com/core/client/grpc/CoreGrpcClient.java new file mode 100644 index 00000000..fc759102 --- /dev/null +++ b/corefx/src/main/java/com/core/client/grpc/CoreGrpcClient.java @@ -0,0 +1,677 @@ +package com.core.client.grpc; + +import com.core.client.ICoreClient; +import com.core.client.rest.ServiceFile; +import com.core.client.rest.WlanConfig; +import com.core.data.*; +import com.google.protobuf.ByteString; +import io.grpc.ManagedChannel; +import io.grpc.ManagedChannelBuilder; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.File; +import java.io.IOException; +import java.util.*; + +public class CoreGrpcClient implements ICoreClient { + private static final Logger logger = LogManager.getLogger(); + private String address; + private int port; + private Integer sessionId; + private SessionState sessionState; + private CoreApiGrpc.CoreApiBlockingStub blockingStub; + private ManagedChannel channel; + + private CoreProto.Node nodeToProto(CoreNode node) { + CoreProto.Position position = CoreProto.Position.newBuilder() + .setX(node.getPosition().getX().floatValue()) + .setY(node.getPosition().getY().floatValue()) + .build(); + CoreProto.Node.Builder builder = CoreProto.Node.newBuilder() + .addAllServices(node.getServices()) + .setType(CoreProto.NodeType.forNumber(node.getType())) + .setPosition(position); + if (node.getId() != null) { + builder.setId(node.getId()); + } + if (node.getName() != null) { + builder.setName(node.getName()); + } + if (node.getEmane() != null) { + builder.setEmane(node.getEmane()); + } + if (node.getModel() != null) { + builder.setModel(node.getModel()); + } + if (node.getIcon() != null) { + builder.setIcon(node.getIcon()); + } + + return builder.build(); + } + + private CoreProto.LinkOptions linkOptionsToProto(CoreLinkOptions options) { + CoreProto.LinkOptions.Builder builder = CoreProto.LinkOptions.newBuilder(); + boolean unidirectional = false; + if (options.getUnidirectional() != null && options.getUnidirectional() == 1) { + unidirectional = true; + } + if (options.getBandwidth() != null) { + builder.setBandwidth(options.getBandwidth().floatValue()); + } + if (options.getBurst() != null) { + builder.setBurst(options.getBurst().floatValue()); + } + if (options.getDelay() != null) { + builder.setDelay(options.getDelay().floatValue()); + } + if (options.getDup() != null) { + builder.setDup(options.getDup().floatValue()); + } + if (options.getJitter() != null) { + builder.setJitter(options.getJitter().floatValue()); + } + if (options.getMburst() != null) { + builder.setMburst(options.getMburst().floatValue()); + } + if (options.getMer() != null) { + builder.setMer(options.getMer().floatValue()); + } + if (options.getPer() != null) { + builder.setPer(options.getPer().floatValue()); + } + if (options.getKey() != null) { + builder.setKey(options.getKey().toString()); + } + if (options.getOpaque() != null) { + builder.setOpaque(options.getOpaque()); + } + builder.setUnidirectional(unidirectional); + return builder.build(); + } + + private CoreProto.Interface interfaceToProto(CoreInterface coreInterface) { + CoreProto.Interface.Builder builder = CoreProto.Interface.newBuilder(); + if (coreInterface.getName() != null) { + builder.setName(coreInterface.getName()); + } + if (coreInterface.getMac() != null) { + builder.setMac(coreInterface.getMac()); + } + if (coreInterface.getIp4() != null) { + builder.setIp4(coreInterface.getIp4()); + } + if (coreInterface.getIp4Mask() != null) { + builder.setIp4Mask(coreInterface.getIp4Mask()); + } + if (coreInterface.getIp6() != null) { + builder.setIp6(coreInterface.getIp6()); + } + if (coreInterface.getIp6Mask() != null) { + builder.setIp6Mask(Integer.parseInt(coreInterface.getIp6Mask())); + } + return builder.build(); + } + + @Override + public void setConnection(String address, int port) { + this.address = address; + this.port = port; + logger.info("set connection: {}:{}", this.address, this.port); + channel = ManagedChannelBuilder.forAddress(this.address, this.port).usePlaintext().build(); + logger.info("channel: {}", channel); + blockingStub = CoreApiGrpc.newBlockingStub(channel); + logger.info("stub: {}", blockingStub); + } + + @Override + public boolean isLocalConnection() { + return address.equals("127.0.0.1") || address.equals("localhost"); + } + + @Override + public Integer currentSession() { + return sessionId; + } + + @Override + public boolean startThroughput() throws IOException { + return false; + } + + @Override + public boolean stopThroughput() throws IOException { + return false; + } + + @Override + public void updateSession(Integer sessionId) { + this.sessionId = sessionId; + } + + @Override + public void updateState(SessionState state) { + sessionState = state; + } + + @Override + public SessionOverview createSession() throws IOException { + CoreProto.CreateSessionRequest request = CoreProto.CreateSessionRequest.newBuilder().build(); + CoreProto.CreateSessionResponse response = blockingStub.createSession(request); + SessionOverview overview = new SessionOverview(); + overview.setId(response.getId()); + overview.setState(response.getStateValue()); + overview.setNodes(0); + return overview; + } + + @Override + public boolean deleteSession(Integer sessionId) throws IOException { + CoreProto.DeleteSessionRequest request = CoreProto.DeleteSessionRequest.newBuilder().setId(sessionId).build(); + return blockingStub.deleteSession(request).getResult(); + } + + @Override + public List getSessions() throws IOException { + CoreProto.GetSessionsRequest request = CoreProto.GetSessionsRequest.newBuilder().build(); + CoreProto.GetSessionsResponse response = blockingStub.getSessions(request); + List sessions = new ArrayList<>(); + for (CoreProto.SessionSummary summary : response.getSessionsList()) { + SessionOverview overview = new SessionOverview(); + overview.setId(summary.getId()); + overview.setNodes(summary.getNodes()); + overview.setState(summary.getStateValue()); + sessions.add(overview); + } + return sessions; + } + + @Override + public Session getSession(Integer sessionId) throws IOException { + CoreProto.GetSessionRequest request = CoreProto.GetSessionRequest.newBuilder().setId(sessionId).build(); + CoreProto.GetSessionResponse response = blockingStub.getSession(request); + Session session = new Session(); + for (CoreProto.Node protoNode : response.getSession().getNodesList()) { + CoreNode node = new CoreNode(protoNode.getId()); + node.setName(protoNode.getName()); + node.setEmane(protoNode.getEmane()); + node.setIcon(protoNode.getIcon()); + node.setModel(protoNode.getModel()); + node.setServices(new HashSet<>(protoNode.getServicesList())); + node.getPosition().setX((double) protoNode.getPosition().getX()); + node.getPosition().setY((double) protoNode.getPosition().getY()); + node.getPosition().setZ((double) protoNode.getPosition().getZ()); + node.setNodeType(NodeType.get(protoNode.getTypeValue())); + } + for (CoreProto.Link linkProto : response.getSession().getLinksList()) { + CoreLink link = new CoreLink(); + link.setNodeOne(linkProto.getNodeOne()); + link.setNodeTwo(linkProto.getNodeOne()); + CoreProto.Interface interfaceOneProto = linkProto.getInterfaceOne(); + CoreInterface interfaceOne = new CoreInterface(); + interfaceOne.setId(interfaceOneProto.getId()); + interfaceOne.setName(interfaceOneProto.getName()); + interfaceOne.setMac(interfaceOneProto.getMac()); + interfaceOne.setIp4(interfaceOneProto.getIp4()); + interfaceOne.setIp4Mask(interfaceOneProto.getIp4Mask()); + interfaceOne.setIp6(interfaceOneProto.getIp6()); + interfaceOne.setIp6Mask(Integer.toString(interfaceOneProto.getIp6Mask())); + link.setInterfaceOne(interfaceOne); + + CoreProto.Interface interfaceTwoProto = linkProto.getInterfaceTwo(); + CoreInterface interfaceTwo = new CoreInterface(); + interfaceTwo.setId(interfaceTwoProto.getId()); + interfaceTwo.setName(interfaceTwoProto.getName()); + interfaceTwo.setMac(interfaceTwoProto.getMac()); + interfaceTwo.setIp4(interfaceTwoProto.getIp4()); + interfaceTwo.setIp4Mask(interfaceTwoProto.getIp4Mask()); + interfaceTwo.setIp6(interfaceTwoProto.getIp6()); + interfaceTwo.setIp6Mask(Integer.toString(interfaceTwoProto.getIp6Mask())); + link.setInterfaceTwo(interfaceTwo); + + CoreLinkOptions options = new CoreLinkOptions(); + CoreProto.LinkOptions protoOptions = linkProto.getOptions(); + options.setBandwidth((double) protoOptions.getBandwidth()); + options.setDelay((double) protoOptions.getDelay()); + options.setDup((double) protoOptions.getDup()); + options.setJitter((double) protoOptions.getJitter()); + options.setPer((double) protoOptions.getPer()); + options.setBurst((double) protoOptions.getBurst()); + options.setKey(Integer.parseInt(protoOptions.getKey())); + options.setMburst((double) protoOptions.getMburst()); + options.setMer((double) protoOptions.getMer()); + options.setOpaque(protoOptions.getOpaque()); + options.setUnidirectional(protoOptions.getUnidirectional() ? 1 : 0); + link.setOptions(options); + } + session.setState(response.getSession().getStateValue()); + return session; + } + + @Override + public boolean start(Collection nodes, Collection links, List hooks) throws IOException { + boolean result = setState(SessionState.DEFINITION); + if (!result) { + return false; + } + + result = setState(SessionState.CONFIGURATION); + if (!result) { + return false; + } + + for (Hook hook : hooks) { + if (!createHook(hook)) { + return false; + } + } + + for (CoreNode node : nodes) { + // must pre-configure wlan nodes, if not already + if (node.getNodeType().getValue() == NodeType.WLAN) { + WlanConfig config = getWlanConfig(node); + setWlanConfig(node, config); + } + + if (!createNode(node)) { + return false; + } + } + + for (CoreLink link : links) { + if (!createLink(link)) { + return false; + } + } + + return setState(SessionState.INSTANTIATION); + } + + @Override + public boolean stop() throws IOException { + return setState(SessionState.SHUTDOWN); + } + + @Override + public boolean setState(SessionState state) throws IOException { + CoreProto.SetSessionStateRequest request = CoreProto.SetSessionStateRequest.newBuilder() + .setId(sessionId) + .setStateValue(state.getValue()) + .build(); + CoreProto.SetSessionStateResponse response = blockingStub.setSessionState(request); + return response.getResult(); + } + + @Override + public Map> getServices() throws IOException { + CoreProto.GetServicesRequest request = CoreProto.GetServicesRequest.newBuilder().build(); + CoreProto.GetServicesResponse response = blockingStub.getServices(request); + Map> servicesMap = new HashMap<>(); + for (CoreProto.Service protoService : response.getServicesList()) { + List services = servicesMap.computeIfAbsent(protoService.getGroup(), x -> new ArrayList<>()); + services.add(protoService.getName()); + } + return servicesMap; + } + + @Override + public Map> getDefaultServices() throws IOException { + CoreProto.GetServiceDefaultsRequest request = CoreProto.GetServiceDefaultsRequest.newBuilder().build(); + CoreProto.GetServiceDefaultsResponse response = blockingStub.getServiceDefaults(request); + Map> servicesMap = new HashMap<>(); + for (CoreProto.ServiceDefaults serviceDefaults : response.getDefaultsList()) { + servicesMap.put(serviceDefaults.getNodeType(), serviceDefaults.getServicesList()); + } + return servicesMap; + } + + @Override + public boolean setDefaultServices(Map> defaults) throws IOException { + List allDefaults = new ArrayList<>(); + for (Map.Entry> entry : defaults.entrySet()) { + String nodeType = entry.getKey(); + Set services = entry.getValue(); + CoreProto.ServiceDefaults serviceDefaults = CoreProto.ServiceDefaults.newBuilder() + .setNodeType(nodeType) + .addAllServices(services) + .build(); + allDefaults.add(serviceDefaults); + } + + CoreProto.SetServiceDefaultsRequest request = CoreProto.SetServiceDefaultsRequest.newBuilder() + .setSession(sessionId) + .addAllDefaults(allDefaults) + .build(); + CoreProto.SetServiceDefaultsResponse response = blockingStub.setServiceDefaults(request); + return response.getResult(); + } + + @Override + public CoreService getService(CoreNode node, String serviceName) throws IOException { + CoreProto.GetNodeServiceRequest request = CoreProto.GetNodeServiceRequest.newBuilder().build(); + CoreProto.GetNodeServiceResponse response = blockingStub.getNodeService(request); + CoreProto.NodeServiceData nodeServiceData = response.getService(); + CoreService service = new CoreService(); + service.setShutdown(nodeServiceData.getShutdownList()); + service.setStartup(nodeServiceData.getStartupList()); + service.setValidate(nodeServiceData.getValidateList()); + service.setConfigs(nodeServiceData.getConfigsList()); + service.setDependencies(nodeServiceData.getDependenciesList()); + service.setDirs(nodeServiceData.getDirsList()); + service.setExecutables(nodeServiceData.getExecutablesList()); + service.setMeta(nodeServiceData.getMeta()); + service.setValidationMode(nodeServiceData.getValidationMode().name()); + service.setValidationTimer(Integer.toString(nodeServiceData.getValidationTimer())); + return service; + } + + @Override + public boolean setService(CoreNode node, String serviceName, CoreService service) throws IOException { + CoreProto.SetNodeServiceRequest request = CoreProto.SetNodeServiceRequest.newBuilder() + .setId(node.getId()) + .setSession(sessionId) + .setService(serviceName) + .build(); + request.getShutdownList().addAll(service.getShutdown()); + request.getValidateList().addAll(service.getValidate()); + request.getStartupList().addAll(service.getStartup()); + return blockingStub.setNodeService(request).getResult(); + } + + @Override + public String getServiceFile(CoreNode node, String serviceName, String fileName) throws IOException { + CoreProto.GetNodeServiceFileRequest request = CoreProto.GetNodeServiceFileRequest.newBuilder() + .setSession(sessionId) + .setId(node.getId()) + .setService(serviceName) + .build(); + CoreProto.GetNodeServiceFileResponse response = blockingStub.getNodeServiceFile(request); + return response.getData().toStringUtf8(); + } + + @Override + public boolean startService(CoreNode node, String serviceName) throws IOException { + CoreProto.ServiceActionRequest request = CoreProto.ServiceActionRequest.newBuilder() + .setSession(sessionId) + .setId(node.getId()) + .setService(serviceName) + .setAction(CoreProto.ServiceAction.SERVICE_START) + .build(); + return blockingStub.serviceAction(request).getResult(); + } + + @Override + public boolean stopService(CoreNode node, String serviceName) throws IOException { + CoreProto.ServiceActionRequest request = CoreProto.ServiceActionRequest.newBuilder() + .setSession(sessionId) + .setId(node.getId()) + .setService(serviceName) + .setAction(CoreProto.ServiceAction.SERVICE_STOP) + .build(); + return blockingStub.serviceAction(request).getResult(); + } + + @Override + public boolean restartService(CoreNode node, String serviceName) throws IOException { + CoreProto.ServiceActionRequest request = CoreProto.ServiceActionRequest.newBuilder() + .setSession(sessionId) + .setId(node.getId()) + .setService(serviceName) + .setAction(CoreProto.ServiceAction.SERVICE_RESTART) + .build(); + return blockingStub.serviceAction(request).getResult(); + } + + @Override + public boolean validateService(CoreNode node, String serviceName) throws IOException { + CoreProto.ServiceActionRequest request = CoreProto.ServiceActionRequest.newBuilder() + .setSession(sessionId) + .setId(node.getId()) + .setService(serviceName) + .setAction(CoreProto.ServiceAction.SERVICE_VALIDATE) + .build(); + return blockingStub.serviceAction(request).getResult(); + } + + @Override + public boolean setServiceFile(CoreNode node, String serviceName, ServiceFile serviceFile) throws IOException { + CoreProto.SetNodeServiceFileRequest request = CoreProto.SetNodeServiceFileRequest.newBuilder() + .setSession(sessionId) + .setId(node.getId()) + .setService(serviceName) + .setFile(serviceFile.getName()) + .setData(ByteString.copyFromUtf8(serviceFile.getData())) + .build(); + CoreProto.SetNodeServiceFileResponse response = blockingStub.setNodeServiceFile(request); + return response.getResult(); + } + + @Override + public List getEmaneConfig(CoreNode node) throws IOException { + return null; + } + + @Override + public List getEmaneModels() throws IOException { + return null; + } + + @Override + public boolean setEmaneConfig(CoreNode node, List options) throws IOException { + return false; + } + + @Override + public List getEmaneModelConfig(Integer id, String model) throws IOException { + return null; + } + + @Override + public boolean setEmaneModelConfig(Integer id, String model, List options) throws IOException { + return false; + } + + @Override + public boolean isRunning() { + return sessionState == SessionState.RUNTIME; + } + + @Override + public void saveSession(File file) throws IOException { + + } + + @Override + public SessionOverview openSession(File file) throws IOException { + return null; + } + + @Override + public List getSessionConfig() throws IOException { + return null; + } + + @Override + public boolean setSessionConfig(List configOptions) throws IOException { + return false; + } + + @Override + public boolean createNode(CoreNode node) throws IOException { + CoreProto.Node protoNode = nodeToProto(node); + CoreProto.AddNodeRequest request = CoreProto.AddNodeRequest.newBuilder() + .setSession(sessionId) + .setNode(protoNode) + .build(); + blockingStub.addNode(request); + return true; + } + + @Override + public String nodeCommand(CoreNode node, String command) throws IOException { + return null; + } + + @Override + public boolean editNode(CoreNode node) throws IOException { + CoreProto.Position position = CoreProto.Position.newBuilder() + .setX(node.getPosition().getX().floatValue()) + .setY(node.getPosition().getY().floatValue()) + .build(); + CoreProto.EditNodeRequest request = CoreProto.EditNodeRequest.newBuilder() + .setSession(sessionId) + .setId(node.getId()) + .setPosition(position) + .build(); + CoreProto.EditNodeResponse response = blockingStub.editNode(request); + return response.getResult(); + } + + @Override + public boolean deleteNode(CoreNode node) throws IOException { + CoreProto.DeleteNodeRequest request = CoreProto.DeleteNodeRequest.newBuilder() + .build(); + CoreProto.DeleteNodeResponse response = blockingStub.deleteNode(request); + return response.getResult(); + } + + @Override + public boolean createLink(CoreLink link) throws IOException { + CoreProto.Link.Builder builder = CoreProto.Link.newBuilder() + .setTypeValue(link.getType()); + if (link.getNodeOne() != null) { + builder.setNodeOne(link.getNodeOne()); + } + if (link.getNodeTwo() != null) { + builder.setNodeTwo(link.getNodeTwo()); + } + if (link.getInterfaceOne() != null) { + builder.setInterfaceOne(interfaceToProto(link.getInterfaceOne())); + } + if (link.getInterfaceTwo() != null) { + builder.setInterfaceTwo(interfaceToProto(link.getInterfaceTwo())); + } + if (link.getOptions() != null) { + builder.setOptions(linkOptionsToProto(link.getOptions())); + } + CoreProto.Link protoLink = builder.build(); + CoreProto.AddLinkRequest request = CoreProto.AddLinkRequest.newBuilder() + .setSession(sessionId) + .setLink(protoLink) + .build(); + CoreProto.AddLinkResponse response = blockingStub.addLink(request); + return response.getResult(); + } + + @Override + public boolean editLink(CoreLink link) throws IOException { + CoreProto.EditLinkRequest.Builder builder = CoreProto.EditLinkRequest.newBuilder() + .setSession(sessionId); + if (link.getNodeOne() != null) { + builder.setNodeOne(link.getNodeOne()); + } + if (link.getNodeTwo() != null) { + builder.setNodeTwo(link.getNodeTwo()); + } + if (link.getInterfaceOne() != null) { + builder.setInterfaceOne(link.getInterfaceOne().getId()); + } + if (link.getInterfaceTwo() != null) { + builder.setInterfaceTwo(link.getInterfaceTwo().getId()); + } + if (link.getOptions() != null) { + CoreProto.LinkOptions protoOptions = linkOptionsToProto(link.getOptions()); + builder.setOptions(protoOptions); + } + CoreProto.EditLinkRequest request = builder.build(); + CoreProto.EditLinkResponse response = blockingStub.editLink(request); + return response.getResult(); + } + + @Override + public boolean createHook(Hook hook) throws IOException { + CoreProto.Hook hookProto = CoreProto.Hook.newBuilder() + .setStateValue(hook.getState()) + .setData(ByteString.copyFromUtf8(hook.getData())) + .setFile(hook.getFile()) + .build(); + CoreProto.AddHookRequest request = CoreProto.AddHookRequest.newBuilder() + .setHook(hookProto) + .build(); + CoreProto.AddHookResponse response = blockingStub.addHook(request); + return response.getResult(); + } + + @Override + public List getHooks() throws IOException { + CoreProto.GetHooksRequest request = CoreProto.GetHooksRequest.newBuilder().setSession(sessionId).build(); + CoreProto.GetHooksResponse response = blockingStub.getHooks(request); + List hooks = new ArrayList<>(); + for (CoreProto.Hook protoHook : response.getHooksList()) { + Hook hook = new Hook(); + hook.setFile(protoHook.getFile()); + hook.setData(protoHook.getData().toStringUtf8()); + hook.setState(protoHook.getStateValue()); + hooks.add(hook); + } + return hooks; + } + + @Override + public WlanConfig getWlanConfig(CoreNode node) throws IOException { + return null; + } + + @Override + public boolean setWlanConfig(CoreNode node, WlanConfig config) throws IOException { + return false; + } + + @Override + public String getTerminalCommand(CoreNode node) throws IOException { + return null; + } + + @Override + public Map getMobilityConfigs() throws IOException { + CoreProto.GetMobilityConfigsRequest request = CoreProto.GetMobilityConfigsRequest.newBuilder() + .setSession(sessionId).build(); + CoreProto.GetMobilityConfigsResponse response = blockingStub.getMobilityConfigs(request); + + Map mobilityConfigs = new HashMap<>(); + for (Integer nodeId : response.getConfigsMap().keySet()) { + CoreProto.GetMobilityConfigsResponse.MobilityConfig protoMobilityConfig = response.getConfigsMap() + .get(nodeId); + MobilityConfig mobilityConfig = new MobilityConfig(); + CoreProto.ConfigGroup configGroup = protoMobilityConfig.getGroups(0); + mobilityConfigs.put(nodeId, mobilityConfig); + } + return mobilityConfigs; + } + + @Override + public boolean setMobilityConfig(CoreNode node, MobilityConfig config) throws IOException { + return false; + } + + @Override + public MobilityConfig getMobilityConfig(CoreNode node) throws IOException { + return null; + } + + @Override + public boolean mobilityAction(CoreNode node, String action) throws IOException { + return false; + } + + @Override + public LocationConfig getLocationConfig() throws IOException { + return null; + } + + @Override + public boolean setLocationConfig(LocationConfig config) throws IOException { + return false; + } +} diff --git a/corefx/src/main/java/com/core/client/rest/CoreRestClient.java b/corefx/src/main/java/com/core/client/rest/CoreRestClient.java new file mode 100644 index 00000000..3e963583 --- /dev/null +++ b/corefx/src/main/java/com/core/client/rest/CoreRestClient.java @@ -0,0 +1,415 @@ +package com.core.client.rest; + +import com.core.client.ICoreClient; +import com.core.data.*; +import com.core.utils.WebUtils; +import lombok.Data; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.File; +import java.io.IOException; +import java.util.*; + +@Data +public class CoreRestClient implements ICoreClient { + private static final Logger logger = LogManager.getLogger(); + private String address; + private int port; + private Integer sessionId; + private SessionState sessionState; + + @Override + public void setConnection(String address, int port) { + this.address = address; + this.port = port; + } + + @Override + public boolean isLocalConnection() { + return address.equals("127.0.0.1") || address.equals("localhost"); + } + + @Override + public Integer currentSession() { + return sessionId; + } + + @Override + public void updateState(SessionState state) { + sessionState = state; + } + + @Override + public void updateSession(Integer sessionId) { + this.sessionId = sessionId; + } + + private String getUrl(String path) { + return String.format("http://%s:%s/%s", address, port, path); + } + + @Override + public SessionOverview createSession() throws IOException { + String url = getUrl("sessions"); + return WebUtils.post(url, SessionOverview.class); + } + + @Override + public boolean deleteSession(Integer sessionId) throws IOException { + String path = String.format("sessions/%s", sessionId); + String url = getUrl(path); + return WebUtils.delete(url); + } + + public Map> getServices() throws IOException { + String url = getUrl("services"); + GetServices getServices = WebUtils.getJson(url, GetServices.class); + return getServices.getGroups(); + } + + @Override + public Session getSession(Integer sessionId) throws IOException { + String path = String.format("sessions/%s", sessionId); + String url = getUrl(path); + return WebUtils.getJson(url, Session.class); + } + + @Override + public List getSessions() throws IOException { + String url = getUrl("sessions"); + GetSessions getSessions = WebUtils.getJson(url, GetSessions.class); + return getSessions.getSessions(); + } + + @Override + public boolean start(Collection nodes, Collection links, List hooks) throws IOException { + boolean result = setState(SessionState.DEFINITION); + if (!result) { + return false; + } + + result = setState(SessionState.CONFIGURATION); + if (!result) { + return false; + } + + for (Hook hook : hooks) { + if (!createHook(hook)) { + return false; + } + } + + for (CoreNode node : nodes) { + // must pre-configure wlan nodes, if not already + if (node.getNodeType().getValue() == NodeType.WLAN) { + WlanConfig config = getWlanConfig(node); + setWlanConfig(node, config); + } + + if (!createNode(node)) { + return false; + } + } + + for (CoreLink link : links) { + if (!createLink(link)) { + return false; + } + } + + return setState(SessionState.INSTANTIATION); + } + + @Override + public boolean stop() throws IOException { + return setState(SessionState.SHUTDOWN); + } + + @Override + public boolean setState(SessionState state) throws IOException { + String url = getUrl(String.format("sessions/%s/state", sessionId)); + Map data = new HashMap<>(); + data.put("state", state.getValue()); + boolean result = WebUtils.putJson(url, data); + + if (result) { + sessionState = state; + } + return result; + } + + private boolean uploadFile(File file) throws IOException { + String url = getUrl("upload"); + return WebUtils.postFile(url, file); + } + + @Override + public boolean startThroughput() throws IOException { + String url = getUrl("throughput/start"); + return WebUtils.putJson(url); + } + + @Override + public boolean stopThroughput() throws IOException { + String url = getUrl("throughput/stop"); + return WebUtils.putJson(url); + } + + @Override + public Map> getDefaultServices() throws IOException { + String url = getUrl(String.format("sessions/%s/services/default", sessionId)); + GetDefaultServices getDefaultServices = WebUtils.getJson(url, GetDefaultServices.class); + return getDefaultServices.getDefaults(); + } + + @Override + public boolean setDefaultServices(Map> defaults) throws IOException { + String url = getUrl(String.format("sessions/%s/services/default", sessionId)); + return WebUtils.postJson(url, defaults); + } + + @Override + public CoreService getService(CoreNode node, String serviceName) throws IOException { + String url = getUrl(String.format("sessions/%s/nodes/%s/services/%s", sessionId, node.getId(), serviceName)); + return WebUtils.getJson(url, CoreService.class); + } + + @Override + public boolean setService(CoreNode node, String serviceName, CoreService service) throws IOException { + String url = getUrl(String.format("sessions/%s/nodes/%s/services/%s", sessionId, node.getId(), serviceName)); + return WebUtils.putJson(url, service); + } + + @Override + public String getServiceFile(CoreNode node, String serviceName, String fileName) throws IOException { + String url = getUrl(String.format("sessions/%s/nodes/%s/services/%s/file", sessionId, node.getId(), + serviceName)); + Map args = new HashMap<>(); + args.put("file", fileName); + return WebUtils.getJson(url, String.class, args); + } + + @Override + public boolean startService(CoreNode node, String serviceName) throws IOException { + String url = getUrl(String.format("sessions/%s/nodes/%s/services/%s/start", sessionId, node.getId(), + serviceName)); + return WebUtils.putJson(url); + } + + @Override + public boolean stopService(CoreNode node, String serviceName) throws IOException { + String url = getUrl(String.format("sessions/%s/nodes/%s/services/%s/stop", sessionId, node.getId(), + serviceName)); + return WebUtils.putJson(url); + } + + @Override + public boolean restartService(CoreNode node, String serviceName) throws IOException { + String url = getUrl(String.format("sessions/%s/nodes/%s/services/%s/restart", sessionId, node.getId(), + serviceName)); + return WebUtils.putJson(url); + } + + @Override + public boolean validateService(CoreNode node, String serviceName) throws IOException { + String url = getUrl(String.format("sessions/%s/nodes/%s/services/%s/validate", sessionId, node.getId(), + serviceName)); + return WebUtils.putJson(url); + } + + @Override + public boolean setServiceFile(CoreNode node, String serviceName, ServiceFile serviceFile) throws IOException { + String url = getUrl(String.format("sessions/%s/nodes/%s/services/%s/file", sessionId, node.getId(), + serviceName)); + return WebUtils.putJson(url, serviceFile); + } + + @Override + public List getEmaneModels() throws IOException { + String url = getUrl(String.format("sessions/%s/emane/models", sessionId)); + GetEmaneModels getEmaneModels = WebUtils.getJson(url, GetEmaneModels.class); + return getEmaneModels.getModels(); + } + + @Override + public List getEmaneModelConfig(Integer id, String model) throws IOException { + String url = getUrl(String.format("sessions/%s/emane/model/config", sessionId)); + Map args = new HashMap<>(); + args.put("node", id.toString()); + args.put("name", model); + GetConfig getConfig = WebUtils.getJson(url, GetConfig.class, args); + return getConfig.getGroups(); + } + + @Override + public List getEmaneConfig(CoreNode node) throws IOException { + String url = getUrl(String.format("sessions/%s/emane/config", sessionId)); + Map args = new HashMap<>(); + args.put("node", node.getId().toString()); + GetConfig getConfig = WebUtils.getJson(url, GetConfig.class, args); + return getConfig.getGroups(); + } + + @Override + public boolean setEmaneConfig(CoreNode node, List options) throws IOException { + String url = getUrl(String.format("sessions/%s/emane/config", sessionId)); + SetEmaneConfig setEmaneConfig = new SetEmaneConfig(); + setEmaneConfig.setNode(node.getId()); + setEmaneConfig.setValues(options); + return WebUtils.putJson(url, setEmaneConfig); + } + + @Override + public boolean setEmaneModelConfig(Integer id, String model, List options) throws IOException { + String url = getUrl(String.format("sessions/%s/emane/model/config", sessionId)); + SetEmaneModelConfig setEmaneModelConfig = new SetEmaneModelConfig(); + setEmaneModelConfig.setNode(id); + setEmaneModelConfig.setName(model); + setEmaneModelConfig.setValues(options); + return WebUtils.putJson(url, setEmaneModelConfig); + } + + @Override + public boolean isRunning() { + return sessionState == SessionState.RUNTIME; + } + + @Override + public void saveSession(File file) throws IOException { + String path = String.format("sessions/%s/xml", sessionId); + String url = getUrl(path); + WebUtils.getFile(url, file); + } + + @Override + public SessionOverview openSession(File file) throws IOException { + String url = getUrl("sessions/xml"); + return WebUtils.postFile(url, file, SessionOverview.class); + } + + @Override + public List getSessionConfig() throws IOException { + String url = getUrl(String.format("sessions/%s/options", sessionId)); + GetConfig getConfig = WebUtils.getJson(url, GetConfig.class); + return getConfig.getGroups(); + } + + @Override + public boolean setSessionConfig(List configOptions) throws IOException { + String url = getUrl(String.format("sessions/%s/options", sessionId)); + SetConfig setConfig = new SetConfig(configOptions); + return WebUtils.putJson(url, setConfig); + } + + @Override + public LocationConfig getLocationConfig() throws IOException { + String url = getUrl(String.format("sessions/%s/location", sessionId)); + return WebUtils.getJson(url, LocationConfig.class); + } + + @Override + public boolean setLocationConfig(LocationConfig config) throws IOException { + String url = getUrl(String.format("sessions/%s/location", sessionId)); + return WebUtils.putJson(url, config); + } + + @Override + public String nodeCommand(CoreNode node, String command) throws IOException { + String url = getUrl(String.format("sessions/%s/nodes/%s/command", sessionId, node.getId())); + return WebUtils.putJson(url, command, String.class); + } + + @Override + public boolean createNode(CoreNode node) throws IOException { + String url = getUrl(String.format("sessions/%s/nodes", sessionId)); + return WebUtils.postJson(url, node); + } + + + @Override + public boolean editNode(CoreNode node) throws IOException { + String url = getUrl(String.format("sessions/%s/nodes/%s", sessionId, node.getId())); + return WebUtils.putJson(url, node); + } + + @Override + public boolean deleteNode(CoreNode node) throws IOException { + String url = getUrl(String.format("/sessions/%s/nodes/%s", sessionId, node.getId())); + return WebUtils.delete(url); + } + + @Override + public boolean createLink(CoreLink link) throws IOException { + String url = getUrl(String.format("sessions/%s/links", sessionId)); + return WebUtils.postJson(url, link); + } + + @Override + public boolean editLink(CoreLink link) throws IOException { + String url = getUrl(String.format("sessions/%s/links", sessionId)); + return WebUtils.putJson(url, link); + } + + @Override + public boolean createHook(Hook hook) throws IOException { + String url = getUrl(String.format("sessions/%s/hooks", sessionId)); + return WebUtils.postJson(url, hook); + } + + @Override + public List getHooks() throws IOException { + String url = getUrl(String.format("sessions/%s/hooks", sessionId)); + GetHooks getHooks = WebUtils.getJson(url, GetHooks.class); + return getHooks.getHooks(); + } + + @Override + public WlanConfig getWlanConfig(CoreNode node) throws IOException { + String url = getUrl(String.format("sessions/%s/nodes/%s/wlan", sessionId, node.getId())); + return WebUtils.getJson(url, WlanConfig.class); + } + + @Override + public boolean setWlanConfig(CoreNode node, WlanConfig config) throws IOException { + String url = getUrl(String.format("sessions/%s/nodes/%s/wlan", sessionId, node.getId())); + return WebUtils.putJson(url, config); + } + + @Override + public String getTerminalCommand(CoreNode node) throws IOException { + String url = getUrl(String.format("sessions/%s/nodes/%s/terminal", sessionId, node.getId())); + return WebUtils.getJson(url, String.class); + } + + @Override + public boolean setMobilityConfig(CoreNode node, MobilityConfig config) throws IOException { + boolean uploaded = uploadFile(config.getScriptFile()); + if (!uploaded) { + throw new IOException("failed to upload mobility script"); + } + + String url = getUrl(String.format("sessions/%s/nodes/%s/mobility", sessionId, node.getId())); + config.setFile(config.getScriptFile().getName()); + return WebUtils.postJson(url, config); + } + + @Override + public Map getMobilityConfigs() throws IOException { + String url = getUrl(String.format("sessions/%s/mobility/configs", sessionId)); + GetMobilityConfigs getMobilityConfigs = WebUtils.getJson(url, GetMobilityConfigs.class); + return getMobilityConfigs.getConfigurations(); + } + + @Override + public MobilityConfig getMobilityConfig(CoreNode node) throws IOException { + String url = getUrl(String.format("sessions/%s/nodes/%s/mobility", sessionId, node.getId())); + return WebUtils.getJson(url, MobilityConfig.class); + } + + @Override + public boolean mobilityAction(CoreNode node, String action) throws IOException { + String url = getUrl(String.format("sessions/%s/nodes/%s/mobility/%s", sessionId, node.getId(), action)); + return WebUtils.putJson(url); + } +} diff --git a/corefx/src/main/java/com/core/client/rest/GetConfig.java b/corefx/src/main/java/com/core/client/rest/GetConfig.java new file mode 100644 index 00000000..61a843b6 --- /dev/null +++ b/corefx/src/main/java/com/core/client/rest/GetConfig.java @@ -0,0 +1,12 @@ +package com.core.client.rest; + +import com.core.data.ConfigGroup; +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +@Data +public class GetConfig { + private List groups = new ArrayList<>(); +} diff --git a/corefx/src/main/java/com/core/client/rest/GetDefaultServices.java b/corefx/src/main/java/com/core/client/rest/GetDefaultServices.java new file mode 100644 index 00000000..b134cbc0 --- /dev/null +++ b/corefx/src/main/java/com/core/client/rest/GetDefaultServices.java @@ -0,0 +1,16 @@ +package com.core.client.rest; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class GetDefaultServices { + private Map> defaults = new HashMap<>(); +} diff --git a/corefx/src/main/java/com/core/client/rest/GetEmaneModels.java b/corefx/src/main/java/com/core/client/rest/GetEmaneModels.java new file mode 100644 index 00000000..43cef1c6 --- /dev/null +++ b/corefx/src/main/java/com/core/client/rest/GetEmaneModels.java @@ -0,0 +1,11 @@ +package com.core.client.rest; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +@Data +public class GetEmaneModels { + private List models = new ArrayList<>(); +} diff --git a/corefx/src/main/java/com/core/client/rest/GetHooks.java b/corefx/src/main/java/com/core/client/rest/GetHooks.java new file mode 100644 index 00000000..fe26e828 --- /dev/null +++ b/corefx/src/main/java/com/core/client/rest/GetHooks.java @@ -0,0 +1,12 @@ +package com.core.client.rest; + +import com.core.data.Hook; +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +@Data +public class GetHooks { + private List hooks = new ArrayList<>(); +} diff --git a/corefx/src/main/java/com/core/client/rest/GetMobilityConfigs.java b/corefx/src/main/java/com/core/client/rest/GetMobilityConfigs.java new file mode 100644 index 00000000..e5905b7e --- /dev/null +++ b/corefx/src/main/java/com/core/client/rest/GetMobilityConfigs.java @@ -0,0 +1,12 @@ +package com.core.client.rest; + +import com.core.data.MobilityConfig; +import lombok.Data; + +import java.util.HashMap; +import java.util.Map; + +@Data +public class GetMobilityConfigs { + private Map configurations = new HashMap<>(); +} diff --git a/corefx/src/main/java/com/core/client/rest/GetServices.java b/corefx/src/main/java/com/core/client/rest/GetServices.java new file mode 100644 index 00000000..17e04ddc --- /dev/null +++ b/corefx/src/main/java/com/core/client/rest/GetServices.java @@ -0,0 +1,13 @@ +package com.core.client.rest; + +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; +import java.util.Map; + +@Data +@NoArgsConstructor +public class GetServices { + private Map> groups; +} diff --git a/corefx/src/main/java/com/core/client/rest/GetSessions.java b/corefx/src/main/java/com/core/client/rest/GetSessions.java new file mode 100644 index 00000000..674fc83a --- /dev/null +++ b/corefx/src/main/java/com/core/client/rest/GetSessions.java @@ -0,0 +1,14 @@ +package com.core.client.rest; + +import com.core.data.SessionOverview; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.ArrayList; +import java.util.List; + +@Data +@NoArgsConstructor +public class GetSessions { + private List sessions = new ArrayList<>(); +} diff --git a/corefx/src/main/java/com/core/client/rest/ServiceFile.java b/corefx/src/main/java/com/core/client/rest/ServiceFile.java new file mode 100644 index 00000000..977ed984 --- /dev/null +++ b/corefx/src/main/java/com/core/client/rest/ServiceFile.java @@ -0,0 +1,13 @@ +package com.core.client.rest; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class ServiceFile { + private String name; + private String data; +} diff --git a/corefx/src/main/java/com/core/client/rest/SetConfig.java b/corefx/src/main/java/com/core/client/rest/SetConfig.java new file mode 100644 index 00000000..0d3f11ea --- /dev/null +++ b/corefx/src/main/java/com/core/client/rest/SetConfig.java @@ -0,0 +1,16 @@ +package com.core.client.rest; + +import com.core.data.ConfigOption; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.ArrayList; +import java.util.List; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class SetConfig { + private List values = new ArrayList<>(); +} diff --git a/corefx/src/main/java/com/core/client/rest/SetEmaneConfig.java b/corefx/src/main/java/com/core/client/rest/SetEmaneConfig.java new file mode 100644 index 00000000..ee947c51 --- /dev/null +++ b/corefx/src/main/java/com/core/client/rest/SetEmaneConfig.java @@ -0,0 +1,13 @@ +package com.core.client.rest; + +import com.core.data.ConfigOption; +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +@Data +public class SetEmaneConfig { + private Integer node; + private List values = new ArrayList<>(); +} diff --git a/corefx/src/main/java/com/core/client/rest/SetEmaneModelConfig.java b/corefx/src/main/java/com/core/client/rest/SetEmaneModelConfig.java new file mode 100644 index 00000000..e5c37092 --- /dev/null +++ b/corefx/src/main/java/com/core/client/rest/SetEmaneModelConfig.java @@ -0,0 +1,14 @@ +package com.core.client.rest; + +import com.core.data.ConfigOption; +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +@Data +public class SetEmaneModelConfig { + private Integer node; + private String name; + private List values = new ArrayList<>(); +} diff --git a/corefx/src/main/java/com/core/client/rest/WlanConfig.java b/corefx/src/main/java/com/core/client/rest/WlanConfig.java new file mode 100644 index 00000000..45b9805f --- /dev/null +++ b/corefx/src/main/java/com/core/client/rest/WlanConfig.java @@ -0,0 +1,12 @@ +package com.core.client.rest; + +import lombok.Data; + +@Data +public class WlanConfig { + private String range; + private String bandwidth; + private String jitter; + private String delay; + private String error; +} diff --git a/corefx/src/main/java/com/core/data/BridgeThroughput.java b/corefx/src/main/java/com/core/data/BridgeThroughput.java new file mode 100644 index 00000000..167fb402 --- /dev/null +++ b/corefx/src/main/java/com/core/data/BridgeThroughput.java @@ -0,0 +1,9 @@ +package com.core.data; + +import lombok.Data; + +@Data +public class BridgeThroughput { + private int node; + private Double throughput; +} diff --git a/corefx/src/main/java/com/core/data/ConfigDataType.java b/corefx/src/main/java/com/core/data/ConfigDataType.java new file mode 100644 index 00000000..3dd44864 --- /dev/null +++ b/corefx/src/main/java/com/core/data/ConfigDataType.java @@ -0,0 +1,40 @@ +package com.core.data; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +public enum ConfigDataType { + UINT8(1), + UINT16(2), + UINT32(3), + UINT64(4), + INT8(5), + INT16(6), + INT32(7), + INT64(8), + FLOAT(9), + STRING(10), + BOOL(11); + + private static final Map LOOKUP = new HashMap<>(); + + static { + Arrays.stream(ConfigDataType.values()).forEach(x -> LOOKUP.put(x.getValue(), x)); + } + + private final int value; + + ConfigDataType(int value) { + this.value = value; + } + + public int getValue() { + return value; + } + + + public static ConfigDataType get(int value) { + return LOOKUP.get(value); + } +} diff --git a/corefx/src/main/java/com/core/data/ConfigGroup.java b/corefx/src/main/java/com/core/data/ConfigGroup.java new file mode 100644 index 00000000..9b45bdb7 --- /dev/null +++ b/corefx/src/main/java/com/core/data/ConfigGroup.java @@ -0,0 +1,12 @@ +package com.core.data; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +@Data +public class ConfigGroup { + private String name; + private List options = new ArrayList<>(); +} diff --git a/corefx/src/main/java/com/core/data/ConfigOption.java b/corefx/src/main/java/com/core/data/ConfigOption.java new file mode 100644 index 00000000..77af265e --- /dev/null +++ b/corefx/src/main/java/com/core/data/ConfigOption.java @@ -0,0 +1,15 @@ +package com.core.data; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +@Data +public class ConfigOption { + private String label; + private String name; + private String value; + private Integer type; + private List select = new ArrayList<>(); +} diff --git a/corefx/src/main/java/com/core/data/CoreEvent.java b/corefx/src/main/java/com/core/data/CoreEvent.java new file mode 100644 index 00000000..9ac8ed03 --- /dev/null +++ b/corefx/src/main/java/com/core/data/CoreEvent.java @@ -0,0 +1,19 @@ +package com.core.data; + +import com.fasterxml.jackson.annotation.JsonSetter; +import lombok.Data; + +@Data +public class CoreEvent { + private Integer session; + private Integer node; + private String name; + private Double time; + private EventType eventType; + private String data; + + @JsonSetter("event_type") + public void setEventType(int value) { + eventType = EventType.get(value); + } +} diff --git a/corefx/src/main/java/com/core/data/CoreInterface.java b/corefx/src/main/java/com/core/data/CoreInterface.java new file mode 100644 index 00000000..f159f10e --- /dev/null +++ b/corefx/src/main/java/com/core/data/CoreInterface.java @@ -0,0 +1,19 @@ +package com.core.data; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +public class CoreInterface { + private Integer id; + private String name; + private String mac; + private String ip4; + @JsonProperty("ip4mask") + private Integer ip4Mask; + private String ip6; + @JsonProperty("ip6mask") + private String ip6Mask; +} diff --git a/corefx/src/main/java/com/core/data/CoreLink.java b/corefx/src/main/java/com/core/data/CoreLink.java new file mode 100644 index 00000000..f3d4a215 --- /dev/null +++ b/corefx/src/main/java/com/core/data/CoreLink.java @@ -0,0 +1,56 @@ +package com.core.data; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@EqualsAndHashCode(onlyExplicitlyIncluded = true) +public class CoreLink { + @EqualsAndHashCode.Include + private Integer id; + + @JsonIgnore + private Float weight = 1.0f; + + @JsonIgnore + private boolean loaded = true; + + @JsonIgnore + private double throughput; + + @JsonIgnore + private boolean visible = true; + + @JsonProperty("message_type") + private Integer messageType; + + private Integer type = 1; + + @JsonProperty("node_one") + private Integer nodeOne; + + @JsonProperty("node_two") + private Integer nodeTwo; + + @JsonProperty("interface_one") + private CoreInterface interfaceOne; + + @JsonProperty("interface_two") + private CoreInterface interfaceTwo; + + private CoreLinkOptions options = new CoreLinkOptions(); + + public CoreLink(Integer id) { + this.id = id; + this.weight = (float) id; + this.loaded = false; + } + + public boolean isWireless() { + return interfaceOne == null && interfaceTwo == null; + } +} diff --git a/corefx/src/main/java/com/core/data/CoreLinkOptions.java b/corefx/src/main/java/com/core/data/CoreLinkOptions.java new file mode 100644 index 00000000..6556d3c0 --- /dev/null +++ b/corefx/src/main/java/com/core/data/CoreLinkOptions.java @@ -0,0 +1,21 @@ +package com.core.data; + +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +public class CoreLinkOptions { + private String opaque; + private Integer session; + private Double jitter; + private Integer key; + private Double mburst; + private Double mer; + private Double per; + private Double bandwidth; + private Double burst; + private Double delay; + private Double dup; + private Integer unidirectional; +} diff --git a/corefx/src/main/java/com/core/data/CoreNode.java b/corefx/src/main/java/com/core/data/CoreNode.java new file mode 100644 index 00000000..f441bd69 --- /dev/null +++ b/corefx/src/main/java/com/core/data/CoreNode.java @@ -0,0 +1,59 @@ +package com.core.data; + +import com.core.graph.RadioIcon; +import com.core.utils.IconUtils; +import com.fasterxml.jackson.annotation.JsonIgnore; +import edu.uci.ics.jung.visualization.LayeredIcon; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.HashSet; +import java.util.Set; + +@Data +@NoArgsConstructor +@EqualsAndHashCode(onlyExplicitlyIncluded = true) +public class CoreNode { + private static final Logger logger = LogManager.getLogger(); + @EqualsAndHashCode.Include + private Integer id; + private String name; + private Integer type; + private String model; + private Position position = new Position(); + private Set services = new HashSet<>(); + private String emane; + private String url; + @JsonIgnore + private NodeType nodeType; + @JsonIgnore + private String icon; + @JsonIgnore + private boolean loaded = true; + @JsonIgnore + private LayeredIcon graphIcon; + @JsonIgnore + private RadioIcon radioIcon = new RadioIcon(); + + public CoreNode(Integer id) { + this.id = id; + this.name = String.format("Node%s", this.id); + this.loaded = false; + } + + public void setNodeType(NodeType nodeType) { + type = nodeType.getValue(); + model = nodeType.getModel(); + icon = nodeType.getIcon(); + if (icon.startsWith("file:")) { + graphIcon = IconUtils.getExternalLayeredIcon(icon); + } else { + graphIcon = IconUtils.getLayeredIcon(icon); + } + graphIcon.add(radioIcon); + this.nodeType = nodeType; + } +} diff --git a/corefx/src/main/java/com/core/data/CoreService.java b/corefx/src/main/java/com/core/data/CoreService.java new file mode 100644 index 00000000..ed9dd230 --- /dev/null +++ b/corefx/src/main/java/com/core/data/CoreService.java @@ -0,0 +1,23 @@ +package com.core.data; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +@Data +public class CoreService { + private List executables = new ArrayList<>(); + private List dependencies = new ArrayList<>(); + private List dirs = new ArrayList<>(); + private List configs = new ArrayList<>(); + private List startup = new ArrayList<>(); + private List validate = new ArrayList<>(); + @JsonProperty("validation_mode") + private String validationMode; + @JsonProperty("validation_timer") + private String validationTimer; + private List shutdown = new ArrayList<>(); + private String meta; +} diff --git a/corefx/src/main/java/com/core/data/EventType.java b/corefx/src/main/java/com/core/data/EventType.java new file mode 100644 index 00000000..487de4c1 --- /dev/null +++ b/corefx/src/main/java/com/core/data/EventType.java @@ -0,0 +1,45 @@ +package com.core.data; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +public enum EventType { + NONE(0), + DEFINITION_STATE(1), + CONFIGURATION_STATE(2), + INSTANTIATION_STATE(3), + RUNTIME_STATE(4), + DATACOLLECT_STATE(5), + SHUTDOWN_STATE(6), + START(7), + STOP(8), + PAUSE(9), + RESTART(10), + FILE_OPEN(11), + FILE_SAVE(12), + SCHEDULED(13), + RECONFIGURE(14), + INSTANTIATION_COMPLETE(15); + + private static final Map LOOKUP = new HashMap<>(); + + static { + Arrays.stream(EventType.values()).forEach(x -> LOOKUP.put(x.getValue(), x)); + } + + private final int value; + + EventType(int value) { + this.value = value; + } + + public int getValue() { + return value; + } + + + public static EventType get(int value) { + return LOOKUP.get(value); + } +} diff --git a/corefx/src/main/java/com/core/data/Hook.java b/corefx/src/main/java/com/core/data/Hook.java new file mode 100644 index 00000000..35b1a934 --- /dev/null +++ b/corefx/src/main/java/com/core/data/Hook.java @@ -0,0 +1,13 @@ +package com.core.data; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import lombok.Data; + +@Data +public class Hook { + private String file; + private Integer state; + @JsonIgnore + private String stateDisplay; + private String data; +} diff --git a/corefx/src/main/java/com/core/data/InterfaceThroughput.java b/corefx/src/main/java/com/core/data/InterfaceThroughput.java new file mode 100644 index 00000000..1c6303e0 --- /dev/null +++ b/corefx/src/main/java/com/core/data/InterfaceThroughput.java @@ -0,0 +1,12 @@ +package com.core.data; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +@Data +public class InterfaceThroughput { + private int node; + @JsonProperty("interface") + private int nodeInterface; + private double throughput; +} diff --git a/corefx/src/main/java/com/core/data/LinkTypes.java b/corefx/src/main/java/com/core/data/LinkTypes.java new file mode 100644 index 00000000..4bfaf1dc --- /dev/null +++ b/corefx/src/main/java/com/core/data/LinkTypes.java @@ -0,0 +1,31 @@ +package com.core.data; + +import java.util.HashMap; +import java.util.Map; + +public enum LinkTypes { + WIRELESS(0), + WIRED(1); + + private static final Map LOOKUP = new HashMap<>(); + + static { + for (LinkTypes state : LinkTypes.values()) { + LOOKUP.put(state.getValue(), state); + } + } + + private final int value; + + LinkTypes(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static LinkTypes get(int value) { + return LOOKUP.get(value); + } +} diff --git a/corefx/src/main/java/com/core/data/Location.java b/corefx/src/main/java/com/core/data/Location.java new file mode 100644 index 00000000..f5c399c4 --- /dev/null +++ b/corefx/src/main/java/com/core/data/Location.java @@ -0,0 +1,10 @@ +package com.core.data; + +import lombok.Data; + +@Data +public class Location { + private Double latitude = 0.0; + private Double longitude = 0.0; + private Double altitude = 0.0; +} diff --git a/corefx/src/main/java/com/core/data/LocationConfig.java b/corefx/src/main/java/com/core/data/LocationConfig.java new file mode 100644 index 00000000..10ba11ac --- /dev/null +++ b/corefx/src/main/java/com/core/data/LocationConfig.java @@ -0,0 +1,10 @@ +package com.core.data; + +import lombok.Data; + +@Data +public class LocationConfig { + private Position position = new Position(); + private Location location = new Location(); + private Double scale; +} diff --git a/corefx/src/main/java/com/core/data/MessageFlags.java b/corefx/src/main/java/com/core/data/MessageFlags.java new file mode 100644 index 00000000..6272099b --- /dev/null +++ b/corefx/src/main/java/com/core/data/MessageFlags.java @@ -0,0 +1,36 @@ +package com.core.data; + +import java.util.HashMap; +import java.util.Map; + +public enum MessageFlags { + ADD(1), + DELETE(2), + CRI(4), + LOCAL(8), + STRING(16), + TEXT(32), + TTY(64); + + private static final Map LOOKUP = new HashMap<>(); + + static { + for (MessageFlags state : MessageFlags.values()) { + LOOKUP.put(state.getValue(), state); + } + } + + private final int value; + + MessageFlags(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static MessageFlags get(int value) { + return LOOKUP.get(value); + } +} diff --git a/corefx/src/main/java/com/core/data/MobilityConfig.java b/corefx/src/main/java/com/core/data/MobilityConfig.java new file mode 100644 index 00000000..67b5d769 --- /dev/null +++ b/corefx/src/main/java/com/core/data/MobilityConfig.java @@ -0,0 +1,25 @@ +package com.core.data; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.io.File; + +@Data +public class MobilityConfig { + private String file; + @JsonIgnore + private File scriptFile; + @JsonProperty("refresh_ms") + private Integer refresh; + private String loop; + private String autostart; + private String map; + @JsonProperty("script_start") + private String startScript; + @JsonProperty("script_pause") + private String pauseScript; + @JsonProperty("script_stop") + private String stopScript; +} diff --git a/corefx/src/main/java/com/core/data/NodeType.java b/corefx/src/main/java/com/core/data/NodeType.java new file mode 100644 index 00000000..4af7dc3d --- /dev/null +++ b/corefx/src/main/java/com/core/data/NodeType.java @@ -0,0 +1,86 @@ +package com.core.data; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; + +@Data +@EqualsAndHashCode(onlyExplicitlyIncluded = true) +public class NodeType { + private static final Logger logger = LogManager.getLogger(); + private static final AtomicInteger idGenerator = new AtomicInteger(0); + private static final Map ID_LOOKUP = new HashMap<>(); + public static final int DEFAULT = 0; + public static final int SWITCH = 4; + public static final int HUB = 5; + public static final int WLAN = 6; + public static final int EMANE = 10; + @EqualsAndHashCode.Include + private final int id; + private final int value; + private final Set services = new TreeSet<>(); + private String display; + private String model; + private String icon; + + // PHYSICAL = 1 +// RJ45 = 7 +// TUNNEL = 8 +// KTUNNEL = 9 +// EMANE = 10 +// TAP_BRIDGE = 11 +// PEER_TO_PEER = 12 +// CONTROL_NET = 13 +// EMANE_NET = 14; + + static { + add(new NodeType(SWITCH, "lanswitch", "Switch", "/icons/switch-100.png")); + add(new NodeType(HUB, "hub", "Hub", "/icons/hub-100.png")); + add(new NodeType(WLAN, "wlan", "WLAN", "/icons/wlan-100.png")); + add(new NodeType(EMANE, "wlan", "EMANE", "/icons/emane-100.png")); + } + + + public NodeType(int value, String model, String display, String icon) { + this.id = idGenerator.incrementAndGet(); + this.value = value; + this.model = model; + this.display = display; + this.icon = icon; + } + + public static void add(NodeType nodeType) { + ID_LOOKUP.put(nodeType.getId(), nodeType); + } + + public static void remove(NodeType nodeType) { + ID_LOOKUP.remove(nodeType.getId()); + } + + public static NodeType get(Integer id) { + return ID_LOOKUP.get(id); + } + + public static Collection getAll() { + return ID_LOOKUP.values(); + } + + public static NodeType find(Integer type, String model) { + return ID_LOOKUP.values().stream() + .filter(nodeType -> { + boolean sameType = nodeType.getValue() == type; + boolean sameModel; + if (model != null) { + sameModel = model.equals(nodeType.getModel()); + } else { + sameModel = nodeType.getModel() == null; + } + return sameType && sameModel; + }) + .findFirst().orElse(null); + } +} diff --git a/corefx/src/main/java/com/core/data/Position.java b/corefx/src/main/java/com/core/data/Position.java new file mode 100644 index 00000000..c5bfa728 --- /dev/null +++ b/corefx/src/main/java/com/core/data/Position.java @@ -0,0 +1,12 @@ +package com.core.data; + +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +public class Position { + private Double x; + private Double y; + private Double z; +} diff --git a/corefx/src/main/java/com/core/data/Session.java b/corefx/src/main/java/com/core/data/Session.java new file mode 100644 index 00000000..374e379d --- /dev/null +++ b/corefx/src/main/java/com/core/data/Session.java @@ -0,0 +1,15 @@ +package com.core.data; + +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.ArrayList; +import java.util.List; + +@Data +@NoArgsConstructor +public class Session { + private Integer state; + private List nodes = new ArrayList<>(); + private List links = new ArrayList<>(); +} diff --git a/corefx/src/main/java/com/core/data/SessionOverview.java b/corefx/src/main/java/com/core/data/SessionOverview.java new file mode 100644 index 00000000..47773338 --- /dev/null +++ b/corefx/src/main/java/com/core/data/SessionOverview.java @@ -0,0 +1,13 @@ +package com.core.data; + +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +public class SessionOverview { + private Integer id; + private Integer state; + private Integer nodes = 0; + private String url; +} diff --git a/corefx/src/main/java/com/core/data/SessionState.java b/corefx/src/main/java/com/core/data/SessionState.java new file mode 100644 index 00000000..c9e92904 --- /dev/null +++ b/corefx/src/main/java/com/core/data/SessionState.java @@ -0,0 +1,38 @@ +package com.core.data; + +import java.util.HashMap; +import java.util.Map; + +public enum SessionState { + DEFINITION(1), + CONFIGURATION(2), + INSTANTIATION(3), + RUNTIME(4), + DATA_COLLECT(5), + SHUTDOWN(6), + START(7), + STOP(8), + PAUSE(9); + + private static final Map LOOKUP = new HashMap<>(); + + static { + for (SessionState state : SessionState.values()) { + LOOKUP.put(state.getValue(), state); + } + } + + private final int value; + + SessionState(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static SessionState get(int value) { + return LOOKUP.get(value); + } +} diff --git a/corefx/src/main/java/com/core/data/Throughputs.java b/corefx/src/main/java/com/core/data/Throughputs.java new file mode 100644 index 00000000..c02a7f6f --- /dev/null +++ b/corefx/src/main/java/com/core/data/Throughputs.java @@ -0,0 +1,12 @@ +package com.core.data; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +@Data +public class Throughputs { + private List interfaces = new ArrayList<>(); + private List bridges = new ArrayList<>(); +} diff --git a/corefx/src/main/java/com/core/datavis/CoreGraph.java b/corefx/src/main/java/com/core/datavis/CoreGraph.java new file mode 100644 index 00000000..f5a0c0fd --- /dev/null +++ b/corefx/src/main/java/com/core/datavis/CoreGraph.java @@ -0,0 +1,11 @@ +package com.core.datavis; + +import lombok.Data; + +@Data +public class CoreGraph { + private String title; + private CoreGraphAxis xAxis; + private CoreGraphAxis yAxis; + private GraphType graphType; +} diff --git a/corefx/src/main/java/com/core/datavis/CoreGraphAxis.java b/corefx/src/main/java/com/core/datavis/CoreGraphAxis.java new file mode 100644 index 00000000..8631f3a1 --- /dev/null +++ b/corefx/src/main/java/com/core/datavis/CoreGraphAxis.java @@ -0,0 +1,15 @@ +package com.core.datavis; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class CoreGraphAxis { + private String label; + private Double lower; + private Double upper; + private Double tick; +} diff --git a/corefx/src/main/java/com/core/datavis/CoreGraphData.java b/corefx/src/main/java/com/core/datavis/CoreGraphData.java new file mode 100644 index 00000000..ff0d4c77 --- /dev/null +++ b/corefx/src/main/java/com/core/datavis/CoreGraphData.java @@ -0,0 +1,15 @@ +package com.core.datavis; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class CoreGraphData { + private String name; + private Double x; + private Double y; + private Double weight; +} diff --git a/corefx/src/main/java/com/core/datavis/CoreGraphWrapper.java b/corefx/src/main/java/com/core/datavis/CoreGraphWrapper.java new file mode 100644 index 00000000..eae92e8f --- /dev/null +++ b/corefx/src/main/java/com/core/datavis/CoreGraphWrapper.java @@ -0,0 +1,157 @@ +package com.core.datavis; + +import javafx.scene.chart.*; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +public class CoreGraphWrapper { + private static final Logger logger = LogManager.getLogger(); + private final GraphType graphType; + private PieChart pieChart; + private final Map pieData = new HashMap<>(); + private BarChart barChart; + private final Map> barMap = new HashMap<>(); + private XYChart xyChart; + private final XYChart.Series series = new XYChart.Series<>(); + private final XYChart.Series barSeries = new XYChart.Series<>(); + private AtomicInteger timeValue = new AtomicInteger(0); + + public CoreGraphWrapper(CoreGraph coreGraph) { + graphType = coreGraph.getGraphType(); + createChart(coreGraph); + } + + public Chart getChart() { + switch (graphType) { + case PIE: + return pieChart; + case BAR: + return barChart; + default: + return xyChart; + } + } + + public void add(CoreGraphData coreGraphData) { + switch (graphType) { + case PIE: + case BAR: + add(coreGraphData.getName(), coreGraphData.getY()); + break; + case TIME: + add(coreGraphData.getY()); + break; + case BUBBLE: + add(coreGraphData.getX(), coreGraphData.getY(), coreGraphData.getWeight()); + break; + default: + add(coreGraphData.getX(), coreGraphData.getY()); + } + } + + public void add(String name, double value) { + if (GraphType.PIE == graphType) { + PieChart.Data data = pieData.computeIfAbsent(name, x -> { + PieChart.Data newData = new PieChart.Data(x, value); + pieChart.getData().add(newData); + return newData; + }); + data.setPieValue(value); + } else { + XYChart.Data data = barMap.computeIfAbsent(name, x -> { + XYChart.Data newData = new XYChart.Data<>(name, value); + barSeries.getData().add(newData); + return newData; + }); + data.setYValue(value); + } + } + + public void add(Number y) { + series.getData().add(new XYChart.Data<>(timeValue.getAndIncrement(), y)); + } + + public void add(Number x, Number y) { + series.getData().add(new XYChart.Data<>(x, y)); + } + + public void add(Number x, Number y, Number weight) { + series.getData().add(new XYChart.Data<>(x, y, weight)); + } + + private NumberAxis getAxis(CoreGraphAxis graphAxis) { + return new NumberAxis(graphAxis.getLabel(), graphAxis.getLower(), + graphAxis.getUpper(), graphAxis.getTick()); + } + + private void createChart(CoreGraph coreGraph) { + NumberAxis xAxis; + NumberAxis yAxis; + + switch (coreGraph.getGraphType()) { + case AREA: + xAxis = getAxis(coreGraph.getXAxis()); + yAxis = getAxis(coreGraph.getYAxis()); + xyChart = new AreaChart<>(xAxis, yAxis); + xyChart.setTitle(coreGraph.getTitle()); + xyChart.setLegendVisible(false); + xyChart.getData().add(series); + break; + case TIME: + xAxis = new NumberAxis(); + xAxis.setLabel(coreGraph.getXAxis().getLabel()); + xAxis.setTickUnit(1); + xAxis.setLowerBound(0); + yAxis = getAxis(coreGraph.getYAxis()); + xyChart = new LineChart<>(xAxis, yAxis); + xyChart.setTitle(coreGraph.getTitle()); + xyChart.setLegendVisible(false); + xyChart.getData().add(series); + break; + case LINE: + xAxis = getAxis(coreGraph.getXAxis()); + yAxis = getAxis(coreGraph.getYAxis()); + xyChart = new LineChart<>(xAxis, yAxis); + xyChart.setTitle(coreGraph.getTitle()); + xyChart.setLegendVisible(false); + xyChart.getData().add(series); + break; + case BUBBLE: + xAxis = getAxis(coreGraph.getXAxis()); + yAxis = getAxis(coreGraph.getYAxis()); + xyChart = new BubbleChart<>(xAxis, yAxis); + xyChart.setTitle(coreGraph.getTitle()); + xyChart.setLegendVisible(false); + xyChart.getData().add(series); + break; + case SCATTER: + xAxis = getAxis(coreGraph.getXAxis()); + yAxis = getAxis(coreGraph.getYAxis()); + xyChart = new ScatterChart<>(xAxis, yAxis); + xyChart.setTitle(coreGraph.getTitle()); + xyChart.setLegendVisible(false); + xyChart.getData().add(series); + break; + case PIE: + pieChart = new PieChart(); + pieChart.setTitle(coreGraph.getTitle()); + break; + case BAR: + CategoryAxis categoryAxis = new CategoryAxis(); + categoryAxis.setLabel(coreGraph.getXAxis().getLabel()); + yAxis = getAxis(coreGraph.getYAxis()); + barChart = new BarChart<>(categoryAxis, yAxis); + barChart.setLegendVisible(false); + barChart.setTitle(coreGraph.getTitle()); + barChart.getData().add(barSeries); + break; + default: + throw new IllegalArgumentException(String.format("unknown graph type: %s", + coreGraph.getGraphType())); + } + } +} diff --git a/corefx/src/main/java/com/core/datavis/GraphType.java b/corefx/src/main/java/com/core/datavis/GraphType.java new file mode 100644 index 00000000..7609eee3 --- /dev/null +++ b/corefx/src/main/java/com/core/datavis/GraphType.java @@ -0,0 +1,11 @@ +package com.core.datavis; + +public enum GraphType { + PIE, + LINE, + TIME, + AREA, + BAR, + SCATTER, + BUBBLE +} diff --git a/corefx/src/main/java/com/core/graph/AbstractNodeContextMenu.java b/corefx/src/main/java/com/core/graph/AbstractNodeContextMenu.java new file mode 100644 index 00000000..d04097c2 --- /dev/null +++ b/corefx/src/main/java/com/core/graph/AbstractNodeContextMenu.java @@ -0,0 +1,22 @@ +package com.core.graph; + +import com.core.Controller; +import com.core.data.CoreNode; +import javafx.event.ActionEvent; +import javafx.event.EventHandler; +import javafx.scene.control.MenuItem; + +abstract class AbstractNodeContextMenu extends GraphContextMenu { + final CoreNode coreNode; + + AbstractNodeContextMenu(Controller controller, CoreNode coreNode) { + super(controller); + this.coreNode = coreNode; + } + + void addMenuItem(String text, EventHandler handler) { + MenuItem menuItem = new MenuItem(text); + menuItem.setOnAction(handler); + getItems().add(menuItem); + } +} diff --git a/corefx/src/main/java/com/core/graph/BackgroundPaintable.java b/corefx/src/main/java/com/core/graph/BackgroundPaintable.java new file mode 100644 index 00000000..b0cfe6a3 --- /dev/null +++ b/corefx/src/main/java/com/core/graph/BackgroundPaintable.java @@ -0,0 +1,51 @@ +package com.core.graph; + +import edu.uci.ics.jung.visualization.Layer; +import edu.uci.ics.jung.visualization.VisualizationViewer; + +import javax.imageio.ImageIO; +import javax.swing.*; +import java.awt.*; +import java.awt.geom.AffineTransform; +import java.io.IOException; +import java.nio.file.Paths; + +public class BackgroundPaintable implements VisualizationViewer.Paintable { + private final ImageIcon imageIcon; + private final VisualizationViewer vv; + private final String imagePath; + + public BackgroundPaintable(String imagePath, VisualizationViewer vv) throws IOException { + this.imagePath = imagePath; + Image image = ImageIO.read(Paths.get(imagePath).toFile()); + imageIcon = new ImageIcon(image); + this.vv = vv; + } + + public String getImage() { + return imagePath; + } + + @Override + public void paint(Graphics g) { + Graphics2D g2d = (Graphics2D) g; + AffineTransform oldXform = g2d.getTransform(); + AffineTransform lat = + vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT).getTransform(); + AffineTransform vat = + vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.VIEW).getTransform(); + AffineTransform at = new AffineTransform(); + at.concatenate(g2d.getTransform()); + at.concatenate(vat); + at.concatenate(lat); + g2d.setTransform(at); + g.drawImage(imageIcon.getImage(), 0, 0, + imageIcon.getIconWidth(), imageIcon.getIconHeight(), vv); + g2d.setTransform(oldXform); + } + + @Override + public boolean useTransform() { + return false; + } +} diff --git a/corefx/src/main/java/com/core/graph/CoreAddresses.java b/corefx/src/main/java/com/core/graph/CoreAddresses.java new file mode 100644 index 00000000..5db724b6 --- /dev/null +++ b/corefx/src/main/java/com/core/graph/CoreAddresses.java @@ -0,0 +1,41 @@ +package com.core.graph; + +import com.core.data.CoreInterface; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.Collection; + +public class CoreAddresses { + private static final Logger logger = LogManager.getLogger(); + public static final int IP4_MASK = 24; + public static final int IP4_INDEX = (IP4_MASK / 8) - 1; + + private String ip4Base; + + public CoreAddresses(String ip4Base) { + this.ip4Base = ip4Base; + } + + public int getSubnet(Collection nodeOneInterfaces, Collection nodeTwoInterfaces) { + int subOne = getMaxSubnet(nodeOneInterfaces); + int subTwo = getMaxSubnet(nodeTwoInterfaces); + logger.info("next subnet: {} - {}", subOne, subTwo); + return Math.max(subOne, subTwo) + 1; + } + + private int getMaxSubnet(Collection coreInterfaces) { + int sub = 0; + for (CoreInterface coreInterface : coreInterfaces) { + String[] values = coreInterface.getIp4().split("\\."); + int currentSub = Integer.parseInt(values[IP4_INDEX]); + logger.info("checking {} value {}", coreInterface.getIp4(), currentSub); + sub = Math.max(currentSub, sub); + } + return sub; + } + + public String getIp4Address(int sub, int id) { + return String.format("%s.%s.%s", ip4Base, sub, id); + } +} diff --git a/corefx/src/main/java/com/core/graph/CoreAnnotatingGraphMousePlugin.java b/corefx/src/main/java/com/core/graph/CoreAnnotatingGraphMousePlugin.java new file mode 100644 index 00000000..6cbd1bf8 --- /dev/null +++ b/corefx/src/main/java/com/core/graph/CoreAnnotatingGraphMousePlugin.java @@ -0,0 +1,57 @@ +package com.core.graph; + +import com.core.Controller; +import edu.uci.ics.jung.visualization.RenderContext; +import edu.uci.ics.jung.visualization.VisualizationViewer; +import edu.uci.ics.jung.visualization.annotations.AnnotatingGraphMousePlugin; +import edu.uci.ics.jung.visualization.annotations.Annotation; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.MouseEvent; +import java.awt.geom.Point2D; +import java.awt.geom.RectangularShape; + +public class CoreAnnotatingGraphMousePlugin extends AnnotatingGraphMousePlugin { + private static final Logger logger = LogManager.getLogger(); + private final Controller controller; + private JFrame frame = new JFrame(); + + public CoreAnnotatingGraphMousePlugin(Controller controller, RenderContext renderContext) { + super(renderContext); + this.controller = controller; + frame.setVisible(false); + frame.setAlwaysOnTop(true); + } + + @Override + public void mouseReleased(MouseEvent e) { + VisualizationViewer vv = (VisualizationViewer) e.getSource(); + if (e.isPopupTrigger()) { + frame.setLocationRelativeTo(vv); + String annotationString = JOptionPane.showInputDialog(frame, "Annotation:", + "Annotation Label", JOptionPane.PLAIN_MESSAGE); + if (annotationString != null && annotationString.length() > 0) { + Point2D p = vv.getRenderContext().getMultiLayerTransformer().inverseTransform(this.down); + Annotation annotation = new Annotation(annotationString, this.layer, + this.annotationColor, this.fill, p); + this.annotationManager.add(this.layer, annotation); + } + } else if (e.getModifiers() == this.modifiers && this.down != null) { + Point2D out = e.getPoint(); + RectangularShape arect = (RectangularShape) this.rectangularShape.clone(); + arect.setFrameFromDiagonal(this.down, out); + Shape s = vv.getRenderContext().getMultiLayerTransformer().inverseTransform(arect); + Annotation annotation = new Annotation(s, this.layer, this.annotationColor, this.fill, out); + this.annotationManager.add(this.layer, annotation); + } + + this.down = null; + vv.removePostRenderPaintable(this.lensPaintable); + vv.repaint(); + } + + +} diff --git a/corefx/src/main/java/com/core/graph/CoreEditingModalGraphMouse.java b/corefx/src/main/java/com/core/graph/CoreEditingModalGraphMouse.java new file mode 100644 index 00000000..9d80183e --- /dev/null +++ b/corefx/src/main/java/com/core/graph/CoreEditingModalGraphMouse.java @@ -0,0 +1,17 @@ +package com.core.graph; + +import com.core.Controller; +import com.google.common.base.Supplier; +import edu.uci.ics.jung.visualization.RenderContext; +import edu.uci.ics.jung.visualization.control.EditingModalGraphMouse; + +public class CoreEditingModalGraphMouse extends EditingModalGraphMouse { + public CoreEditingModalGraphMouse(Controller controller, NetworkGraph networkGraph, + RenderContext rc, Supplier vertexFactory, Supplier edgeFactory) { + super(rc, vertexFactory, edgeFactory); + remove(annotatingPlugin); + remove(popupEditingPlugin); + annotatingPlugin = new CoreAnnotatingGraphMousePlugin<>(controller, rc); + popupEditingPlugin = new CorePopupGraphMousePlugin<>(controller, networkGraph, vertexFactory, edgeFactory); + } +} diff --git a/corefx/src/main/java/com/core/graph/CoreObservableGraph.java b/corefx/src/main/java/com/core/graph/CoreObservableGraph.java new file mode 100644 index 00000000..cbf678f0 --- /dev/null +++ b/corefx/src/main/java/com/core/graph/CoreObservableGraph.java @@ -0,0 +1,31 @@ +package com.core.graph; + +import edu.uci.ics.jung.graph.Graph; +import edu.uci.ics.jung.graph.ObservableGraph; +import edu.uci.ics.jung.graph.util.EdgeType; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +public class CoreObservableGraph extends ObservableGraph { + private static final Logger logger = LogManager.getLogger(); + + public CoreObservableGraph(Graph graph) { + super(graph); + } + + @Override + public boolean addEdge(E e, V v1, V v2, EdgeType edgeType) { + if (v1 == null || v2 == null) { + return false; + } + return super.addEdge(e, v1, v2, edgeType); + } + + @Override + public boolean addEdge(E e, V v1, V v2) { + if (v1 == null || v2 == null) { + return false; + } + return super.addEdge(e, v1, v2); + } +} diff --git a/corefx/src/main/java/com/core/graph/CorePopupGraphMousePlugin.java b/corefx/src/main/java/com/core/graph/CorePopupGraphMousePlugin.java new file mode 100644 index 00000000..3b96c9fa --- /dev/null +++ b/corefx/src/main/java/com/core/graph/CorePopupGraphMousePlugin.java @@ -0,0 +1,78 @@ +package com.core.graph; + +import com.core.Controller; +import com.core.data.CoreLink; +import com.core.data.CoreNode; +import com.core.data.NodeType; +import com.google.common.base.Supplier; +import edu.uci.ics.jung.algorithms.layout.GraphElementAccessor; +import edu.uci.ics.jung.algorithms.layout.Layout; +import edu.uci.ics.jung.visualization.control.EditingPopupGraphMousePlugin; +import javafx.application.Platform; +import javafx.scene.control.ContextMenu; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.awt.event.MouseEvent; +import java.awt.geom.Point2D; + +public class CorePopupGraphMousePlugin extends EditingPopupGraphMousePlugin { + private static final Logger logger = LogManager.getLogger(); + private final Controller controller; + private final NetworkGraph networkGraph; + private final Layout graphLayout; + private final GraphElementAccessor pickSupport; + + public CorePopupGraphMousePlugin(Controller controller, NetworkGraph networkGraph, + Supplier vertexFactory, Supplier edgeFactory) { + super(vertexFactory, edgeFactory); + this.controller = controller; + this.networkGraph = networkGraph; + graphLayout = this.networkGraph.getGraphLayout(); + pickSupport = this.networkGraph.getGraphViewer().getPickSupport(); + } + + @Override + protected void handlePopup(MouseEvent e) { + logger.info("showing popup!"); + final Point2D p = e.getPoint(); + + final CoreNode node = pickSupport.getVertex(graphLayout, p.getX(), p.getY()); + final CoreLink link = pickSupport.getEdge(graphLayout, p.getX(), p.getY()); + + final ContextMenu contextMenu; + if (node != null) { + contextMenu = handleNodeContext(node); + } else if (link != null) { + contextMenu = new LinkContextMenu(controller, link); + } else { + contextMenu = new ContextMenu(); + } + + if (!contextMenu.getItems().isEmpty()) { + logger.info("showing context menu"); + Platform.runLater(() -> contextMenu.show(controller.getWindow(), + e.getXOnScreen(), e.getYOnScreen())); + } + } + + private ContextMenu handleNodeContext(final CoreNode node) { + ContextMenu contextMenu = new ContextMenu(); + switch (node.getType()) { + case NodeType.DEFAULT: + contextMenu = new NodeContextMenu(controller, node); + break; + case NodeType.WLAN: + contextMenu = new WlanContextMenu(controller, node); + break; + case NodeType.EMANE: + contextMenu = new EmaneContextMenu(controller, node); + break; + default: + logger.warn("no context menu for node: {}", node.getType()); + break; + } + + return contextMenu; + } +} diff --git a/corefx/src/main/java/com/core/graph/CoreVertexLabelRenderer.java b/corefx/src/main/java/com/core/graph/CoreVertexLabelRenderer.java new file mode 100644 index 00000000..25c49fa3 --- /dev/null +++ b/corefx/src/main/java/com/core/graph/CoreVertexLabelRenderer.java @@ -0,0 +1,43 @@ +package com.core.graph; + +import edu.uci.ics.jung.visualization.renderers.DefaultVertexLabelRenderer; + +import javax.swing.*; +import javax.swing.border.EmptyBorder; +import java.awt.*; + +public class CoreVertexLabelRenderer extends DefaultVertexLabelRenderer { + private Color foregroundColor = Color.WHITE; + private Color backgroundColor = Color.BLACK; + + CoreVertexLabelRenderer() { + super(Color.YELLOW); + } + + public void setColors(Color foregroundColor, Color backgroundColor) { + this.foregroundColor = foregroundColor; + this.backgroundColor = backgroundColor; + } + + @Override + public Component getVertexLabelRendererComponent(JComponent vv, Object value, Font font, boolean isSelected, V vertex) { + super.setForeground(foregroundColor); + if (isSelected) { + this.setForeground(this.pickedVertexLabelColor); + } + + super.setBackground(backgroundColor); + if (font != null) { + this.setFont(font); + } else { + this.setFont(vv.getFont()); + } + + this.setIcon(null); + EmptyBorder padding = new EmptyBorder(5, 5, 5, 5); + this.setBorder(padding); + this.setValue(value); + setFont(getFont().deriveFont(Font.BOLD)); + return this; + } +} diff --git a/corefx/src/main/java/com/core/graph/EmaneContextMenu.java b/corefx/src/main/java/com/core/graph/EmaneContextMenu.java new file mode 100644 index 00000000..848bbfb3 --- /dev/null +++ b/corefx/src/main/java/com/core/graph/EmaneContextMenu.java @@ -0,0 +1,26 @@ +package com.core.graph; + +import com.core.Controller; +import com.core.data.CoreNode; +import com.core.ui.dialogs.MobilityPlayerDialog; + +class EmaneContextMenu extends AbstractNodeContextMenu { + EmaneContextMenu(Controller controller, CoreNode coreNode) { + super(controller, coreNode); + setup(); + } + + private void setup() { + addMenuItem("EMANE Settings", event -> controller.getNodeEmaneDialog().showDialog(coreNode)); + if (controller.getCoreClient().isRunning()) { + MobilityPlayerDialog mobilityPlayerDialog = controller.getMobilityPlayerDialogs().get(coreNode.getId()); + if (mobilityPlayerDialog != null && !mobilityPlayerDialog.getStage().isShowing()) { + addMenuItem("Mobility Script", event -> mobilityPlayerDialog.show()); + } + } else { + addMenuItem("Mobility", event -> controller.getMobilityDialog().showDialog(coreNode)); + addMenuItem("Link MDRs", event -> controller.getNetworkGraph().linkMdrs(coreNode)); + addMenuItem("Delete Node", event -> controller.deleteNode(coreNode)); + } + } +} diff --git a/corefx/src/main/java/com/core/graph/GraphContextMenu.java b/corefx/src/main/java/com/core/graph/GraphContextMenu.java new file mode 100644 index 00000000..c93ecba9 --- /dev/null +++ b/corefx/src/main/java/com/core/graph/GraphContextMenu.java @@ -0,0 +1,22 @@ +package com.core.graph; + +import com.core.Controller; +import javafx.event.ActionEvent; +import javafx.event.EventHandler; +import javafx.scene.control.ContextMenu; +import javafx.scene.control.MenuItem; + +abstract class GraphContextMenu extends ContextMenu { + final Controller controller; + + GraphContextMenu(Controller controller) { + super(); + this.controller = controller; + } + + void addMenuItem(String text, EventHandler handler) { + MenuItem menuItem = new MenuItem(text); + menuItem.setOnAction(handler); + getItems().add(menuItem); + } +} diff --git a/corefx/src/main/java/com/core/graph/LinkContextMenu.java b/corefx/src/main/java/com/core/graph/LinkContextMenu.java new file mode 100644 index 00000000..d9b11115 --- /dev/null +++ b/corefx/src/main/java/com/core/graph/LinkContextMenu.java @@ -0,0 +1,21 @@ +package com.core.graph; + +import com.core.Controller; +import com.core.data.CoreLink; + +class LinkContextMenu extends GraphContextMenu { + final CoreLink coreLink; + + LinkContextMenu(Controller controller, CoreLink coreLink) { + super(controller); + this.coreLink = coreLink; + setup(); + } + + private void setup() { + if (!controller.getCoreClient().isRunning()) { + addMenuItem("Delete Link", + event -> controller.getNetworkGraph().removeLink(coreLink)); + } + } +} diff --git a/corefx/src/main/java/com/core/graph/NetworkGraph.java b/corefx/src/main/java/com/core/graph/NetworkGraph.java new file mode 100644 index 00000000..7d55cdb2 --- /dev/null +++ b/corefx/src/main/java/com/core/graph/NetworkGraph.java @@ -0,0 +1,492 @@ +package com.core.graph; + +import com.core.Controller; +import com.core.data.*; +import com.core.ui.Toast; +import com.core.ui.dialogs.TerminalDialog; +import com.core.utils.Configuration; +import com.core.utils.IconUtils; +import com.google.common.base.Supplier; +import edu.uci.ics.jung.algorithms.layout.StaticLayout; +import edu.uci.ics.jung.graph.ObservableGraph; +import edu.uci.ics.jung.graph.event.GraphEvent; +import edu.uci.ics.jung.graph.event.GraphEventListener; +import edu.uci.ics.jung.graph.util.Pair; +import edu.uci.ics.jung.visualization.RenderContext; +import edu.uci.ics.jung.visualization.VisualizationViewer; +import edu.uci.ics.jung.visualization.annotations.AnnotationControls; +import edu.uci.ics.jung.visualization.control.EditingModalGraphMouse; +import edu.uci.ics.jung.visualization.control.GraphMouseListener; +import edu.uci.ics.jung.visualization.control.ModalGraphMouse; +import edu.uci.ics.jung.visualization.decorators.EdgeShape; +import edu.uci.ics.jung.visualization.renderers.Renderer; +import javafx.application.Platform; +import lombok.Data; +import org.apache.commons.net.util.SubnetUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.MouseEvent; +import java.awt.geom.Ellipse2D; +import java.io.IOException; +import java.util.*; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + + +@Data +public class NetworkGraph { + private static final Logger logger = LogManager.getLogger(); + private static final int EDGE_LABEL_OFFSET = -5; + private static final int EDGE_WIDTH = 5; + private Controller controller; + private ObservableGraph graph; + private StaticLayout graphLayout; + private VisualizationViewer graphViewer; + private EditingModalGraphMouse graphMouse; + private AnnotationControls annotationControls; + + private SubnetUtils subnetUtils = new SubnetUtils("10.0.0.0/24"); + private CoreAddresses coreAddresses = new CoreAddresses("10.0"); + private NodeType nodeType; + private Map nodeMap = new ConcurrentHashMap<>(); + private int vertexId = 1; + private int linkId = 1; + private Supplier vertexFactory = () -> new CoreNode(vertexId++); + private Supplier linkFactory = () -> new CoreLink(linkId++); + private CorePopupGraphMousePlugin customPopupPlugin; + private CoreAnnotatingGraphMousePlugin customAnnotatingPlugin; + private BackgroundPaintable backgroundPaintable; + private CoreVertexLabelRenderer nodeLabelRenderer = new CoreVertexLabelRenderer(); + + // display options + private boolean showThroughput = false; + private Double throughputLimit = null; + private int throughputWidth = 10; + + public NetworkGraph(Controller controller) { + this.controller = controller; + graph = new CoreObservableGraph<>(new UndirectedSimpleGraph<>()); + graph.addGraphEventListener(graphEventListener); + graphLayout = new StaticLayout<>(graph); + graphViewer = new VisualizationViewer<>(graphLayout); + graphViewer.setBackground(Color.WHITE); + graphViewer.getRenderer().getVertexLabelRenderer().setPosition(Renderer.VertexLabel.Position.S); + + RenderContext renderContext = graphViewer.getRenderContext(); + + // node render properties + renderContext.setVertexLabelTransformer(CoreNode::getName); + renderContext.setVertexLabelRenderer(nodeLabelRenderer); + renderContext.setVertexShapeTransformer(node -> { + double offset = -(IconUtils.ICON_SIZE / 2.0); + return new Ellipse2D.Double(offset, offset, IconUtils.ICON_SIZE, IconUtils.ICON_SIZE); + }); + renderContext.setVertexIconTransformer(vertex -> { + long wirelessLinks = wirelessLinkCount(vertex); + vertex.getRadioIcon().setWiressLinks(wirelessLinks); + return vertex.getGraphIcon(); + }); + + // link render properties + renderContext.setEdgeLabelTransformer(edge -> { + if (!showThroughput || edge == null) { + return null; + } + double kbps = edge.getThroughput() / 1000.0; + return String.format("%.2f kbps", kbps); + }); + renderContext.setLabelOffset(EDGE_LABEL_OFFSET); + renderContext.setEdgeStrokeTransformer(edge -> { + // determine edge width + int width = EDGE_WIDTH; + if (throughputLimit != null && edge.getThroughput() > throughputLimit) { + width = throughputWidth; + } + + LinkTypes linkType = LinkTypes.get(edge.getType()); + if (LinkTypes.WIRELESS == linkType) { + float[] dash = {15.0f}; + return new BasicStroke(width, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_ROUND, + 0, dash, 0); + } else { + return new BasicStroke(width); + } + }); + renderContext.setEdgeShapeTransformer(EdgeShape.line(graph)); + renderContext.setEdgeDrawPaintTransformer(edge -> { + LinkTypes linkType = LinkTypes.get(edge.getType()); + if (LinkTypes.WIRELESS == linkType) { + return Color.BLUE; + } else { + return Color.BLACK; + } + }); + renderContext.setEdgeIncludePredicate(predicate -> predicate.element.isVisible()); + + graphViewer.setVertexToolTipTransformer(renderContext.getVertexLabelTransformer()); + graphMouse = new CoreEditingModalGraphMouse<>(controller, this, renderContext, + vertexFactory, linkFactory); + graphViewer.setGraphMouse(graphMouse); + + // mouse events + graphViewer.addGraphMouseListener(new GraphMouseListener() { + @Override + public void graphClicked(CoreNode node, MouseEvent mouseEvent) { + // double click + logger.info("click count: {}, running?: {}", mouseEvent.getClickCount(), + controller.getCoreClient().isRunning()); + + if (mouseEvent.getClickCount() == 2 && controller.getCoreClient().isRunning()) { + if (controller.getCoreClient().isLocalConnection()) { + try { + String shellCommand = controller.getConfiguration().getShellCommand(); + String terminalCommand = controller.getCoreClient().getTerminalCommand(node); + terminalCommand = String.format("%s %s", shellCommand, terminalCommand); + logger.info("launching node terminal: {}", terminalCommand); + String[] commands = terminalCommand.split("\\s+"); + logger.info("launching node terminal: {}", Arrays.toString(commands)); + Process p = new ProcessBuilder(commands).start(); + try { + if (!p.waitFor(5, TimeUnit.SECONDS)) { + Toast.error("Node terminal command failed"); + } + } catch (InterruptedException ex) { + logger.error("error waiting for terminal to start", ex); + } + } catch (IOException ex) { + logger.error("error launching terminal", ex); + Toast.error("Node terminal failed to start"); + } + } else { + Platform.runLater(() -> { + TerminalDialog terminalDialog = new TerminalDialog(controller); + terminalDialog.setOwner(controller.getWindow()); + terminalDialog.showDialog(node); + }); + } + } + } + + @Override + public void graphPressed(CoreNode node, MouseEvent mouseEvent) { + logger.debug("graph pressed: {} - {}", node, mouseEvent); + } + + @Override + public void graphReleased(CoreNode node, MouseEvent mouseEvent) { + if (SwingUtilities.isLeftMouseButton(mouseEvent)) { + Double newX = graphLayout.getX(node); + Double newY = graphLayout.getY(node); + Double oldX = node.getPosition().getX(); + Double oldY = node.getPosition().getY(); + if (newX.equals(oldX) && newY.equals(oldY)) { + return; + } + logger.debug("graph moved node({}): {},{}", node.getName(), newX, newY); + node.getPosition().setX(newX); + node.getPosition().setY(newY); + + // upate node when session is active + if (controller.getCoreClient().isRunning()) { + try { + controller.getCoreClient().editNode(node); + } catch (IOException ex) { + Toast.error("failed to update node location"); + } + } + } + } + }); + } + + private Color convertJfxColor(String hexValue) { + javafx.scene.paint.Color color = javafx.scene.paint.Color.web(hexValue); + return new Color((float) color.getRed(), (float) color.getGreen(), (float) color.getBlue()); + } + + public void updatePreferences(Configuration configuration) { + Color nodeLabelColor = convertJfxColor(configuration.getNodeLabelColor()); + Color nodeLabelBackgroundColor = convertJfxColor(configuration.getNodeLabelBackgroundColor()); + nodeLabelRenderer.setColors(nodeLabelColor, nodeLabelBackgroundColor); + throughputLimit = configuration.getThroughputLimit(); + if (configuration.getThroughputWidth() != null) { + throughputWidth = configuration.getThroughputWidth(); + } + graphViewer.repaint(); + } + + public void setBackground(String imagePath) { + try { + backgroundPaintable = new BackgroundPaintable<>(imagePath, graphViewer); + graphViewer.addPreRenderPaintable(backgroundPaintable); + graphViewer.repaint(); + } catch (IOException ex) { + logger.error("error setting background", ex); + } + } + + public void removeBackground() { + if (backgroundPaintable != null) { + graphViewer.removePreRenderPaintable(backgroundPaintable); + graphViewer.repaint(); + backgroundPaintable = null; + } + } + + public void setMode(ModalGraphMouse.Mode mode) { + graphMouse.setMode(mode); + } + + public void reset() { + logger.info("network graph reset"); + vertexId = 1; + linkId = 1; + for (CoreNode node : nodeMap.values()) { + graph.removeVertex(node); + } + nodeMap.clear(); + graphViewer.repaint(); + } + + public void updatePositions() { + for (CoreNode node : graph.getVertices()) { + Double x = graphLayout.getX(node); + Double y = graphLayout.getY(node); + node.getPosition().setX(x); + node.getPosition().setY(y); + logger.debug("updating node position node({}): {},{}", node, x, y); + } + } + + public CoreNode getVertex(int id) { + return nodeMap.get(id); + } + + private GraphEventListener graphEventListener = graphEvent -> { + logger.info("graph event: {}", graphEvent.getType()); + switch (graphEvent.getType()) { + case EDGE_ADDED: + handleEdgeAdded((GraphEvent.Edge) graphEvent); + break; + case EDGE_REMOVED: + handleEdgeRemoved((GraphEvent.Edge) graphEvent); + break; + case VERTEX_ADDED: + handleVertexAdded((GraphEvent.Vertex) graphEvent); + break; + case VERTEX_REMOVED: + handleVertexRemoved((GraphEvent.Vertex) graphEvent); + break; + } + }; + + private void handleEdgeAdded(GraphEvent.Edge edgeEvent) { + CoreLink link = edgeEvent.getEdge(); + if (!link.isLoaded()) { + Pair endpoints = graph.getEndpoints(link); + + CoreNode nodeOne = endpoints.getFirst(); + CoreNode nodeTwo = endpoints.getSecond(); + + // create interfaces for nodes + int sub = coreAddresses.getSubnet( + getInterfaces(nodeOne), + getInterfaces(nodeTwo) + ); + + link.setNodeOne(nodeOne.getId()); + if (isNode(nodeOne)) { + int interfaceOneId = nextInterfaceId(nodeOne); + CoreInterface interfaceOne = createInterface(nodeOne, sub, interfaceOneId); + link.setInterfaceOne(interfaceOne); + } + + link.setNodeTwo(nodeTwo.getId()); + if (isNode(nodeTwo)) { + int interfaceTwoId = nextInterfaceId(nodeTwo); + CoreInterface interfaceTwo = createInterface(nodeTwo, sub, interfaceTwoId); + link.setInterfaceTwo(interfaceTwo); + } + + boolean isVisible = !checkForWirelessNode(nodeOne, nodeTwo); + link.setVisible(isVisible); + + logger.info("adding user created edge: {}", link); + } + } + + public List getInterfaces(CoreNode node) { + return graph.getIncidentEdges(node).stream() + .map(link -> { + if (node.getId().equals(link.getNodeOne())) { + return link.getInterfaceOne(); + } else { + return link.getInterfaceTwo(); + } + }) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + } + + private int nextInterfaceId(CoreNode node) { + Set interfaceIds = graph.getIncidentEdges(node).stream() + .map(link -> { + if (node.getId().equals(link.getNodeOne())) { + return link.getInterfaceOne(); + } else { + return link.getInterfaceTwo(); + } + }) + .filter(Objects::nonNull) + .map(CoreInterface::getId) + .collect(Collectors.toSet()); + + int i = 0; + while (true) { + if (!interfaceIds.contains(i)) { + return i; + } + + i += 1; + } + } + + private boolean isNode(CoreNode node) { + return node.getType() == NodeType.DEFAULT; + } + + private CoreInterface createInterface(CoreNode node, int sub, int interfaceId) { + CoreInterface coreInterface = new CoreInterface(); + coreInterface.setId(interfaceId); + coreInterface.setName(String.format("eth%s", interfaceId)); + String nodeOneIp4 = coreAddresses.getIp4Address(sub, node.getId()); + coreInterface.setIp4(nodeOneIp4); + coreInterface.setIp4Mask(CoreAddresses.IP4_MASK); + return coreInterface; + } + + private void handleEdgeRemoved(GraphEvent.Edge edgeEvent) { + CoreLink link = edgeEvent.getEdge(); + logger.info("removed edge: {}", link); + } + + private void handleVertexAdded(GraphEvent.Vertex vertexEvent) { + CoreNode node = vertexEvent.getVertex(); + if (!node.isLoaded()) { + node.setNodeType(nodeType); + if (node.getType() == NodeType.EMANE) { + String emaneModel = controller.getNodeEmaneDialog().getModels().get(0); + node.setEmane(emaneModel); + } + + logger.info("adding user created node: {}", node); + nodeMap.put(node.getId(), node); + } + } + + private void handleVertexRemoved(GraphEvent.Vertex vertexEvent) { + CoreNode node = vertexEvent.getVertex(); + logger.info("removed vertex: {}", node); + nodeMap.remove(node.getId()); + } + + public void addNode(CoreNode node) { + vertexId = Math.max(node.getId() + 1, node.getId()); + double x = Math.abs(node.getPosition().getX()); + double y = Math.abs(node.getPosition().getY()); + logger.info("adding session node: {}", node); + graph.addVertex(node); + graphLayout.setLocation(node, x, y); + nodeMap.put(node.getId(), node); + } + + public void setNodeLocation(CoreNode nodeData) { + // update actual graph node + CoreNode node = nodeMap.get(nodeData.getId()); + node.getPosition().setX(nodeData.getPosition().getX()); + node.getPosition().setY(nodeData.getPosition().getY()); + + // set graph node location + double x = Math.abs(node.getPosition().getX()); + double y = Math.abs(node.getPosition().getY()); + graphLayout.setLocation(node, x, y); + graphViewer.repaint(); + } + + public void removeNode(CoreNode node) { + try { + controller.getCoreClient().deleteNode(node); + } catch (IOException ex) { + logger.error("error deleting node"); + Toast.error(String.format("Error deleting node: %s", node.getName())); + } + graphViewer.getPickedVertexState().pick(node, false); + graph.removeVertex(node); + graphViewer.repaint(); + } + + private boolean isWirelessNode(CoreNode node) { + return node.getType() == NodeType.EMANE || node.getType() == NodeType.WLAN; + } + + private boolean checkForWirelessNode(CoreNode nodeOne, CoreNode nodeTwo) { + boolean result = isWirelessNode(nodeOne); + return result || isWirelessNode(nodeTwo); + } + + private long wirelessLinkCount(CoreNode node) { + return graph.getNeighbors(node).stream() + .filter(this::isWirelessNode) + .count(); + } + + public void addLink(CoreLink link) { + logger.info("adding session link: {}", link); + link.setId(linkId++); + CoreNode nodeOne = nodeMap.get(link.getNodeOne()); + CoreNode nodeTwo = nodeMap.get(link.getNodeTwo()); + + boolean isVisible = !checkForWirelessNode(nodeOne, nodeTwo); + link.setVisible(isVisible); + + graph.addEdge(link, nodeOne, nodeTwo); + } + + public void removeWirelessLink(CoreLink link) { + logger.info("deleting link: {}", link); + CoreNode nodeOne = nodeMap.get(link.getNodeOne()); + CoreNode nodeTwo = nodeMap.get(link.getNodeTwo()); + + CoreLink existingLink = graph.findEdge(nodeOne, nodeTwo); + if (existingLink != null) { + graph.removeEdge(existingLink); + } + } + + public void removeLink(CoreLink link) { + graphViewer.getPickedEdgeState().pick(link, false); + graph.removeEdge(link); + graphViewer.repaint(); + } + + public void linkMdrs(CoreNode node) { + for (CoreNode currentNode : graph.getVertices()) { + if (!"mdr".equals(currentNode.getModel())) { + continue; + } + + // only links mdrs we have not already linked + Collection links = graph.findEdgeSet(node, currentNode); + if (links.isEmpty()) { + CoreLink link = linkFactory.get(); + graph.addEdge(link, currentNode, node); + graphViewer.repaint(); + } + } + } +} diff --git a/corefx/src/main/java/com/core/graph/NodeContextMenu.java b/corefx/src/main/java/com/core/graph/NodeContextMenu.java new file mode 100644 index 00000000..d50cd58a --- /dev/null +++ b/corefx/src/main/java/com/core/graph/NodeContextMenu.java @@ -0,0 +1,116 @@ +package com.core.graph; + +import com.core.Controller; +import com.core.data.CoreNode; +import com.core.ui.Toast; +import javafx.scene.control.Menu; +import javafx.scene.control.MenuItem; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.IOException; +import java.util.Collections; +import java.util.Set; + +class NodeContextMenu extends AbstractNodeContextMenu { + private static final Logger logger = LogManager.getLogger(); + + NodeContextMenu(Controller controller, CoreNode coreNode) { + super(controller, coreNode); + setup(); + } + + private MenuItem createStartItem(String service) { + MenuItem menuItem = new MenuItem("Start"); + menuItem.setOnAction(event -> { + try { + boolean result = controller.getCoreClient().startService(coreNode, service); + if (result) { + Toast.success("Started " + service); + } else { + Toast.error("Failure to start " + service); + } + } catch (IOException ex) { + Toast.error("Error starting " + service, ex); + } + }); + return menuItem; + } + + private MenuItem createStopItem(String service) { + MenuItem menuItem = new MenuItem("Stop"); + menuItem.setOnAction(event -> { + try { + boolean result = controller.getCoreClient().stopService(coreNode, service); + if (result) { + Toast.success("Stopped " + service); + } else { + Toast.error("Failure to stop " + service); + } + } catch (IOException ex) { + Toast.error("Error stopping " + service, ex); + } + }); + return menuItem; + } + + private MenuItem createRestartItem(String service) { + MenuItem menuItem = new MenuItem("Restart"); + menuItem.setOnAction(event -> { + try { + boolean result = controller.getCoreClient().restartService(coreNode, service); + if (result) { + Toast.success("Restarted " + service); + } else { + Toast.error("Failure to restart " + service); + } + } catch (IOException ex) { + Toast.error("Error restarting " + service, ex); + } + }); + return menuItem; + } + + private MenuItem createValidateItem(String service) { + MenuItem menuItem = new MenuItem("Validate"); + menuItem.setOnAction(event -> { + try { + boolean result = controller.getCoreClient().validateService(coreNode, service); + if (result) { + Toast.success("Validated " + service); + } else { + Toast.error("Validation failed for " + service); + } + } catch (IOException ex) { + Toast.error("Error validating " + service, ex); + } + }); + return menuItem; + } + + private void setup() { + if (controller.getCoreClient().isRunning()) { + Set services = coreNode.getServices(); + if (services.isEmpty()) { + services = controller.getDefaultServices().getOrDefault(coreNode.getModel(), Collections.emptySet()); + } + + if (!services.isEmpty()) { + Menu menu = new Menu("Manage Services"); + for (String service : services) { + Menu serviceMenu = new Menu(service); + MenuItem startItem = createStartItem(service); + MenuItem stopItem = createStopItem(service); + MenuItem restartItem = createRestartItem(service); + MenuItem validateItem = createValidateItem(service); + serviceMenu.getItems().addAll(startItem, stopItem, restartItem, validateItem); + menu.getItems().add(serviceMenu); + } + getItems().add(menu); + } + } else { + addMenuItem("Services", event -> controller.getNodeServicesDialog().showDialog(coreNode)); + addMenuItem("Delete Node", event -> controller.deleteNode(coreNode)); + } + } +} diff --git a/corefx/src/main/java/com/core/graph/RadioIcon.java b/corefx/src/main/java/com/core/graph/RadioIcon.java new file mode 100644 index 00000000..e3139eaf --- /dev/null +++ b/corefx/src/main/java/com/core/graph/RadioIcon.java @@ -0,0 +1,31 @@ +package com.core.graph; + +import com.core.utils.IconUtils; +import lombok.Data; + +import javax.swing.*; +import java.awt.*; + +@Data +public class RadioIcon implements Icon { + private long wiressLinks = 0; + + @Override + public int getIconHeight() { + return IconUtils.ICON_SIZE; + } + + @Override + public int getIconWidth() { + return IconUtils.ICON_SIZE; + } + + @Override + public void paintIcon(Component c, Graphics g, int x, int y) { + g.setColor(Color.black); + for (int i = 0; i < wiressLinks; i++) { + g.fillOval(x, y, 10, 10); + x += 15; + } + } +} diff --git a/corefx/src/main/java/com/core/graph/UndirectedSimpleGraph.java b/corefx/src/main/java/com/core/graph/UndirectedSimpleGraph.java new file mode 100644 index 00000000..355b44a0 --- /dev/null +++ b/corefx/src/main/java/com/core/graph/UndirectedSimpleGraph.java @@ -0,0 +1,24 @@ +package com.core.graph; + +import edu.uci.ics.jung.graph.UndirectedSparseGraph; +import edu.uci.ics.jung.graph.util.EdgeType; +import edu.uci.ics.jung.graph.util.Pair; + +public class UndirectedSimpleGraph extends UndirectedSparseGraph { + @Override + public boolean addEdge(E edge, Pair endpoints, EdgeType edgeType) { + Pair newEndpoints = getValidatedEndpoints(edge, endpoints); + if (newEndpoints == null) { + return false; + } + + V first = newEndpoints.getFirst(); + V second = newEndpoints.getSecond(); + + if (first.equals(second)) { + return false; + } else { + return super.addEdge(edge, endpoints, edgeType); + } + } +} diff --git a/corefx/src/main/java/com/core/graph/WlanContextMenu.java b/corefx/src/main/java/com/core/graph/WlanContextMenu.java new file mode 100644 index 00000000..937eef15 --- /dev/null +++ b/corefx/src/main/java/com/core/graph/WlanContextMenu.java @@ -0,0 +1,26 @@ +package com.core.graph; + +import com.core.Controller; +import com.core.data.CoreNode; +import com.core.ui.dialogs.MobilityPlayerDialog; + +class WlanContextMenu extends AbstractNodeContextMenu { + WlanContextMenu(Controller controller, CoreNode coreNode) { + super(controller, coreNode); + setup(); + } + + private void setup() { + addMenuItem("WLAN Settings", event -> controller.getNodeWlanDialog().showDialog(coreNode)); + if (controller.getCoreClient().isRunning()) { + MobilityPlayerDialog mobilityPlayerDialog = controller.getMobilityPlayerDialogs().get(coreNode.getId()); + if (mobilityPlayerDialog != null && !mobilityPlayerDialog.getStage().isShowing()) { + addMenuItem("Mobility Script", event -> mobilityPlayerDialog.show()); + } + } else { + addMenuItem("Mobility", event -> controller.getMobilityDialog().showDialog(coreNode)); + addMenuItem("Link MDRs", event -> controller.getNetworkGraph().linkMdrs(coreNode)); + addMenuItem("Delete Node", event -> controller.deleteNode(coreNode)); + } + } +} diff --git a/corefx/src/main/java/com/core/ui/AnnotationToolbar.java b/corefx/src/main/java/com/core/ui/AnnotationToolbar.java new file mode 100644 index 00000000..b32d3c8b --- /dev/null +++ b/corefx/src/main/java/com/core/ui/AnnotationToolbar.java @@ -0,0 +1,104 @@ +package com.core.ui; + +import com.core.graph.NetworkGraph; +import com.core.utils.FxmlUtils; +import com.jfoenix.controls.JFXColorPicker; +import com.jfoenix.controls.JFXComboBox; +import com.jfoenix.controls.JFXToggleButton; +import edu.uci.ics.jung.visualization.annotations.Annotation; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.scene.layout.GridPane; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.awt.*; +import java.awt.geom.Ellipse2D; +import java.awt.geom.RectangularShape; +import java.awt.geom.RoundRectangle2D; + +public class AnnotationToolbar extends GridPane { + private static final Logger logger = LogManager.getLogger(); + private static final String RECTANGLE = "Rectangle"; + private static final String ROUND_RECTANGLE = "RoundRectangle"; + private static final String ELLIPSE = "Ellipse"; + private static final String UPPER_LAYER = "Upper"; + private static final String LOWER_LAYER = "Lower"; + private NetworkGraph graph; + @FXML private JFXComboBox shapeCombo; + @FXML private JFXColorPicker colorPicker; + @FXML private JFXComboBox layerCombo; + @FXML private JFXToggleButton fillToggle; + + public AnnotationToolbar(NetworkGraph graph) { + this.graph = graph; + FxmlUtils.loadRootController(this, "/fxml/annotation_toolbar.fxml"); + + // setup annotation shape combo + shapeCombo.getItems().addAll(RECTANGLE, ROUND_RECTANGLE, ELLIPSE); + shapeCombo.setOnAction(this::shapeChange); + shapeCombo.getSelectionModel().selectFirst(); + + // setup annotation layer combo + layerCombo.getItems().addAll(LOWER_LAYER, UPPER_LAYER); + layerCombo.setOnAction(this::layerChange); + layerCombo.getSelectionModel().selectFirst(); + + // setup annotation color picker + colorPicker.setOnAction(this::colorChange); + colorPicker.setValue(javafx.scene.paint.Color.AQUA); + colorPicker.fireEvent(new ActionEvent()); + + // setup annotation toggle fill + fillToggle.setOnAction(this::fillChange); + } + + private void fillChange(ActionEvent event) { + boolean selected = fillToggle.isSelected(); + graph.getGraphMouse().getAnnotatingPlugin().setFill(selected); + } + + private void colorChange(ActionEvent event) { + javafx.scene.paint.Color fxColor = colorPicker.getValue(); + java.awt.Color color = new java.awt.Color( + (float) fxColor.getRed(), + (float) fxColor.getGreen(), + (float) fxColor.getBlue(), + (float) fxColor.getOpacity() + ); + logger.info("color selected: {}", fxColor); + graph.getGraphMouse().getAnnotatingPlugin().setAnnotationColor(color); + } + + private void layerChange(ActionEvent event) { + String selected = layerCombo.getSelectionModel().getSelectedItem(); + logger.info("annotation layer selected: {}", selected); + Annotation.Layer layer; + if (LOWER_LAYER.equals(selected)) { + layer = Annotation.Layer.LOWER; + } else { + layer = Annotation.Layer.UPPER; + } + graph.getGraphMouse().getAnnotatingPlugin().setLayer(layer); + } + + private void shapeChange(ActionEvent event) { + String selected = shapeCombo.getSelectionModel().getSelectedItem(); + logger.info("annotation shape selected: {}", selected); + RectangularShape shape = new Rectangle(); + switch (selected) { + case RECTANGLE: + shape = new Rectangle(); + break; + case ROUND_RECTANGLE: + shape = new RoundRectangle2D.Double(0, 0, 0, 0, 50.0, 50.0); + break; + case ELLIPSE: + shape = new Ellipse2D.Double(); + break; + default: + Toast.error("Unknown annotation shape " + selected); + } + graph.getGraphMouse().getAnnotatingPlugin().setRectangularShape(shape); + } +} diff --git a/corefx/src/main/java/com/core/ui/GraphToolbar.java b/corefx/src/main/java/com/core/ui/GraphToolbar.java new file mode 100644 index 00000000..f64ca73a --- /dev/null +++ b/corefx/src/main/java/com/core/ui/GraphToolbar.java @@ -0,0 +1,297 @@ +package com.core.ui; + +import com.core.Controller; +import com.core.data.NodeType; +import com.core.utils.FxmlUtils; +import com.core.utils.IconUtils; +import com.jfoenix.controls.JFXButton; +import com.jfoenix.controls.JFXListView; +import com.jfoenix.controls.JFXPopup; +import com.jfoenix.svg.SVGGlyph; +import edu.uci.ics.jung.visualization.control.ModalGraphMouse; +import javafx.application.Platform; +import javafx.css.PseudoClass; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.scene.control.ComboBox; +import javafx.scene.control.Label; +import javafx.scene.control.Tooltip; +import javafx.scene.image.ImageView; +import javafx.scene.layout.VBox; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Map; + +public class GraphToolbar extends VBox { + private static final Logger logger = LogManager.getLogger(); + private static final int ICON_SIZE = 40; + private static final int NODES_ICON_SIZE = 20; + private static final PseudoClass START_CLASS = PseudoClass.getPseudoClass("start"); + private static final PseudoClass STOP_CLASS = PseudoClass.getPseudoClass("stop"); + private static final PseudoClass SELECTED_CLASS = PseudoClass.getPseudoClass("selected"); + private final Controller controller; + private final Map labelMap = new HashMap<>(); + private SVGGlyph startIcon; + private SVGGlyph stopIcon; + private JFXListView