Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
106 lines
3.6 KiB
C++
106 lines
3.6 KiB
C++
// SPDX-License-Identifier: MIT
|
|
// SPDX-FileCopyrightText: 2026 Martino Ferrari
|
|
|
|
#pragma once
|
|
|
|
#include "source/data_source.hpp"
|
|
#include "source/flatudp/flatudp_signals.hpp"
|
|
#include "source/source_config.hpp"
|
|
|
|
#include <atomic>
|
|
#include <chrono>
|
|
#include <cstdint>
|
|
#include <mutex>
|
|
#include <set>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
namespace udpscope::flatudp {
|
|
|
|
constexpr int kDefaultPort = 44488; ///< `UDPSender`'s own default
|
|
|
|
bool valid_ipv4(const std::string& s);
|
|
/// An IPv4 address in 224.0.0.0/4.
|
|
bool valid_multicast(const std::string& s);
|
|
|
|
/// Everything a configured flat UDP source needs, parsed once from the flat `SourceConfig`.
|
|
struct FlatUdpSettings {
|
|
int port = kDefaultPort;
|
|
std::string bind; ///< local address; empty is any
|
|
std::string multicast; ///< group to join; empty is plain unicast
|
|
std::string iface; ///< local interface address for the join; empty is the system's choice
|
|
double rate = 0.0; ///< the sender's cycle rate in Hz; 0 means not given
|
|
bool big_endian = false;
|
|
bool endian_ok = true; ///< false when `endian` is neither "little" nor "big"
|
|
std::vector<Row> rows;
|
|
std::string signals_error; ///< why the `signals` text did not parse; `rows` is empty then
|
|
|
|
static FlatUdpSettings from(const SourceConfig& cfg);
|
|
/// The first reason this cannot connect, in words for the user; empty when it can.
|
|
std::string problem() const;
|
|
/// `[multicast:]port`; names the session file.
|
|
std::string identity() const;
|
|
/// Datagrams per frame: 1 for a list with a waveform row, else `clamp(round(rate / 100), 1, 1024)`.
|
|
std::size_t block() const;
|
|
};
|
|
|
|
class FlatUdpSource final : public DataSource {
|
|
public:
|
|
explicit FlatUdpSource(const SourceConfig& cfg);
|
|
~FlatUdpSource() override;
|
|
|
|
void open(SourceSink& sink) override;
|
|
void poll(int timeout_ms) override;
|
|
void close() override;
|
|
SourceStats stats() const override;
|
|
bool connected() const override { return connected_.load(std::memory_order_relaxed); }
|
|
void set_datagram_tap(DatagramTap tap) override { tap_ = std::move(tap); }
|
|
|
|
const FlatUdpSettings& settings() const { return cfg_; }
|
|
/// Datagrams of the expected size / of any other size; for the probe, readable from any thread.
|
|
std::uint64_t valid_datagrams() const { return valid_.load(std::memory_order_relaxed); }
|
|
std::uint64_t wrong_size_datagrams() const { return wrong_.load(std::memory_order_relaxed); }
|
|
std::string last_message() const;
|
|
|
|
private:
|
|
using Clock = std::chrono::steady_clock;
|
|
|
|
void close_fd();
|
|
void drain_socket();
|
|
void accept(const std::uint8_t* data);
|
|
void reject(std::size_t len);
|
|
void flush();
|
|
void note(const std::string& msg);
|
|
|
|
FlatUdpSettings cfg_;
|
|
SourceSink* sink_ = nullptr;
|
|
DatagramTap tap_;
|
|
int fd_ = -1;
|
|
bool configured_ = false;
|
|
bool ever_connected_ = false;
|
|
bool recv_error_noted_ = false; ///< a failing `recv` is logged once per open
|
|
std::size_t expected_ = 0; ///< bytes of a valid datagram
|
|
std::size_t block_ = 1;
|
|
|
|
std::vector<SignalInfo> signals_;
|
|
std::vector<std::uint8_t> rx_;
|
|
std::vector<std::vector<double>> pending_; ///< decoded, not yet framed: [signal][value]
|
|
std::vector<SignalValues> views_;
|
|
std::size_t pending_n_ = 0; ///< datagrams in `pending_`
|
|
Clock::time_point batch_start_;
|
|
Clock::time_point last_valid_;
|
|
std::set<std::size_t> reported_sizes_;
|
|
|
|
std::atomic<bool> connected_{false};
|
|
std::atomic<std::uint64_t> valid_{0};
|
|
std::atomic<std::uint64_t> wrong_{0};
|
|
mutable std::mutex msg_mu_;
|
|
std::string last_message_;
|
|
|
|
std::uint32_t frame_counter_ = 0;
|
|
std::uint64_t frames_ = 0;
|
|
std::uint64_t reconnects_ = 0;
|
|
};
|
|
|
|
} // namespace udpscope::flatudp
|