ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
lwm2m_tlv.h
Go to the documentation of this file.
1// ProtoCore v1.0.16 - Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file lwm2m_tlv.h
6 * @brief The OMA LightweightM2M TLV data format (PROTOCORE_ENABLE_LWM2M): a zero-heap writer and
7 * reader for `application/vnd.oma.lwm2m+tlv`.
8 *
9 * TLV is not an IETF format. It is specified by the Open Mobile Alliance in
10 * OMA-TS-LightweightM2M_Core-V1_2-20201110-A sec 7.4.5. The octets travel as a CoAP payload
11 * (RFC 7252), tagged by the Content-Format Option (RFC 7252 sec 5.10.3); LwM2M Core sec 7.4
12 * Table 7.4.-1 pairs the media type `application/vnd.oma.lwm2m+tlv` with the numeric
13 * Content-Format 11542.
14 *
15 * LwM2M Core sec 7.4.5 Table 7.4.5.-1 lays one entry out as
16 * `Type(1) Identifier(1-2) Length(0-3) Value(n)`:
17 * - Type bits 7-6 indicate the type of Identifier: 00 Object Instance, whose Value holds one or
18 * more Resource TLVs; 01 Resource Instance with Value, for use within a multiple Resource TLV;
19 * 10 multiple Resource, whose Value holds one or more Resource Instance TLVs; 11 Resource with
20 * Value.
21 * - Type bit 5 indicates the Length of the Identifier: 0 is 8 bits, 1 is 16 bits.
22 * - Type bits 4-3 indicate the type of Length: 00 is no Length field and the Value is the length
23 * bits 2-0 give, 01 an 8-bit Length field, 10 a 16-bit one, 11 a 24-bit one, and in those three
24 * "Bits 2-0 MUST be ignored".
25 * - Type bits 2-0 are a 3-bit unsigned integer holding the Length of the Value.
26 * Identifier and Length are unsigned integers in network byte order, so the maximum Value is
27 * 16.7 MB.
28 *
29 * LwM2M Core Appendix C Table C.-2 gives the Value forms: Integer is a binary signed integer in
30 * network byte order and two's complement representation, 1, 2, 4 or 8 octets; Float is binary32
31 * or binary64, and this writer emits binary64; Boolean is an 8-bit unsigned integer 0 or 1 whose
32 * "Length of a Boolean value MUST always be 1 byte"; String is UTF-8 of Length octets; Opaque is
33 * Length octets as they stand.
34 *
35 * The writer emits entries into a caller buffer and fails closed: the first entry that does not fit
36 * poisons the cursor, so a finish reports 0 rather than a truncated payload. The reader is a cursor
37 * over a caller buffer that decodes the entry at its head and advances past it, pointing at the
38 * Value where it lies rather than copying it.
39 *
40 * No slot member: the module keeps one writer cursor and one reader cursor, so no call names a row.
41 *
42 * The module exports one symbol, @ref Lwm2mTlv. Everything in lwm2m_tlv.c has internal linkage.
43 *
44 * @author Douglas Quigg (dstroy0)
45 * @date 2026
46 */
47
48#ifndef PROTOCORE_LWM2M_TLV_H
49#define PROTOCORE_LWM2M_TLV_H
50
51#include "protocore_config.h" // the entry point: protocore_types.h for the widths
52
53#if PROTOCORE_ENABLE_LWM2M
54
56
57// Type byte bit-fields (LwM2M Core sec 7.4.5 Table 7.4.5.-1).
58#define LWM2M_TLV_IDTYPE_MASK 0xC0 ///< bits 7-6: the type of Identifier
59#define LWM2M_TLV_ID16_FLAG 0x20 ///< bit 5: the Identifier field is 16 bits, else 8
60#define LWM2M_TLV_LENTYPE_SHIFT 3 ///< bits 4-3: the type of Length, at this position
61#define LWM2M_TLV_LENTYPE_MASK 0x03 ///< its value: 0 none, 1 8-bit, 2 16-bit, 3 24-bit
62#define LWM2M_TLV_INLINE_LEN_MASK 0x07 ///< bits 2-0: the Length of the Value when there is no Length field
63
64/** @brief LwM2M Core sec 7.4.5 Table 7.4.5.-1, Type bits 7-6: what the Identifier names. */
65typedef enum PROTO_ENUM_PACKED
66{
67 LWM2M_TLV_OBJECT_INSTANCE = 0x00, ///< 00: the Value contains one or more Resource TLVs
68 LWM2M_TLV_RESOURCE_INSTANCE = 0x40, ///< 01: Resource Instance with Value, within a multiple Resource TLV
69 LWM2M_TLV_MULTIPLE_RESOURCE = 0x80, ///< 10: the Value contains one or more Resource Instance TLVs
70 LWM2M_TLV_RESOURCE_WITH_VALUE = 0xC0, ///< 11: Resource with Value
71} Lwm2mTlvIdType;
72
73/** @brief Where a writer's octets land. */
74typedef struct
75{
76 uint8_t *buf; ///< the caller buffer every entry is emitted into
77 size_t cap; ///< how many octets it holds
78} Lwm2mTlvSinkArgs;
79
80/** @brief The octets a reader walks. */
81typedef struct
82{
83 const uint8_t *buf; ///< the TLV array a read decodes, left where it lies
84 size_t len; ///< how many octets of it are readable
85} Lwm2mTlvSourceArgs;
86
87/** @brief The Type and Identifier fields of one entry (LwM2M Core sec 7.4.5 Table 7.4.5.-1). */
88typedef struct
89{
90 Lwm2mTlvIdType id_type; ///< Type bits 7-6: the type of Identifier
91 uint16_t id; ///< the Identifier field: the Object Instance, Resource or Resource Instance ID
92} Lwm2mTlvHeaderArgs;
93
94/** @brief The Value field of one entry, in the forms LwM2M Core Appendix C Table C.-2 gives. */
95typedef struct
96{
97 const uint8_t *opaque; ///< Opaque: the Value octets as they stand, and where a read points
98 size_t len; ///< the Length field: how many octets the Value is
99 int64_t integer_value; ///< Integer: staged into 1, 2, 4 or 8 octets, two's complement
100 double float_value; ///< Float: staged as binary64, 8 octets
101 proto_bool boolean_value; ///< Boolean: staged as one octet, 0 for False and 1 for True
102 const char *string_value; ///< String: UTF-8, measured to its NUL within the sink's capacity
103} Lwm2mTlvValueArgs;
104
105/**
106 * @brief The OMA LwM2M TLV codec.
107 *
108 * A caller sets the members a call takes, invokes it through ::Lwm2mTlv, and reads the outcome off
109 * the same handle. @c hdr and @c val are what a write emits and what a read fills, so an entry
110 * decoded from one buffer is re-emitted into another with no field moved by hand.
111 *
112 * @var Lwm2mTlvNs::sink the buffer a writer emits into, taken by an open
113 * @var Lwm2mTlvNs::source the buffer a reader walks, taken by a parse
114 * @var Lwm2mTlvNs::hdr the Type and Identifier fields: set for a write, filled by a next
115 * @var Lwm2mTlvNs::val the Value field: set for a write, filled by a next
116 * @var Lwm2mTlvNs::ok a call's true/false outcome
117 * @var Lwm2mTlvNs::n the octets a finish counts, 0 if any write did not fit
118 * @var Lwm2mTlvNs::open bind @c sink and clear the writer cursor
119 * @var Lwm2mTlvNs::write emit one entry carrying @c val.opaque for @c val.len octets
120 * @var Lwm2mTlvNs::write_integer stage @c val.integer_value as 1/2/4/8 octets and emit it
121 * @var Lwm2mTlvNs::write_boolean stage @c val.boolean_value as one octet and emit it
122 * @var Lwm2mTlvNs::write_string measure @c val.string_value and emit its UTF-8 octets
123 * @var Lwm2mTlvNs::write_float stage @c val.float_value as binary64 and emit it
124 * @var Lwm2mTlvNs::finish count the octets emitted into @c n
125 * @var Lwm2mTlvNs::parse bind @c source and clear the reader cursor
126 * @var Lwm2mTlvNs::next decode the entry at the cursor into @c hdr and @c val, and advance past it
127 * @var Lwm2mTlvNs::value_integer decode @c val.opaque for @c val.len octets into @c val.integer_value
128 */
129typedef struct
130{
131 Lwm2mTlvSinkArgs sink; ///< where a writer's octets land
132 Lwm2mTlvSourceArgs source; ///< the octets a reader walks
133 Lwm2mTlvHeaderArgs hdr; ///< one entry's Type and Identifier fields
134 Lwm2mTlvValueArgs val; ///< its Value field
135 proto_bool ok;
136 size_t n;
137} Lwm2mTlvVars;
138
139/** @brief The operands and the outcome. */
140extern Lwm2mTlvVars Lwm2mTlvV;
141
142/** @brief The entries. */
143typedef struct
144{
145 void (*const open)(uint8_t *work);
146 void (*const write)(uint8_t *work);
147 void (*const write_integer)(uint8_t *work);
148 void (*const write_boolean)(uint8_t *work);
149 void (*const write_string)(uint8_t *work);
150 void (*const write_float)(uint8_t *work);
151 void (*const finish)(uint8_t *work);
152 void (*const parse)(uint8_t *work);
153 void (*const next)(uint8_t *work);
154 void (*const value_integer)(uint8_t *work);
155} Lwm2mTlvNs;
156
157// What the table binds, defined once in the .c and taking one parameter each: everything
158// else an entry needs is an operand in Lwm2mTlvV or a region of the borrow at a fixed offset.
159void protocore_lwm2m_tlv_open(uint8_t *work);
160void protocore_lwm2m_tlv_write(uint8_t *work);
161void protocore_lwm2m_tlv_write_integer(uint8_t *work);
162void protocore_lwm2m_tlv_write_boolean(uint8_t *work);
163void protocore_lwm2m_tlv_write_string(uint8_t *work);
164void protocore_lwm2m_tlv_write_float(uint8_t *work);
165void protocore_lwm2m_tlv_finish(uint8_t *work);
166void protocore_lwm2m_tlv_parse(uint8_t *work);
167void protocore_lwm2m_tlv_next(uint8_t *work);
168void protocore_lwm2m_tlv_value_integer(uint8_t *work);
169
170// `static const`, initialised HERE rather than `extern` against a definition in the .c: a
171// const object whose initializer every translation unit can see is a COMPILE-TIME FACT, so
172// `Lwm2mTlv.open(work)` resolves to a named function and becomes a DIRECT call. An extern table
173// leaves the call indirect and the symbol live at every level, -O2 -flto included.
174static const Lwm2mTlvNs Lwm2mTlv __attribute__((unused)) = {
175 .open = protocore_lwm2m_tlv_open,
176 .write = protocore_lwm2m_tlv_write,
177 .write_integer = protocore_lwm2m_tlv_write_integer,
178 .write_boolean = protocore_lwm2m_tlv_write_boolean,
179 .write_string = protocore_lwm2m_tlv_write_string,
180 .write_float = protocore_lwm2m_tlv_write_float,
181 .finish = protocore_lwm2m_tlv_finish,
182 .parse = protocore_lwm2m_tlv_parse,
183 .next = protocore_lwm2m_tlv_next,
184 .value_integer = protocore_lwm2m_tlv_value_integer,
185};
186
187/**
188 * @brief The PROTOCORE_LWM2M_TLV_BORROW bytes this module's state lives in.
189 *
190 * Stated beside the namespace rather than on it: an entry takes a borrow, and this is where
191 * that borrow comes from. Taken once from the end of the pool, which no mark and no release
192 * walks, so the state lasts the life of the program.
193 *
194 * @return the span.
195 */
196uint8_t *protocore_lwm2m_tlv_span(void);
197
199
200#endif // PROTOCORE_ENABLE_LWM2M
201
202#endif // PROTOCORE_LWM2M_TLV_H
PROTO_ENUM_PACKED
Application protocol spoken on a listener port or connection slot.
#define PROTOCORE_BEGIN_DECLS
Give a header's declarations C linkage, so their symbol names carry no parameter types.
Definition types.h:96
_Bool proto_bool
The truth value.
Definition types.h:64
#define PROTOCORE_END_DECLS
Definition types.h:97