SatCat5
coap_connection.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/coap_connection.h>
7 #include <satcat5/coap_endpoint.h>
8 #include <satcat5/coap_reader.h>
9 #include <satcat5/coap_writer.h>
10 #include <satcat5/log.h>
11 #include <satcat5/udp_dispatch.h>
12 
21 namespace log = satcat5::log;
22 
23 // Set verbosity level (0/1/2)
24 static const unsigned DEBUG_VERBOSE = 0;
25 
26 // Define fields for the "m_flags" variable.
27 constexpr u8 FLAG_TKL = 0x0F; // LSBs = token length
28 constexpr u8 FLAG_SEP = 0x10; // Separated response?
29 constexpr u8 FLAG_CON = 0x20; // Confirmable request
30 
31 // Set safe or aggressive transmission parameters?
32 // * Fast (1) = Aggressively optimized for less-constrained networks.
33 // * Safe (0) = Within limits from Section 4.8 and 4.8.2.
34 #ifndef SATCAT5_COAP_FAST
35 #define SATCAT5_COAP_FAST 1
36 #endif
37 
38 #if SATCAT5_COAP_FAST
39  // Aggressively optimized for less-constrained networks.
40  // Note: Listed timeouts are for first attempt only.
41  // Maximum timeout is ACK_TIMEOUT_MSEC * 2^(MAX_RETRANSMIT-1)
42  constexpr unsigned ACK_TIMEOUT_MSEC = 125;
43  constexpr unsigned MAX_LEISURE_MSEC = 500;
44  constexpr unsigned PROBE_TIMEOUT_MSEC = 1000;
45  constexpr unsigned MAX_RETRANSMIT = 6;
46 #else
47  // Within safe limits from Section 4.8 and 4.8.2.
48  constexpr unsigned ACK_TIMEOUT_MSEC = 1000;
49  constexpr unsigned MAX_LEISURE_MSEC = 2000;
50  constexpr unsigned PROBE_TIMEOUT_MSEC = 3000;
51  constexpr unsigned MAX_RETRANSMIT = 5;
52 #endif
53 
54 // Derived constants from above parameters:
55 constexpr unsigned MAX_TRANSMIT_SPAN
56  = (ACK_TIMEOUT_MSEC * (1u << MAX_RETRANSMIT) * 3) / 2;
57 constexpr unsigned MAX_SEPARATE_SPAN
58  = (MAX_TRANSMIT_SPAN * 3) / 2;
59 
60 Connection::Connection(Endpoint* endpoint, satcat5::net::Address* addr)
61  : Protocol(satcat5::net::TYPE_NONE)
62  , m_coap(nullptr)
63  , m_addr(addr)
64  , m_state(State::IDLE)
65  , m_proxy_token(0)
66  , m_allow_reuse(1)
67  , m_tx_count(0)
68  , m_meta_idx(0)
69  , m_meta_count(0)
70  , m_flags{}
71  , m_msgid{}
72  , m_token{}
73 {
74  init(endpoint);
75 }
76 
77 #if SATCAT5_ALLOW_DELETION
78 Connection::~Connection() {
79  if (m_coap) {
80  m_coap->remove_connection(this);
81  if (m_filter.as_u32()) {
82  m_coap->iface()->remove(this);
83  }
84  }
85 }
86 #endif
87 
88 void Connection::init(Endpoint* endpoint) {
89  if (endpoint && !m_coap) {
90  m_coap = endpoint;
91  m_coap->add_connection(this);
92  }
93 }
94 
95 bool Connection::is_match_coap(const ReadHeader* msg) const {
96  // Idle state can never match anything.
97  if (m_state == State::IDLE) return false;
98  // Check message-ID match (mid) and token match (tok).
99  bool mid = (msg->msg_id() == msg_id());
100  bool tok = (msg->tkl() == tkl() && msg->token() == token());
101  // Is this the start of a separated response?
102  bool sep = (is_request() && msg->type() == TYPE_CON);
103  // Request/response matching rules (Section 5.3.2):
104  if (msg->code() == CODE_EMPTY) {
105  // Empty messages omit the token, comparing message ID only.
106  return mid;
107  } else {
108  // Separated messages may have same token, different ID.
109  // All others should match both token and ID.
110  return tok && (mid || sep);
111  }
112 }
113 
115  if (m_coap && m_filter.as_u32()) m_coap->iface()->remove(this);
116  m_filter = satcat5::net::TYPE_NONE;
117  m_addr->close();
118  reset_hard();
119  m_allow_reuse = 1;
120 }
121 
122 // Event-handler for the child's connect(...) method.
123 bool Connection::connected(bool allow_reuse) {
124  // Set the flag to allow or prevent automatic reuse of idle connections.
125  // (Manual connections may want to remain open until explicitly closed.)
126  m_allow_reuse = allow_reuse ? 1 : 0;
127  // If required, set a timeout to retry connection (e.g., ARP query).
128  if (!m_addr->ready()) {
129  ++m_tx_count;
130  m_state = State::CONNECT_IDLE;
131  timer_rand(ACK_TIMEOUT_MSEC);
132  }
133  return true;
134 }
135 
136 bool Connection::ping(u16 msg_id) {
137  // Are we in a state that can send a ping?
138  if (DEBUG_VERBOSE > 0) log::Log(log::DEBUG, "CoAP: Ping");
139  return ready() && send_empty(TYPE_CON, msg_id);
140 }
141 
142 bool Connection::ready() const {
143  // Are we in a state that can send a new request?
144  if (m_state == State::CONNECT_IDLE) return true;
145  return (m_state == State::IDLE) && m_addr->ready();
146 }
147 
149  // Are we in a state that can send a new request?
150  if (DEBUG_VERBOSE > 1) log::Log(log::DEBUG, "CoAP: open_request");
151  if (!ready()) return 0; // Are we in idle or pseudo-idle state?
152  write_abort(); // Flush leftovers in buffer.
153  return this; // Wait for user to call write_finalize().
154 }
155 
157  // Are we in a state that can send a response?
158  if (DEBUG_VERBOSE > 1) log::Log(log::DEBUG, "CoAP: open_response");
159  if (!is_await()) return 0;
160  write_abort(); // Flush leftovers in buffer.
161  return this; // Wait for user to call write_finalize().
162 }
163 
165  // Are we in a state that can send a separated response?
166  if (DEBUG_VERBOSE > 1) log::Log(log::DEBUG, "CoAP: open_separate");
167  if (msg->type() != TYPE_CON) return false;
169  if (!wr.ready()) return false;
170  // Write an empty ACK message to the main working buffer.
171  // Note: Do not echo request token (Section 3).
172  wr.write_header(TYPE_ACK, CODE_EMPTY, msg->msg_id());
173  return wr.write_finalize();
174 }
175 
177  // Are we in a state that can continue a separated response?
178  if (DEBUG_VERBOSE > 1) log::Log(log::DEBUG, "CoAP: continue_separate");
179  if (m_state != State::RESPONSE_SEP1) return 0;
180  write_abort(); // Flush leftovers in buffer.
181  return this; // Ready to continue response.
182 }
183 
184 bool Connection::error_response(Code code, const char* why) {
185  // Are we in a state that can send a response? Are given inputs valid?
186  if (DEBUG_VERBOSE > 0) log::Log(log::DEBUG, "CoAP: Returning error");
187  if (!is_await()) return false;
188  if (!code.is_error()) return false;
189  write_abort(); // Flush leftovers in buffer.
190 
191  // Create a reply message of type ACK/NON raising an error
193  if (!wr.ready()) return false;
194  wr.write_header(response_type(), code, msg_id(), token(), tkl());
195  if (why) {
197  wr.write_data()->write_str(why);
198  }
199  return wr.write_finalize();
200 }
201 
203  if (is_separate()) return TYPE_CON;
204  u8 flag_con = m_flags[m_meta_idx] & FLAG_CON;
205  return flag_con ? TYPE_ACK : TYPE_NON;
206 }
207 
208 bool Connection::test_inject(unsigned len, const void* data) {
209  // Test only. Not intended for use in production.
210  Writeable* wr = m_addr->open_write(len);
211  if (!wr) return false; // Unable to send?
212  wr->write_bytes(len, data);
213  return wr->write_finalize();
214 }
215 
217  // Process messages for this connection's unique port.
218  // Shared ports are handled by Endpoint::frame_rcvd().
219  // TODO: Find a way to allow user-defined option handling?
220  satcat5::coap::ReadSimple msg(&src);
221  deliver(&msg);
222 }
223 
224 // Stateful message-handling. Endpoint ensures messages are routed
225 // to the matching Connection object if applicable, so this method
226 // should not attempt to handle responses for other addresses.
227 bool Connection::deliver(Reader* msg) {
228  if (DEBUG_VERBOSE > 1) log::Log(log::DEBUG, "CoAP: deliver")
229  .write(msg->type()).write(msg->code().value).write(msg->msg_id());
230 
231  // Can we accept this message?
232  bool accept = m_coap && (is_idle() || is_match_addr());
233  if (msg->error() || !accept) return false;
234 
235  // If this is a new connection, accept it and reset history.
236  // Make the connection now, while the network stack has the reply address,
237  // in case user logic delays the callback to open_request/open_separate.
238  if (is_idle() && !is_match_addr()) {
239  reset_hard();
240  m_addr->save_reply_address();
241  }
242 
243  // Compare message-ID and token fields.
244  bool match = is_match_coap(msg);
245  if (DEBUG_VERBOSE > 0) log::Log(log::DEBUG, "CoAP: Matched")
246  .write(msg->type()).write(msg->code().value).write(msg->msg_id()).write(match);
247 
248  if (msg->type() == TYPE_CON && msg->code() == CODE_EMPTY) {
249  // CoAP ping request (Section 1.2, Section 4.3).
250  if (DEBUG_VERBOSE > 0) log::Log(log::DEBUG, "CoAP: Ping-rcvd");
251  send_empty(TYPE_RST, msg->msg_id()); // Send ping response
252  } else if (msg->type() == TYPE_RST && msg->code() == CODE_EMPTY) {
253  // CoAP ping response (Section 1.2, Section 4.3).
254  if (DEBUG_VERBOSE > 0) log::Log(log::DEBUG, "CoAP: Pong-rcvd");
255  m_coap->coap_ping(msg); // Notification for user logic
256  } else if (msg->type() == TYPE_RST) {
257  // Reset message forcibly returns connection to the idle state.
258  if (DEBUG_VERBOSE > 1) log::Log(log::DEBUG, "CoAP: rcvd-rst");
259  if (is_request()) error_event(); // Notification for user logic?
260  reset_hard(); // Hard reset of state + history
261  } else if (match && is_request()) {
262  // Response to a query that we issued?
263  if (msg->type() == TYPE_ACK && msg->code() == CODE_EMPTY) {
264  // Separate response start: Wait silently for the full response.
265  // (Timer changes from a retry loop to a transaction timeout.)
266  if (DEBUG_VERBOSE > 1) log::Log(log::DEBUG, "CoAP: rcvd-sep1");
267  m_state = State::REQUEST_SEP; // Pause the retry loop.
268  timer_once(MAX_SEPARATE_SPAN); // Set new overall timeout.
269  m_coap->coap_separate(this, msg); // Notification to parent.
270  } else if (msg->type() == TYPE_CON) {
271  // Completion of separated response.
272  // Note: This may arrive first if the ACK is lost or delayed.
273  if (DEBUG_VERBOSE > 1) log::Log(log::DEBUG, "CoAP: rcvd-sep2");
274  m_flags[m_meta_idx] |= FLAG_SEP; // Set separated flag.
275  m_coap->reply(TYPE_ACK, msg); // Send acknowledgement.
276  reset_soft(); // Return to idle state.
277  m_coap->coap_response(this, msg); // Deliver response to parent.
278  } else {
279  // Normal response. For unicast queries, return to idle state.
280  // For multicast, keep listening for more responses until timeout.
281  if (DEBUG_VERBOSE > 1) log::Log(log::DEBUG, "CoAP: rcvd-ack");
282  if (!m_addr->is_multicast()) reset_soft();
283  m_coap->coap_response(this, msg); // Deliver response to parent.
284  }
285  } else if (match && is_response()) {
286  if (msg->is_request()) {
287  // Repeated request: Retransmit cached response if applicable.
288  if (DEBUG_VERBOSE > 1) log::Log(log::DEBUG, "CoAP: rcvd-rpt1");
289  if (msg->type() == TYPE_CON) send_buffer();
290  } else if (m_state == State::RESPONSE_SEP2) {
291  // Separate response ACK: Exchange completed, return to idle.
292  if (DEBUG_VERBOSE > 1) log::Log(log::DEBUG, "CoAP: rcvd-sep3");
293  reset_soft(); // Return to idle state.
294  }
295  } else if (msg->is_request()) {
296  // Is this a fresh request? Check recent history.
297  int recent = match_history(msg);
298  if (match && is_await()) {
299  // Received a duplicate request while waiting in the "await" state.
300  // (see below). Issue a notification for unusual endpoints, such as
301  // reverse-proxies that may switch over to separated-response mode.
302  if (DEBUG_VERBOSE > 1) log::Log(log::DEBUG, "CoAP: rcvd-dupe");
303  m_coap->coap_reqwait(this, msg); // Notify user of the event
304  } else if (recent >= 0) {
305  // Stale requests are ignored, but may need to resend an ACK.
306  // (i.e., We got the separated response but the ACK was lost.)
307  if (DEBUG_VERBOSE > 1) log::Log(log::DEBUG, "CoAP: rcvd-rpt2");
308  u8 sep = m_flags[recent] & FLAG_SEP;
309  if (msg->type() == TYPE_CON && sep) m_coap->reply(TYPE_ACK, msg);
310  } else {
311  // Received a new request. Before we ask user Endpoint to respond,
312  // enter "await" state to set a watchdog timeout for that response.
313  if (DEBUG_VERBOSE > 1) log::Log(log::DEBUG, "CoAP: rcvd-req");
314  m_state = m_addr->reply_is_multicast()
315  ? State::WAIT_RESPONSE_M : State::WAIT_RESPONSE_U;
316  timer_once(MAX_TRANSMIT_SPAN); // Timeout for user response
317  push_history(msg); // Note message-ID and token.
318  // User logic must process the request and issue a response.
319  // (This may occur inside the callback or after a short delay.)
320  m_coap->coap_request(this, msg); // Notify user of the request
321  }
322  } else {
323  // Stale responses are simply discarded.
324  if (DEBUG_VERBOSE > 1) log::Log(log::DEBUG, "CoAP: rcvd-stale");
325  }
326  return true;
327 }
328 
329 void Connection::error_event() {
330  // Set ERROR state to block user callback from trying to transmit.
331  // (This call should usually be followed by a hard or soft reset.)
332  m_state = State::ERROR;
333  m_coap->coap_error(this);
334 }
335 
336 int Connection::match_history(const ReadHeader* msg) const {
337  // Is this a new ID, or does it appear in our recent history?
338  // Return matching index, or -1 if none is found.
339  for (u8 a = 0 ; a < m_meta_count ; ++a) {
340  u8 sep = m_flags[a] & FLAG_SEP;
341  u8 tkl = m_flags[a] & FLAG_TKL;
342  bool match = (msg->tkl() == tkl)
343  && (msg->token() == m_token[a])
344  && (msg->msg_id() == m_msgid[a] || sep);
345  if (match) return int(a);
346  }
347  return -1;
348 }
349 
350 void Connection::push_history(const ReadHeader* msg) {
351  // Ignore duplicate request/response.
352  if (is_match_coap(msg)) return;
353 
354  // Update the write index.
355  if (m_meta_count < SATCAT5_COAP_HISTORY) {
356  m_meta_idx = m_meta_count++; // Index lags by one
357  } else if (++m_meta_idx >= SATCAT5_COAP_HISTORY) {
358  m_meta_idx = 0; // Increment with wraparound
359  }
360 
361  // Note the new message identifiers.
362  u8 flags = msg->tkl();
363  if (msg->type() == TYPE_CON) flags |= FLAG_CON;
364  m_flags[m_meta_idx] = flags;
365  m_msgid[m_meta_idx] = msg->msg_id();
366  m_token[m_meta_idx] = msg->token();
367 }
368 
369 void Connection::reset_hard() {
370  if (DEBUG_VERBOSE > 1) log::Log(log::DEBUG, "CoAP: reset_hard");
371  // Hard reset clears history.
372  reset_soft();
373  m_meta_idx = 0;
374  m_meta_count = 0;
375  m_proxy_token = 0;
376 }
377 
378 void Connection::reset_soft() {
379  if (DEBUG_VERBOSE > 1) log::Log(log::DEBUG, "CoAP: reset_soft");
380  // Soft reset reverts to idle.
381  m_state = State::IDLE;
382  m_tx_count = 0;
383  timer_stop();
384 }
385 
386 bool Connection::send_buffer() {
387  if (DEBUG_VERBOSE > 0)
388  log::Log(log::DEBUG, "CoAP: send_buffer").write(msg_id());
389 
390  // Increment the transmit counter.
391  ++m_tx_count;
392 
393  // Attempt to send the buffer contents.
394  Writeable* wr = m_addr->open_write(written_len());
395  if (!wr) return false; // Unable to send?
396  wr->write_bytes(written_len(), buffer());
397  return wr->write_finalize();
398 }
399 
401  if (DEBUG_VERBOSE > 1)
402  log::Log(log::DEBUG, "CoAP: timer_event").write(msg_id());
403  if (!m_coap) return;
404 
405  if (is_connecting() && m_addr->ready()) {
406  // Connection ready, transmit message if one is queued.
407  if (m_state == State::CONNECT_BUSY) send_first();
408  else reset_soft();
409  } else if (is_connecting() && m_tx_count < MAX_RETRANSMIT) {
410  // Retry ARP query and set a timer for the next attempt.
411  m_addr->retry();
412  timer_rand(ACK_TIMEOUT_MSEC << m_tx_count);
413  ++m_tx_count; // Increase timeout for next time.
414  } else if (m_state == State::RESPONSE_DEFER) {
415  // Send the deferred response, then back to idle.
416  // (Multicast cache is optional per Section 8.2.1.)
417  send_buffer();
418  reset_soft();
419  } else if (m_state == State::REQUEST_CON && m_tx_count < MAX_RETRANSMIT) {
420  // Retry CoAP request and set a timer for the next attempt.
421  send_buffer();
422  timer_rand(ACK_TIMEOUT_MSEC << m_tx_count);
423  } else if (m_state == State::RESPONSE_SEP2 && m_tx_count < MAX_RETRANSMIT) {
424  // Retry CoAP separated response and set a timer for the next attempt.
425  send_buffer();
426  timer_rand(ACK_TIMEOUT_MSEC << m_tx_count);
427  } else {
428  // Ultimate timeout reached, report error if applicable.
429  // (For some states, timeouts may be expected during normal operation.)
430  if (m_state == State::REQUEST_NON) {
431  m_coap->coap_timeout(this);
432  } else if (m_state != State::RESPONSE_CACHE) {
433  m_coap->coap_error(this);
434  }
435  reset_soft();
436  }
437 }
438 
439 bool Connection::send_empty(u8 typ, u16 id) {
440  // Construct the outgoing message in a temporary buffer.
441  // (This is easier than trying to predict the total length.)
443  satcat5::coap::Writer hdr(&msg);
444  hdr.write_header(typ, CODE_EMPTY, id);
445  hdr.write_finalize(); // Empty message (no options or data)
446 
447  // Send the message using the previously-configured connection.
448  Writeable* wr = m_addr->open_write(msg.written_len());
449  if (wr) wr->write_bytes(msg.written_len(), msg.buffer());
450  return wr && wr->write_finalize();
451 }
452 
453 void Connection::timer_rand(u32 base_msec) {
454  // Randomize timeouts by a factor of [1.0..1.5] per Section 4.8.1.
455  // (This helps prevent ensemble-lockstep synchronization effects.)
456  timer_once(base_msec + satcat5::util::prng.next(0, base_msec / 2));
457 }
458 
460  // Call the parent's event-handler, which aborts on overflow.
461  // Otherwise, proceed with header parsing to set initial state.
462  return ArrayWrite::write_finalize() && send_first();
463 }
464 
465 bool Connection::send_first() {
466  // Parse the CoAP header from the transmit buffer contents...
468  satcat5::coap::ReadHeader msg(&rdbuf);
469  if (msg.error()) return false; // Abort for invalid message?
470  if (DEBUG_VERBOSE > 1)
471  log::Log(log::DEBUG, "CoAP: write_finalize").write(msg_id());
472 
473  // Block all outgoing messages from the ERROR state.
474  if (m_state == State::ERROR) return false;
475 
476  // For outgoing multicast requests and responses to multicast requests,
477  // the only permissible message type is nonconfirmable (Section 8.1).
478  if (m_addr->is_multicast() && msg.type() != TYPE_NON) return false;
479  if (m_state == State::WAIT_RESPONSE_M && msg.type() != TYPE_NON) return false;
480 
481  // During the initial connection phase, reject unexpected messages.
482  if (is_connecting() && !msg.is_request()) return false;
483 
484  // On reaching this point, the message is accepted for transmission,
485  // either immediately or after a short delay. Note ID/token/etc.
486  push_history(&msg);
487 
488  // Set the new state, and set timer if applicable.
489  if (m_state == State::CONNECT_IDLE && !m_addr->ready()) {
490  // Defer outgoing requests until we're connected.
491  // (Polling/retry logic will call this method again once ready.)
492  m_state = State::CONNECT_BUSY;
493  return true;
494  } else if (m_state == State::RESPONSE_SEP1 && msg.type() == TYPE_CON) {
495  // Separated response, set retry timer.
496  m_state = State::RESPONSE_SEP2;
497  timer_rand(ACK_TIMEOUT_MSEC);
498  } else if (m_state == State::WAIT_RESPONSE_M) {
499  // Respond to multicast queries after a random delay (Section 8.2).
500  m_state = State::RESPONSE_DEFER;
501  timer_once(satcat5::util::prng.next(1, MAX_LEISURE_MSEC));
502  } else if (msg.type() == TYPE_CON) {
503  // Confirmable request, set retry timer.
504  m_state = State::REQUEST_CON;
505  timer_rand(ACK_TIMEOUT_MSEC);
506  } else if (msg.type() == TYPE_NON) {
507  // Nonconfirmable request, set rate-limit timer.
508  // (i.e., No more outgoing requests until response or timeout.)
509  m_state = State::REQUEST_NON;
510  timer_rand(PROBE_TIMEOUT_MSEC);
511  } else if (msg.type() == TYPE_ACK && msg.code() == CODE_EMPTY) {
512  // Separated response, set cache-expiration timeout.
513  m_state = State::RESPONSE_SEP1;
514  timer_once(MAX_SEPARATE_SPAN);
515  } else if (msg.type() == TYPE_ACK) {
516  // Ack/Response, set cache-expiration timeout.
517  m_state = State::RESPONSE_CACHE;
518  timer_once(MAX_TRANSMIT_SPAN);
519  } else if (msg.type() == TYPE_RST) {
520  // Hard reset of state + history.
521  reset_hard();
522  }
523 
524  // Except for the deferred-response case, send immediately.
525  m_tx_count = 0;
526  return (m_state == State::RESPONSE_DEFER) || send_buffer();
527 }
528 
530  : Connection(endpoint, &m_spp), m_spp(iface)
531 {
532  // Nothing else to initialize.
533 }
534 
535 bool ConnectionSpp::connect(u16 apid) {
536  // Sanity check: Don't break active connections.
537  if (m_state != State::IDLE) return false;
538 
539  // Close and reopen with the new APID.
540  // (Outgoing requests are always telecommands.)
541  close();
542  m_spp.connect(true, apid);
543 
544  if (DEBUG_VERBOSE > 0)
545  log::Log(log::DEBUG, "CoAP: Connect").write(apid);
546  return connected(true);
547 }
548 
550  const satcat5::udp::Addr& dstaddr,
551  const satcat5::udp::Port& dstport,
552  const satcat5::udp::Port& srcport,
553  bool allow_reuse)
554 {
555  // Sanity check: Don't break active connections.
556  if (m_state != State::IDLE) return false;
557  if (!m_coap) return false;
558 
559  // Close and reopen with the new connection.
560  close();
561  m_udp.connect(dstaddr, dstport, srcport);
562 
563  // If we have a unique port number, register for incoming messages.
564  if (m_udp.srcport() != m_coap->srcport()) {
565  m_filter = satcat5::net::Type(m_udp.srcport().value);
566  m_coap->iface()->add(this);
567  }
568 
569  if (DEBUG_VERBOSE > 0)
570  log::Log(log::DEBUG, "CoAP: Connect").write(dstaddr);
571  return connected(allow_reuse);
572 }
573 
575  const satcat5::udp::Addr& dstaddr,
576  const satcat5::udp::Port& dstport) const
577 {
578  return m_udp.dstaddr() == dstaddr
579  && m_udp.dstport() == dstport;
580 }
581 
583  Connection::init(endpoint);
584  m_udp.init(iface);
585 }
void connect(bool cmd, u16 apid)
Set the packet type and APID.
Definition: ccsds_spp.h:177
Implemention of "net::Dispatch" API for CCSDS-SPP packets.
Definition: ccsds_spp.h:199
CoAP request/response handling for a single client-server connection.
bool is_separate() const
< Awaiting separate response?
bool write_finalize() override
Mark end of frame and release temporary working data.
bool open_separate(const satcat5::coap::ReadHeader *msg)
If able, send the first half of a separated response.
bool is_connecting() const
< Connection in progress?
u8 tkl() const
< Most recent token length
bool is_match_addr() const
< Match reply endpoint?
u64 token() const
< Most recent message token
bool ready() const
Ready to send a request?
u8 response_type() const
Determine the expected response type for an incoming request.
bool is_request() const
< Any request state?
void timer_event() override
Child class MUST override this method.
void frame_rcvd(satcat5::io::LimitedRead &src) override
Dispatch calls frame_rcvd(...) for each incoming frame with with a matching net::Type value.
satcat5::io::Writeable * continue_separate()
If able, send the second half of a separated response.
bool is_response() const
< Any response state?
bool test_inject(unsigned len, const void *data)
Test only: Send a message using the active connection.
bool ping(u16 msg_id)
If able, send a ping request to the remote client.
u16 msg_id() const
< Most recent message ID
bool is_idle() const
< Idle and ready for use?
bool error_response(satcat5::coap::Code code, const char *why=0)
If able, return an error in response to an incoming request from a remote client.
satcat5::io::Writeable * open_response()
If able, accept an incoming request from a remote client.
bool is_match_coap(const satcat5::coap::ReadHeader *msg) const
bool is_await() const
< Awaiting initial response?
satcat5::io::Writeable * open_request()
If able, send a request to the current remote server.
void init(satcat5::coap::Endpoint *endpoint)
Deferred initialization of the upstream interface.
void close()
Close any open connections and reset state.
Variant of coap::Connection for CCSDS-SPP connections.
satcat5::ccsds_spp::Address m_spp
Connection to a specific APID.
bool connect(u16 apid)
Set remote APID for later calls to open_request().
ConnectionSpp(satcat5::coap::Endpoint *endpoint, satcat5::ccsds_spp::Dispatch *iface)
Create cache object and link it to the designated endpoint.
Variant of coap::Connection for UDP connections.
void init(satcat5::coap::Endpoint *endpoint, satcat5::udp::Dispatch *iface)
Deferred initialization of the upstream interface.
bool connect(const satcat5::udp::Addr &dstaddr, const satcat5::udp::Port &dstport=satcat5::udp::PORT_COAP, const satcat5::udp::Port &srcport=satcat5::udp::PORT_NONE, bool allow_reuse=false)
Set remote endpoint for later calls to open_request().
satcat5::udp::Address m_udp
Connection to a specific IP address and UDP port.
CoAP endpoint (i.e., client, server, or combined client+server).
Definition: coap_endpoint.h:41
satcat5::udp::Port srcport() const
For UDP only, query the local port number.
Definition: coap_endpoint.h:47
virtual void coap_reqwait(Connection *obj, Reader *msg)
The Child class overrides these event-handlers:
virtual void coap_request(Connection *obj, Reader *msg)
The Child class overrides these event-handlers:
virtual void coap_separate(Connection *obj, Reader *msg)
The Child class overrides these event-handlers:
satcat5::net::Dispatch * iface() const
Fetch the associated network interface.
Definition: coap_endpoint.h:44
virtual void coap_error(Connection *obj)
The Child class overrides these event-handlers:
virtual void coap_ping(const Reader *msg)
The Child class overrides these event-handlers:
virtual void coap_timeout(Connection *obj)
The Child class overrides these event-handlers:
virtual void coap_response(Connection *obj, Reader *msg)
The Child class overrides these event-handlers:
Parser for CoAP message headers only.
Definition: coap_reader.h:80
u8 type() const
< Type (T)
Definition: coap_reader.h:110
bool error() const
< Error during parsing?
Definition: coap_reader.h:102
u8 tkl() const
< Token length (TKL)
Definition: coap_reader.h:112
Code code() const
< Response code (CODE)
Definition: coap_reader.h:114
bool is_request() const
< CON or NON request?
Definition: coap_reader.h:120
u64 token() const
< Token value
Definition: coap_reader.h:118
u16 msg_id() const
< Message ID
Definition: coap_reader.h:116
Wrapper for coap::ReadOptions that automatically parses options, rejecting any message with unrecogni...
Definition: coap_reader.h:234
Base-class for parsing CoAP message headers and options.
Definition: coap_reader.h:162
Message formatting for the Constrained Applications Protocol (CoAP).
Definition: coap_writer.h:26
bool ready() const
Is this object ready for writing? Note: This is the only safe method if m_dst is null.
Definition: coap_writer.h:37
bool write_finalize()
After the last option, finish with an empty message.
Definition: coap_writer.h:67
bool write_option(u16 id, unsigned len, const void *data)
Write option(s) one at a time, in various formats.
Definition: coap_writer.cc:49
satcat5::io::Writeable * write_data()
After the last option, start writing message data.
Definition: coap_writer.cc:88
bool write_header(u8 type, Code code, u16 msg_id, u64 token=0, u8 tkl=0)
Always start by writing the header, with optional token.
Definition: coap_writer.cc:25
Ephemeral Readable interface for a simple array.
Definition: io_readable.h:206
unsigned written_len() const
Report total length after write_finalize() is called.
Definition: io_writeable.h:151
void write_abort() override
If possible, abort the current partially-written packet.
const u8 * buffer() const
Read-only access to the working buffer.
Definition: io_writeable.h:147
Thin wrapper for ArrayWrite with a built-in buffer.
Definition: io_writeable.h:169
Limited read of next N bytes.
Definition: io_readable.h:255
Abstract API for writing byte-streams and packets.
Definition: io_writeable.h:24
virtual void write_bytes(unsigned nbytes, const void *src)
Write 0 or more bytes from a buffer.
void write_str(const char *str)
Write the contents of a null-terminated string.
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 & write(const char *str)
Formatting methods for various data types.
Definition: log.cc:198
Defines a generic API for sending data to a specific destination, such as a MAC address,...
Definition: net_address.h:30
virtual bool reply_is_multicast() const =0
Was the parent interface's incoming message sent to a multicast address? (i.e., Could that message ha...
virtual bool ready() const =0
Is this address object ready for use? Child MUST override this method.
virtual void retry()
If this Address is not in the ready() state, reattempt any steps required to do so,...
Definition: net_address.h:52
virtual void save_reply_address()=0
Bind this Address object to the parent interface's current reply address, as provided in net::Dispatc...
virtual satcat5::io::Writeable * open_write(unsigned len)=0
Open a new frame to the designated address and type.
virtual void close()=0
Close any open connections and revert to idle.
virtual bool is_multicast() const =0
Is the destination a broadcast or multicast address? Child MUST override these method.
void add(satcat5::net::Protocol *proto)
Register a Protocol object.
Definition: net_dispatch.h:55
void remove(satcat5::net::Protocol *proto)
Unregister a Protocol object.
Definition: net_dispatch.h:59
satcat5::net::Type m_filter
Incoming packet filter.
Definition: net_protocol.h:52
void timer_stop()
Stop all future notifications.
Definition: polling.cc:326
void timer_once(unsigned msec)
Configure a one-time notification after X milliseconds.
Definition: polling.cc:316
void connect(const satcat5::udp::Addr &dstaddr, const satcat5::eth::MacAddr &dstmac, const satcat5::udp::Port &dstport, const satcat5::udp::Port &srcport=satcat5::udp::PORT_NONE, const satcat5::eth::VlanTag &vtag=satcat5::eth::VTAG_NONE)
Manual address resolution (user supplies IP + MAC).
Definition: udp_core.cc:30
void init(satcat5::udp::Dispatch *iface)
Deferred initialization of the upstream interface.
Definition: udp_core.cc:23
Dispatcher sorts incoming UDP messages by port index.
Definition: udp_dispatch.h:20
constexpr u16 FORMAT_TEXT
text/plain;charset=utf-8
constexpr u16 OPTION_FORMAT
Option: Content-Format.
Message parsing for the Constrained Applications Protocol (CoAP)
Diagnostic logging to UART and/or Ethernet ports.
CoAP message header CODE field (Section 12.1).
u8 value
Raw value.
constexpr bool is_error() const
Category tests: 0.00 = Empty (may be request or response) 0.01-0.31 = Request 2.00-2....
IPv4 address is a 32-bit unsigned integer.
Definition: ip_core.h:15
UDP and TCP ports are both 16-bit unsigned integers.
Definition: ip_core.h:119
u16 value
Raw access to the underlying representation.
Definition: ip_core.h:121
Multipurpose filter for matching fields in network packets.
Definition: net_type.h:38
u32 as_u32() const
Accessors for m_value.
Definition: net_type.h:66