ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
amqp.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 amqp.h
6 * @brief AMQP 0-9-1 wire-level framing (PROTOCORE_ENABLE_AMQP): the frames a client builds and the
7 * frames it reads back, into and out of caller buffers.
8 *
9 * The governing document is not an IETF RFC. It is "AMQP, Advanced Message Queuing Protocol,
10 * Protocol Specification, Version 0-9-1, 13 November 2008", published by the AMQP Working Group.
11 * Every section number below is that document's. AMQP 1.0 is a separate OASIS Standard (OASIS
12 * Advanced Message Queuing Protocol (AMQP) Version 1.0 Part 2: Transport, OASIS Standard,
13 * 29 October 2012) whose frame carries an 8 octet fixed header and no frame-end octet; nothing
14 * here speaks it.
15 *
16 * Sec 4.2.2, the 8 octets a client opens the connection with:
17 * @code
18 * protocol-header = literal-AMQP protocol-id protocol-version
19 * literal-AMQP = %d65.77.81.80 ; "AMQP"
20 * protocol-id = %d0
21 * protocol-version = %d0.9.1
22 * @endcode
23 *
24 * Sec 2.3.5 and sec 4.2.3, every frame after it, the size field counting the payload alone:
25 * @code
26 * type(1) channel(2) size(4) payload(size) frame-end(1)
27 * @endcode
28 * with type an octet, channel and size held high byte first (sec 4.2.5.1 holds every integer in
29 * network byte order), and `frame-end = %xCE` (sec 4.2.1). Sec 4.2.3 states the frame-end octet
30 * MUST always be %xCE and that a peer MUST check it before decoding the frame, which is the check
31 * ::AmqpNs::parse_frame makes.
32 *
33 * Sec 4.2.3 names the four frame types METHOD, HEADER, BODY and HEARTBEAT. Its prose gives
34 * HEARTBEAT as type 4, while the sec 4.2.1 grammar reads `heartbeat = %d8 %d0 %d0 frame-end` and
35 * the specification's own constant table reads `frame-heartbeat = 8`. @ref AMQP_FRAME_HEARTBEAT
36 * takes 8.
37 *
38 * A method payload is `class-id(2) method-id(2)` then the method's arguments (sec 2.3.5.1, sec
39 * 4.2.4). A content header payload is `class-id(2) weight(2) body-size(8) property-flags(2)` then
40 * the property list (sec 4.2.6.1), the weight field unused and zero, the body size the sum of the
41 * body sizes of the content body frames that follow. A heartbeat carries channel 0 and an empty
42 * payload (sec 4.2.7).
43 *
44 * The arguments in a method payload and the property list in a content header are the
45 * application's octets; this module frames them and validates the framing.
46 *
47 * The module exports one symbol, @ref Amqp. Everything in amqp.c has internal linkage.
48 *
49 * @author Douglas Quigg (dstroy0)
50 * @date 2026
51 */
52
53#ifndef PROTOCORE_AMQP_H
54#define PROTOCORE_AMQP_H
55
56#include "protocore_config.h" // the entry point: protocore_types.h for the widths
57
58#if PROTOCORE_ENABLE_AMQP
59
61
62// Frame types, octet 0 of a frame (sec 4.2.3).
63#define AMQP_FRAME_METHOD 1 ///< method frame
64#define AMQP_FRAME_HEADER 2 ///< content header frame
65#define AMQP_FRAME_BODY 3 ///< content body frame
66#define AMQP_FRAME_HEARTBEAT 8 ///< heartbeat frame
67
68#define AMQP_FRAME_END 0xCE ///< `frame-end = %xCE` (sec 4.2.1)
69#define AMQP_FRAME_OVERHEAD 8 ///< type(1) + channel(2) + size(4) + frame-end(1)
70
71/** @brief Where a build writes its frame. */
72typedef struct
73{
74 uint8_t *buf; ///< the buffer the octets land in
75 size_t cap; ///< how many octets it holds
76} AmqpOutArgs;
77
78/** @brief The octets a parse reads, one frame at their head. */
79typedef struct
80{
81 const uint8_t *buf; ///< the first octet of a frame
82 size_t len; ///< how many octets are buffered from there
83} AmqpInArgs;
84
85/** @brief Sec 4.2.3: the type octet and the channel a frame header carries. */
86typedef struct
87{
88 uint16_t channel; ///< 0 for frames global to the connection, 1..65535 otherwise
89 uint8_t type; ///< METHOD, HEADER, BODY or HEARTBEAT
90} AmqpFrameArgs;
91
92/** @brief Sec 4.2.3: the payload the size field counts, the frame-end octet excluded. */
93typedef struct
94{
95 const uint8_t *data; ///< the payload octets; a parse points this into @ref AmqpInArgs::buf
96 size_t len; ///< how many of them there are
97} AmqpPayloadArgs;
98
99/** @brief Sec 4.2.4: a method payload, `class-id method-id *amqp-field`. */
100typedef struct
101{
102 const uint8_t *args; ///< the encoded method arguments
103 size_t args_len; ///< their octet count
104 uint16_t class_id; ///< the class the method belongs to
105 uint16_t method_id; ///< the method within that class
106} AmqpMethodArgs;
107
108/** @brief Sec 4.2.6.1: a content header payload, less the unused weight field. */
109typedef struct
110{
111 uint64_t body_size; ///< total octets of the content body frames that follow
112 const uint8_t *property_list; ///< the encoded values of the set property flags
113 size_t property_list_len; ///< their octet count
114 uint16_t class_id; ///< matches the class-id of the method frame it follows
115 uint16_t property_flags; ///< bit 15 marks the first property, bit 0 marks a further flags field
116} AmqpContentArgs;
117
118/**
119 * @brief The AMQP 0-9-1 frame codec.
120 *
121 * A caller sets the members a call takes, invokes it through ::Amqp, and reads the outcome off the
122 * same handle. A build reads @c out and writes @c n; a parse reads @c in and writes @c frame,
123 * @c payload and @c consumed. @c payload carries a build's payload in and a parse's payload out, so
124 * a parse_frame of a METHOD frame hands parse_method its payload with nothing to move.
125 *
126 * No slot member: the codec holds no rows, so no call names one.
127 *
128 * @var AmqpNs::out the buffer a build writes its frame into
129 * @var AmqpNs::in the octets a parse reads a frame from
130 * @var AmqpNs::frame the type and channel of the frame (sec 4.2.3)
131 * @var AmqpNs::payload the payload octets the size field counts (sec 4.2.3)
132 * @var AmqpNs::method the class-id, method-id and arguments of a method payload (sec 4.2.4)
133 * @var AmqpNs::content the class-id, body size, property flags and property list of a content
134 * header payload (sec 4.2.6.1)
135 * @var AmqpNs::ok a call's true/false outcome
136 * @var AmqpNs::n the octets a build wrote, 0 when it wrote none
137 * @var AmqpNs::consumed the whole frame length a parse read, for advancing over it
138 * @var AmqpNs::protocol_header write the 8 octet protocol-header "AMQP" 0 0 9 1 (sec 4.2.2)
139 * @var AmqpNs::build_frame frame @c payload under @c frame.type and @c frame.channel
140 * @var AmqpNs::build_method frame a METHOD payload from @c method (sec 4.2.4)
141 * @var AmqpNs::build_content_header frame a HEADER payload from @c content, weight 0 (sec 4.2.6.1)
142 * @var AmqpNs::build_heartbeat frame a heartbeat, channel 0, empty payload (sec 4.2.7)
143 * @var AmqpNs::parse_frame split one frame off @c in, the %xCE frame-end checked first
144 * @var AmqpNs::parse_method split @c payload into class-id, method-id and arguments
145 */
146typedef struct
147{
148 AmqpOutArgs out; ///< where a build writes
149 AmqpInArgs in; ///< what a parse reads
150 AmqpFrameArgs frame; ///< the frame header fields
151 AmqpPayloadArgs payload; ///< the framed octets
152 AmqpMethodArgs method; ///< a method payload's fields
153 AmqpContentArgs content; ///< a content header payload's fields
154 proto_bool ok;
155 size_t n;
156 size_t consumed;
157} AmqpVars;
158
159/** @brief The operands and the outcome. */
160extern AmqpVars AmqpV;
161
162/** @brief The entries. */
163typedef struct
164{
165 void (*const protocol_header)(uint8_t *work);
166 void (*const build_frame)(uint8_t *work);
167 void (*const build_method)(uint8_t *work);
168 void (*const build_content_header)(uint8_t *work);
169 void (*const build_heartbeat)(uint8_t *work);
170 void (*const parse_frame)(uint8_t *work);
171 void (*const parse_method)(uint8_t *work);
172} AmqpNs;
173
174// What the table binds, defined once in the .c and taking one parameter each: everything
175// else an entry needs is an operand in AmqpV or a region of the borrow at a fixed offset.
176void protocore_amqp_protocol_header(uint8_t *work);
177void protocore_amqp_build_frame(uint8_t *work);
178void protocore_amqp_build_method(uint8_t *work);
179void protocore_amqp_build_content_header(uint8_t *work);
180void protocore_amqp_build_heartbeat(uint8_t *work);
181void protocore_amqp_parse_frame(uint8_t *work);
182void protocore_amqp_parse_method(uint8_t *work);
183
184// `static const`, initialised HERE rather than `extern` against a definition in the .c: a
185// const object whose initializer every translation unit can see is a COMPILE-TIME FACT, so
186// `Amqp.protocol_header(work)` resolves to a named function and becomes a DIRECT call. An extern table
187// leaves the call indirect and the symbol live at every level, -O2 -flto included.
188static const AmqpNs Amqp __attribute__((unused)) = {
189 .protocol_header = protocore_amqp_protocol_header,
190 .build_frame = protocore_amqp_build_frame,
191 .build_method = protocore_amqp_build_method,
192 .build_content_header = protocore_amqp_build_content_header,
193 .build_heartbeat = protocore_amqp_build_heartbeat,
194 .parse_frame = protocore_amqp_parse_frame,
195 .parse_method = protocore_amqp_parse_method,
196};
197
199
200#endif // PROTOCORE_ENABLE_AMQP
201
202#endif // PROTOCORE_AMQP_H
#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