wireforge 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. wireforge/__init__.py +3 -0
  2. wireforge/_data/cmake/WireForgeConfig.cmake +29 -0
  3. wireforge/_data/cmake/WireForgeConfigVersion.cmake +15 -0
  4. wireforge/_data/cmake/WireForgeGenerate.cmake +64 -0
  5. wireforge/_data/runtime/LICENSE +21 -0
  6. wireforge/_data/runtime/include/wireforge/array_access.hpp +35 -0
  7. wireforge/_data/runtime/include/wireforge/bit_utils.hpp +226 -0
  8. wireforge/_data/runtime/include/wireforge/byte_io.hpp +387 -0
  9. wireforge/_data/runtime/include/wireforge/checksum.hpp +201 -0
  10. wireforge/_data/runtime/include/wireforge/codec_support.hpp +412 -0
  11. wireforge/_data/runtime/include/wireforge/config.hpp +41 -0
  12. wireforge/_data/runtime/include/wireforge/delegate.hpp +101 -0
  13. wireforge/_data/runtime/include/wireforge/dispatcher.hpp +339 -0
  14. wireforge/_data/runtime/include/wireforge/expr_support.hpp +38 -0
  15. wireforge/_data/runtime/include/wireforge/frame_decoder.hpp +262 -0
  16. wireforge/_data/runtime/include/wireforge/frame_layout.hpp +20 -0
  17. wireforge/_data/runtime/include/wireforge/link.hpp +223 -0
  18. wireforge/_data/runtime/include/wireforge/lock.hpp +146 -0
  19. wireforge/_data/runtime/include/wireforge/ring_buffer.hpp +226 -0
  20. wireforge/_data/runtime/include/wireforge/span.hpp +116 -0
  21. wireforge/_data/runtime/include/wireforge/static_vector.hpp +264 -0
  22. wireforge/_data/runtime/include/wireforge/status.hpp +75 -0
  23. wireforge/_data/runtime/include/wireforge/transport.hpp +95 -0
  24. wireforge/_data/runtime/include/wireforge/validation.hpp +153 -0
  25. wireforge/_data/runtime/include/wireforge/version.hpp +22 -0
  26. wireforge/_data/runtime/requirements.yaml +438 -0
  27. wireforge/_data/schema/deviations.schema.json +47 -0
  28. wireforge/_data/schema/fragment.schema.json +79 -0
  29. wireforge/_data/schema/llr_ids.schema.json +25 -0
  30. wireforge/_data/schema/project.schema.json +1240 -0
  31. wireforge/_data/schema/runtime_requirements.schema.json +34 -0
  32. wireforge/_data/ui/assets/index-6Pomu6U4.js +44 -0
  33. wireforge/_data/ui/assets/index-GVdDyUAG.css +1 -0
  34. wireforge/_data/ui/index.html +13 -0
  35. wireforge/bundle.py +177 -0
  36. wireforge/ci_check.py +155 -0
  37. wireforge/cli.py +617 -0
  38. wireforge/codec.py +897 -0
  39. wireforge/compat.py +536 -0
  40. wireforge/coverage_points.py +64 -0
  41. wireforge/cpp.py +273 -0
  42. wireforge/cpp_codec.py +440 -0
  43. wireforge/cpp_interface.py +874 -0
  44. wireforge/cpp_tests.py +1993 -0
  45. wireforge/differential.py +586 -0
  46. wireforge/docs_markdown.py +72 -0
  47. wireforge/expr.py +598 -0
  48. wireforge/findings.py +187 -0
  49. wireforge/fmt.py +351 -0
  50. wireforge/generate.py +644 -0
  51. wireforge/golden.py +253 -0
  52. wireforge/hlr_import.py +112 -0
  53. wireforge/impact.py +249 -0
  54. wireforge/importers.py +402 -0
  55. wireforge/issues.py +27 -0
  56. wireforge/layout.py +681 -0
  57. wireforge/lifecycle.py +1266 -0
  58. wireforge/lifecycle_results.py +205 -0
  59. wireforge/lifecycle_rules.py +78 -0
  60. wireforge/lifecycle_runtime.py +375 -0
  61. wireforge/llr.py +693 -0
  62. wireforge/llr_ids.py +104 -0
  63. wireforge/loader.py +422 -0
  64. wireforge/mcp_server.py +357 -0
  65. wireforge/model.py +322 -0
  66. wireforge/plain.py +15 -0
  67. wireforge/py.typed +0 -0
  68. wireforge/reqif.py +289 -0
  69. wireforge/resources.py +43 -0
  70. wireforge/results.py +377 -0
  71. wireforge/runtime_reqs.py +147 -0
  72. wireforge/schema.py +151 -0
  73. wireforge/server/__init__.py +1 -0
  74. wireforge/server/app.py +619 -0
  75. wireforge/server/oidc.py +99 -0
  76. wireforge/server/storage.py +146 -0
  77. wireforge/service.py +1354 -0
  78. wireforge/templates/command/project.yaml +38 -0
  79. wireforge/templates/raw/project.yaml +28 -0
  80. wireforge/templates/telemetry/project.yaml +51 -0
  81. wireforge/test_cases.py +475 -0
  82. wireforge/test_project.py +478 -0
  83. wireforge/trace.py +442 -0
  84. wireforge/validate.py +1241 -0
  85. wireforge/vcs.py +44 -0
  86. wireforge/vectors.py +222 -0
  87. wireforge/versioning.py +137 -0
  88. wireforge/wireshark.py +792 -0
  89. wireforge/wireshark_runtime.lua +725 -0
  90. wireforge-0.1.0.dist-info/METADATA +91 -0
  91. wireforge-0.1.0.dist-info/RECORD +96 -0
  92. wireforge-0.1.0.dist-info/WHEEL +5 -0
  93. wireforge-0.1.0.dist-info/entry_points.txt +2 -0
  94. wireforge-0.1.0.dist-info/licenses/LICENSE +98 -0
  95. wireforge-0.1.0.dist-info/licenses/runtime/LICENSE +21 -0
  96. wireforge-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,223 @@
