-
Notifications
You must be signed in to change notification settings - Fork 11
/
Copy pathftp_server.hpp
160 lines (135 loc) · 4.5 KB
/
ftp_server.hpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
#pragma once
#include <atomic>
#include <filesystem>
#include <functional>
#include <map>
#include <memory>
#include <mutex>
#include <string>
#include <vector>
#if defined(ESP_PLATFORM)
#include <esp_random.h>
#else
#include <random>
#endif
#include "logger.hpp"
#include "task.hpp"
#include "tcp_socket.hpp"
#include "ftp_client_session.hpp"
namespace espp {
/// \brief A class that implements a FTP server.
class FtpServer {
public:
/// \brief A class that implements a FTP server.
/// \note The IP Address is not currently used to select the right
/// interface, but is instead passed to the FtpClientSession so that
/// it can be used in the PASV command.
/// \param ip_address The IP address to listen on.
/// \param port The port to listen on.
/// \param root The root directory of the FTP server.
FtpServer(std::string_view ip_address, uint16_t port, const std::filesystem::path &root)
: ip_address_(ip_address), port_(port), server_({.log_level = Logger::Verbosity::WARN}),
root_(root), logger_({.tag = "FtpServer", .level = Logger::Verbosity::WARN}) {}
/// \brief Destroy the FTP server.
~FtpServer() { stop(); }
/// \brief Start the FTP server.
/// Bind to the port and start accepting connections.
/// \return True if the server was started, false otherwise.
bool start() {
if (accept_task_ && accept_task_->is_started()) {
logger_.error("Server was already started");
return false;
}
if (!server_.bind(port_)) {
logger_.error("Failed to bind to port {}", port_);
return false;
}
int max_pending_connections = 5;
if (!server_.listen(max_pending_connections)) {
logger_.error("Failed to listen on port {}", port_);
return false;
}
using namespace std::placeholders;
accept_task_ = std::make_unique<Task>(Task::Config{
.name = "FtpServer::accept_task",
.callback = std::bind(&FtpServer::accept_task_function, this, _1, _2),
.stack_size_bytes = 1024 * 4,
.log_level = Logger::Verbosity::WARN,
});
accept_task_->start();
return true;
}
/// \brief Stop the FTP server.
void stop() {
clear_clients();
stop_accepting();
}
protected:
/// \brief Stop accepting new connections.
void stop_accepting() {
if (accept_task_ && accept_task_->is_started()) {
accept_task_->stop();
}
}
void clear_clients() {
std::lock_guard<std::mutex> lk(clients_mutex_);
clients_.clear();
}
bool accept_task_function(std::mutex &m, std::condition_variable &cv) {
auto client_ptr = server_.accept();
if (!client_ptr) {
logger_.error("Could not accept connection");
// if we failed to accept that means there are no connections available
// so we should delay a little bit
using namespace std::chrono_literals;
std::unique_lock<std::mutex> lk(m);
cv.wait_for(lk, 1ms);
// don't want to stop the task
return false;
}
// we have a new client, so make a new client id
int client_id = generate_client_id();
logger_.info("Accepted connection from {}, id {}", client_ptr->get_remote_info(), client_id);
// create a new client session
auto client_session_ptr =
std::make_unique<FtpClientSession>(client_id, ip_address_, std::move(client_ptr), root_);
// add the client session to the map of clients
std::lock_guard<std::mutex> lk(clients_mutex_);
// clean up any clients that have disconnected
for (auto it = clients_.begin(); it != clients_.end();) {
if (!it->second->is_connected() || !it->second->is_alive()) {
logger_.info("Client {} disconnected, removing", it->first);
it = clients_.erase(it);
} else {
++it;
}
}
// add the new client
clients_[client_id] = std::move(client_session_ptr);
// don't want to stop the task
return false;
}
int generate_client_id() {
#if defined(ESP_PLATFORM)
return esp_random();
#else
static std::random_device rd;
static std::mt19937 gen(rd());
static std::uniform_int_distribution<> dis(0, std::numeric_limits<int>::max());
return dis(gen);
#endif
}
void remove_client_task(int client_id) {
std::lock_guard<std::mutex> lk(clients_mutex_);
clients_.erase(client_id);
}
std::string ip_address_;
uint16_t port_;
TcpSocket server_;
std::unique_ptr<Task> accept_task_;
std::filesystem::path root_;
std::mutex clients_mutex_;
std::unordered_map<int, std::unique_ptr<FtpClientSession>> clients_;
Logger logger_;
};
} // namespace espp