6 #include <satcat5/coap_connection.h>
7 #include <satcat5/coap_endpoint.h>
9 #include <satcat5/coap_writer.h>
11 #include <satcat5/udp_dispatch.h>
21 namespace log = satcat5::log;
24 static const unsigned DEBUG_VERBOSE = 0;
27 constexpr u8 FLAG_TKL = 0x0F;
28 constexpr u8 FLAG_SEP = 0x10;
29 constexpr u8 FLAG_CON = 0x20;
34 #ifndef SATCAT5_COAP_FAST
35 #define SATCAT5_COAP_FAST 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;
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;
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;
61 : Protocol(satcat5::net::TYPE_NONE)
64 , m_state(State::IDLE)
77 #if SATCAT5_ALLOW_DELETION
78 Connection::~Connection() {
80 m_coap->remove_connection(
this);
89 if (endpoint && !m_coap) {
91 m_coap->add_connection(
this);
97 if (m_state == State::IDLE)
return false;
104 if (msg->
code() == CODE_EMPTY) {
110 return tok && (mid || sep);
123 bool Connection::connected(
bool allow_reuse) {
126 m_allow_reuse = allow_reuse ? 1 : 0;
128 if (!m_addr->
ready()) {
130 m_state = State::CONNECT_IDLE;
131 timer_rand(ACK_TIMEOUT_MSEC);
138 if (DEBUG_VERBOSE > 0)
log::Log(log::DEBUG,
"CoAP: Ping");
144 if (m_state == State::CONNECT_IDLE)
return true;
145 return (m_state == State::IDLE) && m_addr->
ready();
150 if (DEBUG_VERBOSE > 1)
log::Log(log::DEBUG,
"CoAP: open_request");
151 if (!
ready())
return 0;
158 if (DEBUG_VERBOSE > 1)
log::Log(log::DEBUG,
"CoAP: open_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;
178 if (DEBUG_VERBOSE > 1)
log::Log(log::DEBUG,
"CoAP: continue_separate");
179 if (m_state != State::RESPONSE_SEP1)
return 0;
186 if (DEBUG_VERBOSE > 0)
log::Log(log::DEBUG,
"CoAP: Returning error");
193 if (!wr.
ready())
return false;
204 u8 flag_con = m_flags[m_meta_idx] & FLAG_CON;
205 return flag_con ? TYPE_ACK : TYPE_NON;
211 if (!wr)
return false;
227 bool Connection::deliver(
Reader* msg) {
228 if (DEBUG_VERBOSE > 1)
log::Log(log::DEBUG,
"CoAP: deliver")
233 if (msg->
error() || !accept)
return false;
245 if (DEBUG_VERBOSE > 0)
log::Log(log::DEBUG,
"CoAP: Matched")
248 if (msg->
type() == TYPE_CON && msg->
code() == CODE_EMPTY) {
250 if (DEBUG_VERBOSE > 0)
log::Log(log::DEBUG,
"CoAP: Ping-rcvd");
251 send_empty(TYPE_RST, msg->
msg_id());
252 }
else if (msg->
type() == TYPE_RST && msg->
code() == CODE_EMPTY) {
254 if (DEBUG_VERBOSE > 0)
log::Log(log::DEBUG,
"CoAP: Pong-rcvd");
256 }
else if (msg->
type() == TYPE_RST) {
258 if (DEBUG_VERBOSE > 1)
log::Log(log::DEBUG,
"CoAP: rcvd-rst");
263 if (msg->
type() == TYPE_ACK && msg->
code() == CODE_EMPTY) {
266 if (DEBUG_VERBOSE > 1)
log::Log(log::DEBUG,
"CoAP: rcvd-sep1");
267 m_state = State::REQUEST_SEP;
270 }
else if (msg->
type() == TYPE_CON) {
273 if (DEBUG_VERBOSE > 1)
log::Log(log::DEBUG,
"CoAP: rcvd-sep2");
274 m_flags[m_meta_idx] |= FLAG_SEP;
275 m_coap->reply(TYPE_ACK, msg);
281 if (DEBUG_VERBOSE > 1)
log::Log(log::DEBUG,
"CoAP: rcvd-ack");
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) {
292 if (DEBUG_VERBOSE > 1)
log::Log(log::DEBUG,
"CoAP: rcvd-sep3");
297 int recent = match_history(msg);
302 if (DEBUG_VERBOSE > 1)
log::Log(log::DEBUG,
"CoAP: rcvd-dupe");
304 }
else if (recent >= 0) {
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);
313 if (DEBUG_VERBOSE > 1)
log::Log(log::DEBUG,
"CoAP: rcvd-req");
315 ? State::WAIT_RESPONSE_M : State::WAIT_RESPONSE_U;
324 if (DEBUG_VERBOSE > 1)
log::Log(log::DEBUG,
"CoAP: rcvd-stale");
329 void Connection::error_event() {
332 m_state = State::ERROR;
336 int Connection::match_history(
const ReadHeader* msg)
const {
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);
350 void Connection::push_history(
const ReadHeader* msg) {
355 if (m_meta_count < SATCAT5_COAP_HISTORY) {
356 m_meta_idx = m_meta_count++;
357 }
else if (++m_meta_idx >= SATCAT5_COAP_HISTORY) {
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();
369 void Connection::reset_hard() {
370 if (DEBUG_VERBOSE > 1)
log::Log(log::DEBUG,
"CoAP: reset_hard");
378 void Connection::reset_soft() {
379 if (DEBUG_VERBOSE > 1)
log::Log(log::DEBUG,
"CoAP: reset_soft");
381 m_state = State::IDLE;
386 bool Connection::send_buffer() {
387 if (DEBUG_VERBOSE > 0)
395 if (!wr)
return false;
401 if (DEBUG_VERBOSE > 1)
407 if (m_state == State::CONNECT_BUSY) send_first();
412 timer_rand(ACK_TIMEOUT_MSEC << m_tx_count);
414 }
else if (m_state == State::RESPONSE_DEFER) {
419 }
else if (m_state == State::REQUEST_CON && m_tx_count < MAX_RETRANSMIT) {
422 timer_rand(ACK_TIMEOUT_MSEC << m_tx_count);
423 }
else if (m_state == State::RESPONSE_SEP2 && m_tx_count < MAX_RETRANSMIT) {
426 timer_rand(ACK_TIMEOUT_MSEC << m_tx_count);
430 if (m_state == State::REQUEST_NON) {
432 }
else if (m_state != State::RESPONSE_CACHE) {
439 bool Connection::send_empty(u8 typ, u16
id) {
444 hdr.write_header(typ, CODE_EMPTY,
id);
445 hdr.write_finalize();
453 void Connection::timer_rand(u32 base_msec) {
456 timer_once(base_msec + satcat5::util::prng.next(0, base_msec / 2));
462 return ArrayWrite::write_finalize() && send_first();
465 bool Connection::send_first() {
469 if (msg.error())
return false;
470 if (DEBUG_VERBOSE > 1)
474 if (m_state == State::ERROR)
return false;
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;
489 if (m_state == State::CONNECT_IDLE && !m_addr->
ready()) {
492 m_state = State::CONNECT_BUSY;
494 }
else if (m_state == State::RESPONSE_SEP1 && msg.type() == TYPE_CON) {
496 m_state = State::RESPONSE_SEP2;
497 timer_rand(ACK_TIMEOUT_MSEC);
498 }
else if (m_state == State::WAIT_RESPONSE_M) {
500 m_state = State::RESPONSE_DEFER;
501 timer_once(satcat5::util::prng.next(1, MAX_LEISURE_MSEC));
502 }
else if (msg.type() == TYPE_CON) {
504 m_state = State::REQUEST_CON;
505 timer_rand(ACK_TIMEOUT_MSEC);
506 }
else if (msg.type() == TYPE_NON) {
509 m_state = State::REQUEST_NON;
510 timer_rand(PROBE_TIMEOUT_MSEC);
511 }
else if (msg.type() == TYPE_ACK && msg.code() == CODE_EMPTY) {
513 m_state = State::RESPONSE_SEP1;
515 }
else if (msg.type() == TYPE_ACK) {
517 m_state = State::RESPONSE_CACHE;
519 }
else if (msg.type() == TYPE_RST) {
526 return (m_state == State::RESPONSE_DEFER) || send_buffer();
537 if (m_state != State::IDLE)
return false;
544 if (DEBUG_VERBOSE > 0)
546 return connected(
true);
556 if (m_state != State::IDLE)
return false;
557 if (!m_coap)
return false;
569 if (DEBUG_VERBOSE > 0)
571 return connected(allow_reuse);
578 return m_udp.dstaddr() == dstaddr
579 &&
m_udp.dstport() == dstport;
void connect(bool cmd, u16 apid)
Set the packet type and APID.
Implemention of "net::Dispatch" API for CCSDS-SPP packets.
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).
satcat5::udp::Port srcport() const
For UDP only, query the local port number.
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.
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:
Wrapper for coap::ReadOptions that automatically parses options, rejecting any message with unrecogni...
Base-class for parsing CoAP message headers and options.
Message formatting for the Constrained Applications Protocol (CoAP).
bool ready() const
Is this object ready for writing? Note: This is the only safe method if m_dst is null.
bool write_finalize()
After the last option, finish with an empty message.
bool write_option(u16 id, unsigned len, const void *data)
Write option(s) one at a time, in various formats.
satcat5::io::Writeable * write_data()
After the last option, start writing message data.
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.
Ephemeral Readable interface for a simple array.
unsigned written_len() const
Report total length after write_finalize() is called.
void write_abort() override
If possible, abort the current partially-written packet.
const u8 * buffer() const
Read-only access to the working buffer.
Thin wrapper for ArrayWrite with a built-in buffer.
Limited read of next N bytes.
Abstract API for writing byte-streams and packets.
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.
Log & write(const char *str)
Formatting methods for various data types.
Defines a generic API for sending data to a specific destination, such as a MAC address,...
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,...
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.
void remove(satcat5::net::Protocol *proto)
Unregister a Protocol object.
satcat5::net::Type m_filter
Incoming packet filter.
void timer_stop()
Stop all future notifications.
void timer_once(unsigned msec)
Configure a one-time notification after X milliseconds.
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).
void init(satcat5::udp::Dispatch *iface)
Deferred initialization of the upstream interface.
Dispatcher sorts incoming UDP messages by port index.
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).
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.
UDP and TCP ports are both 16-bit unsigned integers.
u16 value
Raw access to the underlying representation.
Multipurpose filter for matching fields in network packets.
u32 as_u32() const
Accessors for m_value.