Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/c-cpp.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -251,3 +251,4 @@ target_link_libraries( networkprotocoldsl_uv PUBLIC networkprotocoldsl ${LIBUV_L

enable_testing()
add_subdirectory(tests)
add_subdirectory(examples)
1 change: 1 addition & 0 deletions examples/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
add_subdirectory(smtpserver)
34 changes: 34 additions & 0 deletions examples/smtpserver/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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}")
13 changes: 13 additions & 0 deletions examples/smtpserver/generate_string_literals.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
if(NOT DEFINED INPUT_FILE OR NOT DEFINED OUTPUT_FILE)
message(FATAL_ERROR "Usage: cmake -DINPUT_FILE=<input> -DOUTPUT_FILE=<output> -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}")
17 changes: 17 additions & 0 deletions examples/smtpserver/interpreted_program.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#include "interpreted_program.hpp"
#include <string>

namespace smtpserver {
std::optional<networkprotocoldsl::InterpretedProgram>
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
13 changes: 13 additions & 0 deletions examples/smtpserver/interpreted_program.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#ifndef SMTPSERVER_INTERPRETED_PROGRAM_HPP
#define SMTPSERVER_INTERPRETED_PROGRAM_HPP

#include <networkprotocoldsl/interpretedprogram.hpp>
#include <optional>

namespace smtpserver {
// Loads the embedded static source and returns an interpreted program.
std::optional<networkprotocoldsl::InterpretedProgram>
load_interpreted_program();
} // namespace smtpserver

#endif // SMTPSERVER_INTERPRETED_PROGRAM_HPP
38 changes: 38 additions & 0 deletions examples/smtpserver/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#include "interpreted_program.hpp"
#include "server_core.hpp"

#include <networkprotocoldsl_uv/asyncworkqueue.hpp>

#include <iostream>
#include <span>
#include <string>
#include <thread>
#include <uv.h>
#include <vector>

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<const char *> args(argv, static_cast<size_t>(argc));
smtpserver::main_server(args, *maybe_program, async_queue);

async_queue.shutdown().wait();
io_thread.join();

return 0;
}
118 changes: 118 additions & 0 deletions examples/smtpserver/server_core.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
#include "server_core.hpp"
#include "server_processor.hpp"

#include <networkprotocoldsl_uv/libuvserverwrapper.hpp>

#include <CLI/CLI.hpp>

#include <condition_variable>
#include <csignal>
#include <future>
#include <iostream>
#include <mutex>
#include <unordered_map>
#include <unordered_set>

namespace {

std::mutex signal_mtx;
std::condition_variable signal_cv;
bool stop_signal_received = false;

void signal_handler(int signum) {
std::lock_guard<std::mutex> 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<std::string, std::unordered_set<std::string>> &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<int>
main_server(const std::span<const char *> &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<std::string> 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<std::string>(bind_result)) {
std::cerr << "Bind failed: " << std::get<std::string>(bind_result)
<< std::endl;
return std::nullopt;
}
std::cerr << "Server started on descriptor: " << std::get<int>(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<std::mutex> 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
34 changes: 34 additions & 0 deletions examples/smtpserver/server_core.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#ifndef SMTPSERVER_SERVER_CORE_HPP
#define SMTPSERVER_SERVER_CORE_HPP

#include <networkprotocoldsl/interpretedprogram.hpp>
#include <networkprotocoldsl_uv/asyncworkqueue.hpp>

#include <optional>
#include <span>

#include <unordered_map>
#include <unordered_set>

// ...existing code...
namespace smtpserver {

struct ServerConfiguration {
std::string server_name;
std::unordered_map<std::string, std::unordered_set<std::string>>
valid_recipients;
std::unordered_map<std::string, std::unordered_set<std::string>>
blocked_senders;
std::unordered_set<std::string> blocked_client_domains;
std::string maildir;
std::string bind_ip;
int bind_port;
};

std::optional<int>
main_server(const std::span<const char *> &args,
const networkprotocoldsl::InterpretedProgram &program,
networkprotocoldsl_uv::AsyncWorkQueue &async_queue);
} // namespace smtpserver

#endif // SMTPSERVER_SERVER_CORE_HPP
Loading