diff --git a/core/src/main/java/org/lflang/federated/extensions/CExtension.java b/core/src/main/java/org/lflang/federated/extensions/CExtension.java index edf5bc8d19..9b4487cc43 100644 --- a/core/src/main/java/org/lflang/federated/extensions/CExtension.java +++ b/core/src/main/java/org/lflang/federated/extensions/CExtension.java @@ -289,7 +289,8 @@ public String generateNetworkSenderBody( + " in federate " + connection.getDstFederate().name); - // In case sendRef is a multiport or is in a bank, this reaction will be triggered when any + // In case sendRef is a multiport or is in a bank, this reaction will be + // triggered when any // channel or bank index of sendRef is present // ex. if a.out[i] is present, the entire output a.out is triggered. if (connection.getSrcBank() != -1 || connection.getSrcChannel() != -1) { @@ -300,10 +301,13 @@ public String generateNetworkSenderBody( result.pr("}"); } - // If the connection is physical and the receiving federate is remote, send it directly on a + // If the connection is physical and the receiving federate is remote, send it + // directly on a // socket. - // If the connection is logical and the coordination mode is centralized, send via RTI. - // If the connection is logical and the coordination mode is decentralized, send directly + // If the connection is logical and the coordination mode is centralized, send + // via RTI. + // If the connection is logical and the coordination mode is decentralized, send + // directly String messageType; // Name of the next immediate destination of this message var next_destination_name = "\"federate " + connection.getDstFederate().id + "\""; @@ -682,10 +686,11 @@ private String generateCodeToInitializeFederate( String.join( "\n", "// Initialize the socket mutexes", - "lf_mutex_init(&lf_outbound_socket_mutex);", - "lf_mutex_init(&socket_mutex);", + "lf_mutex_init(&lf_outbound_netdrv_mutex);", + "lf_mutex_init(&netdrv_mutex);", "lf_cond_init(&lf_port_status_changed, &env->mutex);")); - + // CExtensionUtils.surroundWithIfOpenSSLRequired( + // "OPENSSL_init_crypto(OPENSSL_INIT_NO_ATEXIT, NULL);"), // Find the STA (A.K.A. the global STP offset) for this federate. if (federate.targetConfig.get(CoordinationProperty.INSTANCE) == CoordinationMode.DECENTRALIZED) { @@ -738,14 +743,16 @@ else if (globalSTP instanceof CodeExprImpl) "\n", "// Initialize the array of socket for incoming connections to -1.", "for (int i = 0; i < NUMBER_OF_FEDERATES; i++) {", - " _fed.sockets_for_inbound_p2p_connections[i] = -1;", + " _fed.netdrv_for_inbound_p2p_connections[i] = NULL;", + // " _fed.netdrv_to_inbound[i] = NULL;", "}")); code.pr( String.join( "\n", "// Initialize the array of socket for outgoing connections to -1.", "for (int i = 0; i < NUMBER_OF_FEDERATES; i++) {", - " _fed.sockets_for_outbound_p2p_connections[i] = -1;", + " _fed.netdrv_for_outbound_p2p_connections[i] = NULL;", + // " _fed.netdrv_to_outbound[i] = NULL;", "}")); var clockSyncOptions = federate.targetConfig.getOrDefault(ClockSyncOptionsProperty.INSTANCE); // If a test clock offset has been specified, insert code to set it here. @@ -771,7 +778,7 @@ else if (globalSTP instanceof CodeExprImpl) // Disable clock synchronization for the federate if it resides on the same host as the RTI, // unless that is overridden with the clock-sync-options target property. if (CExtensionUtils.clockSyncIsOn(federate, rtiConfig)) { - code.pr("synchronize_initial_physical_clock_with_rti(&_fed.socket_TCP_RTI);"); + code.pr("synchronize_initial_physical_clock_with_rti(_fed.netdrv_to_rti);"); } if (numberOfInboundConnections > 0) { diff --git a/core/src/main/java/org/lflang/federated/extensions/CExtensionUtils.java b/core/src/main/java/org/lflang/federated/extensions/CExtensionUtils.java index ad982431a2..7a889c6848 100644 --- a/core/src/main/java/org/lflang/federated/extensions/CExtensionUtils.java +++ b/core/src/main/java/org/lflang/federated/extensions/CExtensionUtils.java @@ -28,6 +28,7 @@ import org.lflang.target.property.ClockSyncOptionsProperty; import org.lflang.target.property.ClockSyncOptionsProperty.ClockSyncOptions; import org.lflang.target.property.CmakeIncludeProperty; +import org.lflang.target.property.CommunicationTypeProperty; import org.lflang.target.property.CompileDefinitionsProperty; import org.lflang.target.property.CoordinationOptionsProperty; import org.lflang.target.property.CoordinationProperty; @@ -201,6 +202,10 @@ public static void handleCompileDefinitions( if (federate.targetConfig.get(AuthProperty.INSTANCE)) { definitions.put("FEDERATED_AUTHENTICATED", ""); } + if (federate.targetConfig.isSet(CommunicationTypeProperty.INSTANCE)) { + definitions.put( + "COMM_TYPE", federate.targetConfig.get(CommunicationTypeProperty.INSTANCE).toString()); + } definitions.put("NUMBER_OF_FEDERATES", String.valueOf(federateNames.size())); definitions.put("EXECUTABLE_PREAMBLE", ""); definitions.put("FEDERATE_ID", String.valueOf(federate.id)); @@ -342,7 +347,7 @@ public static String generateFederateNeighborStructure(FederateInstance federate "* information is needed for the RTI to perform the centralized coordination.", "* @see MSG_TYPE_NEIGHBOR_STRUCTURE in net_common.h", "*/", - "void lf_send_neighbor_structure_to_RTI(int rti_socket) {")); + "void lf_send_neighbor_structure_to_RTI(netdrv_t *netdrv) {")); code.indent(); // Initialize the array of information about the federate's immediate upstream // and downstream relayed (through the RTI) logical connections, to send to the @@ -444,8 +449,8 @@ public static String generateFederateNeighborStructure(FederateInstance federate code.pr( String.join( "\n", - "write_to_socket_fail_on_error(", - " &rti_socket, ", + "write_to_netdrv_fail_on_error(", + " netdrv, ", " buffer_size,", " buffer_to_send,", " NULL,", @@ -525,6 +530,20 @@ public static String surroundWithIfFederatedDecentralized(String code) { .formatted(code); } + // /** + // * Surround {@code code} with blocks to ensure that code only executes if the program is + // federated + // * and has a decentralized coordination. + // */ + // public static String surroundWithIfOpenSSLRequired(String code) { + // return """ + // #ifdef OPENSSL_REQUIRED + // %s + // #endif // OPENSSL_REQUIRED + // """ + // .formatted(code); + // } + /** Generate preamble code needed for enabled serializers of the federate. */ public static String generateSerializationIncludes(FederateInstance federate) { CodeBuilder code = new CodeBuilder(); diff --git a/core/src/main/java/org/lflang/federated/generator/FedGenerator.java b/core/src/main/java/org/lflang/federated/generator/FedGenerator.java index e9379dd108..59791e951d 100644 --- a/core/src/main/java/org/lflang/federated/generator/FedGenerator.java +++ b/core/src/main/java/org/lflang/federated/generator/FedGenerator.java @@ -59,6 +59,7 @@ import org.lflang.lf.VarRef; import org.lflang.target.Target; import org.lflang.target.TargetConfig; +import org.lflang.target.property.CommunicationTypeProperty; import org.lflang.target.property.CoordinationProperty; import org.lflang.target.property.DockerProperty; import org.lflang.target.property.DockerProperty.DockerOptions; @@ -159,6 +160,15 @@ public boolean doGenerate(Resource resource, LFGeneratorContext context) throws // for logical connections. replaceFederateConnectionsWithProxies(federation, main, resource); + // Generate Credentials for SST. + if (context + .getTargetConfig() + .get(CommunicationTypeProperty.INSTANCE) + .toString() + .equals("SST")) { + SSTGenerator.setupSST(fileConfig, federates, messageReporter, context); + } + FedEmitter fedEmitter = new FedEmitter( fileConfig, diff --git a/core/src/main/java/org/lflang/federated/generator/FederateInstance.java b/core/src/main/java/org/lflang/federated/generator/FederateInstance.java index 4ce02d4793..6606db2e1e 100644 --- a/core/src/main/java/org/lflang/federated/generator/FederateInstance.java +++ b/core/src/main/java/org/lflang/federated/generator/FederateInstance.java @@ -74,7 +74,7 @@ * @author Soroush Bateni */ public class FederateInstance { - + // TODO: DONGHA: Need to take out network related things. /** * Construct a new federate instance on the basis of an instantiation in the federated reactor. * diff --git a/core/src/main/java/org/lflang/federated/generator/FederationFileConfig.java b/core/src/main/java/org/lflang/federated/generator/FederationFileConfig.java index c714c0586a..5ebe4ef34d 100644 --- a/core/src/main/java/org/lflang/federated/generator/FederationFileConfig.java +++ b/core/src/main/java/org/lflang/federated/generator/FederationFileConfig.java @@ -91,6 +91,26 @@ public Path getFedBinPath() { return getFedGenPath().resolve("bin"); } + public Path getSSTPath() { + return getGenPath().resolve("sst"); + } + + public Path getSSTConfigPath() { + return getSSTPath().resolve("configs"); + } + + public Path getSSTCredentialsPath() { + return getSSTPath().resolve("credentials"); + } + + public Path getSSTGraphsPath() { + return getSSTPath().resolve("graphs"); + } + + public Path getSSTAuthPath() { + return getSSTPath().resolve("auth"); + } + @Override public void doClean() throws IOException { super.doClean(); diff --git a/core/src/main/java/org/lflang/federated/generator/SSTGenerator.java b/core/src/main/java/org/lflang/federated/generator/SSTGenerator.java new file mode 100644 index 0000000000..2c9402abd7 --- /dev/null +++ b/core/src/main/java/org/lflang/federated/generator/SSTGenerator.java @@ -0,0 +1,372 @@ +package org.lflang.federated.generator; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import org.lflang.MessageReporter; +import org.lflang.generator.LFGeneratorContext; +import org.lflang.target.property.SSTPathProperty; +import org.lflang.util.FileUtil; + +/** + * SST related methods. + * + * @author Dongha Kim + */ +public class SSTGenerator { + public static void setupSST( + FederationFileConfig fileConfig, + List federates, + MessageReporter messageReporter, + LFGeneratorContext context) { + if (context.getTargetConfig().get(SSTPathProperty.INSTANCE).isEmpty()) { + context + .getErrorReporter() + .nowhere() + .error( + "Target property `sst-root-path:` has not been defined. `comm-type: SST` requires" + + " `sst-root-path`"); + return; + } + + FileUtil.createDirectoryIfDoesNotExist(fileConfig.getSSTConfigPath().toFile()); + FileUtil.createDirectoryIfDoesNotExist(fileConfig.getSSTCredentialsPath().toFile()); + FileUtil.createDirectoryIfDoesNotExist(fileConfig.getSSTGraphsPath().toFile()); + + // Create graph used when creating credentials. + // Set graph path. + Path graphPath = fileConfig.getSSTGraphsPath().resolve(fileConfig.name + ".graph"); + // Generate the graph file content + JsonObject graphObject = SSTGenerator.generateGraphFile(federates); + // Write the graph object to a JSON file + try (FileWriter fileWriter = new FileWriter(graphPath.toString())) { + Gson gson = new GsonBuilder().setPrettyPrinting().create(); + gson.toJson(graphObject, fileWriter); + messageReporter + .nowhere() + .info("Graph file generated successfully into: " + graphPath.toString()); + } catch (IOException e) { + throw new RuntimeException(e); + } + + // Set root path to execute commands. + Path sstRepoRootPath = Paths.get(context.getTargetConfig().get(SSTPathProperty.INSTANCE)); + ProcessBuilder processBuilder = new ProcessBuilder(); + + // Set the working directory to the specified path + processBuilder.directory(sstRepoRootPath.resolve("examples").toFile()); + + // Clean the old credentials & generate new credentials. + // processBuilder.command("bash", "-c", "echo" + graphPath); + + processBuilder.command("bash", "-c", "./cleanAll.sh ; ./generateAll.sh -g " + graphPath); + + // Start the process + try { + Process process = processBuilder.start(); + + // Wait for the process to complete + int exitCode = process.waitFor(); + + // Output the result + if (exitCode == 0) { + messageReporter.nowhere().info("Credential generation script execution successed."); + } else { + String errorOutput = new String(process.getErrorStream().readAllBytes()); + context + .getErrorReporter() + .nowhere() + .error( + "Script execution failed with exit code: " + + exitCode + + "\nError Output: " + + errorOutput); + } + } catch (IOException | InterruptedException e) { + throw new RuntimeException(e); + } + + // Copy credentials. + try { + SSTGenerator.copyCredentials(fileConfig, sstRepoRootPath); + messageReporter + .nowhere() + .info("Credentials copied into: " + fileConfig.getSSTCredentialsPath().toString()); + SSTGenerator.copyAuthNecessary(fileConfig, sstRepoRootPath); + messageReporter + .nowhere() + .info("Auth necessary files copied into: " + fileConfig.getSSTAuthPath().toString()); + SSTGenerator.updatePropertiesFile(fileConfig); + } catch (IOException e) { + throw new RuntimeException(e); + } + + // Generate SST config for the rti. + SSTGenerator.generateSSTConfig(fileConfig, "rti"); + messageReporter + .nowhere() + .info( + "Federate generated SST config into: " + + SSTGenerator.getSSTConfig(fileConfig, "rti").toString()); + + // Generate SST config for the federates. + for (FederateInstance federate : federates) { + SSTGenerator.generateSSTConfig(fileConfig, federate.name); + messageReporter + .nowhere() + .info( + "Federate generated SST config into: " + + SSTGenerator.getSSTConfig(fileConfig, federate.name).toString()); + } + } + + public static Path getSSTConfig(FederationFileConfig fileConfig, String name) { + return fileConfig.getSSTConfigPath().resolve(name + ".config"); + } + + private static void generateSSTConfig(FederationFileConfig fileConfig, String name) { + // Values to fill in + String entityName = "net1." + name; + String pubkeyRoot = + fileConfig.getSSTCredentialsPath().resolve("auth_certs").toString() + + File.separator + + "Auth101EntityCert.pem"; + String privkeyRoot = + fileConfig.getSSTCredentialsPath().resolve("keys").resolve("net1").toString() + + File.separator + + "Net1." + + name + + "Key.pem"; + String authIpAddress = "127.0.0.1"; + int authPortNumber = 21900; + String entityServerIpAddress = "127.0.0.1"; + int entityServerPortNumber = 15045; + String networkProtocol = "TCP"; + + // Create the configuration content + StringBuilder configContent = new StringBuilder(); + configContent + .append("entityInfo.name=") + .append(entityName) + .append("\n") + .append("entityInfo.purpose={\"group\":\"Servers\"}\n") + .append("entityInfo.number_key=1\n") + .append("authInfo.pubkey.path=") + .append(pubkeyRoot) + .append("\n") + .append("entityInfo.privkey.path=") + .append(privkeyRoot) + .append("\n") + .append("auth.ip.address=") + .append(authIpAddress) + .append("\n") + .append("auth.port.number=") + .append(authPortNumber) + .append("\n") + .append("entity.server.ip.address=") + .append(entityServerIpAddress) + .append("\n") + .append("entity.server.port.number=") + .append(entityServerPortNumber) + .append("\n") + .append("network.protocol=") + .append(networkProtocol) + .append("\n"); + + try { + // Create the new file and write the modified content + Path newFilePath; + newFilePath = fileConfig.getSSTConfigPath().resolve(name + ".config"); + // Create /SST directories if necessary + Files.createDirectories(newFilePath.getParent().getParent()); + // Create /SST/configs directories if necessary + Files.createDirectories(newFilePath.getParent()); // Create parent directories if necessary + BufferedWriter writer = new BufferedWriter(new FileWriter(newFilePath.toFile(), false)); + writer.write(configContent.toString()); + writer.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private static JsonObject generateGraphFile(List federateInstances) { + JsonObject graphObject = new JsonObject(); + + // Auth list + JsonArray authList = new JsonArray(); + authList.add( + createAuthEntry(101, "localhost", "localhost", 21900, 21902, 21901, 21903, 1, false, true)); + authList.add( + createAuthEntry(102, "localhost", "localhost", 21900, 21902, 21901, 21903, 1, false, true)); + graphObject.add("authList", authList); + + // Auth trusts + JsonArray authTrusts = new JsonArray(); + JsonObject trustRelation = new JsonObject(); + trustRelation.addProperty("id1", 101); + trustRelation.addProperty("id2", 102); + authTrusts.add(trustRelation); + graphObject.add("authTrusts", authTrusts); + + // Assignments section + JsonObject assignments = new JsonObject(); + assignments.addProperty("net1.rti", 101); + for (FederateInstance federate : federateInstances) { + assignments.addProperty("net1." + federate.name, 101); // Assuming "101" is a placeholder + } + graphObject.add("assignments", assignments); + + // Entity list section + JsonArray entityList = createEntityList(federateInstances); + graphObject.add("entityList", entityList); + + // File sharing lists (empty for this example) + graphObject.add("filesharingLists", new JsonArray()); + + return graphObject; + } + + private static JsonObject createAuthEntry( + int id, + String entityHost, + String authHost, + int tcpPort, + int udpPort, + int authPort, + int callbackPort, + int dbProtectionMethod, + boolean backupEnabled, + boolean contextualCallbackEnabled) { + JsonObject authEntry = new JsonObject(); + authEntry.addProperty("id", id); + authEntry.addProperty("entityHost", entityHost); + authEntry.addProperty("authHost", authHost); + authEntry.addProperty("tcpPort", tcpPort); + authEntry.addProperty("udpPort", udpPort); + authEntry.addProperty("authPort", authPort); + authEntry.addProperty("callbackPort", callbackPort); + authEntry.addProperty("dbProtectionMethod", dbProtectionMethod); + authEntry.addProperty("backupEnabled", backupEnabled); + authEntry.addProperty("contextualCallbackEnabled", contextualCallbackEnabled); + return authEntry; + } + + private static JsonArray createEntityList(List federateInstances) { + JsonArray entityList = new JsonArray(); + + // RTI entity + JsonObject rti = createEntity("Servers", "net1.rti", "Net1.rti"); + // TODO: Make the two below work on future. + rti.addProperty("port", 15045); + rti.addProperty("host", "localhost"); + entityList.add(rti); + + // Federate entities + for (FederateInstance federate : federateInstances) { + String federateName = federate.name; + JsonObject entity = createEntity("Clients", "net1." + federateName, "Net1." + federateName); + entityList.add(entity); + } + return entityList; + } + + private static JsonObject createEntity(String group, String name, String credentialPrefix) { + JsonObject entity = new JsonObject(); + entity.addProperty("group", group); + entity.addProperty("name", name); + entity.addProperty("distProtocol", "TCP"); + entity.addProperty("usePermanentDistKey", false); + entity.addProperty("distKeyValidityPeriod", "1*hour"); + entity.addProperty("maxSessionKeysPerRequest", 1); + entity.addProperty("netName", "net1"); + entity.addProperty("credentialPrefix", credentialPrefix); + entity.add("backupToAuthIds", new JsonArray()); // Empty array for backupToAuthIds + return entity; + } + + private static void copyCredentials(FederationFileConfig fileConfig, Path sstRepoRootPath) + throws IOException { + // Copy auth_certs. + Path source1 = sstRepoRootPath.resolve("entity").resolve("auth_certs"); + Path destination1 = fileConfig.getSSTCredentialsPath().resolve("auth_certs"); + + // Copy keys. + Path source2 = sstRepoRootPath.resolve("entity").resolve("credentials").resolve("keys"); + Path destination2 = fileConfig.getSSTCredentialsPath().resolve("keys"); + FileUtil.copyDirectoryContents(source1, destination1, false); + FileUtil.copyDirectoryContents(source2, destination2, false); + } + + private static void copyAuthNecessary(FederationFileConfig fileConfig, Path sstRepoRootPath) + throws IOException { + // Copy Auth credentials. + Path source1 = sstRepoRootPath.resolve("auth").resolve("credentials").resolve("ca"); + Path destination1 = fileConfig.getSSTAuthPath().resolve("credentials").resolve("ca"); + + // Copy Auth databases. + Path source2 = sstRepoRootPath.resolve("auth").resolve("databases"); + Path destination2 = fileConfig.getSSTAuthPath().resolve("databases"); + + // Copy Auth properties. + Path source3 = sstRepoRootPath.resolve("auth").resolve("properties"); + Path destination3 = fileConfig.getSSTAuthPath().resolve("properties"); + + FileUtil.copyDirectoryContents(source1, destination1, false); + FileUtil.copyDirectoryContents(source2, destination2, false); + FileUtil.copyDirectoryContents(source3, destination3, false); + } + + private static void updatePropertiesFile(FederationFileConfig fileConfig) throws IOException { + File file = + Paths.get( + fileConfig.getSSTAuthPath().resolve("properties").toString(), + "exampleAuth101.properties") + .toFile(); + List updatedLines = new ArrayList<>(); + String sstAuthPathStr = fileConfig.getSSTAuthPath().toString(); + + try (BufferedReader reader = new BufferedReader(new FileReader(file))) { + String line; + while ((line = reader.readLine()) != null) { + if (line.startsWith("entity_key_store_path=")) { + line = updatePath(line, sstAuthPathStr); + } else if (line.startsWith("internet_key_store_path=")) { + line = updatePath(line, sstAuthPathStr); + } else if (line.startsWith("database_key_store_path=")) { + line = updatePath(line, sstAuthPathStr); + } else if (line.startsWith("database_encryption_key_path=")) { + line = updatePath(line, sstAuthPathStr); + } else if (line.startsWith("trusted_ca_cert_paths=")) { + line = updatePath(line, sstAuthPathStr); + } else if (line.startsWith("auth_database_dir=")) { + line = updatePath(line, sstAuthPathStr); + } + updatedLines.add(line); + } + } + + // Write the updated lines back to the file + try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) { + for (String updatedLine : updatedLines) { + writer.write(updatedLine); + writer.newLine(); + } + } + } + + private static String updatePath(String line, String sstAuthPathStr) { + return line.replace("../", sstAuthPathStr + "/"); + } +} diff --git a/core/src/main/java/org/lflang/federated/launcher/CBuildConfig.java b/core/src/main/java/org/lflang/federated/launcher/CBuildConfig.java index f2211577ac..3caa66e5ef 100644 --- a/core/src/main/java/org/lflang/federated/launcher/CBuildConfig.java +++ b/core/src/main/java/org/lflang/federated/launcher/CBuildConfig.java @@ -28,7 +28,9 @@ import org.lflang.MessageReporter; import org.lflang.federated.generator.FederateInstance; import org.lflang.federated.generator.FederationFileConfig; +import org.lflang.federated.generator.SSTGenerator; import org.lflang.generator.c.CCompiler; +import org.lflang.target.property.CommunicationTypeProperty; /** * Utility class that can be used to create a launcher for federated LF programs that are written in @@ -55,6 +57,14 @@ public String compileCommand() { @Override public String localExecuteCommand() { - return fileConfig.getFedBinPath().resolve(federate.name) + " -i $FEDERATION_ID"; + String commandToReturn = + fileConfig.getFedBinPath().resolve(federate.name) + " -i $FEDERATION_ID"; + if (federate.targetConfig.get(CommunicationTypeProperty.INSTANCE).toString().equals("SST")) { + commandToReturn = + commandToReturn + + " -sst " + + SSTGenerator.getSSTConfig(fileConfig, federate.name).toString(); + } + return commandToReturn; } } diff --git a/core/src/main/java/org/lflang/federated/launcher/FedLauncherGenerator.java b/core/src/main/java/org/lflang/federated/launcher/FedLauncherGenerator.java index 10816dc111..a867773b3f 100644 --- a/core/src/main/java/org/lflang/federated/launcher/FedLauncherGenerator.java +++ b/core/src/main/java/org/lflang/federated/launcher/FedLauncherGenerator.java @@ -36,10 +36,13 @@ import org.lflang.MessageReporter; import org.lflang.federated.generator.FederateInstance; import org.lflang.federated.generator.FederationFileConfig; +import org.lflang.federated.generator.SSTGenerator; import org.lflang.target.TargetConfig; import org.lflang.target.property.AuthProperty; import org.lflang.target.property.ClockSyncModeProperty; import org.lflang.target.property.ClockSyncOptionsProperty; +import org.lflang.target.property.CommunicationTypeProperty; +import org.lflang.target.property.SSTPathProperty; import org.lflang.target.property.TracingProperty; import org.lflang.target.property.type.ClockSyncModeType.ClockSyncMode; @@ -115,6 +118,9 @@ public void doGenerate(List federates, RtiConfig rtiConfig) { StringBuilder shCode = new StringBuilder(); StringBuilder distCode = new StringBuilder(); shCode.append(getSetupCode()).append("\n"); + if (targetConfig.get(CommunicationTypeProperty.INSTANCE).toString().equals("SST")) { + shCode.append(getSSTSetup()).append("\n"); + } String distHeader = getDistHeader(); String host = rtiConfig.getHost(); String target = host; @@ -301,6 +307,20 @@ private String getSetupCode() { "# Launch the federates:"); } + private String getSSTSetup() { + return String.join( + "\n\n", + "# Prompt for the password before starting SST Auth", + "echo \"Enter password for Auth.\"", + "read -s PASSWORD", + "# Launch the SST Auth.", + "java -jar " + + targetConfig.get(SSTPathProperty.INSTANCE) + + "/auth/auth-server/target/auth-server-jar-with-dependencies.jar -p " + + fileConfig.getSSTAuthPath().toString() + + "/properties/exampleAuth101.properties --password=$PASSWORD &"); + } + private String getDistHeader() { return String.join( "\n", @@ -320,6 +340,12 @@ private String getRtiCommand(List federates, boolean isRemote) if (targetConfig.getOrDefault(AuthProperty.INSTANCE)) { commands.add(" -a \\"); } + if (targetConfig.get(CommunicationTypeProperty.INSTANCE).toString().equals("SST")) { + commands.add( + " -sst " + + SSTGenerator.getSSTConfig(fileConfig, "rti").toString() + + " \\"); + } if (targetConfig.getOrDefault(TracingProperty.INSTANCE).isEnabled()) { commands.add(" -t \\"); } diff --git a/core/src/main/java/org/lflang/federated/launcher/RtiConfig.java b/core/src/main/java/org/lflang/federated/launcher/RtiConfig.java index 60e07c9f53..6ff382c4b3 100644 --- a/core/src/main/java/org/lflang/federated/launcher/RtiConfig.java +++ b/core/src/main/java/org/lflang/federated/launcher/RtiConfig.java @@ -11,6 +11,7 @@ public class RtiConfig { private Path directory; + // TODO: DONGHA: Need to change here. /** The host on which the RTI process is to be spawned. */ private String host; diff --git a/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java b/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java index cfb64a0a65..e7a8af0df5 100644 --- a/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java +++ b/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java @@ -37,6 +37,7 @@ import org.lflang.target.property.AuthProperty; import org.lflang.target.property.BuildTypeProperty; import org.lflang.target.property.CmakeIncludeProperty; +import org.lflang.target.property.CommunicationTypeProperty; import org.lflang.target.property.CompileDefinitionsProperty; import org.lflang.target.property.CompilerProperty; import org.lflang.target.property.PlatformProperty; @@ -414,6 +415,11 @@ CodeBuilder generateCMakeCode( cMakeCode.newLine(); } + if (targetConfig.isSet(CommunicationTypeProperty.INSTANCE)) { + cMakeCode.pr("set(COMM_TYPE " + targetConfig.get(CommunicationTypeProperty.INSTANCE) + ")"); + cMakeCode.newLine(); + } + if (!targetConfig.get(SingleThreadedProperty.INSTANCE) && platformOptions.platform() != Platform.ZEPHYR && platformOptions.platform() != Platform.FLEXPRET diff --git a/core/src/main/java/org/lflang/target/Target.java b/core/src/main/java/org/lflang/target/Target.java index c99ed4a0d3..e3a989ba1d 100644 --- a/core/src/main/java/org/lflang/target/Target.java +++ b/core/src/main/java/org/lflang/target/Target.java @@ -34,6 +34,7 @@ import org.lflang.target.property.ClockSyncModeProperty; import org.lflang.target.property.ClockSyncOptionsProperty; import org.lflang.target.property.CmakeIncludeProperty; +import org.lflang.target.property.CommunicationTypeProperty; import org.lflang.target.property.CompileDefinitionsProperty; import org.lflang.target.property.CompilerProperty; import org.lflang.target.property.CoordinationOptionsProperty; @@ -52,6 +53,7 @@ import org.lflang.target.property.Ros2Property; import org.lflang.target.property.RuntimeVersionProperty; import org.lflang.target.property.RustIncludeProperty; +import org.lflang.target.property.SSTPathProperty; import org.lflang.target.property.SchedulerProperty; import org.lflang.target.property.SingleFileProjectProperty; import org.lflang.target.property.SingleThreadedProperty; @@ -588,6 +590,8 @@ public void initialize(TargetConfig config) { ClockSyncModeProperty.INSTANCE, ClockSyncOptionsProperty.INSTANCE, CmakeIncludeProperty.INSTANCE, + CommunicationTypeProperty.INSTANCE, + SSTPathProperty.INSTANCE, CompileDefinitionsProperty.INSTANCE, CompilerProperty.INSTANCE, CoordinationOptionsProperty.INSTANCE, diff --git a/core/src/main/java/org/lflang/target/property/CommunicationTypeProperty.java b/core/src/main/java/org/lflang/target/property/CommunicationTypeProperty.java new file mode 100644 index 0000000000..47e7d29ac9 --- /dev/null +++ b/core/src/main/java/org/lflang/target/property/CommunicationTypeProperty.java @@ -0,0 +1,44 @@ +package org.lflang.target.property; + +import org.lflang.MessageReporter; +import org.lflang.ast.ASTUtils; +import org.lflang.lf.Element; +import org.lflang.target.property.type.CommunicationTypeType; +import org.lflang.target.property.type.CommunicationTypeType.CommunicationType; + +/** Directive to specify the target communication type such as 'TCP', 'SST', or 'MQTT'. */ +public final class CommunicationTypeProperty + extends TargetProperty { + + /** Singleton target property instance. */ + public static final CommunicationTypeProperty INSTANCE = new CommunicationTypeProperty(); + + private CommunicationTypeProperty() { + super(new CommunicationTypeType()); + } + + @Override + public Element toAstElement(CommunicationType value) { + return ASTUtils.toElement(value.toString()); + } + + @Override + public CommunicationType initialValue() { + return CommunicationType.TCP; + } + + @Override + public CommunicationType fromAst(Element node, MessageReporter reporter) { + return fromString(ASTUtils.elementToSingleString(node), reporter); + } + + @Override + protected CommunicationType fromString(String string, MessageReporter reporter) { + return this.type.forName(string); + } + + @Override + public String name() { + return "comm-type"; + } +} diff --git a/core/src/main/java/org/lflang/target/property/SSTPathProperty.java b/core/src/main/java/org/lflang/target/property/SSTPathProperty.java new file mode 100644 index 0000000000..721cc492ea --- /dev/null +++ b/core/src/main/java/org/lflang/target/property/SSTPathProperty.java @@ -0,0 +1,17 @@ +package org.lflang.target.property; + +/** The compiler to invoke, unless a build command has been specified. */ +public final class SSTPathProperty extends StringProperty { + + /** Singleton target property instance. */ + public static final SSTPathProperty INSTANCE = new SSTPathProperty(); + + private SSTPathProperty() { + super(); + } + + @Override + public String name() { + return "sst-root-path"; + } +} diff --git a/core/src/main/java/org/lflang/target/property/type/CommunicationTypeType.java b/core/src/main/java/org/lflang/target/property/type/CommunicationTypeType.java new file mode 100644 index 0000000000..c1e0e35b26 --- /dev/null +++ b/core/src/main/java/org/lflang/target/property/type/CommunicationTypeType.java @@ -0,0 +1,52 @@ +package org.lflang.target.property.type; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; +import org.lflang.target.property.type.CommunicationTypeType.CommunicationType; + +/** Enumeration of communication types */ +public class CommunicationTypeType extends OptionsType { + + @Override + protected Class enumClass() { + return CommunicationType.class; + } + + /** + * Enumeration of communication types. + * + *
    + *
  • TCP: Communications occur through TCP servers/clients. + *
  • SST: Communications occur through SST modules. + *
  • MQTT: Communications occur through a broker and pub/sub methods. + *
