SatCat5
coap_writer.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 <cstring>
7 #include <satcat5/coap_connection.h>
8 #include <satcat5/coap_writer.h>
9 #include <satcat5/io_writeable.h>
10 #include <satcat5/utils.h>
11 
16 
17 // Predict the required length for an integer field.
18 // (Clients SHOULD skip leading zeros to keep messages short.)
19 static unsigned varint_len(u64 x) {
20  unsigned len = 0;
21  while (x) {++len; x >>= 8;}
22  return len;
23 }
24 
25 bool Writer::write_header(u8 type, Code code, u16 msg_id, u64 token, u8 tkl) {
26  // Sanity check before writing.
27  if (!m_dst) return false;
28 
29  // Automatically determine the required token length?
30  if (token && !tkl) tkl = varint_len(token);
31 
32  // Disable auto-insert of Max-Age key for Empty packets
33  if (code == CODE_EMPTY) { m_insert_max_age = false; }
34 
35  // Write the CoAP header (Section 3).
36  m_dst->write_u8(VERSION1 | type | tkl);
37  m_dst->write_u8(code.value);
38  m_dst->write_u16(msg_id);
39  if (tkl) write_varint(token, tkl);
40  return true; // Success!
41 }
42 
43 bool Writer::write_header(Code code, Connection* request) {
44  return write_header(
45  request->response_type(), code,
46  request->msg_id(), request->token(), request->tkl());
47 }
48 
49 bool Writer::write_option(u16 id, unsigned len, const void* data) {
50  if (!m_dst) return false;
51  insert_max_age(id); // Auto-Insert if necessary
52  bool ok = write_optid(id, len);
53  if (ok) m_dst->write_bytes(len, data);
54  return ok;
55 }
56 
57 bool Writer::write_option(u16 id, const char* str) {
58  return write_option(id, strlen(str), str);
59 }
60 
61 bool Writer::write_option(u16 id, u64 value) {
62  if (!m_dst) return false;
63  insert_max_age(id); // Auto-Insert if necessary
64  unsigned len = varint_len(value);
65  bool ok = write_optid(id, len);
66  if (ok) write_varint(value, len);
67  return ok;
68 }
69 
70 bool Writer::write_uri(u16 id, const char* str) {
71  if (!m_dst) return false;
72  unsigned len = 0;
73  // As we find each delimiter, write the preceding segment.
74  while (1) {
75  if (str[len] == 0 || str[len] == '/') {
76  // Write non-empty segment. (Ignore leading or double slashes.)
77  bool ok = (!len) || write_option(id, len, str);
78  // Halt on error or null-terminator.
79  if (ok && str[len]) {str += len + 1; len = 0;}
80  else return ok;
81  } else {
82  // Add normal characters to the current segment.
83  ++len;
84  }
85  }
86 }
87 
89  if (m_dst) {
90  insert_max_age(); // Insert Max-Age even if no options added
91  m_dst->write_u8(PAYLOAD_MARKER);
92  }
93  return m_dst;
94 }
95 
96 bool Writer::write_optid(u16 id, unsigned len) {
97  // Sanity check before writing.
98  if (!m_dst) return false;
99 
100  // Options must be written in ascending order.
101  if (id < m_last_opt) return false;
102  u16 delta = id - m_last_opt;
103  m_last_opt = id;
104 
105  // Determine 4-bit "option delta" and "option length" codes.
106  // (Both use the same structure for 0/1/2 extended bytes.)
107  u8 pre_id, pre_len;
108  if (delta < 13) pre_id = u8(delta << 4);
109  else if (delta < 269) pre_id = 0xD0;
110  else pre_id = 0xE0;
111  if (len < 13) pre_len = u8(len);
112  else if (len < 269) pre_len = 0x0D;
113  else pre_len = 0x0E;
114 
115  // Write the combined variable-length tag (Section 3.1).
116  m_dst->write_u8(pre_id | pre_len);
117  if (pre_id == 0xD0) m_dst->write_u8(delta - 13);
118  if (pre_id == 0xE0) m_dst->write_u16(delta - 269);
119  if (pre_len == 0x0D) m_dst->write_u8(len - 13);
120  if (pre_len == 0x0E) m_dst->write_u16(len - 269);
121  return true; // Success!
122 }
123 
124 void Writer::write_varint(u64 x, unsigned len) {
125  // Convert to network order, then skip leading bytes.
126  u8 tmp[8]; write_be_u64(tmp, x);
127  unsigned skip = 8 - len;
128  m_dst->write_bytes(len, tmp + skip);
129 }
130 
131 void Writer::insert_max_age(u16 next_id) {
132  // Auto-insert Max-Age=0 to explicitly disable caching (Section 5.6.1)
133  if (!m_insert_max_age) { return; } // Not requested or already done
134  if (next_id != 0 && next_id < OPTION_MAX_AGE) { return; } // Wait for ID
135  if (next_id == OPTION_MAX_AGE) { // Max-Age overwritten
136  m_insert_max_age = false; return;
137  }
138  write_optid(OPTION_MAX_AGE, 0); // 0-length is implicity =0
139  m_insert_max_age = false; // Done
140 }
CoAP request/response handling for a single client-server connection.
u8 tkl() const
< Most recent token length
u64 token() const
< Most recent message token
u8 response_type() const
Determine the expected response type for an incoming request.
u16 msg_id() const
< Most recent message ID
Message formatting for the Constrained Applications Protocol (CoAP).
Definition: coap_writer.h:26
bool write_uri(u16 id, const char *str)
Convert a URI to consecutive Uri-Path options.
Definition: coap_writer.cc:70
bool m_insert_max_age
Add Max-Age=0 to disable caching.
Definition: coap_writer.h:78
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
u16 m_last_opt
Previous option-ID.
Definition: coap_writer.h:77
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_u8(u8 data)
One of many functions for writing integer/floating point values, see details.
Definition: io_writeable.cc:20
constexpr u8 PAYLOAD_MARKER
CoAP message header VERSION and TYPE fields (Section 3).
"Writeable" I/O interface core definitions
CoAP message header CODE field (Section 12.1).
u8 value
Raw value.
Miscellaneous mathematical utility functions.
void write_be_u64(u8 *dst, u64 val)
Store fields into a big-endian byte array.
Definition: utils.cc:183