diff --git a/.github/workflows/c-cpp.yml b/.github/workflows/c-cpp.yml index 3980be3..9e3adbd 100644 --- a/.github/workflows/c-cpp.yml +++ b/.github/workflows/c-cpp.yml @@ -14,7 +14,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: build-depends - run: sudo apt update && sudo apt -y install libgtest-dev build-essential cmake libgtest-dev libuv1-dev && sudo apt clean + run: sudo apt update && sudo apt -y install libgtest-dev build-essential cmake libgtest-dev libuv1-dev libcli11-dev && sudo apt clean - name: configure run: mkdir build && cd build && cmake ../ -DCMAKE_BUILD_TYPE=Debug - name: make diff --git a/CMakeLists.txt b/CMakeLists.txt index 9fe8435..619bfb7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -251,3 +251,4 @@ target_link_libraries( networkprotocoldsl_uv PUBLIC networkprotocoldsl ${LIBUV_L enable_testing() add_subdirectory(tests) +add_subdirectory(examples) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt new file mode 100644 index 0000000..dd44fa9 --- /dev/null +++ b/examples/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(smtpserver) \ No newline at end of file diff --git a/examples/smtpserver/CMakeLists.txt b/examples/smtpserver/CMakeLists.txt new file mode 100644 index 0000000..0f6a58a --- /dev/null +++ b/examples/smtpserver/CMakeLists.txt @@ -0,0 +1,34 @@ +# Add a custom command to generate the embedded file. + +set(SMTP_SOURCE_BASENAME smtp.networkprotocoldsl) +set(SMTP_SOURCE_FILE ${CMAKE_CURRENT_SOURCE_DIR}/${SMTP_SOURCE_BASENAME}) +set(SMTP_STRING_LITERAL_FILE ${CMAKE_CURRENT_BINARY_DIR}/${SMTP_SOURCE_BASENAME}.literal) +add_custom_command( + OUTPUT ${SMTP_STRING_LITERAL_FILE} + COMMAND ${CMAKE_COMMAND} + -DINPUT_FILE="${SMTP_SOURCE_FILE}" + -DOUTPUT_FILE="${SMTP_STRING_LITERAL_FILE}" + -P "${CMAKE_CURRENT_SOURCE_DIR}/generate_string_literals.cmake" + DEPENDS ${SMTP_SOURCE_FILE} generate_string_literals.cmake + COMMENT "Generating ${SMTP_STRING_LITERAL_FILE} from ${SMTP_SOURCE_FILE}" +) + +# Instead of defining a separate target, mark main.cpp as dependent on the generated file. +set_source_files_properties( + interpreted_program.cpp PROPERTIES OBJECT_DEPENDS ${SMTP_STRING_LITERAL_FILE} +) + +# Build the smtpserver executable and make it depend on the generated file. +find_package(CLI11 REQUIRED) + +add_executable(smtpserver + main.cpp + interpreted_program.cpp + interpreted_program.hpp + server_core.cpp + server_core.hpp + server_processor.cpp + server_processor.hpp +) +target_link_libraries(smtpserver PRIVATE networkprotocoldsl networkprotocoldsl_uv CLI11::CLI11) +target_compile_definitions(smtpserver PRIVATE -DSMTP_NETWORKPROTOCOLDSL_LITERAL="${SMTP_STRING_LITERAL_FILE}") diff --git a/examples/smtpserver/generate_string_literals.cmake b/examples/smtpserver/generate_string_literals.cmake new file mode 100644 index 0000000..681f1bc --- /dev/null +++ b/examples/smtpserver/generate_string_literals.cmake @@ -0,0 +1,13 @@ +if(NOT DEFINED INPUT_FILE OR NOT DEFINED OUTPUT_FILE) + message(FATAL_ERROR "Usage: cmake -DINPUT_FILE= -DOUTPUT_FILE= -P generate_string_literals.cmake") +endif() + +file(READ "${INPUT_FILE}" RAW_CONTENT) +# Escape backslashes. +string(REPLACE "\\" "\\\\" RAW_CONTENT_ESCAPED_BS "${RAW_CONTENT}") +# Escape double quotes. +string(REPLACE "\"" "\\\"" RAW_CONTENT_ESCAPED "${RAW_CONTENT_ESCAPED_BS}") +# Replace newlines with newline escape and closing/opening quotes. +string(REPLACE "\n" "\\n\"\n\"" TRANSFORMED_CONTENT "${RAW_CONTENT_ESCAPED}") +set(TRANSFORMED_CONTENT "\"${TRANSFORMED_CONTENT}\"") +file(WRITE "${OUTPUT_FILE}" "${TRANSFORMED_CONTENT}") diff --git a/examples/smtpserver/interpreted_program.cpp b/examples/smtpserver/interpreted_program.cpp new file mode 100644 index 0000000..c76d674 --- /dev/null +++ b/examples/smtpserver/interpreted_program.cpp @@ -0,0 +1,17 @@ +#include "interpreted_program.hpp" +#include + +namespace smtpserver { +std::optional +load_interpreted_program() { + // Load the embedded static source code via the compile definition. + std::string source_code = std::string( +#ifndef SMTP_NETWORKPROTOCOLDSL_LITERAL +#error "SMTP_NETWORKPROTOCOLDSL_LITERAL not defined" +#endif +#include SMTP_NETWORKPROTOCOLDSL_LITERAL + ); + return networkprotocoldsl::InterpretedProgram::generate_server_from_source( + source_code); +} +} // namespace smtpserver \ No newline at end of file diff --git a/examples/smtpserver/interpreted_program.hpp b/examples/smtpserver/interpreted_program.hpp new file mode 100644 index 0000000..8e76495 --- /dev/null +++ b/examples/smtpserver/interpreted_program.hpp @@ -0,0 +1,13 @@ +#ifndef SMTPSERVER_INTERPRETED_PROGRAM_HPP +#define SMTPSERVER_INTERPRETED_PROGRAM_HPP + +#include +#include + +namespace smtpserver { +// Loads the embedded static source and returns an interpreted program. +std::optional +load_interpreted_program(); +} // namespace smtpserver + +#endif // SMTPSERVER_INTERPRETED_PROGRAM_HPP diff --git a/examples/smtpserver/main.cpp b/examples/smtpserver/main.cpp new file mode 100644 index 0000000..57f53bf --- /dev/null +++ b/examples/smtpserver/main.cpp @@ -0,0 +1,38 @@ +#include "interpreted_program.hpp" +#include "server_core.hpp" + +#include + +#include +#include +#include +#include +#include +#include + +int main(int argc, const char **argv) { + std::cerr << "SMTP Server example started." << std::endl; + + // Retrieve the interpreted program from the embedded static source. + auto maybe_program = smtpserver::load_interpreted_program(); + if (!maybe_program.has_value()) { + std::cerr << "Failed to load the interpreted program." << std::endl; + return 1; + } + + // Set up libuv loop and async work queue. + uv_loop_t *loop = uv_default_loop(); + networkprotocoldsl_uv::AsyncWorkQueue async_queue(loop); + + // Start the libuv loop in a separate thread. + std::thread io_thread([&]() { uv_run(loop, UV_RUN_DEFAULT); }); + + // Create a span from argv without creating a vector. + std::span args(argv, static_cast(argc)); + smtpserver::main_server(args, *maybe_program, async_queue); + + async_queue.shutdown().wait(); + io_thread.join(); + + return 0; +} \ No newline at end of file diff --git a/examples/smtpserver/server_core.cpp b/examples/smtpserver/server_core.cpp new file mode 100644 index 0000000..f460cb0 --- /dev/null +++ b/examples/smtpserver/server_core.cpp @@ -0,0 +1,118 @@ +#include "server_core.hpp" +#include "server_processor.hpp" + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace { + +std::mutex signal_mtx; +std::condition_variable signal_cv; +bool stop_signal_received = false; + +void signal_handler(int signum) { + std::lock_guard lock(signal_mtx); + stop_signal_received = true; + std::cerr << "Received signal: " << signum << std::endl; + signal_cv.notify_one(); +} + +} // anonymous namespace + +namespace smtpserver { + +void parse_email_list( + const std::string &str, + std::unordered_map> &map) { + auto pos = str.find('@'); + if (pos == std::string::npos) { + throw CLI::ValidationError("Invalid format for email"); + } + map[str.substr(pos + 1)].insert(str.substr(0, pos)); +} + +std::optional +main_server(const std::span &args, + const networkprotocoldsl::InterpretedProgram &program, + networkprotocoldsl_uv::AsyncWorkQueue &async_queue) { + // Setup CLI11 parser. + ServerConfiguration config; + + // add some sample values to the configuration + config.valid_recipients["example.com"].insert("user1"); + config.valid_recipients["example.com"].insert("user2"); + config.valid_recipients["other.com"].insert("user3"); + config.blocked_senders["bad.com"].insert("user"); + config.blocked_client_domains.insert("bad.com"); + + CLI::App app{"Server configuration"}; + app.add_option("--bind-ip", config.bind_ip, "IP address to bind") + ->default_val("127.0.0.1"); + app.add_option("--bind-port", config.bind_port, "Port to bind") + ->default_val(8080); + app.add_option("--server-name", config.server_name, "Server name") + ->default_val("testsrv"); + app.add_option("--maildir", config.maildir, "Mail directory") + ->default_val("./maildir/"); + app.add_option("--valid-recipients", config.valid_recipients, + "Valid recipients"); + app.add_option("--blocked-senders", config.blocked_senders, + "Blocked senders"); + app.add_option("--blocked-client-domains", config.blocked_client_domains, + "Blocked client domains"); + + // Convert span to vector (skip program name). + std::vector args_cli(++args.begin(), args.end()); + try { + app.parse(args_cli); + } catch (const CLI::ParseError &e) { + return app.exit(e); + } + + std::cerr << "Binding to IP: " << config.bind_ip + << ", Port: " << config.bind_port << std::endl; + + // Get the server callbacks. + auto server_callbacks = get_sever_callbacks(config); + + // Create server wrapper and start the server. + networkprotocoldsl_uv::LibuvServerWrapper server_wrapper( + program, server_callbacks, async_queue); + auto bind_future = server_wrapper.start(config.bind_ip, config.bind_port); + bind_future.wait(); + auto bind_result = bind_future.get(); + if (std::holds_alternative(bind_result)) { + std::cerr << "Bind failed: " << std::get(bind_result) + << std::endl; + return std::nullopt; + } + std::cerr << "Server started on descriptor: " << std::get(bind_result) + << std::endl; + + // Setup signal handling for SIGINT. + std::signal(SIGINT, signal_handler); + std::cerr << "Server is running; press Ctrl+C to stop." << std::endl; + + { + std::unique_lock lock(signal_mtx); + signal_cv.wait(lock, [] { return stop_signal_received; }); + } + + std::cerr << "Stopping server... " << std::flush; + server_wrapper.stop(); + + std::cerr << "done" << std::endl; + + return 0; +} + +} // namespace smtpserver diff --git a/examples/smtpserver/server_core.hpp b/examples/smtpserver/server_core.hpp new file mode 100644 index 0000000..ba042fc --- /dev/null +++ b/examples/smtpserver/server_core.hpp @@ -0,0 +1,34 @@ +#ifndef SMTPSERVER_SERVER_CORE_HPP +#define SMTPSERVER_SERVER_CORE_HPP + +#include +#include + +#include +#include + +#include +#include + +// ...existing code... +namespace smtpserver { + +struct ServerConfiguration { + std::string server_name; + std::unordered_map> + valid_recipients; + std::unordered_map> + blocked_senders; + std::unordered_set blocked_client_domains; + std::string maildir; + std::string bind_ip; + int bind_port; +}; + +std::optional +main_server(const std::span &args, + const networkprotocoldsl::InterpretedProgram &program, + networkprotocoldsl_uv::AsyncWorkQueue &async_queue); +} // namespace smtpserver + +#endif // SMTPSERVER_SERVER_CORE_HPP diff --git a/examples/smtpserver/server_processor.cpp b/examples/smtpserver/server_processor.cpp new file mode 100644 index 0000000..c2d2494 --- /dev/null +++ b/examples/smtpserver/server_processor.cpp @@ -0,0 +1,293 @@ +#include "server_processor.hpp" +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +// Function to split an email address into user and domain parts. +std::pair split_email(const std::string &email) { + auto at_pos = email.find('@'); + if (at_pos == std::string::npos) { + throw std::invalid_argument("Invalid email address"); + } + return {email.substr(0, at_pos), email.substr(at_pos + 1)}; +} + +// Include the necessary DSL headers. +#include +#include + +namespace smtpserver { +using namespace networkprotocoldsl; + +// Helper function to wrap a string in an Octets value. +static value::Octets _o(const std::string &str) { + return value::Octets{std::make_shared(str)}; +} + +static Value onOpen(const ServerConfiguration &config, + const std::vector &args) { + std::cerr << "Connection opened by client" << std::endl; + return value::DynamicList{ + _o("SMTP Server Greeting"), + value::Dictionary{ + {{"code_tens", 22}, {"msg", _o("Welcome to the SMTP server")}}}}; +} + +// Called when the server is in state "AwaitServerEHLOResponse". +// Sends the EHLO response. +static Value onAwaitServerEHLOResponse(const ServerConfiguration &config, + const std::vector &args) { + // Extract the data dictionary from the incoming message. + value::Dictionary dict = std::get(args[0]); + auto client_domain_member = dict.members->at("client_domain"); + std::string client_domain = + *std::get(client_domain_member).data; + if (config.blocked_client_domains.find(client_domain) != + config.blocked_client_domains.end()) { + return value::DynamicList{ + {_o("SMTP EHLO Failure Response"), + value::Dictionary{{{"client_domain", client_domain_member}, + {"code_tens", 55}, + {"msg", _o("Bad client, go away!")}}}}}; + } + + return value::DynamicList{ + {_o("SMTP EHLO Success Response"), + value::Dictionary{{{"client_domain", client_domain_member}, + {"code_tens", 50}, + {"msg", _o("Hello, pleased to meet you")}}}}}; +} + +// Called when the server is in state "AwaitServerMAILFROMResponse". +// Processes the MAIL FROM command by accepting the sender. +static Value onAwaitServerMAILFROMResponse(const ServerConfiguration &config, + const std::vector &args) { + // Extract the data dictionary from the incoming message. + value::Dictionary dict = std::get(args[0]); + auto client_domain = dict.members->at("client_domain"); + std::string sender = + *std::get(dict.members->at("sender")).data; + + // check if the user is blocked + auto [sender_user, sender_domain] = split_email(sender); + auto it = config.blocked_senders.find(sender_domain); + if (it != config.blocked_senders.end() && + it->second.find(sender_user) != it->second.end()) { + return value::DynamicList{ + {_o("SMTP MAIL FROM Failure Response"), + value::Dictionary{{{"client_domain", client_domain}, + {"sender", _o(sender)}, + {"code_tens", 55}, + {"msg", _o("Blocked sender")}}}}}; + } + + return value::DynamicList{ + {_o("SMTP MAIL FROM Success Response"), + value::Dictionary{{{"client_domain", client_domain}, + {"sender", _o(sender)}, + {"code_tens", 50}, + {"msg", _o("Sender OK")}}}}}; +} + +// Called when the server is in state "AwaitServerRCPTTOResponse". +static Value onAwaitServerRCPTTOResponse(const ServerConfiguration &config, + const std::vector &args) { + // Extract the data dictionary from the incoming message. + value::Dictionary dict = std::get(args[0]); + auto client_domain = dict.members->at("client_domain"); + auto sender = dict.members->at("sender"); + std::string recipient = + *std::get(dict.members->at("recipient")).data; + + // Check if this recipient is in the list of valid recipients. + auto [recipient_user, recipient_domain] = split_email(recipient); + auto it = config.valid_recipients.find(recipient_domain); + if (it == config.valid_recipients.end() || + it->second.find(recipient_user) == it->second.end()) { + return value::DynamicList{ + {_o("SMTP RCPT TO Failure Response"), + value::Dictionary{{{"client_domain", client_domain}, + {"sender", sender}, + {"code_tens", 55}, + {"msg", _o("Invalid recipient")}}}}}; + } + + Value recipient_list; + auto r_it = dict.members->find("recipient_list"); + if (r_it != dict.members->end()) { + recipient_list = r_it->second; + if (!std::holds_alternative(recipient_list)) { + recipient_list = value::DynamicList{{_o(recipient)}}; + } else { + auto acc = std::make_shared>( + *(std::get(recipient_list).values)); + acc->push_back(_o(recipient)); + recipient_list = value::DynamicList{acc}; + } + } else { + recipient_list = value::DynamicList{{_o(recipient)}}; + } + + return value::DynamicList{ + {_o("SMTP RCPT TO Success Response"), + value::Dictionary{{{"client_domain", client_domain}, + {"sender", sender}, + {"recipient_list", recipient_list}, + {"code_tens", 50}, + {"msg", _o("Recipient OK")}}}}}; +} + +// Called when the server is in state "AwaitServerDATAResponse". +// Sends the response to the DATA command. +static Value onAwaitServerDATAResponse(const ServerConfiguration &config, + const std::vector &args) { + // Extract the data dictionary from the incoming message. + value::Dictionary dict = std::get(args[0]); + auto client_domain = dict.members->at("client_domain"); + auto sender = dict.members->at("sender"); + auto recipient_list = dict.members->at("recipient_list"); + + return value::DynamicList{ + {_o("SMTP DATA Response"), + value::Dictionary{ + {{"client_domain", client_domain}, + {"sender", sender}, + {"recipient_list", recipient_list}, + {"code_tens", 54}, + {"msg", _o("Start mail input; end with .")}}}}}; +} + +// Called when the server is in state "AwaitServerDATAContent". +// Processes the DATA content sent by the client and writes it into a maildir. +static Value onAwaitServerDATAContentResponse(const ServerConfiguration &config, + const std::vector &args) { + // Extract the data dictionary from the incoming message. + value::Dictionary dict = std::get(args[0]); + auto client_domain = dict.members->at("client_domain"); + auto sender = dict.members->at("sender"); + auto recipient_list = + std::get(dict.members->at("recipient_list")); + + auto now = std::chrono::system_clock::now(); + auto now_ms = std::chrono::duration_cast( + now.time_since_epoch()) + .count(); + // generate the time in the format for the received header + auto now_time_t = std::chrono::system_clock::to_time_t(now); + + // iterate over the recipient_list and drop a new message on the user's new + // maildir. This server is will use ${config.maildir}/domain/user/new/ as the + // directory where the new mail will be stored. + auto content = *std::get(dict.members->at("content")).data; + auto content_sstream = std::stringstream(); + // insert the "Received" header before the content + content_sstream << "Received: from " << *std::get(sender).data + << " by " << *std::get(client_domain).data + << " with SMTP; " << now_time_t << "\n"; + content_sstream << content; + for (const auto &recipient : *(recipient_list.values)) { + auto recipient_email = *std::get(recipient).data; + auto [recipient_user, recipient_domain] = split_email(recipient_email); + auto path_parts = + std::vector{recipient_domain, recipient_user, "new"}; + std::string maildir = + std::accumulate(path_parts.begin(), path_parts.end(), config.maildir, + [](const std::string &acc, const std::string &part) { + auto target = acc + "/" + part; + // check if acc exists, if not create it as a + // directory then check if target exists, if not + // create it as a directory + if (!std::filesystem::exists(acc)) { + std::filesystem::create_directory(acc); + } + if (!std::filesystem::exists(target)) { + std::filesystem::create_directory(target); + } + return target; + }) + + "/"; + // make sure the directory exists + // Generate a unique filename based on the current time. + std::string filename = maildir + std::to_string(now_ms) + ".mail"; + std::cerr << "Writing email to " << filename << std::endl; + + std::ofstream ofs(filename); + if (!ofs) { + std::cerr << "Error writing email to file: " << filename << std::endl; + return value::DynamicList{ + {_o("SMTP DATA Write Error"), + value::Dictionary{{"client_domain", client_domain}}}}; + } + ofs << content_sstream.str(); + ofs.close(); + } + + return value::DynamicList{ + {_o("SMTP DATA Written"), + value::Dictionary{{{"client_domain", client_domain}, + {"code_tens", 50}, + {"msg", _o("Message accepted for delivery")}}}}}; +} + +// Called when the server is in state "AwaitServerQUITResponse". +// Sends the QUIT response. +static Value onAwaitServerQUITResponse(const ServerConfiguration &config, + const std::vector &args) { + return value::DynamicList{ + {_o("SMTP QUIT Response"), + value::Dictionary{ + {{"code_tens", 21}, {"msg", _o("Closing connection, goodbye")}}}}}; +} + +// Called when the server enters the "Closed" state. +static Value onClosed(const ServerConfiguration &config, + const std::vector &args) { + std::cerr << "Connection closed by client" << std::endl; + return value::DynamicList{{_o("Closed"), value::Dictionary{}}}; +} + +networkprotocoldsl::InterpreterRunner::callback_map +get_sever_callbacks(const ServerConfiguration &config) { + // Build the server callback map using the on$state naming convention. + return InterpreterRunner::callback_map{ + {"Open", + [&config](const std::vector &args) -> Value { + return onOpen(config, args); + }}, + {"AwaitServerEHLOResponse", + [&config](const std::vector &args) -> Value { + return onAwaitServerEHLOResponse(config, args); + }}, + {"AwaitServerMAILFROMResponse", + [&config](const std::vector &args) -> Value { + return onAwaitServerMAILFROMResponse(config, args); + }}, + {"AwaitServerRCPTTOResponse", + [&config](const std::vector &args) -> Value { + return onAwaitServerRCPTTOResponse(config, args); + }}, + {"AwaitServerDATAResponse", + [&config](const std::vector &args) -> Value { + return onAwaitServerDATAResponse(config, args); + }}, + {"AwaitServerDATAContentResponse", + [&config](const std::vector &args) -> Value { + return onAwaitServerDATAContentResponse(config, args); + }}, + {"AwaitServerQUITResponse", + [&config](const std::vector &args) -> Value { + return onAwaitServerQUITResponse(config, args); + }}, + {"Closed", [&config](const std::vector &args) -> Value { + return onClosed(config, args); + }}}; +} + +} // namespace smtpserver diff --git a/examples/smtpserver/server_processor.hpp b/examples/smtpserver/server_processor.hpp new file mode 100644 index 0000000..9cabf2e --- /dev/null +++ b/examples/smtpserver/server_processor.hpp @@ -0,0 +1,15 @@ +#ifndef SERVER_PROCESSOR_HPP +#define SERVER_PROCESSOR_HPP + +#include "server_core.hpp" +#include +// ...existing includes if needed... + +namespace smtpserver { + +networkprotocoldsl::InterpreterRunner::callback_map +get_sever_callbacks(const ServerConfiguration &config); + +} // namespace smtpserver + +#endif // SERVER_PROCESSOR_HPP diff --git a/examples/smtpserver/smtp.networkprotocoldsl b/examples/smtpserver/smtp.networkprotocoldsl new file mode 100644 index 0000000..844863d --- /dev/null +++ b/examples/smtpserver/smtp.networkprotocoldsl @@ -0,0 +1,365 @@ +message "SMTP Server Greeting" { + when: Open; + then: ClientSendEHLO; + agent: Server; + data: { + code_tens: int; + msg: str; + } + parts { + tokens { "2" code_tens " " msg } + terminator { "\r\n" } + } +} + +message "SMTP EHLO Command" { + when: ClientSendEHLO; + then: AwaitServerEHLOResponse; + agent: Client; + data: { + client_domain: str; + } + parts { + tokens { "EHLO " client_domain } + terminator { "\r\n" } + } +} + +message "SMTP EHLO Success Response" { + when: AwaitServerEHLOResponse; + then: ClientSendCommand; + agent: Server; + data: { + client_domain: str; + code_tens: int; + msg: str; + } + parts { + tokens { "2" code_tens } + terminator { " " } + tokens { msg } + terminator { "\r\n" } + } +} + +message "SMTP EHLO Failure Response" { + when: AwaitServerEHLOResponse; + then: ClientSendEHLO; + agent: Server; + data: { + code_tens: int; + msg: str; + } + parts { + tokens { "5" code_tens } + terminator { " " } + tokens { msg } + terminator { "\r\n" } + } +} + +message "SMTP MAIL FROM Command" { + when: ClientSendCommand; + then: AwaitServerMAILFROMResponse; + agent: Client; + data: { + client_domain: str; + sender: str; + } + parts { + tokens { "MAIL FROM:<" sender ">" } + terminator { "\r\n" } + } +} + +message "SMTP MAIL FROM Success Response" { + when: AwaitServerMAILFROMResponse; + then: ClientSendFirstRCPTTO; + agent: Server; + data: { + client_domain: str; + sender: str; + code_tens: int; + msg: str; + } + parts { + tokens { "2" code_tens } + terminator { " " } + tokens { msg } + terminator { "\r\n" } + } +} + +message "SMTP MAIL FROM Failure Response" { + when: AwaitServerMAILFROMResponse; + then: ClientSendCommand; + agent: Server; + data: { + client_domain: str; + code_tens: int; + msg: str; + } + parts { + tokens { "5" code_tens } + terminator { " " } + tokens { msg } + terminator { "\r\n" } + } +} + +message "SMTP RCPT TO Command" { + when: ClientSendFirstRCPTTO; + then: AwaitServerRCPTTOResponse; + agent: Client; + data: { + client_domain: str; + sender: str; + recipient: str; + rcpt_to_params: str; + recipient_list: array>; + } + parts { + tokens { "RCPT TO:<" recipient ">" } + terminator { "\r\n" } + } +} + +message "SMTP RCPT TO Success Response" { + when: AwaitServerRCPTTOResponse; + then: ClientSendMoreRCPTTOorDATA; + agent: Server; + data: { + client_domain: str; + sender: str; + mail_from_params: str; + recipient_list: array< + element_type=tuple, + parameters=str>, + sizing=Dynamic, + max_length=100>; + code_tens: int; + msg: str; + } + parts { + tokens { "2" code_tens } + terminator { " " } + tokens { msg } + terminator { "\r\n" } + } +} + +message "Additional SMTP RCPT TO Command" { + when: ClientSendMoreRCPTTOorDATA; + then: AwaitServerRCPTTOResponse; + agent: Client; + data: { + client_domain: str; + sender: str; + mail_from_params: str; + recipient: str; + rcpt_to_params: str; + recipient_list: array< + element_type=tuple, + parameters=str>, + sizing=Dynamic, + max_length=100>; + } + parts { + tokens { "RCPT TO:<" recipient ">" rcpt_to_params } + terminator { "\r\n" } + } +} + +message "SMTP RCPT TO Failure Response" { + when: AwaitServerRCPTTOResponse; + then: ClientSendMoreRCPTTOorDATA; + agent: Server; + data: { + client_domain: str; + sender: str; + recipient_list: array>; + code_tens: int; + msg: str; + } + parts { + tokens { "5" code_tens } + terminator { " " } + tokens { msg } + terminator { "\r\n" } + } +} + +message "SMTP DATA Command" { + when: ClientSendMoreRCPTTOorDATA; + then: AwaitServerDATAResponse; + agent: Client; + data: { + client_domain: str; + sender: str; + recipient_list: array>; + } + parts { + tokens { "DATA" } + terminator { "\r\n" } + } +} + +message "SMTP DATA Response" { + when: AwaitServerDATAResponse; + then: ClientSendDATAContent; + agent: Server; + data: { + client_domain: str; + sender: str; + recipient_list: array>; + code_tens: int; + msg: str; + } + parts { + tokens { "3" code_tens } + terminator { " " } + tokens { msg } + terminator { "\r\n" } + } +} + + +message "SMTP DATA Failure Response" { + when: AwaitServerDATAResponse; + then: ClientSendMoreRCPTTOorDATA; + agent: Server; + data: { + client_domain: str; + sender: str; + recipient_list: array>; + code_tens: int; + msg: str; + } + parts { + tokens { "3" code_tens } + terminator { " " } + tokens { msg } + terminator { "\r\n" } + } +} + +message "SMTP DATA Content" { + when: ClientSendDATAContent; + then: AwaitServerDATAContentResponse; + agent: Client; + data: { + client_domain: str; + sender: str; + recipient_list: array>; + content: str; + } + parts { + tokens { content } + terminator { "\r\n.\r\n" } + } +} + +message "SMTP DATA Written" { + when: AwaitServerDATAContentResponse; + then: ClientSendCommand; + agent: Server; + data: { + client_domain: str; + code_tens: int; + msg: str; + } + parts { + tokens { "2" code_tens } + terminator { " " } + tokens { msg } + terminator { "\r\n" } + } +} + +message "SMTP QUIT Command from EHLO" { + when: ClientSendEHLO; + then: AwaitServerQUITResponse; + agent: Client; + data: { + client_domain: str; + } + parts { + tokens { "QUIT" } + terminator { "\r\n" } + } +} + +message "SMTP QUIT Command" { + when: ClientSendCommand; + then: AwaitServerQUITResponse; + agent: Client; + data: { + client_domain: str; + } + parts { + tokens { "QUIT" } + terminator { "\r\n" } + } +} + +message "SMTP QUIT Command from First RCPTTO" { + when: ClientSendFirstRCPTTO; + then: AwaitServerQUITResponse; + agent: Client; + data: { + client_domain: str; + } + parts { + tokens { "QUIT" } + terminator { "\r\n" } + } +} + +message "SMTP QUIT Command from RCPTTO or DATA" { + when: ClientSendMoreRCPTTOorDATA; + then: AwaitServerQUITResponse; + agent: Client; + data: { + client_domain: str; + } + parts { + tokens { "QUIT" } + terminator { "\r\n" } + } +} + +message "SMTP QUIT Response" { + when: AwaitServerQUITResponse; + then: Closed; + agent: Server; + data: { + code_tens: int; + msg: str; + } + parts { + tokens { "2" code_tens } + terminator { " " } + tokens { msg } + terminator { "\r\n" } + } +} diff --git a/src/networkprotocoldsl/generate.cpp b/src/networkprotocoldsl/generate.cpp index d722af7..7d02e9d 100644 --- a/src/networkprotocoldsl/generate.cpp +++ b/src/networkprotocoldsl/generate.cpp @@ -446,7 +446,7 @@ generate_read_state_callback( return std::make_shared( OpTreeNode{operation::DynamicList{}, {{operation::TransitionLookahead{conditions}, {}}, - {operation::DictionaryInitialize{}, {}}}}); + {operation::LexicalPadGet{"dictionary"}, {}}}}); } static std::optional construct_state_map( diff --git a/src/networkprotocoldsl/interpreterrunner.cpp b/src/networkprotocoldsl/interpreterrunner.cpp index 06e59c2..e46ae43 100644 --- a/src/networkprotocoldsl/interpreterrunner.cpp +++ b/src/networkprotocoldsl/interpreterrunner.cpp @@ -13,7 +13,7 @@ #define INTERPRETERRUNNER_DEBUG(x) //#define INTERPRETERRUNNER_DEBUG(x) std::cerr << "InterpreterRunner[" << -//std::this_thread::get_id() << "] " << __func__ << ": " << x << std::endl +// std::this_thread::get_id() << "] " << __func__ << ": " << x << std::endl namespace networkprotocoldsl { diff --git a/src/networkprotocoldsl/interpreterrunner.hpp b/src/networkprotocoldsl/interpreterrunner.hpp index 63289ef..2a60fc7 100644 --- a/src/networkprotocoldsl/interpreterrunner.hpp +++ b/src/networkprotocoldsl/interpreterrunner.hpp @@ -9,7 +9,7 @@ namespace networkprotocoldsl { struct InterpreterRunner { - using callback_function = Value (*)(const std::vector &); + using callback_function = std::function &)>; using callback_map = std::unordered_map; callback_map callbacks; std::atomic exit_when_done = false; diff --git a/src/networkprotocoldsl/value.hpp b/src/networkprotocoldsl/value.hpp index 4e0537d..2877c39 100644 --- a/src/networkprotocoldsl/value.hpp +++ b/src/networkprotocoldsl/value.hpp @@ -76,7 +76,7 @@ struct Octets { struct Dictionary { using Type = std::unordered_map; std::shared_ptr members; - Dictionary() = default; + Dictionary() : members(std::make_shared()){}; explicit Dictionary(Type &&m) : members(std::make_shared(std::move(m))) {} explicit Dictionary(std::initializer_list init)