Mirror of espurna firmware for wireless switches and more
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1420 lines
31 KiB

/*
NETWORKING MODULE
Copyright (C) 2022 by Maxim Prokhorov <prokhorov dot max at outlook dot com>
*/
#include "espurna.h"
#include <forward_list>
#include <functional>
#include <memory>
#include <utility>
#include <numeric>
#include <lwip/init.h>
#include <lwip/tcp.h>
#include <lwip/dns.h>
#include "network.h"
#include "libs/URL.h"
// not yet CONNECTING or LISTENING
extern "C" struct tcp_pcb *tcp_bound_pcbs;
// accepting or sending data
extern "C" struct tcp_pcb *tcp_active_pcbs;
// // TIME-WAIT status
extern "C" struct tcp_pcb *tcp_tw_pcbs;
namespace espurna {
namespace network {
Packet::~Packet() {
pbuf_free(_pbuf);
}
Packet::Packet(Packet&& other) noexcept :
_pbuf(other._pbuf)
{
other._pbuf = nullptr;
}
Packet& Packet::operator=(Packet&& other) noexcept {
_pbuf = other._pbuf;
other._pbuf = nullptr;
return *this;
}
void Packet::append(pbuf* pb) {
if (!_pbuf) {
_pbuf = pb;
} else {
pbuf_cat(_pbuf, pb);
}
}
void Packet::consume(size_t size) {
if (_pbuf) {
_pbuf = pbuf_free_header(_pbuf, size);
}
}
size_t Packet::size() const {
if (_pbuf) {
return _pbuf->tot_len;
}
return 0;
}
size_t Packet::size_chunk() const {
if (_pbuf) {
return _pbuf->len;
}
return 0;
}
ConstData Packet::peek_chunk(size_t size) {
if (_pbuf) {
const auto available = size_t{ _pbuf->len };
const auto* payload = reinterpret_cast<const uint8_t*>(_pbuf->payload);
return {payload, std::min({size, available, RecvMax})};
}
return {};
}
size_t Packet::peek(Data out) {
if (_pbuf) {
return pbuf_copy_partial(_pbuf, out.data(), out.size(), 0);
}
return 0;
}
std::vector<uint8_t> Packet::peek(size_t size) {
std::vector<uint8_t> out;
if (size) {
size = std::min(size, RecvMax);
out.resize(size);
peek(Data{out.data(), size});
}
return out;
}
size_t Packet::read(Data out) {
const auto received = peek(out);
if (received) {
consume(received);
}
return 0;
}
std::vector<uint8_t> Packet::read(size_t size) {
auto received = peek(size);
consume(received.size());
return received;
}
namespace tcp {
Address remote_address(tcp_pcb* pcb) {
Address out;
if (pcb) {
ip_addr_copy(out.ip, pcb->remote_ip);
out.port = pcb->remote_port;
}
return out;
}
Address remote_address(const Control& control) {
return remote_address(control.get());
}
Address local_address(tcp_pcb* pcb) {
Address out;
if (pcb) {
ip_addr_copy(out.ip, pcb->local_ip);
out.port = pcb->local_port;
}
return out;
}
Address local_address(const Control& control) {
return local_address(control.get());
}
template <typename Result>
std::shared_ptr<IoCompletion> make_io_completion(size_t size, Result&& result) {
return std::make_shared<IoCompletion>(size, std::forward<Result>(result));
}
template <typename Timeout, typename Interval, typename T>
static bool poll(Timeout timeout, Interval interval, T&& blocked) {
return time::blockingDelay(
std::chrono::duration_cast<duration::Milliseconds>(timeout),
std::chrono::duration_cast<duration::Milliseconds>(interval),
std::forward<T>(blocked));
}
template <typename Interval, typename T>
static void poll(Interval interval, T&& blocked) {
for (;;) {
const auto result = blocked();
if (!result) {
break;
}
time::delay(interval);
}
}
std::errc from_lwip_error(err_t err) {
switch (err) {
case ERR_OK:
break;
case ERR_MEM:
return std::errc::not_enough_memory;
case ERR_BUF:
return std::errc::no_buffer_space;
case ERR_TIMEOUT:
return std::errc::timed_out;
case ERR_RTE:
return std::errc::network_unreachable;
case ERR_INPROGRESS:
return std::errc::operation_in_progress;
case ERR_VAL:
return std::errc::invalid_argument;
case ERR_WOULDBLOCK:
return std::errc::operation_would_block;
case ERR_USE:
return std::errc::address_in_use;
case ERR_ALREADY:
return std::errc::already_connected;
case ERR_ISCONN:
return std::errc::already_connected;
case ERR_CONN:
return std::errc::not_connected;
case ERR_IF:
return std::errc::network_down;
case ERR_ABRT:
return std::errc::operation_canceled;
case ERR_RST:
return std::errc::connection_reset;
case ERR_CLSD:
return std::errc::connection_aborted;
case ERR_ARG:
return std::errc::invalid_argument;
}
return std::errc{};
}
void Control::set_nagle() {
if (_pcb) {
tcp_nagle_enable(_pcb);
}
}
void Control::set_nodelay() {
if (_pcb) {
tcp_nagle_disable(_pcb);
}
}
err_t Control::connect(Address address, tcp_connected_fn handler) {
if (_pcb) {
return ERR_VAL;
}
if (_pcb->state == ESTABLISHED) {
return ERR_ALREADY;
}
return tcp_connect(_pcb, &address.ip, address.port, handler);
}
err_t Control::attach(Address address, ServerHandler handler) {
if (_pcb) {
return ERR_VAL;
}
auto* pcb = tcp_new();
if (!pcb) {
return ERR_MEM;
}
pcb->so_options |= SOF_REUSEADDR;
pcb->prio = TCP_PRIO_MIN;
const auto err = tcp_bind(pcb, &address.ip, address.port);
if (err != ERR_OK) {
return err;
}
auto* listen = tcp_listen(pcb);
if (!listen) {
tcp_abort(pcb);
return ERR_VAL;
}
tcp_arg(pcb, handler.arg);
tcp_accept(pcb, handler.on_accept);
_pcb = pcb;
return ERR_OK;
}
err_t Control::attach(ClientHandler handler) {
tcp_arg(_pcb, handler.arg);
tcp_err(_pcb, handler.on_error);
tcp_poll(_pcb, handler.on_poll, 1);
tcp_recv(_pcb, handler.on_recv);
tcp_sent(_pcb, handler.on_sent);
return ERR_OK;
}
err_t Control::attach(tcp_pcb* other, ClientHandler handler) {
if (!_pcb) {
_pcb = other;
attach(handler);
return ERR_OK;
}
return ERR_VAL;
}
size_t Control::write_buffer() const {
if (_pcb) {
return tcp_sndbuf(_pcb);
}
return 0;
}
void Control::recv_consume(size_t size) {
if (_pcb) {
tcp_recved(_pcb, size);
}
}
err_t Control::write(const uint8_t* data, size_t size, uint8_t flags) {
return tcp_write(_pcb, data, size, flags);
}
err_t Control::try_send() {
return tcp_output(_pcb);
}
void Control::detach() noexcept {
if (_pcb) {
tcp_arg(_pcb, nullptr);
tcp_recv(_pcb, nullptr);
tcp_err(_pcb, nullptr);
tcp_sent(_pcb, nullptr);
tcp_poll(_pcb, nullptr, 0);
}
}
err_t Control::close() noexcept {
err_t err = ERR_OK;
if (_pcb) {
detach();
err = tcp_close(_pcb);
if (err != ERR_OK) {
err = abort();
}
_pcb = nullptr;
}
return err;
}
err_t Control::abort(err_t err) {
tcp_abort(_pcb);
_pcb = nullptr;
_last_err = err;
return ERR_ABRT;
}
Client::Client(Control&& control) noexcept :
_remote(remote_address(control.get())),
_control(std::move(control))
{
_control.attach(
Control::ClientHandler{
.arg = this,
.on_error = s_on_tcp_error,
.on_poll = s_on_tcp_poll,
.on_recv = s_on_tcp_recv,
.on_sent = s_on_tcp_sent,
});
_control.set_nagle();
}
Client::Client(tcp_pcb* pcb) noexcept :
Client(Control(pcb))
{}
Client::Client(Client&& other) noexcept :
_remote(other._remote),
_control(std::move(other._control)),
_packet(std::move(other._packet)),
_execution(std::move(other._execution))
{}
Client& Client::operator=(Client&& other) noexcept {
abort();
_control = std::move(other._control);
_execution = std::move(other._execution);
_remote = other._remote;
return *this;
}
Address Client::local() const {
return local_address(_control);
}
std::errc Client::connect(Address address) {
if (_control && !_execution.connection) {
return from_lwip_error(
_control.connect(address, s_on_tcp_connect));
}
if (_execution.connection) {
return std::errc::connection_already_in_progress;
}
return std::errc::invalid_argument;
}
err_t Client::on_connect(err_t err) {
if (!_execution.connection) {
return ERR_OK;
}
decltype(_execution.connection) connection;
std::swap(_execution.connection, connection);
if (err != ERR_OK) {
connection->set_error(from_lwip_error(err));
} else {
connection->set_done();
}
return _control.error();
}
err_t Client::on_poll() {
try_readers();
try_writers();
return _control.error();
}
err_t Client::on_sent(uint16_t) {
try_writers();
return _control.error();
}
void Client::stop_connection(CompletionPtr ptr) {
if (_execution.connection == ptr) {
decltype(_execution.connection) connection;
std::swap(_execution.connection, connection);
connection->set_error(std::errc::operation_canceled);
close();
}
}
bool ClientCancelation::set_timeout(Client& client, TimeSource::duration timeout) {
auto ptr = _ptr.lock();
if (!ptr) {
return false;
}
client._execution.timeouts.push_front(
Client::Timeout{
.timeout = timeout,
.start = TimeSource::now(),
.type = _type,
.completion = ptr,
});
return true;
}
bool ClientCancelation::try_completion_once(Client& client) {
switch (_type) {
case Type::Connection:
break;
case Type::Writing:
client.try_writers();
break;
case Type::Reading:
client.try_readers();
break;
}
return _ptr.expired();
}
void ClientCancelation::try_completion(Client& client, TimeSource::duration interval) {
for (;;) {
if (try_completion_once(client)) {
return;
}
time::delay(std::chrono::duration_cast<duration::Milliseconds>(interval));
}
}
void ClientCancelation::wait_for(Client& client, TimeSource::duration timeout, TimeSource::duration interval) {
const auto result = set_timeout(client, timeout);
if (!result) {
return;
}
try_completion(client, interval);
}
void ClientCancelation::wait_for(Client& client, TimeSource::duration timeout) {
wait_for(client, timeout, duration::Milliseconds{ 10 });
}
void ClientCancelation::wait(Client& client) {
try_completion(client, duration::Milliseconds{ 10 });
}
void ClientCancelation::cancel(Client& client) {
auto ptr = _ptr.lock();
if (!ptr) {
return;
}
client.stop_completion(_type, ptr);
}
size_t Client::available() const {
return _packet.size();
}
size_t Client::available_chunk() const {
return _packet.size_chunk();
}
bool Client::connected() const {
return _control.state() == ESTABLISHED;
}
bool Client::closed() const {
switch (_control.state()) {
case tcp_state::CLOSED:
case tcp_state::CLOSE_WAIT:
return true;
default:
break;
}
return false;
}
void Client::consume(size_t size) {
_packet.consume(size);
_control.recv_consume(size);
}
size_t Client::peek(Data out) {
return _packet.peek(out);
}
std::vector<uint8_t> Client::peek(size_t size) {
return _packet.peek(size);
}
size_t Client::read(Data out) {
const auto received = peek(out);
if (received) {
consume(received);
}
return 0;
}
std::vector<uint8_t> Client::read(size_t size) {
auto received = peek(size);
consume(received.size());
return received;
}
void Client::stop_completion(ClientCancelation::Type type, CompletionPtr ptr) {
switch (type) {
case ClientCancelation::Type::Connection:
stop_connection(ptr);
break;
case ClientCancelation::Type::Reading:
stop_reader(ptr);
break;
case ClientCancelation::Type::Writing:
stop_writer(ptr);
break;
}
}
template <typename Timeouts, typename T>
void try_timeouts_impl(Timeouts& timeouts, T&& handler) {
timeouts.remove_if(
[](const typename Timeouts::value_type& timeout) {
return timeout.completion.expired();
});
if (timeouts.empty()) {
return;
}
const auto now = TimeSource::now();
for (auto& timeout : timeouts) {
if (now - timeout.start < timeout.timeout) {
continue;
}
auto ptr = timeout.completion.lock();
if (!ptr) {
continue;
}
ptr->set_error(std::errc::timed_out);
handler(timeout, ptr);
}
}
void Client::try_timeouts() {
try_timeouts_impl(
_execution.timeouts,
[&](Timeout& timeout, CompletionPtr ptr) {
stop_completion(timeout.type, ptr);
});
}
template <typename Requests, typename Handler>
void Client::try_requests(Requests& requests, Handler&& handler) {
try_timeouts();
for (;;) {
const auto it = requests.begin();
if (it == requests.end()) {
return;
}
switch (handler(*it)) {
// still a pending request, but cannot continue just this moment
case std::errc::operation_in_progress:
return;
// (possibly) interrupted by external means, cannot use `it`
case std::errc::interrupted:
return;
// everything else should discard the request
default:
break;
}
requests.erase(it);
}
}
template <typename Requests, typename Request>
void Client::stop_requests(Requests& requests, CompletionPtr ptr) {
auto it = std::find_if(
requests.begin(),
requests.end(),
[&](const Request& request) {
return request.ptr == ptr;
});
if (it != requests.end()) {
IoCompletionPtr ptr;
std::swap((*it).ptr, ptr);
requests.erase(it);
ptr->set_error(std::errc::operation_canceled);
}
}
std::errc Client::read_some(ReadRequest& request) {
const size_t pending = _packet.size();
if (!pending) {
return std::errc::operation_in_progress;
}
auto completion = request.ptr;
const auto chunk = std::min(request.size, pending);
if (request.at_most && chunk != 0) {
completion->notify_partial(pending);
return std::errc::operation_in_progress;
}
auto err = std::errc::operation_in_progress;
if (chunk == request.size) {
err = std::errc{};
}
if (chunk > 0) {
completion->notify_total(chunk);
}
if (completion->is_done()) {
err = std::errc::interrupted;
}
return err;
}
void Client::try_readers() {
try_requests(_execution.readers,
[&](ReadRequest& request) -> std::errc {
return read_some(request);
});
}
void Client::stop_reader(CompletionPtr ptr) {
stop_requests(_execution.readers, ptr);
}
void Client::try_writers() {
try_requests(_execution.writers,
[&](WriteRequest& request) -> std::errc {
return write_some(request);
});
}
void Client::stop_writer(CompletionPtr ptr) {
stop_requests(_execution.writers, ptr);
}
void Client::start_reader(IoCompletionPtr ptr, size_t size, bool at_most) {
_execution.readers.push_back(ReadRequest{
.ptr = std::move(ptr),
.size = size,
.at_most = at_most,
});
}
std::errc Client::write_some(WriteRequest& request) {
for (;;) {
if (request.current == request.data.end()) {
return std::errc{};
}
if (!_control) {
return std::errc::not_connected;
}
const size_t available = _control.write_buffer();
auto& current = *request.current;
auto size = std::min(current.size(), available);
if (!size) {
return std::errc::operation_in_progress;
}
auto flags = TCP_WRITE_FLAG_COPY;
if ((request.data.end() != (request.current + 1))
|| (size != current.size()))
{
flags |= TCP_WRITE_FLAG_MORE;
}
const auto* ptr = current.data();
if (pointerInFlash(ptr)) {
size = std::min(size, RecvBufferSize);
request.copy_buffer.resize(size);
memcpy_P(
request.copy_buffer.data(), ptr,
request.copy_buffer.size());
ptr = request.copy_buffer.data();
}
auto completion = request.ptr;
auto err = _control.write(ptr, size, flags);
if (err != ERR_OK) {
completion->set_error(from_lwip_error(err));
return std::errc::interrupted;
}
if (current.size() - size != 0) {
current = current.subspan(size);
} else {
std::advance(request.current, 1);
}
err = _control.try_send();
if (err != ERR_OK) {
completion->set_error(from_lwip_error(err));
return std::errc::interrupted;
}
completion->notify_progress(size);
if (completion->is_done()) {
return std::errc::interrupted;
}
}
return std::errc{};
}
void Client::start_writer(IoCompletionPtr ptr, ConstDataSequence data) {
_execution.writers.push_back(WriteRequest{
.ptr = std::move(ptr),
.data = std::move(data),
.current = data.begin(),
.start = TimeSource::now(),
});
}
std::shared_ptr<BasicCompletion> make_basic_completion(BasicCompletion::Result&& result) {
return std::make_shared<BasicCompletion>(std::move(result));
}
std::errc connect(Client& client, Address address) {
auto out = std::errc::operation_in_progress;
auto cancelation = connect_async(
client,
address,
[&](std::errc err) {
out = err;
});
cancelation.wait(client);
return out;
}
ClientCancelation connect_async(Client& client, Address address, BasicCompletion::Result&& result) {
ClientCancelation out;
if (client._execution.connection) {
result(std::errc::operation_in_progress);
return out;
}
const auto err = client.connect(address);
if (err != std::errc{}) {
result(err);
return out;
}
auto completion = make_basic_completion(std::move(result));
client._execution.connection = completion;
return ClientCancelation(ClientCancelation::Type::Connection, completion);
}
err_t Client::close() {
complete(ERR_CLSD);
return _control.close();
}
void Client::complete(err_t err) {
const auto errc = from_lwip_error(err);
decltype(_execution) execution;
std::swap(_execution, execution);
if (execution.connection) {
execution.connection->set_error(errc);
}
for (auto& reader : execution.readers) {
reader.ptr->set_error(errc);
}
for (auto& writer : execution.writers) {
writer.ptr->set_error(errc);
}
}
err_t Client::abort(err_t err) {
detach();
complete(err);
return _control.abort(err);
}
void Client::detach() {
_control.detach();
}
err_t Client::on_recv(pbuf* pb, err_t err) {
// safe to assume on err, just abort
if (!pb || (err != ERR_OK)) {
return close();
}
_packet.append(pb);
try_readers();
return _control.error();
}
ClientCancelation write_async(Client& client, ConstDataSequence data, IoCompletion::Result&& result) {
ClientCancelation out;
if (!client.connected()) {
result(0, std::errc::not_connected);
return out;
}
auto completion = make_io_completion(
data.size(),
std::move(result));
client.start_writer(completion, std::move(data));
return ClientCancelation(ClientCancelation::Type::Writing, completion);
}
WriteResult write(Client& client, ConstDataSequence data) {
WriteResult out;
out.size = 0;
out.err = std::errc::operation_in_progress;
auto completion = write_async(
client,
std::move(data),
[&](size_t size, std::errc err) {
out.size = size;
out.err = err;
});
for (;;) {
if (out.err != std::errc::operation_in_progress) {
break;
}
client.try_writers();
time::delay(duration::Milliseconds{ 10 });
}
return out;
}
ClientCancelation read_async(Client& client, size_t size, bool at_most, IoCompletion::Result&& result) {
ClientCancelation out;
if (!client.connected()) {
result(0, std::errc::not_connected);
return out;
}
auto completion = make_io_completion(size, std::move(result));
client.start_reader(completion, size, at_most);
return ClientCancelation(ClientCancelation::Type::Reading, completion);
}
ReadResult read(Client& client, size_t size) {
ReadResult out;
out.err = std::errc::operation_in_progress;
size_t received = 0;
auto cancelation = read_async(
client,
size,
false,
[&](size_t size, std::errc err) {
out.err = err;
received = size;
});
cancelation.wait(client);
out.data = client.read(received);
return out;
}
Server::~Server() {
close();
}
Address Server::local() const {
return local_address(_control);
}
bool Server::accept(AcceptCompletionPtr ptr, bool retry) {
if (_control) {
_accept.push_back(
Server::Accept{
.completion = std::move(ptr),
.retry = retry,
});
return true;
}
return false;
}
ServerCancelation accept(Server& server, bool retry, AcceptCompletion::Result&& result) {
auto completion = std::make_shared<AcceptCompletion>(std::move(result));
if (!server.accept(completion, retry)) {
return ServerCancelation();
}
return ServerCancelation(completion);
}
ServerCancelation accept_once(Server& server, AcceptCompletion::Result&& result) {
return accept(server, false, std::move(result));
}
ServerCancelation accept(Server& server, AcceptCompletion::Result&& result) {
return accept(server, true, std::move(result));
}
void AcceptCompletion::set_error(std::errc err) {
set_done(nullptr, err);
}
void AcceptCompletion::set_done(ClientPtr ptr, std::errc err) {
if (!_done) {
_result(std::move(ptr), err);
_done = true;
}
}
err_t Server::on_accept(tcp_pcb* pcb, err_t err) {
if (!_accept.size()) {
return ERR_VAL;
}
const auto errc = from_lwip_error(err);
auto it = _accept.begin();
auto accept = (*it);
if (!pcb) {
accept.completion->set_error(errc);
} else if (err != ERR_OK) {
accept.completion->set_error(errc);
} else {
accept.completion->set_done(
std::make_unique<Client>(pcb), std::errc{});
}
const auto last = _control.error();
if (last != ERR_OK) {
return err;
}
if (!accept.retry) {
_accept.erase(it);
return ERR_OK;
}
accept.completion->retry();
return ERR_OK;
}
void Server::complete(err_t err) {
const auto errc = from_lwip_error(err);
decltype(_accept) accept;
std::swap(_accept, accept);
for (auto& handler : accept) {
handler.completion->set_error(errc);
}
}
err_t Server::close() {
complete(ERR_CLSD);
return _control.close();
}
err_t Server::listen(Address address) {
return _control.attach(
address,
Control::ServerHandler{
.arg = this,
.on_accept = s_on_tcp_accept,
});
}
void Server::detach() {
_control.detach();
}
void Server::try_timeouts() {
try_timeouts_impl(
_timeouts,
[&](const Timeout&, CompletionPtr ptr) {
stop_completion(ptr);
});
}
void Server::stop_completion(CompletionPtr ptr) {
_accept.erase(std::remove_if(
_accept.begin(),
_accept.end(),
[ptr](const Accept& accept) {
return accept.completion == ptr;
}));
}
void ServerCancelation::cancel(Server& server) {
auto ptr = _ptr.lock();
if (!ptr) {
return;
}
server.stop_completion(ptr);
}
void ServerCancelation::wait_for(Server& server, TimeSource::duration timeout, TimeSource::duration interval) {
const auto result = poll(
timeout, interval,
[&]() {
return !_ptr.expired();
});
if (result) {
return;
}
const auto ptr = _ptr.lock();
if (!ptr) {
return;
}
ptr->set_error(std::errc::timed_out);
server.stop_completion(ptr);
}
void ServerCancelation::wait_for(Server& server, TimeSource::duration timeout) {
wait_for(server, timeout, duration::Milliseconds{ 10 });
}
void ServerCancelation::wait(Server& server) {
for (;;) {
if (_ptr.expired()) {
return;
}
time::delay(duration::Milliseconds{ 10 });
}
}
} // namespace tcp
namespace dns {
namespace {
struct PendingHost {
HostPtr ptr;
HostCallback callback;
};
namespace internal {
using Pending = std::forward_list<PendingHost>;
Pending pending;
} // namespace internal
void dns_found_callback_impl(const char* name, const ip_addr_t* addr, void* arg) {
auto* pending = reinterpret_cast<Host*>(arg);
if (addr) {
pending->addr = addr;
pending->err = ERR_OK;
} else {
pending->err = ERR_ABRT;
}
internal::pending.remove_if(
[&](const PendingHost& lhs) {
if (lhs.ptr.get() == pending) {
if (lhs.callback) {
lhs.callback(lhs.ptr);
}
return true;
}
return false;
});
}
HostPtr resolve_impl(String hostname, HostCallback callback) {
auto host = std::make_shared<Host>(
Host{
.name = std::move(hostname),
.addr = IPAddress{},
.err = ERR_INPROGRESS,
});
const auto err = dns_gethostbyname(
host->name.c_str(),
host->addr,
dns_found_callback_impl,
host.get());
host->err = err;
switch (err) {
case ERR_OK:
case ERR_MEM:
if (callback) {
callback(host);
}
break;
case ERR_INPROGRESS:
internal::pending.push_front(
PendingHost{
.ptr = host,
.callback = std::move(callback)
});
break;
}
return host;
}
} // namespace
HostPtr resolve(String hostname) {
return resolve_impl(hostname, nullptr);
}
void resolve(String hostname, HostCallback callback) {
if (!callback) {
return;
}
resolve_impl(std::move(hostname), std::move(callback));
}
bool wait_for(HostPtr ptr, duration::Milliseconds timeout) {
if (ptr->err == ERR_OK) {
return true;
}
if (ptr->err != ERR_INPROGRESS) {
return false;
}
time::blockingDelay(
timeout,
duration::Milliseconds{ 10 },
[&]() {
return ptr->err == ERR_INPROGRESS;
});
return ptr->err == ERR_OK;
}
IPAddress gethostbyname(String hostname, duration::Milliseconds timeout) {
IPAddress out;
auto result = resolve(hostname);
if (wait_for(result, timeout)) {
out = result->addr;
}
return out;
}
IPAddress gethostbyname(String hostname) {
return gethostbyname(hostname, duration::Seconds{ 3 });
}
} // namespace dns
namespace {
#if TERMINAL_SUPPORT
namespace terminal {
namespace commands {
PROGMEM_STRING(Host, "HOST");
void host(::terminal::CommandContext&& ctx) {
if (ctx.argv.size() != 2) {
terminalError(ctx, F("HOST <hostname>"));
return;
}
const auto result = dns::gethostbyname(ctx.argv[1]);
if (result.isSet()) {
ctx.output.printf_P(PSTR("%s has address %s\n"),
ctx.argv[1].c_str(), result.toString().c_str());
terminalOK(ctx);
return;
}
ctx.output.printf_P(PSTR("%s not found\n"), ctx.argv[1].c_str());
}
PROGMEM_STRING(Netstat, "NETSTAT");
void netstat(::terminal::CommandContext&& ctx) {
const struct tcp_pcb* pcbs[] {
tcp_active_pcbs,
tcp_tw_pcbs,
tcp_bound_pcbs,
};
for (const auto* list : pcbs) {
for (const tcp_pcb* pcb = list; pcb != nullptr; pcb = pcb->next) {
ctx.output.printf_P(PSTR("state %s local %s:%hu remote %s:%hu\n"),
tcp_debug_state_str(pcb->state),
IPAddress(pcb->local_ip).toString().c_str(),
pcb->local_port,
IPAddress(pcb->remote_ip).toString().c_str(),
pcb->remote_port);
}
}
}
PROGMEM_STRING(Ncat, "NCAT");
void ncat(::terminal::CommandContext&& ctx) {
if (ctx.argv.size() != 3) {
terminalError(ctx, F("NCAT <HOST> <PORT>"));
return;
}
const auto host = dns::gethostbyname(ctx.argv[1]);
if (!host.isSet()) {
terminalError(ctx, F("cannot resolve"));
return;
}
const auto port = settings::internal::convert<uint16_t>(ctx.argv[2]);
auto* pcb = tcp_new();
if (!pcb) {
terminalError(ctx, F("no pcb"));
return;
}
const auto serialize_error = [&](std::errc err) {
String error;
error += "error ";
error += (int)err;
return error;
};
tcp::Client x(pcb);
const auto result = tcp::connect(x, Address{host, port});
if (result != std::errc{}) {
terminalError(ctx, serialize_error(result));
return;
}
auto cancelation = tcp::read_async(x, 1024, true,
[&](size_t size, std::errc err) {
if (err != std::errc{}) {
terminalError(ctx, serialize_error(err));
return;
}
const auto data = x.read(size);
ctx.output.printf("notified %zu read %zu\n",
size, data.size());
ctx.output.println(
hexEncode(data.data(), data.data() + data.size()));
});
cancelation.wait_for(x, duration::Seconds{ 5 });
terminalOK(ctx);
}
#if SECURE_CLIENT == SECURE_CLIENT_BEARSSL
PROGMEM_STRING(MflnProbe, "MFLN.PROBE");
void mfln_probe(::terminal::CommandContext&& ctx) {
if (ctx.argv.size() != 3) {
terminalError(ctx, F("MFLN.PROBE <URL> <SIZE>"));
return;
}
URL parsed(std::move(ctx.argv[1]));
const auto parse_mfln = espurna::settings::internal::convert<uint16_t>;
uint16_t mfln = parse_mfln(ctx.argv[2]);
auto client = std::make_unique<BearSSL::WiFiClientSecure>();
client->setInsecure();
if (client->probeMaxFragmentLength(parsed.host.c_str(), parsed.port, mfln)) {
terminalOK(ctx);
return;
}
terminalError(ctx, F("Buffer size not supported"));
}
#endif
static constexpr ::terminal::Command List[] PROGMEM {
{Host, host},
{Netstat, netstat},
{Ncat, ncat},
#if SECURE_CLIENT == SECURE_CLIENT_BEARSSL
{MflnProbe, mfln_probe},
#endif
};
} // namespace commands
void setup() {
espurna::terminal::add(commands::List);
}
} // namespace terminal
#endif
void setup() {
#if TERMINAL_SUPPORT
terminal::setup();
#endif
}
} // namespace
} // namespace network
} // namespace espurna
void networkSetup() {
espurna::network::setup();
}