SatCat5
posix_utils.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 "posix_utils.h"
7 #include <satcat5/ethernet.h>
8 #include <satcat5/ip_core.h>
9 #include <satcat5/polling.h>
10 #include <satcat5/utils.h>
11 #include <cstdio>
12 #include <ctime>
13 #include <iomanip>
14 #include <sstream>
15 
16 #if SATCAT5_WIN32
17  #include <conio.h> // For kbhit(), getch()
18  #include <windows.h> // All-in-one for Win32 API
19  #undef ERROR // Deconflict Windows "ERROR" macro
20 #else
21  #include <sys/ioctl.h> // For ioctl()
22  #include <termios.h> // For getchar(), tcgetattr(), etc
23  #include <unistd.h> // For usleep()
24 #endif
25 
43 
44 std::string satcat5::io::read_str(Readable* src) {
45  std::string tmp;
46  while (src->get_read_ready())
47  tmp.push_back(src->read_u8());
48  src->read_finalize();
49  return tmp;
50 }
51 
52 BufferedTee::BufferedTee(unsigned nbytes)
53  : HeapAllocator(nbytes)
54  , ArrayWrite(m_buffptr, nbytes)
55 {
56  // Nothing else to initialize.
57 }
58 
60  // Finalize write to parent, then copy to each destination.
61  unsigned count = 0;
63  for (auto ptr = m_list.begin() ; ptr != m_list.end() ; ++ptr) {
64  (*ptr)->write_bytes(written_len(), m_buffptr);
65  if ((*ptr)->write_finalize()) ++count;
66  }
67  }
68  // Indicate success if at least one accepts the data.
69  return count > 0;
70 }
71 
72 BufferedWriterHeap::BufferedWriterHeap(Writeable* dst, unsigned nbytes)
73  : HeapAllocator(nbytes)
74  , BufferedWriter(dst, m_buffptr, nbytes, nbytes/64)
75 {
76  // Nothing else to initialize.
77 }
78 
79 KeyboardStream::KeyboardStream(Writeable* dst, bool line_buffer)
80  : m_dst(dst)
81  , m_line_buffer(line_buffer)
82 {
83 #ifdef _WIN32
84  // No initial setup for Windows (yet).
85 #else
86  // Initial setup for POSIX:
87  tcflush(0, TCIFLUSH);
88  termios term;
89  tcgetattr(0, &term);
90  term.c_lflag &= ~(ICANON | ECHO);
91  tcsetattr(0, TCSANOW, &term);
92 #endif
93 }
94 
95 KeyboardStream::~KeyboardStream() {
96 #ifdef _WIN32
97  // No cleanup for Windows (yet).
98 #else
99  // Cleanup for POSIX:
100  tcflush(0, TCIFLUSH);
101  termios term;
102  tcgetattr(0, &term);
103  term.c_lflag |= ICANON | ECHO;
104  tcsetattr(0, TCSANOW, &term);
105 #endif
106 }
107 
109  // If there's any characters in the queue, copy them.
110 #ifdef _WIN32
111  while (_kbhit()) {
112  write_key(_getch());
113  }
114 #else
115  int byteswaiting;
116  while (1) {
117  ioctl(0, FIONREAD, &byteswaiting);
118  if (byteswaiting < 1) break;
119  write_key(getchar());
120  }
121 #endif
122 }
123 
124 void KeyboardStream::write_key(int ch) {
125  if (m_line_buffer && (ch == '\r' || ch == '\n')) {
126  m_dst->write_finalize(); // EOL flushes input
127  } else if (0 < ch && ch < 128) {
128  m_dst->write_u8(ch); // Forward "normal" keys
129  if (!m_line_buffer) m_dst->write_finalize();
130  }
131 }
132 
133 ToConsole::ToConsole(s8 threshold)
134  : m_threshold(threshold)
135  , m_last_msg()
136  , m_tref(m_timer.now())
137 {
138  // Nothing else to initialize.
139 }
140 
141 bool ToConsole::contains(const char* msg) {
142  return (m_last_msg.find(msg) != std::string::npos);
143 }
144 
145 void ToConsole::suppress(const char* msg) {
146  if (msg) {
147  m_suppress.push_back(std::string(msg));
148  } else {
149  m_suppress.clear();
150  }
151 }
152 
153 void ToConsole::log_event(s8 priority, unsigned nbytes, const char* msg) {
154  // Always store the most recent log-message.
155  m_last_msg = std::string(msg, msg+nbytes);
156 
157  // Don't display anything below designated priority threshold.
158  if (priority < m_threshold) return;
159 
160  // Don't display the message if it matches any saved filter.
161  for (auto filter = m_suppress.begin() ; filter != m_suppress.end() ; ++filter) {
162  if (m_last_msg.find(*filter) != std::string::npos) return;
163  }
164 
165  // Timestamp = Milliseconds since creation of this object.
166  unsigned now = m_tref.elapsed_msec() % 10000;
167 
168  // Print human-readable message to either STDERR or STDOUT.
169  if (priority >= satcat5::log::ERROR) {
170  fprintf(stderr, "Log (ERROR) @%04u: %s\n", now, msg);
171  } else if (priority >= satcat5::log::WARNING) {
172  fprintf(stdout, "Log (WARN) @%04u: %s\n", now, msg);
173  } else if (priority >= satcat5::log::INFO) {
174  fprintf(stdout, "Log (INFO) @%04u: %s\n", now, msg);
175  } else {
176  fprintf(stdout, "Log (DEBUG) @%04u: %s\n", now, msg);
177  }
178 }
179 
180 MultiBufferHeap::MultiBufferHeap(unsigned nbytes)
181  : HeapAllocator(nbytes)
182  , MultiBuffer(m_buffptr, nbytes)
183 {
184  // Nothing else to initialize
185 }
186 
187 PacketBufferHeap::PacketBufferHeap(unsigned nbytes)
188  : HeapAllocator(nbytes)
189  , PacketBuffer(m_buffptr, nbytes, nbytes/64)
190 {
191  // Nothing else to initialize
192 }
193 
194 PacketBufferTee::PacketBufferTee(unsigned nbytes)
195  : ReadableRedirect(&m_buff)
196  , m_buff(nbytes)
197 {
198  add(&m_buff);
199 }
200 
201 StreamBufferHeap::StreamBufferHeap(unsigned nbytes)
202  : HeapAllocator(nbytes)
203  , PacketBuffer(m_buffptr, nbytes, 0)
204 {
205  // Nothing else to initialize
206 }
207 
208 StreamBufferTee::StreamBufferTee(unsigned nbytes)
209  : ReadableRedirect(&m_buff)
210  , m_buff(nbytes)
211 {
212  add(&m_buff);
213 }
214 
215 PosixTimer::PosixTimer()
216  : TimeRef(1000000) // 1 tick = 1 usec
217 {
218  // Nothing else to initialize.
219 }
220 
221 u32 PosixTimer::raw() {
222  struct timespec tv;
223  int errcode = clock_gettime(CLOCK_MONOTONIC, &tv);
224  if (errcode) {
225  // Fallback to clock() function, usually millisecond resolution.
226  const unsigned SCALE = 1000000 / CLOCKS_PER_SEC;
227  return u32(clock() * SCALE);
228  } else {
229  // Higher resolution using clock_gettime(), if available.
230  u32 usec1 = (u32)(tv.tv_sec * 1000000);
231  u32 usec2 = (u32)(tv.tv_nsec / 1000);
232  return usec1 + usec2;
233  }
234 }
235 
236 s64 PosixTimer::gps() const {
237  // Get the POSIX timestamp (sorta-kinda-UTC).
238  // See also: http://www.madore.org/~david/computers/unix-leap-seconds.html
239  struct timespec tv;
240  int errcode = clock_gettime(CLOCK_REALTIME, &tv);
241  if (errcode) return 0;
242  s64 msec1 = s64(tv.tv_sec) * 1000;
243  s64 msec2 = s64(tv.tv_nsec) / 1000000;
244  // Assume this code is being run 2017 or later, so the number of
245  // cumulative leap-seconds is fixed for the foreseeable future.
246  // TODO: Keep this up-to-date if/when leap-seconds resume.
247  // See also: https://stackoverflow.com/questions/16539436/
248  // See also: https://stackoverflow.com/questions/20521750/
249  constexpr s64 GPS_EPOCH = (1000LL) * (315964800 - 18);
250  return msec1 + msec2 - GPS_EPOCH;
251 }
252 
253 PosixTimekeeper::PosixTimekeeper()
254  : m_timer()
255  , m_adapter(&timekeeper)
256 {
257  timekeeper.set_clock(&m_timer);
258 }
259 
260 PosixTimekeeper::~PosixTimekeeper()
261 {
262  timekeeper.set_clock(0);
263 }
264 
265 SwitchCoreHeap::SwitchCoreHeap(unsigned nbytes)
266  : HeapAllocator(nbytes)
267  , SwitchCore(m_buffptr, nbytes)
268 {
269  // Nothing else to initialize.
270 }
271 
272 void satcat5::util::sleep_msec(unsigned msec) {
273 #ifdef _WIN32
274  Sleep(msec);
275 #else
276  usleep(msec * 1000);
277 #endif
278 }
279 
280 void satcat5::util::service_msec(unsigned total_msec, unsigned msec_per_iter) {
281  PosixTimer timer;
282  auto tref = timer.checkpoint_msec(total_msec);
283  while (1) {
284  poll::service_all();
285  if (tref.checkpoint_elapsed()) break;
286  satcat5::util::sleep_msec(msec_per_iter);
287  }
288 }
289 
290 std::string satcat5::log::format(const satcat5::eth::MacAddr& addr) {
291  char tmp[32];
292  snprintf(tmp, sizeof(tmp),
293  "%02X:%02X:%02X:%02X:%02X:%02X",
294  addr.addr[0], addr.addr[1], addr.addr[2],
295  addr.addr[3], addr.addr[4], addr.addr[5]);
296  return std::string(tmp);
297 }
298 
299 std::string satcat5::log::format(const satcat5::ip::Addr& addr) {
300  // Extract individual byte fields from IPv4 address.
301  u8 addr_bytes[4];
302  write_be_u32(addr_bytes, addr.value);
303  // Format using conventional format (e.g., "127.0.0.1")
304  std::stringstream tmp;
305  tmp << (unsigned)addr_bytes[0] << "."
306  << (unsigned)addr_bytes[1] << "."
307  << (unsigned)addr_bytes[2] << "."
308  << (unsigned)addr_bytes[3];
309  return tmp.str();
310 }
Heap-allocated variant of eth::SwitchCore.
Definition: posix_utils.h:60
Ephemeral Writeable interface for a simple array.
Definition: io_writeable.h:127
unsigned written_len() const
Report total length after write_finalize() is called.
Definition: io_writeable.h:151
bool write_finalize() override
Mark end of frame and release temporary working data.
BufferedTee copies incoming data to any number of destinations.
Definition: posix_utils.h:73
bool write_finalize() override
Override end-of-packet handling.
Definition: posix_utils.cc:59
BufferedWriter with heap allocation.
Definition: posix_utils.h:94
BufferedWriter adds an inline buffer to any Writeable interface.
Definition: io_buffer.h:187
Stream keyboard input to a Writeable interface.
Definition: posix_utils.h:102
void poll_always() override
Child class MUST override this method.
Definition: posix_utils.cc:108
MultiBuffer with heap allocation.
Definition: posix_utils.h:119
PacketBuffer with heap allocation (in packet mode).
Definition: posix_utils.h:128
PacketBuffer with optional wiretap(s).
Definition: posix_utils.h:138
Abstract API for reading byte-streams and packets.
Definition: io_readable.h:68
u8 read_u8()
One of many functions for reading integer/floating point values, see details.
Definition: io_readable.cc:44
virtual void read_finalize()
Consume any remaining bytes in this frame, if applicable.
Definition: io_readable.cc:270
virtual unsigned get_read_ready() const =0
How many bytes can be read without blocking?
PacketBuffer with heap allocation (in stream mode).
Definition: posix_utils.h:150
PacketBuffer with optional wiretap(s).
Definition: posix_utils.h:160
Abstract API for writing byte-streams and packets.
Definition: io_writeable.h:24
void write_u8(u8 data)
One of many functions for writing integer/floating point values, see details.
Definition: io_writeable.cc:20
virtual bool write_finalize()
Mark end of frame and release temporary working data.
Helper object that prints log::Log messages to console.
Definition: posix_utils.h:213
std::string m_last_msg
Most recent message (ignores threshold)
Definition: posix_utils.h:235
void suppress(const char *msg)
Suppress messages containing a specific string.
Definition: posix_utils.cc:145
void log_event(s8 priority, unsigned nbytes, const char *msg) override
Callback for each formatted Log message.
Definition: posix_utils.cc:153
bool contains(const char *msg)
Does the last logged message contain the provided substring?
Definition: posix_utils.cc:141
s8 m_threshold
Print only if priority >= threshold.
Definition: posix_utils.h:234
void set_clock(satcat5::util::TimeRef *timer)
Immediately set the system time reference.
Definition: polling.cc:250
u8 *const m_buffptr
Pointer to the underlying buffer.
Definition: posix_utils.h:52
Link a PosixTimer to the main polling timekeeper.
Definition: posix_utils.h:181
Timer object using ctime::clock().
Definition: posix_utils.h:172
constexpr s8 WARNING
Define basic priority codes for log messages.
Definition: log.h:111
constexpr s8 ERROR
Define basic priority codes for log messages.
Definition: log.h:112
constexpr s8 INFO
Define basic priority codes for log messages.
Definition: log.h:110
Core event-processing loop for SatCat5 software.
Timekeeper timekeeper
There is a single global instance of the Timekeeper class.
Definition: polling.cc:50
Miscellaneous POSIX wrappers (e.g., heap allocation, log to console...)
std::string format(const satcat5::eth::MacAddr &addr)
Human-readable formatting for an Ethernet address.
Definition: posix_utils.cc:290
std::string read_str(satcat5::io::Readable *src)
Read contents of a SatCat5 buffer as a string.
Definition: posix_utils.cc:44
void sleep_msec(unsigned msec)
Cross-platform wrapper for sleep()/Sleep()/etc.
Definition: posix_utils.cc:272
void service_msec(unsigned total_msec, unsigned msec_per_iter=10)
Alternate between sleep_msec() and poll::service_all().
Definition: posix_utils.cc:280
An Ethernet MAC address (with serializable interface).
Definition: eth_header.h:29
u8 addr[6]
Byte array in network order (Index 0 = MSB)
Definition: eth_header.h:31
IPv4 address is a 32-bit unsigned integer.
Definition: ip_core.h:15
u32 value
Raw access to the underlying representation.
Definition: ip_core.h:17
Timestamp for measuring elapsed time.
Definition: timeref.h:48
unsigned elapsed_msec() const
Elapsed time in milliseconds.
Definition: timeref.cc:29
Miscellaneous mathematical utility functions.
void write_be_u32(u8 *dst, u32 val)
Store fields into a big-endian byte array.
Definition: utils.cc:177