SatCat5
eth_switch.cc
1 // Copyright 2024-2025 The Aerospace Corporation.
3 // This file is a part of SatCat5, licensed under CERN-OHL-W v2 or later.
5 
6 #include <satcat5/eth_plugin.h>
7 #include <satcat5/eth_switch.h>
8 #include <satcat5/eth_sw_log.h>
9 #include <satcat5/log.h>
10 #include <satcat5/utils.h>
11 
23 using satcat5::log::Log;
24 
26 
27 // Identify the various watch-points where the debug port can be attached.
28 // Any enabled point(s) will carbon-copy the packet contents to m_debug.
29 // Setting multiple points will result in near-duplicate packets, but may
30 // be useful in diagnosing problems with SwitchCore, SwitchPort, switch
31 // plugins, the router2::Dispatch block, or user-defined child classes.
32 // Note: DEBUG_EGRESS is logged separately for each egress port.
33 static constexpr unsigned
34  DEBUG_INGRESS = (1u << 0), // Immediately on ingress
35  DEBUG_PLUGIN = (1u << 1), // Before plugin processing
36  DEBUG_PLUGOUT = (1u << 2), // After plugin processing
37  DEBUG_DELIVERY = (1u << 3), // During delivery
38  DEBUG_EGRESS = (1u << 4); // During egress (each port)
39 
40 // Set the default watch-point(s) for the debug port.
41 // e.g., (DEBUG_INGRESS | DEBUG_PLUGOUT | DEBUG_EGRESS)
42 #ifndef SATCAT5_SWITCH_DEBUG
43 #define SATCAT5_SWITCH_DEBUG (DEBUG_PLUGOUT)
44 #endif
45 
46 // Set verbosity level for debugging (0/1/2)
47 static constexpr unsigned DEBUG_VERBOSE = 0;
48 
49 SwitchCore::SwitchCore(u8* buff, unsigned nbytes)
50  : MultiBuffer(buff, nbytes)
51  , m_debug(nullptr)
52  , m_pktlogs()
53  , m_free_pmask(PMASK_ALL)
54  , m_prom_mask(0)
55  , m_stats_filter(0)
56  , m_stats_count(0)
57 {
58  // Nothing else to initialize.
59 }
60 
61 void SwitchCore::plugin_add(PluginCore* plugin)
62  { m_plugins.add(plugin); }
63 void SwitchCore::plugin_remove(PluginCore* plugin)
64  { m_plugins.remove(plugin); }
65 
67  // The "free_pmask" sets a bit for each free index.
68  // Starting from the LSB, find and clear the first '1' bit.
69  // If there's no more space, then this function returns zero.
70  SATCAT5_PMASK_TYPE lsb = (m_free_pmask & -m_free_pmask);
71  satcat5::util::clr_mask(m_free_pmask, lsb);
72  return lsb;
73 }
74 
75 void SwitchCore::port_add(satcat5::eth::SwitchPort* port) {
76  // Clear the "free" bit for each associated port.
77  satcat5::util::clr_mask(m_free_pmask, port->port_mask());
78  // Add the object to the list of registered ports.
79  m_ports.push_back(port);
80 }
81 
82 void SwitchCore::port_remove(satcat5::eth::SwitchPort* port) {
83  // Restore the "free" bit for each associated port.
84  port_remove(port->port_mask());
85  // Remove the object from the list of registered ports.
86  m_ports.remove(port);
87 }
88 
89 void SwitchCore::port_remove(const SATCAT5_PMASK_TYPE& mask) {
90  // Restore the "free" bit for each associated port.
91  satcat5::util::set_mask(m_free_pmask, mask);
92 }
93 
94 void SwitchCore::set_promiscuous(unsigned port_idx, bool enable) {
95  SATCAT5_PMASK_TYPE mask = idx2mask(port_idx);
96  satcat5::util::set_mask_if(m_prom_mask, mask, enable);
97 }
98 
100  m_stats_filter = etype;
101  m_stats_count = 0;
102 }
103 
105  u32 temp = m_stats_count;
106  m_stats_count = 0;
107  return temp;
108 }
109 
111  // Attempt to read the Ethernet and IPv4 headers.
112  PluginPacket meta{};
113  if (!meta.read_from(packet)) {
115  return 0;
116  }
117 
118  // Update statistics before additional rule checks.
119  process_stats(meta);
120 
121  // Query applicable plugins (PluginPort and/or PluginCore).
122  // TODO: Handling for pause frames and spanning-tree protocol?
123  auto plg_result = process_plugins(meta);
124  if (plg_result) return plg_result.value();
125 
126  // Promiscuous ports get a copy of every packet, but
127  // switches never allow loopback to the original source(s).
128  satcat5::util::set_mask(meta.dst_mask, m_prom_mask);
129  satcat5::util::clr_mask(meta.dst_mask, meta.src_mask());
130 
131  // Attempt to deliver the packet to each matching port object.
132  return deliver_switch(meta);
133 }
134 
135 void SwitchCore::debug_if(const PluginPacket& pkt, unsigned mask) const {
136  unsigned enable = (SATCAT5_SWITCH_DEBUG) & mask;
137  if (m_debug && enable) {
138  MultiPacket::Reader rd(pkt.pkt); // Read from start of packet.
139  if (pkt.is_adjusted()) {
140  rd.read_consume(pkt.hlen); // Skip original header.
141  pkt.write_to(m_debug); // Write modified header.
142  }
143  rd.copy_and_finalize(m_debug); // Copy remaining data.
144  }
145 }
146 
147 void SwitchCore::debug_log(const MultiPacket* pkt, u8 reason, SATCAT5_PMASK_TYPE dst) const {
148  Header hdr = satcat5::eth::HEADER_NULL;
149  u8 src_port = 255;
150  if (m_pktlogs.head()) {
151  // If possible, read frame header and metadata.
152  // See also: SwitchPort::write_finalize().
153  if (pkt) {
154  auto rd = pkt->peek();
155  hdr.read_from(&rd);
156  src_port = u8(pkt->m_user[0]);
157  }
158 
159  // Construct a KEEP or DROP message.
161  if (reason == SwitchLogMessage::REASON_KEEP) {
162  msg.init_keep(hdr, src_port, dst);
163  } else {
164  msg.init_drop(hdr, src_port, reason);
165  }
166 
167  // Deliver the message to each logging object.
168  satcat5::eth::SwitchLogHandler* item = m_pktlogs.head();
169  while (item) {
170  item->log_packet(msg);
171  item = m_pktlogs.next(item);
172  }
173  }
174 }
175 
177  // Optional carbon-copy to debug port.
178  debug_if(meta, DEBUG_INGRESS);
179 
180  // The main packet counter may be filtered by EtherType.
181  if ((!m_stats_filter) || (m_stats_filter == meta.hdr.type.value)) {
182  ++m_stats_count;
183  }
184  // TODO: Additional statistics and diagnostics?
185 }
186 
188  // Optional carbon-copy to debug port.
189  debug_if(meta, DEBUG_PLUGIN);
190 
191  // Identify the source port and query each port plugin.
192  // Stop early if any plugin drops or diverts the packet.
193  SwitchPort* src = get_port(meta.src_port());
194  if (src) src->plugin_ingress(meta);
195  auto result = pkt_has_dropped(meta, DEBUG_PLUGOUT);
196  if (result) return result;
197 
198  // Query each switch plugin. This may affect packet data and metadata.
199  // Stop early if any plugin drops or diverts the packet.
200  PluginCore* plg = m_plugins.head();
201  while (plg) {
202  plg->query(meta);
203  result = pkt_has_dropped(meta, DEBUG_PLUGOUT);
204  if (result) return result;
205  plg = m_plugins.next(plg);
206  }
207 
208  // In-place buffer overwrite of the modified packet headers?
209  // This method can't tolerate length changes; sound alarm if needed.
210  if (meta.is_adjusted()) {
211  MultiPacket::Overwriter wr(meta.pkt);
212  meta.write_to(&wr);
213  if (wr.write_count() != meta.hlen) {
214  Log(CRITICAL, "Plugin changed header length.");
216  return opt_uint(0); // Discard this packet
217  }
218  }
219 
220  // Optional carbon-copy to debug port.
221  debug_if(meta, DEBUG_PLUGOUT);
222 
223  // Optional diagnostic logging.
224  if (DEBUG_VERBOSE > 1) {
225  auto peek = meta.pkt->peek();
226  Log(DEBUG, "SwitchCore::deliver")
227  .write("\r\n Mask").write(meta.dst_mask)
228  .write("\r\n Data").write(&peek);
229  }
230  return opt_uint();
231 }
232 
234  if (meta.dst_mask == 0) {
235  u8 reason = meta.reason()
237  debug_if(meta, mask); // Carbon-copy if logging is enabled
238  debug_log(meta.pkt, reason);
239  return opt_uint(0); // Dropped
240  } else if (meta.is_diverted()) {
242  return opt_uint(1); // Diverted
243  } else {
244  return opt_uint(); // Success
245  }
246 }
247 
249  // Attempt to deliver the packet to each destination port.
250  unsigned count = 0;
251  SwitchPort* port = m_ports.head();
252  while (port) {
253  if (port->accept(meta.dst_mask, meta.pkt)) ++count;
254  port = m_ports.next(port);
255  }
256 
257  // Optional carbon-copy to debug port.
258  debug_if(meta, DEBUG_DELIVERY);
260 
261  // Optional diagnostic logging.
262  if (DEBUG_VERBOSE > 0)
263  Log(DEBUG, "SwitchCore: Rcvd").write(meta.dst_mask).write10((u32)count);
264  return count;
265 }
266 
268  : MultiWriter(sw)
269  , m_switch(sw)
270  , m_port_mask(sw->next_port_mask())
271  , m_port_index(satcat5::util::log2_floor(m_port_mask))
272  , m_vlan_cfg(satcat5::eth::VCFG_DEFAULT)
273  , m_egress(sw)
274  , m_eg_dst(dst)
275  , m_eg_hdr(false)
276  , m_next(0)
277 {
278  // Sanity check: Is the SwitchCore out of unique port masks?
279  if (m_port_mask) m_switch->port_add(this);
280  else Log(CRITICAL, "SwitchPort overflow");
281  // Are we the callback for processing egress data?
282  if (m_eg_dst) m_egress.set_callback(this);
283 }
284 
285 #if SATCAT5_ALLOW_DELETION
286 SwitchPort::~SwitchPort() {
287  m_switch->port_remove(this);
288 }
289 #endif
290 
292  return (m_port_mask & dst_mask) && m_egress.accept(packet);
293 }
294 
296  { m_plugins.add(plugin); }
298  { m_plugins.remove(plugin); }
299 
301  // Optional diagnostic logging.
302  if (DEBUG_VERBOSE > 1) Log(DEBUG, "SwitchPort::plugin_ingress");
303 
304  // Query each port plugin. This may affect packet data and metadata.
305  // Stop early if any plugin signals that it has diverted the packet.
306  PluginPort* plg = m_plugins.head();
307  while (plg) {
308  plg->ingress(meta);
309  if (meta.dst_mask == 0) return;
310  if (meta.is_diverted()) return;
311  plg = m_plugins.next(plg);
312  }
313 }
314 
316  // Optional diagnostic logging.
317  if (DEBUG_VERBOSE > 1) Log(DEBUG, "SwitchPort::plugin_egress");
318 
319  // Query each port plugin. This may affect packet data and metadata.
320  // Stop early if any plugin signals that it has diverted the packet.
321  PluginPort* plg = m_plugins.head();
322  while (plg) {
323  plg->egress(meta);
324  if (m_switch->pkt_has_dropped(meta, DEBUG_EGRESS)) return;
325  plg = m_plugins.next(plg);
326  }
327 }
328 
330  // Log the CRC/PHY error, then flush buffer contents.
332  MultiWriter::write_abort();
333 }
334 
336  // Use the "m_user" field to store some packet metadata.
337  // Note: Using "m_port_index" only works for single-port objects.
338  // Children with multiple ports MUST override write_finalize().
339  if (m_write_pkt) {
340  static_assert(SATCAT5_MBUFF_USER >= 2,
341  "SATCAT5_MBUFF_USER must be at least 2.");
342  m_write_pkt->m_user[0] = u32(m_port_index);
343  m_write_pkt->m_user[1] = u32(m_vlan_cfg.value);
344  }
345 
346  // Attempt delivery of the packet.
347  if (!m_egress.get_port_enable()) {
348  // Dropped (port disabled)
350  } else if (MultiWriter::write_finalize()) {
351  // Delivered to switch
352  return true;
353  } else {
354  // Dropped (overflow)
356  }
357 
358  // Cleanup after any delivery failure.
359  MultiWriter::write_abort();
360  return false;
361 }
362 
363 // This method is called whenever this port has pending output data.
364 // We need to read the original contents, modify the VLAN tag, then
365 // copy the modified data to the designated Writeable sink. If the
366 // sink cannot accept the entire packet at once, resume work when
367 // data_rcvd() is called again during the next polling interval.
369  if (DEBUG_VERBOSE > 0) Log(DEBUG, "SwitchPort::data_rcvd");
370 
371  // Start of frame only: Read and modify the packet header.
372  if (!m_eg_hdr) {
373  // Proceed only if we can read/modify/write the entire frame header.
374  // (This includes Ethernet header, and may include VTAG/IP/ARP.)
375  PluginPacket pkt{};
376  if (m_eg_dst->get_write_space() < SATCAT5_MBUFF_CHUNK) return;
377  if (pkt.read_from(m_egress.get_packet())) {
378  // Header OK, proceed with egress processing...
379  plugin_egress(pkt);
380  if (pkt.dst_mask == 0) {
381  m_egress.read_finalize(); // Packet dropped?
382  return;
383  } else if (pkt.is_adjusted()) {
384  m_egress.read_consume(pkt.hlen);
385  pkt.write_to(m_eg_dst); // Write new header?
386  }
387  m_eg_hdr = true; // Copy remaining data.
388  // Optional carbon-copy to debug port.
389  m_switch->debug_if(pkt, DEBUG_EGRESS);
390  } else {
391  // Error reading packet, discard.
393  return;
394  }
395  }
396 
397  // Everything after the frame header is a one-for-one copy.
398  // Once finished, call finalize and get ready for the next frame.
400  if (!m_egress.get_read_ready()) {
401  if (DEBUG_VERBOSE > 1) Log(DEBUG, "VlanAdapter::data_rcvd::fin");
404  m_eg_hdr = false;
405  }
406 }
407 
Ethernet switch plugin API.
Definition: eth_plugin.h:166
virtual void query(PluginPacket &pkt)=0
Packet-received callback.
Ethernet port plugin API.
Definition: eth_plugin.h:198
virtual void egress(PluginPacket &pkt)
Packet-transmit callback.
Definition: eth_plugin.h:215
virtual void ingress(PluginPacket &pkt)
Packet-received callback.
Definition: eth_plugin.h:207
A shared-memory Ethernet switch based on the MultiBuffer class.
Definition: eth_switch.h:93
satcat5::util::List< satcat5::eth::SwitchPort > m_ports
Linked list of attached Ethernet ports.
Definition: eth_switch.h:190
unsigned deliver(satcat5::io::MultiPacket *packet) override
Override the MultiBuffer::deliver() method.
Definition: eth_switch.cc:110
unsigned deliver_switch(const satcat5::eth::PluginPacket &pkt)
Internal event-handlers called from deliver(...).
Definition: eth_switch.cc:248
satcat5::util::optional< unsigned > process_plugins(satcat5::eth::PluginPacket &pkt)
Internal event-handlers called from deliver(...).
Definition: eth_switch.cc:187
satcat5::util::List< satcat5::eth::PluginCore > m_plugins
Linked list of attached plugins.
Definition: eth_switch.h:187
satcat5::util::optional< unsigned > pkt_has_dropped(satcat5::eth::PluginPacket &pkt, unsigned mask)
Internal event-handlers called from deliver(...).
Definition: eth_switch.cc:233
void set_promiscuous(unsigned port_idx, bool enable)
Enable or disable "promiscuous" flag on the specified port index.
Definition: eth_switch.cc:94
u32 get_traffic_count()
Query traffic statistics Count received frames that match the current traffic filter,...
Definition: eth_switch.cc:104
satcat5::eth::SwitchPort * get_port(unsigned idx)
Fetch a SwitchPort object by port-index.
Definition: eth_switch.h:101
void process_stats(const satcat5::eth::PluginPacket &pkt)
Internal event-handlers called from deliver(...).
Definition: eth_switch.cc:176
SATCAT5_PMASK_TYPE next_port_mask()
Get next available bit-mask for new SwitchPort objects.
Definition: eth_switch.cc:66
void debug_log(const satcat5::io::MultiPacket *pkt, u8 reason, SATCAT5_PMASK_TYPE dst=0) const
If logging is enabled, record the outcome for this packet.
Definition: eth_switch.cc:147
void debug_if(const satcat5::eth::PluginPacket &pkt, unsigned mask) const
Carbon-copy a packet to the debug port, if it is enabled.
Definition: eth_switch.cc:135
void set_traffic_filter(u16 etype=0)
Configure EtherType filter for traffic reporting. (0 = Any type)
Definition: eth_switch.cc:99
Define the API for packet-logging callbacks from eth::SwitchCore.
Definition: eth_switch.h:64
virtual void log_packet(const satcat5::eth::SwitchLogMessage &msg)=0
This method is called exactly once for each incoming packet.
Generic packetized I/O interface for use with SwitchCore.
Definition: eth_switch.h:245
const unsigned m_port_index
Define this port's connection to the switch.
Definition: eth_switch.h:340
void plugin_ingress(satcat5::eth::PluginPacket &pkt)
Issue notifications to all attached plugins.
Definition: eth_switch.cc:300
bool write_finalize() override
Override write_finalize() to store metadata.
Definition: eth_switch.cc:335
SATCAT5_PMASK_TYPE m_port_mask
Define this port's connection to the switch.
Definition: eth_switch.h:339
void write_abort() override
Override write_abort() to allow additional error handling.
Definition: eth_switch.cc:329
void data_rcvd(satcat5::io::Readable *src) override
The data_rcvd() callback is polled whenever data is available.
Definition: eth_switch.cc:368
void plugin_remove(satcat5::eth::PluginPort *plugin)
Plugin management.
Definition: eth_switch.cc:297
bool m_eg_hdr
Frame header copied?
Definition: eth_switch.h:350
satcat5::eth::VtagPolicy m_vlan_cfg
Metadata required for VLAN functionality.
Definition: eth_switch.h:344
satcat5::eth::SwitchCore *const m_switch
Pointer to the associated switch.
Definition: eth_switch.h:332
satcat5::util::List< satcat5::eth::PluginPort > m_plugins
Linked list of attached plugins.
Definition: eth_switch.h:353
satcat5::io::Writeable *const m_eg_dst
Egress destination.
Definition: eth_switch.h:349
SwitchPort(satcat5::eth::SwitchCore *sw, satcat5::io::Writeable *dst)
Link this port to the designated switch.
Definition: eth_switch.cc:267
void plugin_add(satcat5::eth::PluginPort *plugin)
Plugin management.
Definition: eth_switch.cc:295
satcat5::io::MultiReaderPriority m_egress
Egress data source.
Definition: eth_switch.h:348
void plugin_egress(satcat5::eth::PluginPacket &pkt)
Issue notifications to all attached plugins.
Definition: eth_switch.cc:315
bool accept(SATCAT5_PMASK_TYPE dst_mask, satcat5::io::MultiPacket *packet)
Accept delivery of a given packet?
Definition: eth_switch.cc:291
unsigned get_read_ready() const override
How many bytes can be read without blocking?
Definition: multi_buffer.cc:63
bool read_consume(unsigned nbytes) override
Read and discard 0 or more bytes.
Definition: multi_buffer.cc:88
satcat5::io::MultiPacket * get_packet() const
Get a pointer to the current packet, if active.
Definition: multi_buffer.h:135
virtual bool accept(satcat5::io::MultiPacket *packet)
Accept a packet from the source buffer? Default accepts all packets unless this port is disabled or f...
void read_finalize() override
Consume any remaining bytes in this frame, if applicable.
bool get_port_enable() const
Is this port currently enabled?
Definition: multi_buffer.h:278
satcat5::io::MultiPacket * m_write_pkt
Current write state.
Definition: multi_buffer.h:469
Abstract API for reading byte-streams and packets.
Definition: io_readable.h:68
virtual void set_callback(satcat5::io::EventListener *callback)
Update registered callback for data_rcvd() events.
Definition: io_readable.cc:39
unsigned copy_to(satcat5::io::Writeable *dst)
Copy data to a Writeable object, without finalizing.
Definition: io_readable.cc:214
Abstract API for writing byte-streams and packets.
Definition: io_writeable.h:24
virtual unsigned get_write_space() const =0
How many bytes can be written without blocking?
virtual bool write_finalize()
Mark end of frame and release temporary working data.
The Log class creates and formats one log message.
Definition: log.h:195
Log & write10(s32 val)
Print integer as a decimal value with no leading zeros.
Definition: log.cc:261
Log & write(const char *str)
Formatting methods for various data types.
Definition: log.cc:198
T * next(const T *item) const
Fetch pointer to the next item.
Definition: list.h:261
void push_back(T *item)
Add a new item at the tail of the list.
Definition: list.h:273
void add(T *item)
Add new item to front or back, whichever is simpler.
Definition: list.h:221
void remove(T *item)
Remove the designated item from the list.
Definition: list.h:277
Plugins for the software-defined Ethernet switch and IPv4 router.
Diagnostic logging system for the Ethernet switch.
Software-defined Ethernet switch.
#define SATCAT5_PMASK_TYPE
Set the integer type used to identify source and destination ports.
Definition: eth_switch.h:41
constexpr SATCAT5_PMASK_TYPE idx2mask(unsigned idx)
Macro converting port-index to a bit-mask.
Definition: eth_switch.h:53
constexpr SATCAT5_PMASK_TYPE PMASK_ALL(-1)
Global port-mask indicating every port (i.e., broadcast).
Diagnostic logging to UART and/or Ethernet ports.
constexpr s8 CRITICAL
Define basic priority codes for log messages.
Definition: log.h:113
constexpr s8 DEBUG
Define basic priority codes for log messages.
Definition: log.h:109
An Ethernet frame header.
Definition: eth_header.h:167
satcat5::eth::MacType type
Ethernet header fields.
Definition: eth_header.h:172
bool read_from(satcat5::io::Readable *rd)
Read Ethernet header from the designated stream.
Definition: eth_header.cc:128
u16 value
The 16-bit value is stored in processor-native order.
Definition: eth_header.h:98
Ephemeral data structure provided to plugin callbacks.
Definition: eth_plugin.h:48
satcat5::io::MultiPacket * pkt
Complete packet contents.
Definition: eth_plugin.h:51
u8 reason() const
Accessors and shortcuts for packet metadata.
Definition: eth_plugin.h:145
unsigned src_port() const
Accessors and shortcuts for packet metadata.
Definition: eth_plugin.h:141
u16 hlen
Original header length.
Definition: eth_plugin.h:78
bool is_adjusted() const
Accessors and shortcuts for packet metadata.
Definition: eth_plugin.h:125
SATCAT5_PMASK_TYPE dst_mask
Destination mask for which port(s) receive this packet.
Definition: eth_plugin.h:75
bool is_diverted() const
Accessors and shortcuts for packet metadata.
Definition: eth_plugin.h:127
satcat5::eth::Header hdr
Copy of Ethernet header fields.
Definition: eth_plugin.h:55
void write_to(satcat5::io::Writeable *wr) const
Copy packet headers to the specified destination.
Definition: eth_plugin.cc:42
A single 24-byte packet-log message.
Definition: eth_sw_log.h:74
void init_keep(const satcat5::eth::Header &hdr, u8 src, u32 dst)
Initialize a KEEP message.
Definition: eth_sw_log.cc:73
static constexpr u8 DROP_UNKNOWN
Other unspecified error.
Definition: eth_sw_log.h:88
static constexpr u8 DROP_BADFRM
Frame length, source MAC, etc.
Definition: eth_sw_log.h:81
void init_drop(const satcat5::eth::Header &hdr, u8 src, u8 why)
Initialize a DROP message.
Definition: eth_sw_log.cc:80
static constexpr u8 DROP_BADFCS
Invalid frame check sequence.
Definition: eth_sw_log.h:80
static constexpr u8 DROP_DISABLED
Ingress or egress port disabled.
Definition: eth_sw_log.h:87
static constexpr u8 REASON_KEEP
Packet accepted / not dropped.
Definition: eth_sw_log.h:78
static constexpr u8 DROP_OVERFLOW
FIFO overflow (Rx or Tx)
Definition: eth_sw_log.h:79
u32 value
Packed value holds the tag policy, port number, and default VID.
Definition: switch_cfg.h:71
A packet is a linked-list of memory blocks, plus metadata.
Definition: multi_buffer.h:91
u32 m_user[SATCAT5_MBUFF_USER]
Packet metadata.
Definition: multi_buffer.h:107
satcat5::io::ArrayRead peek() const
Peek at the first chunk, up to SATCAT5_MBUFF_CHUNK bytes.
Definition: multi_buffer.cc:31
An optional field that may be filled or empty.
Definition: utils.h:53
constexpr satcat5::eth::VtagPolicy VCFG_DEFAULT(0, VTAG_ADMIT_ALL, VTAG_DEFAULT)
Default VLAN tagging policy.
Miscellaneous mathematical utility functions.