SatCat5
pkt_buffer.cc
1 // Copyright 2021-2025 The Aerospace Corporation.
3 // This file is a part of SatCat5, licensed under CERN-OHL-W v2 or later.
5 
6 #include <cstring>
7 #include <satcat5/interrupts.h>
8 #include <satcat5/pkt_buffer.h>
9 #include <satcat5/utils.h>
10 
15 
16 // Safety-check ZCQ calls? Safe but slow.
17 static const unsigned DEBUG_SAFE_ZCW = 0;
18 
19 // Label for AtomicLock statistics tracking.
20 static const char* LBL_PKT = "PKT";
21 
22 void PacketBuffer::clear() {
23  AtomicLock lock(LBL_PKT);
24  m_buff_rdidx = 0;
25  m_buff_rdcount = 0;
26  m_pkt_rdidx = 0;
27  m_next_wrpos = 0;
28  m_next_wrlen = 0;
29  m_shared_rdavail = 0;
31 }
32 
33 unsigned PacketBuffer::byte_count(bool reset) {
34  return satcat5::util::poll_counter(m_total_bytes, reset);
35 }
36 
37 unsigned PacketBuffer::frame_count(bool reset) {
38  return satcat5::util::poll_counter(m_total_frames, reset);
39 }
40 
42  unsigned wralloc = m_buff_size - m_shared_rdavail;
43  if (m_next_wrlen >= wralloc)
44  return 100;
45  unsigned wr_pct = (100 * (m_shared_rdavail + m_next_wrlen)) / m_buff_size;
46  unsigned pkt_pct = 0;
47  if (m_pkt_maxct)
48  pkt_pct = (100 * m_shared_pktcount) / m_pkt_maxct;
49  return (u8)satcat5::util::max_unsigned(wr_pct, pkt_pct);
50 }
51 
53  // No space if we overflow the buffer (wrlen = UINT32_MAX).
54  unsigned wralloc = m_buff_size - m_shared_rdavail;
55  if (m_next_wrlen > wralloc)
56  return 0;
57  // In packet mode, also restricted by max packet size UINT16_MAX
58  // and by the number of packets, regardless of size.
59  if (m_pkt_maxct) {
60  if (m_next_wrlen >= UINT16_MAX) return 0;
61  if (m_shared_pktcount >= m_pkt_maxct) return 0;
62  return min_unsigned(wralloc - m_next_wrlen, UINT16_MAX - m_next_wrlen);
63  } else {
64  // Otherwise we can simply compute the remaining space
65  return wralloc - m_next_wrlen;
66  }
67 }
68 
70  return m_next_wrlen;
71 }
72 
73 void PacketBuffer::write_bytes(unsigned nbytes, const void* src) {
74  // For performance, use memcpy rather than repeated write_next().
75  const u8* src_u8 = (const u8*)src;
76  if (get_write_space() >= nbytes) {
77  unsigned wridx = modulo_add_uns(m_next_wrpos + m_next_wrlen, m_buff_size);
78  unsigned wrap = m_buff_size - wridx;
79  if (nbytes > wrap) { // Two segments (wrap)
80  memcpy(m_buff + wridx, src_u8, wrap);
81  memcpy(m_buff, src_u8 + wrap, nbytes - wrap);
82  } else { // One segment
83  memcpy(m_buff + wridx, src_u8, nbytes);
84  }
85  m_next_wrlen += nbytes;
86  } else {write_overflow();}
87 }
88 
90  m_next_wrlen = 0; // Operation cancelled
91 }
92 
93 void PacketBuffer::write_next(u8 data) {
94  // Write to the appropriate location in the circular buffer.
95  unsigned wridx = modulo_add_uns(m_next_wrpos + m_next_wrlen, m_buff_size);
96  m_buff[wridx] = data;
97  // Increment temporary write pointer location.
98  ++m_next_wrlen;
99 }
100 
102  m_next_wrlen = UINT32_MAX; // Overflow / Error
103 }
104 
106  AtomicLock lock(LBL_PKT);
107 
108  // Whatever happens, clear m_next_wrlen.
109  unsigned next_len = m_next_wrlen;
110  m_next_wrlen = 0;
111 
112  // Handle empty packets or overflow.
113  unsigned wrmax = m_buff_size - m_shared_rdavail;
114  if ((next_len == 0) || (next_len > wrmax)) {
115  return false;
116  }
117 
118  // Update per-packet state, if applicable.
119  if (m_pkt_maxct) {
121  // Packet accepted, update the next stored length.
122  unsigned wridx = modulo_add_uns(m_pkt_rdidx + m_shared_pktcount, m_pkt_maxct);
123  m_pkt_lbuff[wridx] = next_len;
124  ++m_total_frames;
126  } else if (m_pkt_maxct) {
127  // No room in the length buffer, discard unwritten data.
128  return false;
129  }
130  }
131 
132  // Write accepted, update overall buffer state.
133  m_total_bytes += next_len;
134  m_shared_rdavail += next_len;
135  m_next_wrpos = modulo_add_uns(m_next_wrpos + next_len, m_buff_size);
136 
137  // Success! Request follow-up for received-data callback.
138  request_poll();
139  return true;
140 }
141 
142 unsigned PacketBuffer::zcw_maxlen() const {
143  unsigned wralloc = m_buff_size - m_shared_rdavail;
144  if (m_next_wrlen < wralloc) {
145  unsigned wridx = modulo_add_uns(m_next_wrpos + m_next_wrlen, m_buff_size);
146  unsigned max_write = get_write_space();
147  unsigned max_wrap = m_buff_size - wridx;
148  return min_unsigned(max_write, max_wrap);
149  } else {
150  return 0; // Not an error unless user tries to write.
151  }
152 }
153 
155  // Safety check: Confirm maxlen > 0.
156  if (DEBUG_SAFE_ZCW && !zcw_maxlen())
157  return 0; // Not an error unless user tries to write.
158 
159  // Otherwise, calculate write index.
160  unsigned wridx = modulo_add_uns(m_next_wrpos + m_next_wrlen, m_buff_size);
161  return m_buff + wridx;
162 }
163 
164 void PacketBuffer::zcw_write(unsigned nbytes) {
165  // Safety check: Confirm this was a safe write.
166  if (DEBUG_SAFE_ZCW) {
167  unsigned max_safe = zcw_maxlen();
168  if (nbytes > max_safe) {
169  m_next_wrlen = UINT32_MAX;
170  return;
171  }
172  }
173  // Increment the amount of temporary working data.
174  m_next_wrlen += nbytes;
175 }
176 
178  if (!m_pkt_maxct) { // Non-packet mode
180  } else if (m_shared_pktcount) { // Remainder of current packet
181  return m_pkt_lbuff[m_pkt_rdidx];
182  } else { // No packets in buffer
183  return 0;
184  }
185 }
186 
187 bool PacketBuffer::read_bytes(unsigned nbytes, void* dst) {
188  // For performance, use memcpy rather than repeated read_next().
189  u8* dst_u8 = (u8*)dst;
190  if (can_read_internal(nbytes)) {
191  unsigned wrap = m_buff_size - m_buff_rdidx;
192  if (nbytes > wrap) { // Two segments (wraparound)
193  memcpy(dst_u8, m_buff + m_buff_rdidx, wrap);
194  memcpy(dst_u8 + wrap, m_buff, nbytes - wrap);
195  } else { // One segment
196  memcpy(dst_u8, m_buff + m_buff_rdidx, nbytes);
197  }
198  consume_internal(nbytes);
199  return true; // Success
200  } else {
201  read_underflow();
202  return false;
203  }
204 }
205 
207  // Return the next byte.
208  u8 temp = m_buff[m_buff_rdidx];
209  consume_internal(1);
210  return temp;
211 }
212 
213 bool PacketBuffer::can_read_internal(unsigned nbytes) const {
214  if (!m_pkt_maxct) {
215  return (nbytes <= m_shared_rdavail - m_buff_rdcount);
216  } else if (m_shared_pktcount) {
217  return (nbytes <= m_pkt_lbuff[m_pkt_rdidx]);
218  } else {
219  return false;
220  }
221 }
222 
223 void PacketBuffer::consume_internal(unsigned nbytes) {
224  // Increment read pointer.
226 
227  // Decrement global and per-packet length counters.
228  m_buff_rdcount += nbytes;
229  if (m_pkt_maxct) {
230  m_pkt_lbuff[m_pkt_rdidx] -= nbytes;
231  }
232 
233  // Non-packet mode: Immediately free consumed data.
234  // In packet mode, this occurs during read_finalize().
235  if (!m_pkt_maxct) {
236  AtomicLock lock(LBL_PKT);
238  m_buff_rdcount = 0;
239  }
240 }
241 
243  unsigned max_read = get_read_ready();
244  unsigned max_wrap = m_buff_size - m_buff_rdidx;
245  return min_unsigned(max_read, max_wrap);
246 }
247 
248 const u8* PacketBuffer::peek(unsigned nbytes) const {
249  if (nbytes <= get_peek_ready()) {
250  return m_buff + m_buff_rdidx;
251  } else {
252  return NULL;
253  }
254 }
255 
256 bool PacketBuffer::read_consume(unsigned nbytes) {
257  if (can_read_internal(nbytes)) {
258  consume_internal(nbytes);
259  return true;
260  } else {
261  read_underflow();
262  return false;
263  }
264 }
265 
267  AtomicLock lock(LBL_PKT);
268 
269  // Move to next packet, if applicable.
271  // Is there anything left in the current packet?
272  unsigned nrem = m_pkt_lbuff[m_pkt_rdidx];
273  if (nrem) consume_internal(nrem);
274  // Move to the next packet.
275  m_pkt_rdidx = modulo_add_uns(m_pkt_rdidx + 1, m_pkt_maxct);
277  }
278 
279  // Update current read state.
281  m_buff_rdcount = 0;
282 
283  // Special case if that was the very last byte:
284  // Reset reduces the cost of handling buffer-wraparound in peek().
285  if (m_shared_rdavail == 0 && m_next_wrlen == 0) {
286  clear();
287  }
288 }
The PacketBuffer class is a wrapper for a circular buffer, with optional logic to support retention o...
Definition: pkt_buffer.h:66
const u8 * peek(unsigned nbytes) const
Peek nbytes into the circular buffer.
Definition: pkt_buffer.cc:248
void clear()
Reset buffer contents.
Definition: pkt_buffer.cc:22
unsigned m_next_wrpos
Working state for writes (write domain)
Definition: pkt_buffer.h:173
unsigned frame_count(bool reset=true) override
Frames transferred since the last query.
Definition: pkt_buffer.cc:37
unsigned m_total_bytes
Cumulative total-bytes and total-frames statistics counters.
Definition: pkt_buffer.h:167
unsigned byte_count(bool reset=true) override
Bytes transferred since the last query.
Definition: pkt_buffer.cc:33
unsigned get_write_partial() const
Get number of bytes in a partial packet, or -1 on overflow.
Definition: pkt_buffer.cc:69
unsigned get_peek_ready() const
Find the longest available contiguous segment that can be requested by peek().
Definition: pkt_buffer.cc:242
void write_next(u8 data) override
Write the next byte to the underlying buffer or device.
Definition: pkt_buffer.cc:93
bool write_finalize() override
Mark end of frame and release temporary working data.
Definition: pkt_buffer.cc:105
unsigned m_pkt_rdidx
Store packet lengths in an auxiliary buffer (read domain)
Definition: pkt_buffer.h:162
u8 read_next() override
Read the next byte from the underlying buffer or device.
Definition: pkt_buffer.cc:206
bool read_bytes(unsigned nbytes, void *dst) override
Read 0 or more bytes into a buffer.
Definition: pkt_buffer.cc:187
unsigned get_write_space() const override
How many bytes can be written without blocking?
Definition: pkt_buffer.cc:52
volatile unsigned m_shared_pktcount
Shared state is constant except for cross-domain events.
Definition: pkt_buffer.h:180
void zcw_write(unsigned nbytes)
Zero-copy write (zcw) mode, required for UART interface.
Definition: pkt_buffer.cc:164
void write_bytes(unsigned nbytes, const void *src) override
Write 0 or more bytes from a buffer.
Definition: pkt_buffer.cc:73
void write_overflow() override
Optional error handling for write overflow.
Definition: pkt_buffer.cc:101
volatile unsigned m_shared_rdavail
Shared state is constant except for cross-domain events.
Definition: pkt_buffer.h:179
u8 *const m_buff
State for the main circular buffer (read domain)
Definition: pkt_buffer.h:152
unsigned m_total_frames
Cumulative total-bytes and total-frames statistics counters.
Definition: pkt_buffer.h:168
unsigned get_read_ready() const override
How many bytes can be read without blocking?
Definition: pkt_buffer.cc:177
bool read_consume(unsigned nbytes) override
Read and discard 0 or more bytes.
Definition: pkt_buffer.cc:256
const unsigned m_pkt_maxct
Store packet lengths in an auxiliary buffer (read domain)
Definition: pkt_buffer.h:161
unsigned m_buff_rdcount
State for the main circular buffer (read domain)
Definition: pkt_buffer.h:155
unsigned m_buff_rdidx
State for the main circular buffer (read domain)
Definition: pkt_buffer.h:154
unsigned zcw_maxlen() const
Max contiguous write length (ZCW).
Definition: pkt_buffer.cc:142
unsigned m_next_wrlen
Working state for writes (write domain)
Definition: pkt_buffer.h:174
void write_abort() override
If possible, abort the current partially-written packet.
Definition: pkt_buffer.cc:89
const unsigned m_buff_size
State for the main circular buffer (read domain)
Definition: pkt_buffer.h:153
void read_finalize() override
Consume any remaining bytes in this frame, if applicable.
Definition: pkt_buffer.cc:266
u8 get_percent_full() const
Get overall buffer occupancy as percentage full (0-100%).
Definition: pkt_buffer.cc:41
u8 * zcw_start()
Pointer to a contiguous buffer (ZCW).
Definition: pkt_buffer.cc:154
u16 *const m_pkt_lbuff
Store packet lengths in an auxiliary buffer (read domain)
Definition: pkt_buffer.h:160
virtual void read_underflow()
Optional error handling for read underflow.
Definition: io_readable.cc:271
Automatic lock or mutex.
void request_poll()
Call this method to request polling at a later time.
Definition: polling.cc:208
Platform-agnostic API for interrupt management.
Miscellaneous mathematical utility functions.
constexpr unsigned min_unsigned(unsigned a, unsigned b)
Min and max functions.
Definition: utils.h:111
constexpr unsigned modulo_add_uns(unsigned sum, unsigned m)
Modulo addition function.
Definition: utils.h:182