init commit

This commit is contained in:
Ayzen
2026-03-05 14:42:33 +03:00
commit fd4618b20d
964 changed files with 325114 additions and 0 deletions
@@ -0,0 +1,336 @@
#include "shared_types.hpp"
#include <chrono>
#include <cstring>
#include <limits>
#include <stdexcept>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>
namespace radar::ipc {
namespace {
constexpr std::uint32_t kRawCollectionMagic = 0x31574152U; // RAW1
constexpr std::uint32_t kPreprocessedCollectionMagic = 0x31525050U; // PRP1
constexpr std::uint32_t kResultCollectionMagic = 0x314C5352U; // RSL1
template <typename T>
concept TriviallySerializable = std::is_trivially_copyable_v<T>;
[[nodiscard]] auto checked_count_to_u32(std::size_t count, const std::string& label) -> std::uint32_t {
if (count > std::numeric_limits<std::uint32_t>::max()) {
throw std::runtime_error(label + " exceeds uint32 wire-format limit");
}
return static_cast<std::uint32_t>(count);
}
void ensure_equal_sizes(std::size_t left, std::size_t right, const std::string& label) {
if (left != right) {
throw std::runtime_error(label + " has inconsistent vector sizes");
}
}
class BinaryWriter {
public:
template <TriviallySerializable T>
void write(const T& value) {
const auto old_size = bytes_.size();
bytes_.resize(old_size + sizeof(T));
std::memcpy(bytes_.data() + old_size, &value, sizeof(T));
}
void write_string(const std::string& value) {
if (value.size() > std::numeric_limits<std::uint16_t>::max()) {
throw std::runtime_error("String is too large for wire format");
}
write(static_cast<std::uint16_t>(value.size()));
const auto old_size = bytes_.size();
bytes_.resize(old_size + value.size());
std::memcpy(bytes_.data() + old_size, value.data(), value.size());
}
[[nodiscard]] auto finish() && -> std::vector<std::uint8_t> {
return std::move(bytes_);
}
private:
std::vector<std::uint8_t> bytes_{};
};
class BinaryReader {
public:
explicit BinaryReader(std::span<const std::uint8_t> bytes) : bytes_(bytes) {}
template <TriviallySerializable T>
[[nodiscard]] auto read() -> T {
ensure_available(sizeof(T));
T value{};
std::memcpy(&value, bytes_.data() + offset_, sizeof(T));
offset_ += sizeof(T);
return value;
}
[[nodiscard]] auto read_string() -> std::string {
const auto size = read<std::uint16_t>();
ensure_available(size);
const auto* begin = reinterpret_cast<const char*>(bytes_.data() + offset_);
std::string value(begin, begin + size);
offset_ += size;
return value;
}
[[nodiscard]] auto is_consumed() const -> bool {
return offset_ == bytes_.size();
}
private:
void ensure_available(std::size_t size) const {
if (offset_ + size > bytes_.size()) {
throw std::runtime_error("Unexpected end of serialized payload");
}
}
std::span<const std::uint8_t> bytes_{};
std::size_t offset_ = 0;
};
void write_trace_block(BinaryWriter& writer, const SweepTraceBlock& trace) {
ensure_equal_sizes(trace.frequency_hz.size(), trace.s21.size(), "Sweep trace");
writer.write(trace.combo.input_pos);
writer.write(trace.combo.output_pos);
writer.write(checked_count_to_u32(trace.frequency_hz.size(), "Trace point count"));
for (const auto frequency_hz : trace.frequency_hz) {
writer.write(frequency_hz);
}
for (const auto& point : trace.s21) {
writer.write(point.re);
writer.write(point.im);
}
}
[[nodiscard]] auto read_trace_block(BinaryReader& reader) -> SweepTraceBlock {
SweepTraceBlock trace{};
trace.combo.input_pos = reader.read<std::uint32_t>();
trace.combo.output_pos = reader.read<std::uint32_t>();
const auto point_count = reader.read<std::uint32_t>();
trace.frequency_hz.reserve(point_count);
trace.s21.reserve(point_count);
for (std::uint32_t index = 0; index < point_count; ++index) {
trace.frequency_hz.push_back(reader.read<float>());
}
for (std::uint32_t index = 0; index < point_count; ++index) {
trace.s21.push_back(Complex32{
.re = reader.read<float>(),
.im = reader.read<float>(),
});
}
return trace;
}
void write_trace_collection(BinaryWriter& writer, std::uint32_t magic, const RawSweepCollection& collection) {
writer.write(magic);
writer.write(collection.collection_id);
writer.write(collection.monotonic_ns);
writer.write(checked_count_to_u32(collection.traces.size(), "Trace count"));
for (const auto& trace : collection.traces) {
write_trace_block(writer, trace);
}
}
[[nodiscard]] auto read_trace_collection(BinaryReader& reader, std::uint32_t expected_magic) -> RawSweepCollection {
const auto magic = reader.read<std::uint32_t>();
if (magic != expected_magic) {
throw std::runtime_error("Unexpected trace collection magic");
}
RawSweepCollection collection{};
collection.collection_id = reader.read<std::uint64_t>();
collection.monotonic_ns = reader.read<std::uint64_t>();
const auto trace_count = reader.read<std::uint32_t>();
collection.traces.reserve(trace_count);
for (std::uint32_t index = 0; index < trace_count; ++index) {
collection.traces.push_back(read_trace_block(reader));
}
return collection;
}
void write_trace_result_payload(BinaryWriter& writer, const ResultPayload& payload) {
ensure_equal_sizes(payload.frequency_hz.size(), payload.trace.size(), "Result trace payload");
writer.write(checked_count_to_u32(payload.trace.size(), "Result trace point count"));
for (const auto frequency_hz : payload.frequency_hz) {
writer.write(frequency_hz);
}
for (const auto& sample : payload.trace) {
writer.write(sample.re);
writer.write(sample.im);
}
}
auto read_trace_result_payload(BinaryReader& reader, ResultPayload* payload) -> void {
const auto point_count = reader.read<std::uint32_t>();
payload->frequency_hz.reserve(point_count);
payload->trace.reserve(point_count);
for (std::uint32_t index = 0; index < point_count; ++index) {
payload->frequency_hz.push_back(reader.read<float>());
}
for (std::uint32_t index = 0; index < point_count; ++index) {
payload->trace.push_back(Complex32{
.re = reader.read<float>(),
.im = reader.read<float>(),
});
}
}
void write_result_payload(BinaryWriter& writer, const ResultPayload& payload) {
writer.write(static_cast<std::uint8_t>(payload.kind));
writer.write_string(payload.processing_name);
switch (payload.kind) {
case ResultKind::TraceComplex:
write_trace_result_payload(writer, payload);
return;
case ResultKind::ScalarF32:
writer.write(payload.scalar_value);
return;
default:
throw std::runtime_error("Unsupported result payload kind");
}
}
[[nodiscard]] auto read_result_payload(BinaryReader& reader) -> ResultPayload {
ResultPayload payload{};
payload.kind = static_cast<ResultKind>(reader.read<std::uint8_t>());
payload.processing_name = reader.read_string();
switch (payload.kind) {
case ResultKind::TraceComplex:
read_trace_result_payload(reader, &payload);
return payload;
case ResultKind::ScalarF32:
payload.scalar_value = reader.read<float>();
return payload;
default:
throw std::runtime_error("Unsupported result payload kind in stream");
}
}
void write_result_block(BinaryWriter& writer, const ResultBlock& block) {
writer.write(block.combo.input_pos);
writer.write(block.combo.output_pos);
writer.write(checked_count_to_u32(block.payloads.size(), "Result payload count"));
for (const auto& payload : block.payloads) {
write_result_payload(writer, payload);
}
}
[[nodiscard]] auto read_result_block(BinaryReader& reader) -> ResultBlock {
ResultBlock block{};
block.combo.input_pos = reader.read<std::uint32_t>();
block.combo.output_pos = reader.read<std::uint32_t>();
const auto payload_count = reader.read<std::uint32_t>();
block.payloads.reserve(payload_count);
for (std::uint32_t index = 0; index < payload_count; ++index) {
block.payloads.push_back(read_result_payload(reader));
}
return block;
}
void ensure_reader_consumed(const BinaryReader& reader, const std::string& payload_label) {
if (!reader.is_consumed()) {
throw std::runtime_error("Unexpected trailing bytes in " + payload_label);
}
}
} // namespace
auto serialize_raw_collection(const RawSweepCollection& collection) -> std::vector<std::uint8_t> {
BinaryWriter writer{};
write_trace_collection(writer, kRawCollectionMagic, collection);
return std::move(writer).finish();
}
auto deserialize_raw_collection(std::span<const std::uint8_t> bytes) -> RawSweepCollection {
BinaryReader reader(bytes);
auto collection = read_trace_collection(reader, kRawCollectionMagic);
ensure_reader_consumed(reader, "raw collection");
return collection;
}
auto serialize_preprocessed_collection(const PreprocessedCollection& collection) -> std::vector<std::uint8_t> {
BinaryWriter writer{};
write_trace_collection(writer, kPreprocessedCollectionMagic, collection);
return std::move(writer).finish();
}
auto deserialize_preprocessed_collection(std::span<const std::uint8_t> bytes) -> PreprocessedCollection {
BinaryReader reader(bytes);
auto collection = read_trace_collection(reader, kPreprocessedCollectionMagic);
ensure_reader_consumed(reader, "preprocessed collection");
return collection;
}
auto serialize_result_collection(const ResultCollection& collection) -> std::vector<std::uint8_t> {
BinaryWriter writer{};
writer.write(kResultCollectionMagic);
writer.write(collection.collection_id);
writer.write(collection.monotonic_ns);
writer.write(checked_count_to_u32(collection.blocks.size(), "Result block count"));
for (const auto& block : collection.blocks) {
write_result_block(writer, block);
}
return std::move(writer).finish();
}
auto deserialize_result_collection(std::span<const std::uint8_t> bytes) -> ResultCollection {
BinaryReader reader(bytes);
const auto magic = reader.read<std::uint32_t>();
if (magic != kResultCollectionMagic) {
throw std::runtime_error("Unexpected result collection magic");
}
ResultCollection collection{};
collection.collection_id = reader.read<std::uint64_t>();
collection.monotonic_ns = reader.read<std::uint64_t>();
const auto block_count = reader.read<std::uint32_t>();
collection.blocks.reserve(block_count);
for (std::uint32_t index = 0; index < block_count; ++index) {
collection.blocks.push_back(read_result_block(reader));
}
ensure_reader_consumed(reader, "result collection");
return collection;
}
auto current_monotonic_ns() -> std::uint64_t {
const auto now = std::chrono::steady_clock::now().time_since_epoch();
const auto now_ns = std::chrono::duration_cast<std::chrono::nanoseconds>(now).count();
return static_cast<std::uint64_t>(now_ns);
}
} // namespace radar::ipc
@@ -0,0 +1,380 @@
#include "shm_ring.hpp"
#include <atomic>
#include <chrono>
#include <cerrno>
#include <cstring>
#include <fcntl.h>
#include <new>
#include <stdexcept>
#include <string>
#include <string_view>
#include <sys/mman.h>
#include <sys/stat.h>
#include <thread>
#include <unistd.h>
#include <utility>
namespace radar::ipc {
struct alignas(64) ShmRing::Header {
char magic[8]{};
std::uint32_t version = 0;
std::uint32_t capacity = 0;
std::uint32_t slot_size_bytes = 0;
std::uint32_t reserved = 0;
std::atomic<std::uint64_t> write_seq{};
std::atomic<std::uint64_t> read_seq{};
std::atomic<std::uint64_t> dropped{};
};
struct alignas(16) ShmRing::SlotHeader {
std::uint32_t payload_size = 0;
std::uint32_t reserved = 0;
std::uint64_t sequence = 0;
};
namespace {
constexpr std::string_view kRingMagic = "RDRRING2";
constexpr std::uint32_t kRingVersion = 1;
constexpr auto kHeaderInitWaitTimeout = std::chrono::milliseconds(1000);
constexpr auto kHeaderInitPollInterval = std::chrono::milliseconds(2);
[[nodiscard]] auto checked_u64_diff(std::uint64_t left, std::uint64_t right) -> std::uint64_t {
return left >= right ? left - right : 0;
}
[[nodiscard]] auto errno_message(const std::string& action, const std::string& name) -> std::runtime_error {
return std::runtime_error(action + " " + name + ": " + std::strerror(errno));
}
class ScopedFd {
public:
explicit ScopedFd(int fd) : fd_(fd) {}
~ScopedFd() {
if (fd_ >= 0) {
::close(fd_);
}
}
ScopedFd(const ScopedFd&) = delete;
auto operator=(const ScopedFd&) -> ScopedFd& = delete;
ScopedFd(ScopedFd&& other) noexcept : fd_(std::exchange(other.fd_, -1)) {}
auto release() -> int {
return std::exchange(fd_, -1);
}
private:
int fd_ = -1;
};
class ScopedMmap {
public:
ScopedMmap(void* address, std::size_t size) : address_(address), size_(size) {}
~ScopedMmap() {
if (address_ != nullptr && address_ != MAP_FAILED) {
munmap(address_, size_);
}
}
ScopedMmap(const ScopedMmap&) = delete;
auto operator=(const ScopedMmap&) -> ScopedMmap& = delete;
ScopedMmap(ScopedMmap&& other) noexcept
: address_(std::exchange(other.address_, nullptr)),
size_(std::exchange(other.size_, 0U)) {}
auto release() -> std::pair<void*, std::size_t> {
return {std::exchange(address_, nullptr), std::exchange(size_, 0U)};
}
private:
void* address_ = nullptr;
std::size_t size_ = 0;
};
} // namespace
ShmRing::~ShmRing() {
close();
}
ShmRing::ShmRing(ShmRing&& other) noexcept {
*this = std::move(other);
}
auto ShmRing::operator=(ShmRing&& other) noexcept -> ShmRing& {
if (this != &other) {
close();
fd_ = std::exchange(other.fd_, -1);
mapped_size_ = std::exchange(other.mapped_size_, 0U);
mapped_ = std::exchange(other.mapped_, nullptr);
header_ = std::exchange(other.header_, nullptr);
}
return *this;
}
auto ShmRing::open_or_create(
const std::string& name,
std::uint32_t capacity,
std::uint32_t slot_size_bytes
) -> ShmRing {
validate_name(name);
if (capacity == 0U) {
throw std::runtime_error("Ring capacity must be > 0");
}
if (slot_size_bytes == 0U) {
throw std::runtime_error("Ring slot size must be > 0");
}
bool created = false;
int fd = shm_open(name.c_str(), O_RDWR | O_CREAT | O_EXCL, 0660);
if (fd >= 0) {
created = true;
} else if (errno == EEXIST) {
fd = shm_open(name.c_str(), O_RDWR, 0660);
}
if (fd < 0) {
throw errno_message("Failed to open shared memory ring", name);
}
ScopedFd scoped_fd(fd);
const auto slot_stride = sizeof(SlotHeader) + static_cast<std::size_t>(slot_size_bytes);
const auto mapped_size = sizeof(Header) + slot_stride * capacity;
if (created) {
if (ftruncate(fd, static_cast<off_t>(mapped_size)) != 0) {
throw errno_message("Failed to resize shared memory ring", name);
}
}
void* mapped = mmap(nullptr, mapped_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (mapped == MAP_FAILED) {
throw errno_message("Failed to mmap shared memory ring", name);
}
ScopedMmap scoped_mmap(mapped, mapped_size);
auto* header = static_cast<Header*>(mapped);
if (created) {
std::memset(mapped, 0, mapped_size);
std::memcpy(header->magic, kRingMagic.data(), kRingMagic.size());
header->version = kRingVersion;
header->capacity = capacity;
header->slot_size_bytes = slot_size_bytes;
new (&header->write_seq) std::atomic<std::uint64_t>(0);
new (&header->read_seq) std::atomic<std::uint64_t>(0);
new (&header->dropped) std::atomic<std::uint64_t>(0);
std::atomic_thread_fence(std::memory_order_release);
} else {
const auto deadline = std::chrono::steady_clock::now() + kHeaderInitWaitTimeout;
while (true) {
std::atomic_thread_fence(std::memory_order_acquire);
const bool magic_ok = std::memcmp(header->magic, kRingMagic.data(), kRingMagic.size()) == 0;
const bool version_ok = header->version == kRingVersion;
const bool geometry_ok = header->capacity == capacity && header->slot_size_bytes == slot_size_bytes;
if (magic_ok && version_ok && geometry_ok) {
break;
}
if (std::chrono::steady_clock::now() >= deadline) {
if (!magic_ok) {
throw std::runtime_error("Shared memory ring magic mismatch for " + name);
}
if (!version_ok) {
throw std::runtime_error("Shared memory ring version mismatch for " + name);
}
throw std::runtime_error("Shared memory ring geometry mismatch for " + name);
}
std::this_thread::sleep_for(kHeaderInitPollInterval);
}
}
ShmRing ring{};
ring.fd_ = scoped_fd.release();
const auto [released_mapped, released_size] = scoped_mmap.release();
ring.mapped_ = released_mapped;
ring.mapped_size_ = released_size;
ring.header_ = static_cast<Header*>(released_mapped);
return ring;
}
auto ShmRing::open_existing(const std::string& name) -> ShmRing {
validate_name(name);
const int fd = shm_open(name.c_str(), O_RDWR, 0660);
if (fd < 0) {
throw errno_message("Failed to open shared memory ring", name);
}
ScopedFd scoped_fd(fd);
struct stat info {};
if (fstat(fd, &info) != 0) {
throw errno_message("Failed to stat shared memory ring", name);
}
if (info.st_size < static_cast<off_t>(sizeof(Header))) {
throw std::runtime_error("Shared memory ring size is too small for " + name);
}
const auto mapped_size = static_cast<std::size_t>(info.st_size);
void* mapped = mmap(nullptr, mapped_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (mapped == MAP_FAILED) {
throw errno_message("Failed to mmap shared memory ring", name);
}
ScopedMmap scoped_mmap(mapped, mapped_size);
auto* header = static_cast<Header*>(mapped);
if (std::memcmp(header->magic, kRingMagic.data(), kRingMagic.size()) != 0) {
throw std::runtime_error("Shared memory ring magic mismatch for " + name);
}
if (header->version != kRingVersion) {
throw std::runtime_error("Shared memory ring version mismatch for " + name);
}
ShmRing ring{};
ring.fd_ = scoped_fd.release();
const auto [released_mapped, released_size] = scoped_mmap.release();
ring.mapped_ = released_mapped;
ring.mapped_size_ = released_size;
ring.header_ = static_cast<Header*>(released_mapped);
return ring;
}
void ShmRing::unlink_ring(const std::string& name) {
validate_name(name);
if (shm_unlink(name.c_str()) != 0 && errno != ENOENT) {
throw errno_message("Failed to unlink shared memory ring", name);
}
}
auto ShmRing::is_open() const -> bool {
return header_ != nullptr;
}
auto ShmRing::capacity() const -> std::uint32_t {
return header_ != nullptr ? header_->capacity : 0;
}
auto ShmRing::slot_size_bytes() const -> std::uint32_t {
return header_ != nullptr ? header_->slot_size_bytes : 0;
}
auto ShmRing::dropped_count() const -> std::uint64_t {
if (header_ == nullptr) {
return 0;
}
return header_->dropped.load(std::memory_order_acquire);
}
auto ShmRing::push(std::span<const std::uint8_t> payload) -> bool {
if (header_ == nullptr) {
throw std::runtime_error("Ring is not open");
}
if (payload.size() > header_->slot_size_bytes) {
return false;
}
const auto write_seq = header_->write_seq.load(std::memory_order_relaxed);
const auto read_seq = header_->read_seq.load(std::memory_order_acquire);
if (checked_u64_diff(write_seq, read_seq) >= header_->capacity) {
header_->read_seq.store(read_seq + 1U, std::memory_order_release);
header_->dropped.fetch_add(1U, std::memory_order_relaxed);
}
auto* slot = slot_header(write_seq);
slot->payload_size = static_cast<std::uint32_t>(payload.size());
slot->sequence = write_seq + 1U;
std::memcpy(slot_payload(slot), payload.data(), payload.size());
std::atomic_thread_fence(std::memory_order_release);
header_->write_seq.store(write_seq + 1U, std::memory_order_release);
return true;
}
auto ShmRing::pop(std::vector<std::uint8_t>& payload) -> bool {
if (header_ == nullptr) {
throw std::runtime_error("Ring is not open");
}
const auto read_seq = header_->read_seq.load(std::memory_order_relaxed);
const auto write_seq = header_->write_seq.load(std::memory_order_acquire);
if (read_seq >= write_seq) {
return false;
}
auto* slot = slot_header(read_seq);
if (slot->sequence != read_seq + 1U) {
// Producer overwrote this slot before consumer read it. Resync to latest.
header_->read_seq.store(write_seq, std::memory_order_release);
return false;
}
const auto payload_size = slot->payload_size;
if (payload_size > header_->slot_size_bytes) {
header_->read_seq.store(write_seq, std::memory_order_release);
throw std::runtime_error("Invalid payload size in shared memory slot");
}
payload.resize(payload_size);
std::memcpy(payload.data(), slot_payload(slot), payload_size);
std::atomic_thread_fence(std::memory_order_acquire);
header_->read_seq.store(read_seq + 1U, std::memory_order_release);
return true;
}
auto ShmRing::slot_stride_bytes() const -> std::size_t {
return sizeof(SlotHeader) + header_->slot_size_bytes;
}
auto ShmRing::slot_header(std::uint64_t sequence) const -> SlotHeader* {
const auto index = sequence % header_->capacity;
auto* slots_begin = static_cast<std::uint8_t*>(mapped_) + sizeof(Header);
return reinterpret_cast<SlotHeader*>(slots_begin + index * slot_stride_bytes());
}
auto ShmRing::slot_payload(SlotHeader* slot) const -> std::uint8_t* {
return reinterpret_cast<std::uint8_t*>(slot) + sizeof(SlotHeader);
}
void ShmRing::close() {
if (mapped_ != nullptr) {
munmap(mapped_, mapped_size_);
mapped_ = nullptr;
}
if (fd_ >= 0) {
::close(fd_);
fd_ = -1;
}
mapped_size_ = 0;
header_ = nullptr;
}
void ShmRing::validate_name(const std::string& name) {
if (name.empty() || name.front() != '/') {
throw std::runtime_error("POSIX shm name must start with '/'");
}
}
} // namespace radar::ipc