+ */ + public enum CommunicationType { + TCP("TCP"), + SST("SST"), + MQTT("MQTT"); + + /** Alias used in toString method. */ + private final String alias; + + /** Private constructor for Cmake build types. */ + CommunicationType(String alias) { + this.alias = alias; + } + + /** Return the alias. */ + @Override + public String toString() { + return this.alias; + } + + public static List optionsList() { + return Arrays.stream(CommunicationType.values()).collect(Collectors.toList()); + } + + public static CommunicationType getDefault() { + return CommunicationType.TCP; + } + } +} diff --git a/core/src/main/resources/lib/c/reactor-c b/core/src/main/resources/lib/c/reactor-c index 862e0b9617..2abafc9a02 160000 --- a/core/src/main/resources/lib/c/reactor-c +++ b/core/src/main/resources/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit 862e0b9617ca55b4fe5f0b2248c63846a8630d3d +Subproject commit 2abafc9a022a12c3f2e687f36432cf2a7646dbd6 diff --git a/test/C/src/federated/DecentralizedP2PComm.lf b/test/C/src/federated/DecentralizedP2PComm.lf index cd5b1757db..a555273f0b 100644 --- a/test/C/src/federated/DecentralizedP2PComm.lf +++ b/test/C/src/federated/DecentralizedP2PComm.lf @@ -2,7 +2,8 @@ target C { timeout: 5 sec, tracing: true, clock-sync: off, - coordination: decentralized + coordination: decentralized, + logging: DEBUG } reactor Platform( diff --git a/test/C/src/federated/HelloDistributed.lf b/test/C/src/federated/HelloDistributed.lf index fc7c10da3c..ff34bc06e3 100644 --- a/test/C/src/federated/HelloDistributed.lf +++ b/test/C/src/federated/HelloDistributed.lf @@ -4,7 +4,10 @@ * coordination of the advancement of time (HLA or Ptides) is needed. * @author Edward A. Lee */ -target C +target C { + logging: DEBUG, + comm-type: TCP +} preamble {= #include diff --git a/test/C/src/federated/LargeFileReader.lf b/test/C/src/federated/LargeFileReader.lf new file mode 100644 index 0000000000..45daf4fc38 --- /dev/null +++ b/test/C/src/federated/LargeFileReader.lf @@ -0,0 +1,94 @@ +/** Test reading a file at a location relative to the source file. */ +target C { + logging: DEBUG, + timeout: 0 s, + tracing: true +} + +reactor Source { + output out: char* // Use char*, not string, so memory is freed. + + reaction(startup) -> out {= + // char* file_path = + // LF_SOURCE_DIRECTORY + // LF_FILE_SEPARATOR ".." + // LF_FILE_SEPARATOR "lib" + // LF_FILE_SEPARATOR "LargeFileReader.txt"; + char* file_path = "/home/dongha/project/lingua-franca/test/C/src/lib/LargeFileReader.txt"; + + FILE* file = fopen(file_path, "rb"); + if (file == NULL) lf_print_error_and_exit("Error opening file at path %s.", file_path); + + // Determine the file size + fseek(file, 0, SEEK_END); + long file_size = ftell(file); + fseek(file, 0, SEEK_SET); + + // Allocate memory for the buffer + char* buffer = (char *) malloc(file_size + 1); + if (buffer == NULL) lf_print_error_and_exit("Out of memory."); + + // Read the file into the buffer + fread(buffer, file_size, 1, file); + buffer[file_size] = '\0'; + fclose(file); + + // For federated version, have to use lf_set_array so array size is know + // to the serializer. + lf_set_array(out, buffer, file_size + 1); + =} +} + +reactor Check { + preamble {= + #include + =} + input in: char* + state received: bool = false + + reaction(in) {= + printf("Received: %s\n", in->value); + self->received = true; + + // char* file_path = + // LF_SOURCE_DIRECTORY + // LF_FILE_SEPARATOR ".." + // LF_FILE_SEPARATOR "lib" + // LF_FILE_SEPARATOR "LargeFileReader.txt"; + + char* file_path = "/home/dongha/project/cleanlf/test/C/src/lib/LargeFileReader.txt"; + + FILE* file = fopen(file_path, "rb"); + if (file == NULL) lf_print_error_and_exit("Error opening file at path %s.", file_path); + + // Determine the file size + fseek(file, 0, SEEK_END); + long file_size = ftell(file); + fseek(file, 0, SEEK_SET); + + // Allocate memory for the buffer + char* buffer = (char *) malloc(file_size + 1); + if (buffer == NULL) lf_print_error_and_exit("Out of memory."); + + // Read the file into the buffer + fread(buffer, file_size, 1, file); + buffer[file_size] = '\0'; + fclose(file); + + if (strcmp(buffer, in->value) != 0) { + lf_print_error_and_exit("Expected 'Hello World'"); + } + =} + + reaction(shutdown) {= + if (!self->received) { + lf_print_error_and_exit("No input received."); + } + =} +} + +federated reactor { + s = new Source() + c = new Check() + s.out -> c.in +} diff --git a/test/C/src/federated/SimpleFederated.lf b/test/C/src/federated/SimpleFederated.lf index cb6a798f8b..d393010241 100644 --- a/test/C/src/federated/SimpleFederated.lf +++ b/test/C/src/federated/SimpleFederated.lf @@ -1,6 +1,9 @@ target C { timeout: 2 secs, - build-type: RelWithDebInfo + build-type: RelWithDebInfo, + comm-type: SST, + logging: DEBUG, + clock-sync: OFF } reactor Fed { diff --git a/test/C/src/lib/LargeFileReader.txt b/test/C/src/lib/LargeFileReader.txt new file mode 100644 index 0000000000..e6be3e8171 Binary files /dev/null and b/test/C/src/lib/LargeFileReader.txt differ