diff --git a/.gitignore b/.gitignore
index 2cbc88c3..038146aa 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,7 +8,7 @@ Makefile
Makefile.in
aclocal.m4
autom4te.cache
-config
+/config
config.h
config.h.in
config.log
@@ -55,3 +55,6 @@ coverage.xml
netns/setup.py
daemon/setup.py
ns3/setup.py
+
+# ignore corefx build
+corefx/target
diff --git a/README.md b/README.md
index c5f7e182..76435683 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
CORE: Common Open Research Emulator
-Copyright (c)2005-2018 the Boeing Company.
+Copyright (c)2005-2019 the Boeing Company.
See the LICENSE file included in this distribution.
diff --git a/configure.ac b/configure.ac
index 763c6c4a..17637088 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2,7 +2,7 @@
# Process this file with autoconf to produce a configure script.
# this defines the CORE version number, must be static for AC_INIT
-AC_INIT(core, 5.2.1, core-dev@nrl.navy.mil)
+AC_INIT(core, 5.2.1)
# autoconf and automake initialization
AC_CONFIG_SRCDIR([netns/version.h.in])
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..7a5d9be8
--- /dev/null
+++ b/corefx/src/main/java/com/core/Controller.java
@@ -0,0 +1,529 @@
+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.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.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();
+
+ // 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) {
+ executorService.submit(() -> {
+ try {
+ coreClient.setConnection(address, port);
+ initialJoin();
+ } catch (IOException 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();
+ logger.info("emane models: {}", emaneModels);
+ 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);
+
+ // setup event handlers
+ coreClient.setupEventHandlers(this);
+
+ // 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 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 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) {
+ 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(Controller.this);
+ } 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..7e0350aa
--- /dev/null
+++ b/corefx/src/main/java/com/core/client/ICoreClient.java
@@ -0,0 +1,121 @@
+package com.core.client;
+
+import com.core.Controller;
+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(Controller controller) 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;
+
+ void setupEventHandlers(Controller controller) 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..bc9098b3
--- /dev/null
+++ b/corefx/src/main/java/com/core/client/grpc/CoreGrpcClient.java
@@ -0,0 +1,1238 @@
+package com.core.client.grpc;
+
+import com.core.Controller;
+import com.core.client.ICoreClient;
+import com.core.client.rest.ServiceFile;
+import com.core.client.rest.WlanConfig;
+import com.core.data.*;
+import com.core.ui.dialogs.MobilityPlayerDialog;
+import com.google.protobuf.ByteString;
+import io.grpc.Context;
+import io.grpc.ManagedChannel;
+import io.grpc.ManagedChannelBuilder;
+import io.grpc.StatusRuntimeException;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.*;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+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 final ExecutorService executorService = Executors.newFixedThreadPool(6);
+ private boolean handlingEvents = false;
+ private boolean handlingThroughputs = false;
+
+ 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.Enum.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 List protoToConfigGroups(List protoConfigs) {
+ List configs = new ArrayList<>();
+ for (CoreProto.ConfigGroup protoConfig : protoConfigs) {
+ ConfigGroup config = new ConfigGroup();
+ config.setName(protoConfig.getName());
+ for (CoreProto.ConfigOption protoOption : protoConfig.getOptionsList()) {
+ ConfigOption option = new ConfigOption();
+ option.setType(protoOption.getType());
+ option.setLabel(protoOption.getLabel());
+ option.setName(protoOption.getName());
+ option.setValue(protoOption.getValue());
+ option.setSelect(protoOption.getSelectList());
+ config.getOptions().add(option);
+ }
+ configs.add(config);
+ }
+ return configs;
+ }
+
+ 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();
+ }
+
+ private Map configOptionListToMap(List options) {
+ Map config = new HashMap<>();
+ for (ConfigOption option : options) {
+ config.put(option.getName(), option.getValue());
+ }
+ return config;
+ }
+
+ private CoreNode protoToNode(CoreProto.Node protoNode) {
+ 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.setType(protoNode.getTypeValue());
+ return node;
+ }
+
+ private CoreInterface protoToInterface(CoreProto.Interface protoInterface) {
+ CoreInterface coreInterface = new CoreInterface();
+ coreInterface.setId(protoInterface.getId());
+ coreInterface.setName(protoInterface.getName());
+ coreInterface.setMac(protoInterface.getMac());
+ coreInterface.setIp4(protoInterface.getIp4());
+ coreInterface.setIp4Mask(protoInterface.getIp4Mask());
+ coreInterface.setIp6(protoInterface.getIp6());
+ coreInterface.setIp6Mask(Integer.toString(protoInterface.getIp6Mask()));
+ return coreInterface;
+ }
+
+ private CoreLink protoToLink(CoreProto.Link linkProto) {
+ CoreLink link = new CoreLink();
+ link.setNodeOne(linkProto.getNodeOneId());
+ link.setNodeTwo(linkProto.getNodeTwoId());
+ CoreInterface interfaceOne = protoToInterface(linkProto.getInterfaceOne());
+ link.setInterfaceOne(interfaceOne);
+ CoreInterface interfaceTwo = protoToInterface(linkProto.getInterfaceTwo());
+ 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());
+ if (!protoOptions.getKey().isEmpty()) {
+ 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);
+
+ return link;
+ }
+
+ @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(Controller controller) throws IOException {
+ CoreProto.ThroughputsRequest request = CoreProto.ThroughputsRequest.newBuilder().build();
+ try {
+ handlingThroughputs = true;
+ executorService.submit(() -> {
+ Context.CancellableContext context = Context.current().withCancellation();
+ context.run(() -> {
+ try {
+ Iterator iterator = blockingStub.throughputs(request);
+ while (handlingThroughputs) {
+ CoreProto.ThroughputsEvent event = iterator.next();
+ logger.info("handling throughputs: {}", event);
+ Throughputs throughputs = new Throughputs();
+ for (CoreProto.BridgeThroughput protoBridge : event.getBridgeThroughputsList()) {
+ BridgeThroughput bridge = new BridgeThroughput();
+ bridge.setNode(protoBridge.getNodeId());
+ bridge.setThroughput(protoBridge.getThroughput());
+ throughputs.getBridges().add(bridge);
+ }
+ for (CoreProto.InterfaceThroughput protoInterface : event.getInterfaceThroughputsList()) {
+ InterfaceThroughput interfaceThroughput = new InterfaceThroughput();
+ interfaceThroughput.setNode(protoInterface.getNodeId());
+ interfaceThroughput.setNodeInterface(protoInterface.getInterfaceId());
+ interfaceThroughput.setThroughput(protoInterface.getThroughput());
+ throughputs.getInterfaces().add(interfaceThroughput);
+ }
+ controller.handleThroughputs(throughputs);
+ }
+ logger.info("exiting handling throughputs");
+ } catch (StatusRuntimeException ex) {
+ logger.error("error handling session events", ex);
+ } finally {
+ context.cancel(null);
+ context.close();
+ }
+ });
+ });
+ return true;
+ } catch (StatusRuntimeException ex) {
+ throw new IOException("setup event handlers error", ex);
+ }
+ }
+
+ @Override
+ public boolean stopThroughput() throws IOException {
+ logger.info("cancelling throughputs");
+ handlingThroughputs = false;
+ return true;
+ }
+
+ @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();
+ try {
+ CoreProto.CreateSessionResponse response = blockingStub.createSession(request);
+ SessionOverview overview = new SessionOverview();
+ overview.setId(response.getSessionId());
+ overview.setState(response.getStateValue());
+ overview.setNodes(0);
+ return overview;
+ } catch (StatusRuntimeException ex) {
+ throw new IOException(ex);
+ }
+ }
+
+ @Override
+ public boolean deleteSession(Integer sessionId) throws IOException {
+ CoreProto.DeleteSessionRequest request = CoreProto.DeleteSessionRequest.newBuilder()
+ .setSessionId(sessionId).build();
+ try {
+ return blockingStub.deleteSession(request).getResult();
+ } catch (StatusRuntimeException ex) {
+ throw new IOException(ex);
+ }
+ }
+
+ @Override
+ public List getSessions() throws IOException {
+ CoreProto.GetSessionsRequest request = CoreProto.GetSessionsRequest.newBuilder().build();
+ try {
+ 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;
+ } catch (StatusRuntimeException ex) {
+ throw new IOException(ex);
+ }
+ }
+
+ @Override
+ public Session getSession(Integer sessionId) throws IOException {
+ logger.info("getting session: {}", sessionId);
+ CoreProto.GetSessionRequest request = CoreProto.GetSessionRequest.newBuilder().setSessionId(sessionId).build();
+ try {
+ CoreProto.GetSessionResponse response = blockingStub.getSession(request);
+ Session session = new Session();
+ for (CoreProto.Node protoNode : response.getSession().getNodesList()) {
+ if (CoreProto.NodeType.Enum.PEER_TO_PEER == protoNode.getType()) {
+ continue;
+ }
+
+ logger.info("adding node: {}", protoNode);
+ CoreNode node = protoToNode(protoNode);
+ session.getNodes().add(node);
+ }
+ for (CoreProto.Link linkProto : response.getSession().getLinksList()) {
+ logger.info("adding link: {} - {}", linkProto.getNodeOneId(), linkProto.getNodeTwoId());
+ CoreLink link = protoToLink(linkProto);
+ session.getLinks().add(link);
+ }
+ session.setState(response.getSession().getStateValue());
+ return session;
+ } catch (StatusRuntimeException ex) {
+ throw new IOException(ex);
+ }
+ }
+
+ @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 {
+ handlingEvents = false;
+ return setState(SessionState.SHUTDOWN);
+ }
+
+ @Override
+ public boolean setState(SessionState state) throws IOException {
+ CoreProto.SetSessionStateRequest request = CoreProto.SetSessionStateRequest.newBuilder()
+ .setSessionId(sessionId)
+ .setStateValue(state.getValue())
+ .build();
+ try {
+ CoreProto.SetSessionStateResponse response = blockingStub.setSessionState(request);
+ if (response.getResult()) {
+ sessionState = state;
+ }
+ return response.getResult();
+ } catch (StatusRuntimeException ex) {
+ throw new IOException(ex);
+ }
+ }
+
+ @Override
+ public Map> getServices() throws IOException {
+ CoreProto.GetServicesRequest request = CoreProto.GetServicesRequest.newBuilder().build();
+ try {
+ 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;
+ } catch (StatusRuntimeException ex) {
+ throw new IOException(ex);
+ }
+ }
+
+ @Override
+ public Map> getDefaultServices() throws IOException {
+ CoreProto.GetServiceDefaultsRequest request = CoreProto.GetServiceDefaultsRequest.newBuilder().build();
+ try {
+ CoreProto.GetServiceDefaultsResponse response = blockingStub.getServiceDefaults(request);
+ Map> servicesMap = new HashMap<>();
+ for (CoreProto.ServiceDefaults serviceDefaults : response.getDefaultsList()) {
+ servicesMap.put(serviceDefaults.getNodeType(), serviceDefaults.getServicesList());
+ }
+ return servicesMap;
+ } catch (StatusRuntimeException ex) {
+ throw new IOException(ex);
+ }
+ }
+
+ @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()
+ .setSessionId(sessionId)
+ .addAllDefaults(allDefaults)
+ .build();
+ try {
+ CoreProto.SetServiceDefaultsResponse response = blockingStub.setServiceDefaults(request);
+ return response.getResult();
+ } catch (StatusRuntimeException ex) {
+ throw new IOException(ex);
+ }
+ }
+
+ @Override
+ public CoreService getService(CoreNode node, String serviceName) throws IOException {
+ CoreProto.GetNodeServiceRequest request = CoreProto.GetNodeServiceRequest.newBuilder().build();
+ try {
+ 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;
+ } catch (StatusRuntimeException ex) {
+ throw new IOException(ex);
+ }
+ }
+
+ @Override
+ public boolean setService(CoreNode node, String serviceName, CoreService service) throws IOException {
+ CoreProto.SetNodeServiceRequest request = CoreProto.SetNodeServiceRequest.newBuilder()
+ .setNodeId(node.getId())
+ .setSessionId(sessionId)
+ .setService(serviceName)
+ .build();
+ request.getShutdownList().addAll(service.getShutdown());
+ request.getValidateList().addAll(service.getValidate());
+ request.getStartupList().addAll(service.getStartup());
+ try {
+ return blockingStub.setNodeService(request).getResult();
+ } catch (StatusRuntimeException ex) {
+ throw new IOException(ex);
+ }
+ }
+
+ @Override
+ public String getServiceFile(CoreNode node, String serviceName, String fileName) throws IOException {
+ CoreProto.GetNodeServiceFileRequest request = CoreProto.GetNodeServiceFileRequest.newBuilder()
+ .setSessionId(sessionId)
+ .setNodeId(node.getId())
+ .setService(serviceName)
+ .build();
+ try {
+ CoreProto.GetNodeServiceFileResponse response = blockingStub.getNodeServiceFile(request);
+ return response.getData().toStringUtf8();
+ } catch (StatusRuntimeException ex) {
+ throw new IOException(ex);
+ }
+ }
+
+ @Override
+ public boolean startService(CoreNode node, String serviceName) throws IOException {
+ CoreProto.ServiceActionRequest request = CoreProto.ServiceActionRequest.newBuilder()
+ .setSessionId(sessionId)
+ .setNodeId(node.getId())
+ .setService(serviceName)
+ .setAction(CoreProto.ServiceAction.Enum.START)
+ .build();
+ try {
+ return blockingStub.serviceAction(request).getResult();
+ } catch (StatusRuntimeException ex) {
+ throw new IOException(ex);
+ }
+ }
+
+ @Override
+ public boolean stopService(CoreNode node, String serviceName) throws IOException {
+ CoreProto.ServiceActionRequest request = CoreProto.ServiceActionRequest.newBuilder()
+ .setSessionId(sessionId)
+ .setNodeId(node.getId())
+ .setService(serviceName)
+ .setAction(CoreProto.ServiceAction.Enum.STOP)
+ .build();
+ try {
+ return blockingStub.serviceAction(request).getResult();
+ } catch (StatusRuntimeException ex) {
+ throw new IOException(ex);
+ }
+ }
+
+ @Override
+ public boolean restartService(CoreNode node, String serviceName) throws IOException {
+ CoreProto.ServiceActionRequest request = CoreProto.ServiceActionRequest.newBuilder()
+ .setSessionId(sessionId)
+ .setNodeId(node.getId())
+ .setService(serviceName)
+ .setAction(CoreProto.ServiceAction.Enum.RESTART)
+ .build();
+ try {
+ return blockingStub.serviceAction(request).getResult();
+ } catch (StatusRuntimeException ex) {
+ throw new IOException(ex);
+ }
+ }
+
+ @Override
+ public boolean validateService(CoreNode node, String serviceName) throws IOException {
+ CoreProto.ServiceActionRequest request = CoreProto.ServiceActionRequest.newBuilder()
+ .setSessionId(sessionId)
+ .setNodeId(node.getId())
+ .setService(serviceName)
+ .setAction(CoreProto.ServiceAction.Enum.VALIDATE)
+ .build();
+ try {
+ return blockingStub.serviceAction(request).getResult();
+ } catch (StatusRuntimeException ex) {
+ throw new IOException(ex);
+ }
+ }
+
+ @Override
+ public boolean setServiceFile(CoreNode node, String serviceName, ServiceFile serviceFile) throws IOException {
+ CoreProto.SetNodeServiceFileRequest request = CoreProto.SetNodeServiceFileRequest.newBuilder()
+ .setSessionId(sessionId)
+ .setNodeId(node.getId())
+ .setService(serviceName)
+ .setFile(serviceFile.getName())
+ .setData(ByteString.copyFromUtf8(serviceFile.getData()))
+ .build();
+ try {
+ CoreProto.SetNodeServiceFileResponse response = blockingStub.setNodeServiceFile(request);
+ return response.getResult();
+ } catch (StatusRuntimeException ex) {
+ throw new IOException(ex);
+ }
+ }
+
+ @Override
+ public List getEmaneConfig(CoreNode node) throws IOException {
+ CoreProto.GetEmaneConfigRequest request = CoreProto.GetEmaneConfigRequest.newBuilder()
+ .setSessionId(sessionId)
+ .build();
+ try {
+ CoreProto.GetEmaneConfigResponse response = blockingStub.getEmaneConfig(request);
+ return protoToConfigGroups(response.getGroupsList());
+ } catch (StatusRuntimeException ex) {
+ throw new IOException(ex);
+ }
+ }
+
+ @Override
+ public List getEmaneModels() throws IOException {
+ CoreProto.GetEmaneModelsRequest request = CoreProto.GetEmaneModelsRequest.newBuilder()
+ .setSessionId(sessionId)
+ .build();
+ try {
+ CoreProto.GetEmaneModelsResponse response = blockingStub.getEmaneModels(request);
+ return new ArrayList<>(response.getModelsList());
+ } catch (StatusRuntimeException ex) {
+ throw new IOException(ex);
+ }
+ }
+
+ @Override
+ public boolean setEmaneConfig(CoreNode node, List options) throws IOException {
+ Map config = configOptionListToMap(options);
+ CoreProto.SetEmaneConfigRequest request = CoreProto.SetEmaneConfigRequest.newBuilder()
+ .setSessionId(sessionId)
+ .putAllConfig(config)
+ .build();
+ try {
+ CoreProto.SetEmaneConfigResponse response = blockingStub.setEmaneConfig(request);
+ return response.getResult();
+ } catch (StatusRuntimeException ex) {
+ throw new IOException(ex);
+ }
+ }
+
+ @Override
+ public List getEmaneModelConfig(Integer id, String model) throws IOException {
+ CoreProto.GetEmaneModelConfigRequest request = CoreProto.GetEmaneModelConfigRequest.newBuilder()
+ .setSessionId(sessionId)
+ .setNodeId(id)
+ .setModel(model)
+ .build();
+ try {
+ CoreProto.GetEmaneModelConfigResponse response = blockingStub.getEmaneModelConfig(request);
+ return protoToConfigGroups(response.getGroupsList());
+ } catch (StatusRuntimeException ex) {
+ throw new IOException(ex);
+ }
+ }
+
+ @Override
+ public boolean setEmaneModelConfig(Integer id, String model, List options) throws IOException {
+ Map config = configOptionListToMap(options);
+ CoreProto.SetEmaneModelConfigRequest request = CoreProto.SetEmaneModelConfigRequest.newBuilder()
+ .setSessionId(sessionId)
+ .setNodeId(id)
+ .setModel(model)
+ .putAllConfig(config)
+ .build();
+ try {
+ CoreProto.SetEmaneModelConfigResponse response = blockingStub.setEmaneModelConfig(request);
+ return response.getResult();
+ } catch (StatusRuntimeException ex) {
+ throw new IOException(ex);
+ }
+ }
+
+ @Override
+ public boolean isRunning() {
+ return sessionState == SessionState.RUNTIME;
+ }
+
+ @Override
+ public void saveSession(File file) throws IOException {
+ CoreProto.SaveXmlRequest request = CoreProto.SaveXmlRequest.newBuilder()
+ .setSessionId(sessionId)
+ .build();
+ try {
+ CoreProto.SaveXmlResponse response = blockingStub.saveXml(request);
+ try (PrintWriter writer = new PrintWriter(file)) {
+ writer.print(response.getData().toStringUtf8());
+ }
+ } catch (StatusRuntimeException ex) {
+ throw new IOException(ex);
+ }
+ }
+
+ @Override
+ public SessionOverview openSession(File file) throws IOException {
+ ByteString data = ByteString.readFrom(new FileInputStream(file));
+ CoreProto.OpenXmlRequest request = CoreProto.OpenXmlRequest.newBuilder()
+ .setData(data)
+ .build();
+ try {
+ CoreProto.OpenXmlResponse response = blockingStub.openXml(request);
+ SessionOverview sessionOverview = new SessionOverview();
+ sessionOverview.setId(response.getSessionId());
+ return sessionOverview;
+ } catch (StatusRuntimeException ex) {
+ throw new IOException(ex);
+ }
+ }
+
+ @Override
+ public List getSessionConfig() throws IOException {
+ CoreProto.GetSessionOptionsRequest request = CoreProto.GetSessionOptionsRequest.newBuilder()
+ .setSessionId(sessionId)
+ .build();
+ try {
+ CoreProto.GetSessionOptionsResponse response = blockingStub.getSessionOptions(request);
+ return protoToConfigGroups(response.getGroupsList());
+ } catch (StatusRuntimeException ex) {
+ throw new IOException(ex);
+ }
+ }
+
+ @Override
+ public boolean setSessionConfig(List configOptions) throws IOException {
+ Map config = configOptionListToMap(configOptions);
+ CoreProto.SetSessionOptionsRequest request = CoreProto.SetSessionOptionsRequest.newBuilder()
+ .setSessionId(sessionId)
+ .putAllConfig(config)
+ .build();
+ try {
+ CoreProto.SetSessionOptionsResponse response = blockingStub.setSessionOptions(request);
+ return response.getResult();
+ } catch (StatusRuntimeException ex) {
+ throw new IOException(ex);
+ }
+ }
+
+ @Override
+ public boolean createNode(CoreNode node) throws IOException {
+ CoreProto.Node protoNode = nodeToProto(node);
+ CoreProto.AddNodeRequest request = CoreProto.AddNodeRequest.newBuilder()
+ .setSessionId(sessionId)
+ .setNode(protoNode)
+ .build();
+ try {
+ blockingStub.addNode(request);
+ return true;
+ } catch (StatusRuntimeException ex) {
+ throw new IOException(ex);
+ }
+ }
+
+ @Override
+ public String nodeCommand(CoreNode node, String command) throws IOException {
+ CoreProto.NodeCommandRequest request = CoreProto.NodeCommandRequest.newBuilder()
+ .setSessionId(sessionId)
+ .setNodeId(node.getId())
+ .setCommand(command)
+ .build();
+ try {
+ CoreProto.NodeCommandResponse response = blockingStub.nodeCommand(request);
+ return response.getOutput();
+ } catch (StatusRuntimeException ex) {
+ throw new IOException(ex);
+ }
+ }
+
+ @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()
+ .setSessionId(sessionId)
+ .setNodeId(node.getId())
+ .setPosition(position)
+ .build();
+ try {
+ CoreProto.EditNodeResponse response = blockingStub.editNode(request);
+ return response.getResult();
+ } catch (StatusRuntimeException ex) {
+ throw new IOException(ex);
+ }
+ }
+
+ @Override
+ public boolean deleteNode(CoreNode node) throws IOException {
+ CoreProto.DeleteNodeRequest request = CoreProto.DeleteNodeRequest.newBuilder()
+ .build();
+ try {
+ CoreProto.DeleteNodeResponse response = blockingStub.deleteNode(request);
+ return response.getResult();
+ } catch (StatusRuntimeException ex) {
+ throw new IOException(ex);
+ }
+ }
+
+ @Override
+ public boolean createLink(CoreLink link) throws IOException {
+ CoreProto.Link.Builder builder = CoreProto.Link.newBuilder()
+ .setTypeValue(link.getType());
+ if (link.getNodeOne() != null) {
+ builder.setNodeOneId(link.getNodeOne());
+ }
+ if (link.getNodeTwo() != null) {
+ builder.setNodeTwoId(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()
+ .setSessionId(sessionId)
+ .setLink(protoLink)
+ .build();
+ try {
+ CoreProto.AddLinkResponse response = blockingStub.addLink(request);
+ return response.getResult();
+ } catch (StatusRuntimeException ex) {
+ throw new IOException(ex);
+ }
+ }
+
+ @Override
+ public boolean editLink(CoreLink link) throws IOException {
+ CoreProto.EditLinkRequest.Builder builder = CoreProto.EditLinkRequest.newBuilder()
+ .setSessionId(sessionId);
+ if (link.getNodeOne() != null) {
+ builder.setNodeOneId(link.getNodeOne());
+ }
+ if (link.getNodeTwo() != null) {
+ builder.setNodeTwoId(link.getNodeTwo());
+ }
+ if (link.getInterfaceOne() != null) {
+ builder.setInterfaceOneId(link.getInterfaceOne().getId());
+ }
+ if (link.getInterfaceTwo() != null) {
+ builder.setInterfaceTwoId(link.getInterfaceTwo().getId());
+ }
+ if (link.getOptions() != null) {
+ CoreProto.LinkOptions protoOptions = linkOptionsToProto(link.getOptions());
+ builder.setOptions(protoOptions);
+ }
+ CoreProto.EditLinkRequest request = builder.build();
+ try {
+ CoreProto.EditLinkResponse response = blockingStub.editLink(request);
+ return response.getResult();
+ } catch (StatusRuntimeException ex) {
+ throw new IOException(ex);
+ }
+ }
+
+ @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();
+ try {
+ CoreProto.AddHookResponse response = blockingStub.addHook(request);
+ return response.getResult();
+ } catch (StatusRuntimeException ex) {
+ throw new IOException(ex);
+ }
+ }
+
+ @Override
+ public List getHooks() throws IOException {
+ CoreProto.GetHooksRequest request = CoreProto.GetHooksRequest.newBuilder().setSessionId(sessionId).build();
+ try {
+ 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;
+ } catch (StatusRuntimeException ex) {
+ throw new IOException(ex);
+ }
+ }
+
+ @Override
+ public WlanConfig getWlanConfig(CoreNode node) throws IOException {
+ CoreProto.GetWlanConfigRequest request = CoreProto.GetWlanConfigRequest.newBuilder()
+ .setSessionId(sessionId)
+ .setNodeId(node.getId())
+ .build();
+ try {
+ CoreProto.GetWlanConfigResponse response = blockingStub.getWlanConfig(request);
+ Map protoConfig = new HashMap<>();
+ for (CoreProto.ConfigGroup group : response.getGroupsList()) {
+ for (CoreProto.ConfigOption option : group.getOptionsList()) {
+ protoConfig.put(option.getName(), option.getValue());
+ }
+ }
+ WlanConfig config = new WlanConfig();
+ config.setBandwidth(protoConfig.get("bandwidth"));
+ config.setDelay(protoConfig.get("delay"));
+ config.setError(protoConfig.get("error"));
+ config.setJitter(protoConfig.get("jitter"));
+ config.setRange(protoConfig.get("range"));
+ return config;
+ } catch (StatusRuntimeException ex) {
+ throw new IOException(ex);
+ }
+ }
+
+ @Override
+ public boolean setWlanConfig(CoreNode node, WlanConfig config) throws IOException {
+ Map protoConfig = new HashMap<>();
+ protoConfig.put("bandwidth", config.getBandwidth());
+ protoConfig.put("delay", config.getDelay());
+ protoConfig.put("error", config.getError());
+ protoConfig.put("jitter", config.getJitter());
+ protoConfig.put("range", config.getRange());
+ CoreProto.SetWlanConfigRequest request = CoreProto.SetWlanConfigRequest.newBuilder()
+ .setSessionId(sessionId)
+ .setNodeId(node.getId())
+ .putAllConfig(protoConfig)
+ .build();
+ try {
+ CoreProto.SetWlanConfigResponse response = blockingStub.setWlanConfig(request);
+ return response.getResult();
+ } catch (StatusRuntimeException ex) {
+ throw new IOException(ex);
+ }
+ }
+
+ @Override
+ public String getTerminalCommand(CoreNode node) throws IOException {
+ CoreProto.GetNodeTerminalRequest request = CoreProto.GetNodeTerminalRequest.newBuilder()
+ .setSessionId(sessionId)
+ .setNodeId(node.getId())
+ .build();
+ try {
+ return blockingStub.getNodeTerminal(request).getTerminal();
+ } catch (StatusRuntimeException ex) {
+ throw new IOException(ex);
+ }
+ }
+
+ @Override
+ public Map getMobilityConfigs() throws IOException {
+ CoreProto.GetMobilityConfigsRequest request = CoreProto.GetMobilityConfigsRequest.newBuilder()
+ .setSessionId(sessionId).build();
+ try {
+ 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();
+ Map protoConfig = new HashMap<>();
+ for (CoreProto.ConfigGroup group : protoMobilityConfig.getGroupsList()) {
+ for (CoreProto.ConfigOption option : group.getOptionsList()) {
+ protoConfig.put(option.getName(), option.getValue());
+ }
+ }
+ mobilityConfig.setFile(protoConfig.get("file"));
+ mobilityConfig.setRefresh(Integer.parseInt(protoConfig.get("refresh_ms")));
+ mobilityConfig.setAutostart(protoConfig.get("autostart"));
+ mobilityConfig.setLoop(protoConfig.get("loop"));
+ mobilityConfig.setPauseScript(protoConfig.get("script_pause"));
+ mobilityConfig.setStartScript(protoConfig.get("script_start"));
+ mobilityConfig.setStopScript(protoConfig.get("script_stop"));
+ mobilityConfig.setMap(protoConfig.get("map"));
+ mobilityConfigs.put(nodeId, mobilityConfig);
+ }
+ return mobilityConfigs;
+ } catch (StatusRuntimeException ex) {
+ throw new IOException(ex);
+ }
+ }
+
+ @Override
+ public boolean setMobilityConfig(CoreNode node, MobilityConfig config) throws IOException {
+ Map protoConfig = new HashMap<>();
+ protoConfig.put("file", config.getFile());
+ if (config.getRefresh() != null) {
+ protoConfig.put("refresh_ms", config.getRefresh().toString());
+ }
+ protoConfig.put("autostart", config.getAutostart());
+ protoConfig.put("loop", config.getLoop());
+ protoConfig.put("map", config.getMap());
+ protoConfig.put("script_pause", config.getPauseScript());
+ protoConfig.put("script_start", config.getStartScript());
+ protoConfig.put("script_stop", config.getStopScript());
+ CoreProto.SetMobilityConfigRequest request = CoreProto.SetMobilityConfigRequest.newBuilder()
+ .setSessionId(sessionId)
+ .setNodeId(node.getId())
+ .putAllConfig(protoConfig)
+ .build();
+ try {
+ CoreProto.SetMobilityConfigResponse response = blockingStub.setMobilityConfig(request);
+ return response.getResult();
+ } catch (StatusRuntimeException ex) {
+ throw new IOException(ex);
+ }
+ }
+
+ @Override
+ public MobilityConfig getMobilityConfig(CoreNode node) throws IOException {
+ CoreProto.GetMobilityConfigRequest request = CoreProto.GetMobilityConfigRequest.newBuilder()
+ .setSessionId(sessionId)
+ .setNodeId(node.getId())
+ .build();
+ try {
+ CoreProto.GetMobilityConfigResponse response = blockingStub.getMobilityConfig(request);
+ Map protoConfig = new HashMap<>();
+ for (CoreProto.ConfigGroup group : response.getGroupsList()) {
+ for (CoreProto.ConfigOption option : group.getOptionsList()) {
+ protoConfig.put(option.getName(), option.getValue());
+ }
+ }
+ MobilityConfig config = new MobilityConfig();
+ config.setFile(protoConfig.get("file"));
+ config.setRefresh(Integer.parseInt(protoConfig.get("refresh_ms")));
+ config.setAutostart(protoConfig.get("autostart"));
+ config.setLoop(protoConfig.get("loop"));
+ config.setPauseScript(protoConfig.get("script_pause"));
+ config.setStartScript(protoConfig.get("script_start"));
+ config.setStopScript(protoConfig.get("script_stop"));
+ config.setMap(protoConfig.get("map"));
+ return config;
+ } catch (StatusRuntimeException ex) {
+ throw new IOException(ex);
+ }
+ }
+
+ @Override
+ public boolean mobilityAction(CoreNode node, String action) throws IOException {
+ CoreProto.MobilityActionRequest request = CoreProto.MobilityActionRequest.newBuilder()
+ .setSessionId(sessionId)
+ .setNodeId(node.getId())
+ .setAction(CoreProto.MobilityAction.Enum.valueOf(action))
+ .build();
+ try {
+ CoreProto.MobilityActionResponse response = blockingStub.mobilityAction(request);
+ return response.getResult();
+ } catch (StatusRuntimeException ex) {
+ throw new IOException(ex);
+ }
+ }
+
+ @Override
+ public LocationConfig getLocationConfig() throws IOException {
+ CoreProto.GetSessionLocationRequest request = CoreProto.GetSessionLocationRequest.newBuilder()
+ .setSessionId(sessionId)
+ .build();
+ try {
+ CoreProto.GetSessionLocationResponse response = blockingStub.getSessionLocation(request);
+ LocationConfig config = new LocationConfig();
+ config.setScale((double) response.getScale());
+ config.getPosition().setX((double) response.getPosition().getX());
+ config.getPosition().setY((double) response.getPosition().getY());
+ config.getPosition().setZ((double) response.getPosition().getZ());
+ config.getLocation().setLatitude((double) response.getPosition().getLat());
+ config.getLocation().setLongitude((double) response.getPosition().getLon());
+ config.getLocation().setAltitude((double) response.getPosition().getAlt());
+ return config;
+ } catch (StatusRuntimeException ex) {
+ throw new IOException(ex);
+ }
+ }
+
+ @Override
+ public boolean setLocationConfig(LocationConfig config) throws IOException {
+ CoreProto.SetSessionLocationRequest.Builder builder = CoreProto.SetSessionLocationRequest.newBuilder()
+ .setSessionId(sessionId);
+ if (config.getScale() != null) {
+ builder.setScale(config.getScale().floatValue());
+ }
+ CoreProto.Position.Builder positionBuilder = CoreProto.Position.newBuilder();
+ if (config.getPosition().getX() != null) {
+ positionBuilder.setX(config.getPosition().getX().floatValue());
+ }
+ if (config.getPosition().getY() != null) {
+ positionBuilder.setY(config.getPosition().getY().floatValue());
+ }
+ if (config.getPosition().getZ() != null) {
+ positionBuilder.setZ(config.getPosition().getZ().floatValue());
+ }
+ if (config.getLocation().getLongitude() != null) {
+ positionBuilder.setLon(config.getLocation().getLongitude().floatValue());
+ }
+ if (config.getLocation().getLatitude() != null) {
+ positionBuilder.setLat(config.getLocation().getLatitude().floatValue());
+ }
+ if (config.getLocation().getAltitude() != null) {
+ positionBuilder.setAlt(config.getLocation().getAltitude().floatValue());
+ }
+ try {
+ CoreProto.SetSessionLocationResponse response = blockingStub.setSessionLocation(builder.build());
+ return response.getResult();
+ } catch (StatusRuntimeException ex) {
+ throw new IOException(ex);
+ }
+ }
+
+ @Override
+ public void setupEventHandlers(Controller controller) throws IOException {
+ logger.info("setting up event handlers");
+ handlingEvents = true;
+ try {
+ CoreProto.EventsRequest request = CoreProto.EventsRequest.newBuilder()
+ .setSessionId(sessionId)
+ .build();
+
+ Iterator events = blockingStub.events(request);
+ executorService.submit(() -> {
+ Context.CancellableContext context = Context.current().withCancellation();
+ context.run(() -> {
+ try {
+ while (handlingEvents) {
+ CoreProto.Event event = events.next();
+ logger.info("handling event: {}", event);
+ switch (event.getEventTypeCase()) {
+ case SESSION_EVENT:
+ handleSessionEvents(controller, event.getSessionEvent());
+ break;
+ case NODE_EVENT:
+ handleNodeEvents(controller, event.getNodeEvent());
+ break;
+ case LINK_EVENT:
+ handleLinkEvents(controller, event.getLinkEvent());
+ break;
+ case CONFIG_EVENT:
+ handleConfigEvents(controller, event.getConfigEvent());
+ break;
+ case EXCEPTION_EVENT:
+ handleExceptionEvents(controller, event.getExceptionEvent());
+ break;
+ case FILE_EVENT:
+ handleFileEvents(controller, event.getFileEvent());
+ break;
+ default:
+ logger.error("unknown event type: {}", event.getEventTypeCase());
+ }
+ }
+ } catch (StatusRuntimeException ex) {
+ logger.error("error handling session events", ex);
+ } finally {
+ context.cancel(null);
+ context.close();
+ }
+ });
+ });
+ } catch (StatusRuntimeException ex) {
+ throw new IOException("setup event handlers error", ex);
+ }
+ }
+
+ private void handleSessionEvents(Controller controller, CoreProto.SessionEvent event) {
+ logger.info("session event: {}", event);
+ SessionState state = SessionState.get(event.getEvent());
+ if (state == null) {
+ logger.warn("unknown session event: {}", event.getEvent());
+ return;
+ }
+
+ // session state event
+ if (state.getValue() <= 6) {
+ logger.info("event updating session state: {}", state);
+ updateState(state);
+ // mobility script event
+ } else if (state.getValue() <= 9) {
+ Integer nodeId = event.getNodeId();
+ String[] values = event.getData().toStringUtf8().split("\\s+");
+ Integer start = Integer.parseInt(values[0].split("=")[1]);
+ Integer end = Integer.parseInt(values[1].split("=")[1]);
+ logger.info(String.format("node(%s) mobility event (%s) - start(%s) stop(%s)",
+ nodeId, state, start, end));
+ logger.info("all dialogs: {}", controller.getMobilityPlayerDialogs().keySet());
+ MobilityPlayerDialog mobilityPlayerDialog = controller.getMobilityPlayerDialogs().get(nodeId);
+ mobilityPlayerDialog.event(state, start, end);
+ }
+ }
+
+ private void handleNodeEvents(Controller controller, CoreProto.NodeEvent event) {
+ logger.info("node event: {}", event);
+ CoreNode node = protoToNode(event.getNode());
+ controller.getNetworkGraph().setNodeLocation(node);
+ }
+
+ private void handleExceptionEvents(Controller controller, CoreProto.ExceptionEvent event) {
+ logger.info("exception event: {}", event);
+ }
+
+ private void handleConfigEvents(Controller controller, CoreProto.ConfigEvent event) {
+ logger.info("config event: {}", event);
+ }
+
+ private void handleLinkEvents(Controller controller, CoreProto.LinkEvent event) {
+ logger.info("link event: {}", event);
+ CoreLink link = protoToLink(event.getLink());
+ MessageFlags flag = MessageFlags.get(event.getMessageTypeValue());
+ if (MessageFlags.DELETE == flag) {
+ logger.info("delete");
+ controller.getNetworkGraph().removeWirelessLink(link);
+ } else if (MessageFlags.ADD == flag) {
+ link.setLoaded(true);
+ controller.getNetworkGraph().addLink(link);
+ }
+ controller.getNetworkGraph().getGraphViewer().repaint();
+ }
+
+ private void handleFileEvents(Controller controller, CoreProto.FileEvent event) {
+ logger.info("file event: {}", event);
+ }
+}
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..cecc5bf9
--- /dev/null
+++ b/corefx/src/main/java/com/core/client/rest/CoreRestClient.java
@@ -0,0 +1,430 @@
+package com.core.client.rest;
+
+import com.core.Controller;
+import com.core.client.ICoreClient;
+import com.core.data.*;
+import com.core.utils.WebUtils;
+import com.core.websocket.CoreWebSocket;
+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.net.URISyntaxException;
+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;
+ private CoreWebSocket coreWebSocket;
+
+ @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(Controller controller) 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);
+ }
+
+ @Override
+ public void setupEventHandlers(Controller controller) throws IOException {
+ coreWebSocket.stop();
+ coreWebSocket = new CoreWebSocket(controller);
+ try {
+ coreWebSocket.start(address, port);
+ } catch (URISyntaxException ex) {
+ throw new IOException("error starting web socket", ex);
+ }
+ }
+}
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 extends V> 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