1
+ /**
2
+ * @file link.hpp
3
+ * @brief Connects a transport, a frame decoder and a dispatcher with preallocated buffers.
4
+ */
5
+ #ifndef WIREFORGE_LINK_HPP
6
+ #define WIREFORGE_LINK_HPP
7
+
8
+ #include "wireforge/lock.hpp"
9
+ #include "wireforge/ring_buffer.hpp"
10
+ #include "wireforge/span.hpp"
11
+ #include "wireforge/status.hpp"
12
+ #include "wireforge/transport.hpp"
13
+
14
+ #include <array>
15
+ #include <cstddef>
16
+ #include <cstdint>
17
+ #include <type_traits>
18
+ #include <utility>
19
+
20
+ namespace wireforge {
21
+
22
+ /// @brief Decoder type for interfaces without framing: every transport read is one frame.
23
+ struct Unframed {};
24
+
25
+ /// @brief Outcome of one Link::poll() call.
26
+ struct PollResult {
27
+ std::size_t frames = 0U; ///< Frames dispatched successfully.
28
+ std::size_t errors = 0U; ///< Frames rejected by the decoder or the dispatcher.
29
+ Status last_error = Status::Ok; ///< The most recent error, Status::Ok when none.
30
+ };
31
+
32
+ /// @brief Statistics of a link; readable from any thread.
33
+ struct LinkCounters {
34
+ RelaxedCounter rx_bytes; ///< Bytes received from the transport.
35
+ RelaxedCounter tx_bytes; ///< Bytes accepted by the transport.
36
+ RelaxedCounter tx_frames; ///< Frames sent completely.
37
+ RelaxedCounter tx_errors; ///< Frames not sent (encoder error or transport full).
38
+ };
39
+
40
+ /**
41
+ * @brief One communication link: receive path transport -> ring -> decoder -> dispatcher, and a
42
+ * transmit path that encodes into a preallocated buffer and hands it to the transport.
43
+ *
44
+ * Thread safety: poll() runs on a single receiving thread. send() and send_frame() may be called
45
+ * from several threads; they are serialized by lock policy @p L. The transport, decoder and
46
+ * dispatcher are referenced and must outlive the link.
47
+ * @tparam Transport Type satisfying the transport requirements (or ITransport).
48
+ * @tparam Decoder FrameDecoder<...> or Unframed.
49
+ * @tparam Dispatch Dispatcher<...> or RawDispatcher<...> (anything with dispatch_frame()).
50
+ * @tparam MaxFrame Largest frame in bytes (size of the rx and tx frame buffers).
51
+ * @tparam RxCapacity Capacity of the receive ring in bytes (at least MaxFrame).
52
+ * @tparam L Lock policy of the transmit path.
53
+ */
54
+ template <typename Transport, typename Decoder, typename Dispatch, std::size_t MaxFrame,
55
+ std::size_t RxCapacity = 2U * MaxFrame, typename L = DefaultLock>
56
+ class Link {
57
+ static_assert(is_transport_v<Transport>, "Transport must satisfy the transport requirements");
58
+ static_assert(RxCapacity >= MaxFrame, "the receive ring must hold a whole frame");
59
+ static_assert(is_lock_policy_v<L>, "L must satisfy the lock policy contract");
60
+ static constexpr bool kUnframed = std::is_same_v<Decoder, Unframed>;
61
+
62
+ public:
63
+ /**
64
+ * @param transport Byte transport.
65
+ * @param decoder Frame decoder (ignored for Unframed).
66
+ * @param dispatcher Receives every complete frame.
67
+ */
68
+ Link(Transport& transport, Decoder& decoder, const Dispatch& dispatcher) noexcept
69
+ : transport_(&transport), decoder_(&decoder), dispatcher_(&dispatcher) {}
70
+
71
+ /**
72
+ * @brief Reads everything the transport has and dispatches every complete frame.
73
+ *
74
+ * Call regularly from the receiving thread. For Unframed links each non-empty transport read
75
+ * is dispatched as one frame; a read with more than MaxFrame bytes is rejected as
76
+ * Status::InvalidLength and its bytes are dropped.
77
+ */
78
+ PollResult poll() noexcept {
79
+ PollResult result;
80
+ if constexpr (kUnframed) {
81
+ const std::size_t received = transport_->receive(Span<std::uint8_t>(rx_frame_));
82
+ counters_.rx_bytes.add(received);
83
+ if (received == MaxFrame && discard_excess() > 0U) {
84
+ record(result, Status::InvalidLength);
85
+ } else if (received > 0U) {
86
+ record(result, dispatcher_->dispatch_frame(
87
+ Span<const std::uint8_t>(rx_frame_.data(), received)));
88
+ }
89
+ } else {
90
+ for (;;) {
91
+ const std::size_t received = fill_ring();
92
+ drain_ring(result);
93
+ if (received == 0U) {
94
+ break;
95
+ }
96
+ }
97
+ }
98
+ return result;
99
+ }
100
+
101
+ /**
102
+ * @brief Encodes a frame into the transmit buffer and sends it.
103
+ * @param encode Callable `Status(Span<std::uint8_t> buffer, std::size_t& size)` that writes
104
+ * one frame into @p buffer and sets @p size; called under the transmit lock.
105
+ * @return The encoder's error, Status::BufferTooSmall when the transport did not accept the
106
+ * whole frame, otherwise Status::Ok.
107
+ */
108
+ template <typename Encode> Status send(Encode&& encode) noexcept {
109
+ const ScopedLock<L> guard(tx_lock_);
110
+ std::size_t size = 0U;
111
+ const Status status = std::forward<Encode>(encode)(Span<std::uint8_t>(tx_frame_), size);
112
+ if (status != Status::Ok || size > MaxFrame) {
113
+ counters_.tx_errors.increment();
114
+ return status != Status::Ok ? status : Status::BufferTooSmall;
115
+ }
116
+ return transmit(Span<const std::uint8_t>(tx_frame_.data(), size));
117
+ }
118
+
119
+ /**
120
+ * @brief Sends an already encoded frame.
121
+ * @return Status::BufferTooSmall when the transport did not accept the whole frame.
122
+ */
123
+ Status send_frame(Span<const std::uint8_t> frame) noexcept {
124
+ const ScopedLock<L> guard(tx_lock_);
125
+ return transmit(frame);
126
+ }
127
+
128
+ /// @brief Statistics since construction.
129
+ [[nodiscard]] const LinkCounters& counters() const noexcept { return counters_; }
130
+
131
+ private:
132
+ static void record(PollResult& result, Status status) noexcept {
133
+ if (status == Status::Ok) {
134
+ ++result.frames;
135
+ } else {
136
+ ++result.errors;
137
+ result.last_error = status;
138
+ }
139
+ }
140
+
141
+ // Drops what the transport still holds after a read that filled the frame buffer: an
142
+ // unframed read longer than MaxFrame is one oversized frame. Returns the dropped count.
143
+ std::size_t discard_excess() noexcept {
144
+ std::array<std::uint8_t, kChunk> chunk{};
145
+ std::size_t total = 0U;
146
+ for (;;) {
147
+ const std::size_t received = transport_->receive(Span<std::uint8_t>(chunk));
148
+ if (received == 0U) {
149
+ return total;
150
+ }
151
+ counters_.rx_bytes.add(received);
152
+ total += received;
153
+ }
154
+ }
155
+
156
+ // Moves bytes from the transport into the ring until either is exhausted; returns the count.
157
+ std::size_t fill_ring() noexcept {
158
+ std::array<std::uint8_t, kChunk> chunk{};
159
+ std::size_t total = 0U;
160
+ while (rx_ring_.free_space() > 0U) {
161
+ const std::size_t wanted =
162
+ rx_ring_.free_space() < chunk.size() ? rx_ring_.free_space() : chunk.size();
163
+ const std::size_t received =
164
+ transport_->receive(Span<std::uint8_t>(chunk).first(wanted));
165
+ if (received == 0U) {
166
+ break;
167
+ }
168
+ counters_.rx_bytes.add(received);
169
+ static_cast<void>(rx_ring_.write(Span<const std::uint8_t>(chunk.data(), received)));
170
+ total += received;
171
+ }
172
+ return total;
173
+ }
174
+
175
+ void drain_ring(PollResult& result) noexcept {
176
+ for (;;) {
177
+ std::size_t size = 0U;
178
+ const Status status = decoder_->next(rx_ring_, Span<std::uint8_t>(rx_frame_), size);
179
+ if (status == Status::Truncated) {
180
+ return;
181
+ }
182
+ if (status != Status::Ok) {
183
+ record(result, status);
184
+ continue;
185
+ }
186
+ record(result,
187
+ dispatcher_->dispatch_frame(Span<const std::uint8_t>(rx_frame_.data(), size)));
188
+ }
189
+ }
190
+
191
+ Status transmit(Span<const std::uint8_t> frame) noexcept {
192
+ std::size_t sent = 0U;
193
+ while (sent < frame.size()) {
194
+ const std::size_t accepted = transport_->send(frame.subspan(sent));
195
+ if (accepted == 0U) {
196
+ break;
197
+ }
198
+ sent += accepted;
199
+ }
200
+ counters_.tx_bytes.add(sent);
201
+ if (sent != frame.size()) {
202
+ counters_.tx_errors.increment();
203
+ return Status::BufferTooSmall;
204
+ }
205
+ counters_.tx_frames.increment();
206
+ return Status::Ok;
207
+ }
208
+
209
+ static constexpr std::size_t kChunk = 64U;
210
+
211
+ Transport* transport_;
212
+ Decoder* decoder_;
213
+ const Dispatch* dispatcher_;
214
+ StaticSpscByteRing<RxCapacity> rx_ring_;
215
+ std::array<std::uint8_t, MaxFrame> rx_frame_{};
216
+ std::array<std::uint8_t, MaxFrame> tx_frame_{};
217
+ L tx_lock_;
218
+ LinkCounters counters_;
219
+ };
220
+
221
+ } // namespace wireforge
222
+
223
+ #endif // WIREFORGE_LINK_HPP
@@ -0,0 +1,146 @@
1
+ /**
2
+ * @file lock.hpp
3
+ * @brief Lock policies, a scoped guard and a statistics counter.
4
+ *
5
+ * A lock policy is a default-constructible type with `void lock() noexcept` and
6
+ * `void unlock() noexcept`. Runtime classes take the policy as a template parameter, so a port to
7
+ * an RTOS supplies its own type and never needs `<mutex>`:
8
+ * @code
9
+ * struct RtosLock {
10
+ * void lock() noexcept { take(semaphore); }
11
+ * void unlock() noexcept { give(semaphore); }
12
+ * Semaphore semaphore;
13
+ * };
14
+ * @endcode
15
+ */
16
+ #ifndef WIREFORGE_LOCK_HPP
17
+ #define WIREFORGE_LOCK_HPP
18
+
19
+ #include "wireforge/config.hpp"
20
+
21
+ #include <atomic>
22
+ #include <cstdint>
23
+ #include <type_traits>
24
+ #include <utility>
25
+
26
+ #if !defined(WIREFORGE_NO_STD_MUTEX)
27
+ #include <mutex>
28
+ #endif
29
+
30
+ namespace wireforge {
31
+
32
+ /// @brief Detects whether @p L satisfies the lock policy requirements.
33
+ template <typename L, typename = void> struct is_lock_policy : std::false_type {};
34
+
35
+ /// @cond INTERNAL
36
+ template <typename L>
37
+ struct is_lock_policy<
38
+ L, std::void_t<decltype(std::declval<L&>().lock()), decltype(std::declval<L&>().unlock())>>
39
+ : std::bool_constant<std::is_default_constructible_v<L> &&
40
+ noexcept(std::declval<L&>().lock()) &&
41
+ noexcept(std::declval<L&>().unlock())> {};
42
+ /// @endcond
43
+
44
+ /// @brief true when @p L is a lock policy.
45
+ template <typename L> inline constexpr bool is_lock_policy_v = is_lock_policy<L>::value;
46
+
47
+ /// @brief Lock policy that does nothing: for single-threaded use or externally serialized access.
48
+ class NullLock {
49
+ public:
50
+ /// @brief Does nothing.
51
+ static constexpr void lock() noexcept {}
52
+ /// @brief Does nothing.
53
+ static constexpr void unlock() noexcept {}
54
+ };
55
+
56
+ #if !defined(WIREFORGE_NO_STD_MUTEX)
57
+ /**
58
+ * @brief Lock policy backed by `std::mutex`.
59
+ * @note A failing `std::mutex::lock` calls `std::terminate` because lock() is noexcept.
60
+ */
61
+ class StdMutexLock {
62
+ public:
63
+ /// @brief Creates an unlocked mutex.
64
+ StdMutexLock() noexcept = default;
65
+ ~StdMutexLock() = default;
66
+ StdMutexLock(const StdMutexLock&) = delete;
67
+ StdMutexLock& operator=(const StdMutexLock&) = delete;
68
+ StdMutexLock(StdMutexLock&&) = delete;
69
+ StdMutexLock& operator=(StdMutexLock&&) = delete;
70
+
71
+ /// @brief Blocks until the mutex is acquired.
72
+ void lock() noexcept { mutex_.lock(); }
73
+ /// @brief Releases the mutex.
74
+ void unlock() noexcept { mutex_.unlock(); }
75
+
76
+ private:
77
+ std::mutex mutex_;
78
+ };
79
+ #endif
80
+
81
+ #if defined(WIREFORGE_DEFAULT_LOCK)
82
+ using DefaultLock = WIREFORGE_DEFAULT_LOCK;
83
+ #elif !defined(WIREFORGE_NO_STD_MUTEX)
84
+ /**
85
+ * @brief Lock policy used when none is given.
86
+ *
87
+ * `WIREFORGE_DEFAULT_LOCK` when defined, otherwise StdMutexLock, or NullLock when
88
+ * `WIREFORGE_NO_STD_MUTEX` is defined.
89
+ */
90
+ using DefaultLock = StdMutexLock;
91
+ #else
92
+ using DefaultLock = NullLock;
93
+ #endif
94
+
95
+ static_assert(is_lock_policy_v<NullLock>, "NullLock must satisfy the lock policy contract");
96
+ static_assert(is_lock_policy_v<DefaultLock>, "DefaultLock must satisfy the lock policy contract");
97
+
98
+ /**
99
+ * @brief Acquires a lock on construction and releases it on destruction.
100
+ * @tparam L Lock policy.
101
+ */
102
+ template <typename L> class ScopedLock {
103
+ static_assert(is_lock_policy_v<L>, "L must satisfy the lock policy contract");
104
+
105
+ public:
106
+ /// @brief Locks @p lock until this guard is destroyed.
107
+ explicit ScopedLock(L& lock) noexcept : lock_(lock) { lock_.lock(); }
108
+ ~ScopedLock() { lock_.unlock(); }
109
+ ScopedLock(const ScopedLock&) = delete;
110
+ ScopedLock& operator=(const ScopedLock&) = delete;
111
+ ScopedLock(ScopedLock&&) = delete;
112
+ ScopedLock& operator=(ScopedLock&&) = delete;
113
+
114
+ private:
115
+ L& lock_;
116
+ };
117
+
118
+ /**
119
+ * @brief 32-bit counter that one thread updates and any thread reads.
120
+ *
121
+ * Uses relaxed atomics: the value is informational (statistics) and does not order other memory
122
+ * accesses.
123
+ */
124
+ class RelaxedCounter {
125
+ public:
126
+ /// @brief Adds one.
127
+ void increment() noexcept { value_.fetch_add(1U, std::memory_order_relaxed); }
128
+ /// @brief Adds @p amount, an unsigned integer (wraps around modulo 2^32).
129
+ template <typename U> void add(U amount) noexcept {
130
+ static_assert(std::is_unsigned_v<U>, "counters add unsigned amounts");
131
+ value_.fetch_add(static_cast<std::uint32_t>(amount), std::memory_order_relaxed);
132
+ }
133
+ /// @brief Sets the counter to zero.
134
+ void reset() noexcept { value_.store(0U, std::memory_order_relaxed); }
135
+ /// @brief Current value.
136
+ [[nodiscard]] std::uint32_t load() const noexcept {
137
+ return value_.load(std::memory_order_relaxed);
138
+ }
139
+
140
+ private:
141
+ std::atomic<std::uint32_t> value_{0U};
142
+ };
143
+
144
+ } // namespace wireforge
145
+
146
+ #endif // WIREFORGE_LOCK_HPP
@@ -0,0 +1,226 @@
1
+ /**
2
+ * @file ring_buffer.hpp
3
+ * @brief Byte ring buffers: lock-free single-producer/single-consumer and lock-protected variants.
4
+ */
5
+ #ifndef WIREFORGE_RING_BUFFER_HPP
6
+ #define WIREFORGE_RING_BUFFER_HPP
7
+
8
+ #include "wireforge/lock.hpp"
9
+ #include "wireforge/span.hpp"
10
+
11
+ #include <array>
12
+ #include <atomic>
13
+ #include <cstddef>
14
+ #include <cstdint>
15
+
16
+ namespace wireforge {
17
+
18
+ /**
19
+ * @brief Lock-free single-producer/single-consumer byte ring over caller-provided storage.
20
+ *
21
+ * Thread safety: write() from exactly one producer thread; peek(), read() and consume() from
22
+ * exactly one consumer thread; size queries from either. reset() requires both sides to be idle.
23
+ * The whole storage is usable (no sentinel byte). Data written by the producer is visible to the
24
+ * consumer once write() returns.
25
+ */
26
+ class SpscByteRing {
27
+ public:
28
+ /// @brief Uses @p storage as the ring memory; it must outlive the ring.
29
+ explicit SpscByteRing(Span<std::uint8_t> storage) noexcept : storage_(storage) {}
30
+ ~SpscByteRing() = default;
31
+ SpscByteRing(const SpscByteRing&) = delete;
32
+ SpscByteRing& operator=(const SpscByteRing&) = delete;
33
+ SpscByteRing(SpscByteRing&&) = delete;
34
+ SpscByteRing& operator=(SpscByteRing&&) = delete;
35
+
36
+ /// @brief Size of the storage in bytes.
37
+ [[nodiscard]] std::size_t capacity() const noexcept { return storage_.size(); }
38
+
39
+ /// @brief Bytes available to the consumer.
40
+ [[nodiscard]] std::size_t size() const noexcept {
41
+ return distance(head_.load(std::memory_order_acquire),
42
+ tail_.load(std::memory_order_acquire));
43
+ }
44
+ /// @brief Bytes the producer can still write.
45
+ [[nodiscard]] std::size_t free_space() const noexcept { return capacity() - size(); }
46
+ /// @brief true when no bytes are available.
47
+ [[nodiscard]] bool empty() const noexcept { return size() == 0U; }
48
+
49
+ /**
50
+ * @brief Producer: appends as many bytes of @p bytes as fit.
51
+ * @return Number of bytes stored (less than bytes.size() when the ring is full).
52
+ */
53
+ std::size_t write(Span<const std::uint8_t> bytes) noexcept {
54
+ const std::size_t tail = tail_.load(std::memory_order_relaxed);
55
+ const std::size_t head = head_.load(std::memory_order_acquire);
56
+ const std::size_t space = capacity() - distance(head, tail);
57
+ const std::size_t count = bytes.size() < space ? bytes.size() : space;
58
+ std::size_t slot = wrap(tail);
59
+ for (std::size_t index = 0U; index < count; ++index) {
60
+ storage_[slot] = bytes[index];
61
+ slot = slot + 1U == capacity() ? 0U : slot + 1U;
62
+ }
63
+ tail_.store(advance(tail, count), std::memory_order_release);
64
+ return count;
65
+ }
66
+
67
+ /**
68
+ * @brief Consumer: copies bytes without consuming them.
69
+ * @param out Destination; up to out.size() bytes are copied.
70
+ * @param offset Number of available bytes to skip before copying.
71
+ * @return Number of bytes copied.
72
+ */
73
+ std::size_t peek(Span<std::uint8_t> out, std::size_t offset = 0U) const noexcept {
74
+ const std::size_t head = head_.load(std::memory_order_relaxed);
75
+ const std::size_t tail = tail_.load(std::memory_order_acquire);
76
+ const std::size_t used = distance(head, tail);
77
+ if (offset >= used) {
78
+ return 0U;
79
+ }
80
+ const std::size_t available = used - offset;
81
+ const std::size_t count = out.size() < available ? out.size() : available;
82
+ std::size_t slot = wrap(advance(head, offset));
83
+ for (std::size_t index = 0U; index < count; ++index) {
84
+ out[index] = storage_[slot];
85
+ slot = slot + 1U == capacity() ? 0U : slot + 1U;
86
+ }
87
+ return count;
88
+ }
89
+
90
+ /**
91
+ * @brief Consumer: discards up to @p count bytes.
92
+ * @return Number of bytes discarded.
93
+ */
94
+ std::size_t consume(std::size_t count) noexcept {
95
+ const std::size_t head = head_.load(std::memory_order_relaxed);
96
+ const std::size_t tail = tail_.load(std::memory_order_acquire);
97
+ const std::size_t used = distance(head, tail);
98
+ const std::size_t discarded = count < used ? count : used;
99
+ head_.store(advance(head, discarded), std::memory_order_release);
100
+ return discarded;
101
+ }
102
+
103
+ /**
104
+ * @brief Consumer: copies and consumes up to out.size() bytes.
105
+ * @return Number of bytes read.
106
+ */
107
+ std::size_t read(Span<std::uint8_t> out) noexcept { return consume(peek(out)); }
108
+
109
+ /// @brief Drops all content. Neither producer nor consumer may run concurrently.
110
+ void reset() noexcept {
111
+ head_.store(0U, std::memory_order_relaxed);
112
+ tail_.store(0U, std::memory_order_release);
113
+ }
114
+
115
+ private:
116
+ // Indices run in [0, 2 * capacity) so that "full" and "empty" are distinguishable.
117
+ [[nodiscard]] std::size_t distance(std::size_t head, std::size_t tail) const noexcept {
118
+ return tail >= head ? tail - head : (2U * capacity()) - head + tail;
119
+ }
120
+ [[nodiscard]] std::size_t advance(std::size_t index, std::size_t count) const noexcept {
121
+ const std::size_t limit = 2U * capacity();
122
+ const std::size_t next = index + count;
123
+ return next >= limit ? next - limit : next;
124
+ }
125
+ [[nodiscard]] std::size_t wrap(std::size_t index) const noexcept {
126
+ return index >= capacity() ? index - capacity() : index;
127
+ }
128
+
129
+ Span<std::uint8_t> storage_;
130
+ std::atomic<std::size_t> head_{0U};
131
+ std::atomic<std::size_t> tail_{0U};
132
+ };
133
+
134
+ /**
135
+ * @brief Byte ring for any number of producers and consumers.
136
+ *
137
+ * Every operation of an SpscByteRing runs under lock policy @p L.
138
+ * @tparam L Lock policy (see lock.hpp).
139
+ */
140
+ template <typename L = DefaultLock> class LockedByteRing {
141
+ static_assert(is_lock_policy_v<L>, "L must satisfy the lock policy contract");
142
+
143
+ public:
144
+ /// @brief Uses @p storage as the ring memory; it must outlive the ring.
145
+ explicit LockedByteRing(Span<std::uint8_t> storage) noexcept : ring_(storage) {}
146
+
147
+ /// @copydoc SpscByteRing::capacity
148
+ [[nodiscard]] std::size_t capacity() const noexcept { return ring_.capacity(); }
149
+ /// @copydoc SpscByteRing::size
150
+ [[nodiscard]] std::size_t size() const noexcept {
151
+ const ScopedLock<L> guard(lock_);
152
+ return ring_.size();
153
+ }
154
+ /// @copydoc SpscByteRing::free_space
155
+ [[nodiscard]] std::size_t free_space() const noexcept {
156
+ const ScopedLock<L> guard(lock_);
157
+ return ring_.free_space();
158
+ }
159
+ /// @copydoc SpscByteRing::empty
160
+ [[nodiscard]] bool empty() const noexcept { return size() == 0U; }
161
+
162
+ /// @copydoc SpscByteRing::write
163
+ std::size_t write(Span<const std::uint8_t> bytes) noexcept {
164
+ const ScopedLock<L> guard(lock_);
165
+ return ring_.write(bytes);
166
+ }
167
+ /**
168
+ * @brief Stores all of @p bytes or nothing, so records of concurrent producers stay whole.
169
+ * @return true when the bytes were stored.
170
+ */
171
+ bool write_all(Span<const std::uint8_t> bytes) noexcept {
172
+ const ScopedLock<L> guard(lock_);
173
+ if (ring_.free_space() < bytes.size()) {
174
+ return false;
175
+ }
176
+ return ring_.write(bytes) == bytes.size();
177
+ }
178
+ /// @copydoc SpscByteRing::peek
179
+ std::size_t peek(Span<std::uint8_t> out, std::size_t offset = 0U) const noexcept {
180
+ const ScopedLock<L> guard(lock_);
181
+ return ring_.peek(out, offset);
182
+ }
183
+ /// @copydoc SpscByteRing::consume
184
+ std::size_t consume(std::size_t count) noexcept {
185
+ const ScopedLock<L> guard(lock_);
186
+ return ring_.consume(count);
187
+ }
188
+ /// @copydoc SpscByteRing::read
189
+ std::size_t read(Span<std::uint8_t> out) noexcept {
190
+ const ScopedLock<L> guard(lock_);
191
+ return ring_.read(out);
192
+ }
193
+ /// @brief Drops all content.
194
+ void reset() noexcept {
195
+ const ScopedLock<L> guard(lock_);
196
+ ring_.reset();
197
+ }
198
+
199
+ private:
200
+ mutable L lock_;
201
+ SpscByteRing ring_;
202
+ };
203
+
204
+ /// @cond INTERNAL
205
+ namespace detail {
206
+ template <std::size_t N> struct RingStorage {
207
+ std::array<std::uint8_t, N> bytes{};
208
+ };
209
+ } // namespace detail
210
+ /// @endcond
211
+
212
+ /**
213
+ * @brief SpscByteRing with built-in storage of @p N bytes.
214
+ * @tparam N Capacity in bytes.
215
+ */
216
+ template <std::size_t N>
217
+ class StaticSpscByteRing : private detail::RingStorage<N>, public SpscByteRing {
218
+ public:
219
+ /// @brief Creates an empty ring.
220
+ StaticSpscByteRing() noexcept
221
+ : detail::RingStorage<N>(), SpscByteRing(Span<std::uint8_t>(this->bytes)) {}
222
+ };
223
+
224
+ } // namespace wireforge
225
+
226
+ #endif // WIREFORGE_RING_BUFFER_HPP