SatCat5
coap_reader.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_reader.h>
7 
14 
15 unsigned Option::value_str(char* dst) {
16  // Read raw bytes, then null-terminate.
17  if (!read_bytes(m_len, dst)) return 0;
18  dst[m_len] = 0;
19  return m_len;
20 }
21 
23  // Integers may be 0-8 bytes with leading zeros (Section 3.2).
24  u64 accum = 0;
25  for (unsigned a = 0 ; a < m_len ; ++a) {
26  accum = (accum << 8) + read_u8();
27  }
28  return accum;
29 }
30 
32  : m_src(src)
33  , m_state(State::OPTIONS)
34  , m_error_code(CODE_EMPTY)
35  , m_error_msg(nullptr)
36  , m_type(src->read_u8())
37  , m_code(src->read_u8())
38  , m_id(src->read_u16())
39  , m_token(0)
40  , m_opt(src)
41 {
42  if ((version() != satcat5::coap::VERSION1) || (tkl() > 8)) {
43  // Detect illegal header parameters.
44  set_error(CODE_BAD_REQUEST, "Bad header");
45  } else if (m_code.is_empty()) {
46  // Empty messages must really be empty.
47  if (tkl() || src->get_read_ready())
48  set_error(CODE_BAD_REQUEST, "Unexpected data");
49  else
50  m_state = State::DATA;
51  } else {
52  // Read the token: 0-8 bytes with leading zeros.
53  for (unsigned a = 0 ; a < tkl() ; ++a) {
54  m_token = (m_token << 8) + src->read_u8();
55  }
56  }
57 }
58 
60  // Sanity check: Is the parser in the expected state?
61  if (m_state != State::OPTIONS) return false;
62 
63  // Consume any leftovers from the previous option.
65 
66  // If there is no message data, then end-of-frame marks the last option.
67  if (!m_src->get_read_ready()) {
68  m_state = State::DATA; return false;
69  }
70 
71  // Otherwise, read the next byte and check for the data marker.
72  u8 hdr = m_src->read_u8();
73  if (hdr == satcat5::coap::PAYLOAD_MARKER) {
74  m_state = State::DATA; return false;
75  }
76 
77  // Parse the rest of the option header.
78  m_opt.m_id += read_var_int((hdr >> 4) & 0x0F);
79  u16 len_tmp = read_var_int((hdr >> 0) & 0x0F);
80  if (m_state == State::ERROR || m_src->get_read_ready() < len_tmp) {
81  set_error(CODE_BAD_OPTION, "Bad option length");
82  return false;
83  }
84 
85  // Reset m_opt's LimitedRead to the length of the field.
86  m_opt.reset(len_tmp);
87  return true;
88 }
89 
91  // If we're still in the options state, skip ahead to message data.
92  while (m_state == State::OPTIONS) next_option();
93  return (m_state == State::DATA) ? m_src : nullptr;
94 }
95 
97  // "Option Delta" and "Option Length" use the same format (Section 3.1).
98  // We've been given the first nybble. Read up to two remaining bytes.
99  if (nybb <= 12) return u16(nybb);
100  if (nybb == 13) return u16(13u + m_src->read_u8());
101  if (nybb == 14) return u16(269u + m_src->read_u16());
102  // Any other value is a message format error (required per Section 3.1).
103  set_error(CODE_BAD_OPTION, "Bad option length");
104  return 0;
105 }
106 
108  : ReadHeader(src)
109  , m_uri_path{}
110  , m_uri_path_wridx(0)
111 {
112  // Null-terminate the Uri-Path
113  m_uri_path[0] = '\0';
114 }
115 
117  // Read each option...
118  while (next_option()) {
119  // Handle a subset of known options. Others are passed to a separate
120  // function to parse unknown options, which can be overloaded.
121  switch (m_opt.id()) {
122  case OPTION_URI_PATH: // Uri-Path
123  append_uri_path(); // Parse Uri-Path string in separate function
124  break;
125  case OPTION_FORMAT: // Content-Format
127  break;
128  case OPTION_MAX_AGE: // Max-Age (ignored)
129  break;
130  case OPTION_BLOCK1: // Block1 (RFC7959)
132  break;
133  case OPTION_BLOCK2: // Block2 (RFC7959)
135  break;
136  case OPTION_SIZE1: // Size1 (RFC7959)
138  break;
139  default: // All other options...
140  read_user_option(); // Call user-defined handler
141  // Note: This cannot be moved into the Reader constructor due
142  // to order-of-operations when creating the child object.
143  break;
144  }
145  }
146 }
147 
149  // Uri-Path sanity checks, +1 for '/'
150  unsigned str_len = ((m_uri_path_wridx > 0) ? 1 : 0) + m_opt.len();
151  if (m_uri_path_wridx + str_len > SATCAT5_COAP_MAX_URI_PATH_LEN) {
152  set_error(CODE_BAD_OPTION, "Uri-Path exceeded max length");
153  return;
154  }
155 
156  // Append to list with a '/' if necessary
157  if (m_uri_path_wridx > 0) { m_uri_path[m_uri_path_wridx++] = '/'; }
160 }
161 
163  // If we've reached this point, the option is unrecognized.
164  // Handle Critical vs Elective options as required in RFC7252.
165  if (m_opt.is_critical()) {
166  set_error(CODE_BAD_OPTION, "Unrecognized Critical option");
167  }
168 }
Accessor for a single CoAP option field.
Definition: coap_reader.h:48
u16 len() const
Option Number (RFC-7252, Section 3.1).
Definition: coap_reader.h:53
bool is_critical() const
Option bit mapping indicates relevant properties (Section 5.4.6).
Definition: coap_reader.h:63
u16 id() const
Option Number (RFC-7252, Section 3.1).
Definition: coap_reader.h:51
unsigned value_str(char *dst)
Access the Option Value as a UTF-8 string ("string").
Definition: coap_reader.cc:15
u64 value_uint()
Access the Option Value as an unsigned integer ("uint").
Definition: coap_reader.cc:22
Parser for CoAP message headers only.
Definition: coap_reader.h:80
satcat5::coap::Code m_code
Status code x.yy.
Definition: coap_reader.h:148
satcat5::coap::Option m_opt
Contents of the current option.
Definition: coap_reader.h:153
bool next_option()
Consume current option and advance to the next one.
Definition: coap_reader.cc:59
u64 m_token
Token (0-8 bytes)
Definition: coap_reader.h:150
u8 tkl() const
< Token length (TKL)
Definition: coap_reader.h:112
satcat5::io::Readable *const m_src
Source buffer of CoAP message.
Definition: coap_reader.h:139
void set_error(Code code, const char *msg="")
Set error code and stop further parsing.
Definition: coap_reader.h:135
u16 read_var_int(u8 nybb)
Read variable-length integer from Option header.
Definition: coap_reader.cc:96
satcat5::io::Readable * read_data()
Access the message payload.
Definition: coap_reader.cc:90
ReadHeader(satcat5::io::Readable *src)
Create this object and read the message header only.
Definition: coap_reader.cc:31
u8 version() const
< Version (Ver)
Definition: coap_reader.h:108
State
List of possible parser states.
Definition: coap_reader.h:129
State m_state
Parser state.
Definition: coap_reader.h:142
Wrapper for coap::ReadOptions that automatically parses options, rejecting any message with unrecogni...
Definition: coap_reader.h:234
void read_user_option() override
The default implementation simply rejects any unsupported "Critical" Option as defined in RFC 7252,...
Definition: coap_reader.cc:162
Base-class for parsing CoAP message headers and options.
Definition: coap_reader.h:162
void read_options()
Read all option headers, storing supported option fields.
Definition: coap_reader.cc:116
unsigned m_uri_path_wridx
Index into m_uri_path
Definition: coap_reader.h:224
satcat5::util::optional< u64 > m_block2
Block2.
Definition: coap_reader.h:227
satcat5::util::optional< u64 > m_size1
Size1.
Definition: coap_reader.h:228
char m_uri_path[SATCAT5_COAP_MAX_URI_PATH_LEN+1]
URI path for this request.
Definition: coap_reader.h:223
void append_uri_path()
Uri-Path string builder.
Definition: coap_reader.cc:148
satcat5::util::optional< u64 > m_block1
Block1.
Definition: coap_reader.h:226
satcat5::util::optional< u16 > m_format
Content-Format.
Definition: coap_reader.h:225
Reader(satcat5::io::Readable *src)
Create this object and read the message header.
Definition: coap_reader.cc:107
virtual void read_user_option()=0
Handler called for each option with an unknown ID.
Limited read of next N bytes.
Definition: io_readable.h:255
bool read_bytes(unsigned nbytes, void *dst) override
Read 0 or more bytes into a buffer.
Definition: io_readable.cc:296
void read_finalize() override
Consume any remaining bytes in this frame, if applicable.
Definition: io_readable.cc:317
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 unsigned get_read_ready() const =0
How many bytes can be read without blocking?
constexpr Code CODE_BAD_REQUEST(4, 0)
< 2.31 Continue
constexpr u16 OPTION_MAX_AGE
Option: Max-Age.
constexpr Code CODE_BAD_OPTION(4, 2)
< 4.01 Unauthorized
constexpr u8 PAYLOAD_MARKER
CoAP message header VERSION and TYPE fields (Section 3).
constexpr u16 OPTION_BLOCK2
Option: Block transfer (RFC7959)
constexpr u16 OPTION_SIZE1
Option: Size1.
constexpr u16 OPTION_BLOCK1
Option: Block transfer (RFC7959)
constexpr u16 OPTION_URI_PATH
Option: Uri-Path.
constexpr u16 OPTION_FORMAT
Option: Content-Format.
Message parsing for the Constrained Applications Protocol (CoAP)
constexpr bool is_empty() const
Category tests: 0.00 = Empty (may be request or response) 0.01-0.31 = Request 2.00-2....