/src/librabbitmq/librabbitmq/amqp_connection.c
Line | Count | Source |
1 | | // Copyright 2007 - 2021, Alan Antonuk and the rabbitmq-c contributors. |
2 | | // SPDX-License-Identifier: mit |
3 | | |
4 | | #ifdef HAVE_CONFIG_H |
5 | | #include "config.h" |
6 | | #endif |
7 | | |
8 | | #ifdef _MSC_VER |
9 | | #define _CRT_SECURE_NO_WARNINGS |
10 | | #endif |
11 | | |
12 | | #include "amqp_private.h" |
13 | | #include "amqp_time.h" |
14 | | #include "rabbitmq-c/tcp_socket.h" |
15 | | #include <errno.h> |
16 | | #include <stdint.h> |
17 | | #include <stdio.h> |
18 | | #include <stdlib.h> |
19 | | #include <string.h> |
20 | | |
21 | | #ifndef AMQP_INITIAL_FRAME_POOL_PAGE_SIZE |
22 | 4.91k | #define AMQP_INITIAL_FRAME_POOL_PAGE_SIZE 65536 |
23 | | #endif |
24 | | |
25 | | #ifndef AMQP_INITIAL_INBOUND_SOCK_BUFFER_SIZE |
26 | 9.83k | #define AMQP_INITIAL_INBOUND_SOCK_BUFFER_SIZE 131072 |
27 | | #endif |
28 | | |
29 | | #ifndef AMQP_DEFAULT_LOGIN_TIMEOUT_SEC |
30 | 4.91k | #define AMQP_DEFAULT_LOGIN_TIMEOUT_SEC 12 |
31 | | #endif |
32 | | |
33 | | #define ENFORCE_STATE(statevec, statenum) \ |
34 | 4.91k | { \ |
35 | 4.91k | amqp_connection_state_t _check_state = (statevec); \ |
36 | 4.91k | amqp_connection_state_enum _wanted_state = (statenum); \ |
37 | 4.91k | if (_check_state->state != _wanted_state) \ |
38 | 4.91k | amqp_abort( \ |
39 | 0 | "Programming error: invalid AMQP connection state: expected %d, " \ |
40 | 0 | "got %d", \ |
41 | 0 | _wanted_state, _check_state->state); \ |
42 | 4.91k | } |
43 | | |
44 | 4.91k | amqp_connection_state_t amqp_new_connection(void) { |
45 | 4.91k | int res; |
46 | 4.91k | amqp_connection_state_t state = (amqp_connection_state_t)calloc( |
47 | 4.91k | 1, sizeof(struct amqp_connection_state_t_)); |
48 | | |
49 | 4.91k | if (state == NULL) { |
50 | 0 | return NULL; |
51 | 0 | } |
52 | | |
53 | 4.91k | res = amqp_tune_connection(state, 0, AMQP_INITIAL_FRAME_POOL_PAGE_SIZE, 0); |
54 | 4.91k | if (0 != res) { |
55 | 0 | goto out_nomem; |
56 | 0 | } |
57 | | |
58 | 4.91k | state->inbound_buffer.bytes = state->header_buffer; |
59 | 4.91k | state->inbound_buffer.len = sizeof(state->header_buffer); |
60 | | |
61 | 4.91k | state->state = CONNECTION_STATE_INITIAL; |
62 | | /* the server protocol version response is 8 bytes, which conveniently |
63 | | is also the minimum frame size */ |
64 | 4.91k | state->target_size = 8; |
65 | | |
66 | 4.91k | state->sock_inbound_buffer.len = AMQP_INITIAL_INBOUND_SOCK_BUFFER_SIZE; |
67 | 4.91k | state->sock_inbound_buffer.bytes = |
68 | 4.91k | malloc(AMQP_INITIAL_INBOUND_SOCK_BUFFER_SIZE); |
69 | 4.91k | if (state->sock_inbound_buffer.bytes == NULL) { |
70 | 0 | goto out_nomem; |
71 | 0 | } |
72 | | |
73 | 4.91k | init_amqp_pool(&state->properties_pool, 512); |
74 | | |
75 | | /* Use address of the internal_handshake_timeout object by default. */ |
76 | 4.91k | state->internal_handshake_timeout.tv_sec = AMQP_DEFAULT_LOGIN_TIMEOUT_SEC; |
77 | 4.91k | state->internal_handshake_timeout.tv_usec = 0; |
78 | 4.91k | state->handshake_timeout = &state->internal_handshake_timeout; |
79 | | |
80 | 4.91k | return state; |
81 | | |
82 | 0 | out_nomem: |
83 | 0 | free(state->sock_inbound_buffer.bytes); |
84 | 0 | free(state); |
85 | 0 | return NULL; |
86 | 4.91k | } |
87 | | |
88 | 0 | int amqp_get_sockfd(amqp_connection_state_t state) { |
89 | 0 | return state->socket ? amqp_socket_get_sockfd(state->socket) : -1; |
90 | 0 | } |
91 | | |
92 | 0 | void amqp_set_sockfd(amqp_connection_state_t state, int sockfd) { |
93 | 0 | amqp_socket_t *socket = amqp_tcp_socket_new(state); |
94 | 0 | if (!socket) { |
95 | 0 | amqp_abort("%s", strerror(errno)); |
96 | 0 | } |
97 | 0 | amqp_tcp_socket_set_sockfd(socket, sockfd); |
98 | 0 | } |
99 | | |
100 | 0 | void amqp_set_socket(amqp_connection_state_t state, amqp_socket_t *socket) { |
101 | 0 | amqp_socket_delete(state->socket); |
102 | 0 | state->socket = socket; |
103 | 0 | } |
104 | | |
105 | 0 | amqp_socket_t *amqp_get_socket(amqp_connection_state_t state) { |
106 | 0 | return state->socket; |
107 | 0 | } |
108 | | |
109 | | int amqp_tune_connection(amqp_connection_state_t state, int channel_max, |
110 | 4.91k | int frame_max, int heartbeat) { |
111 | 4.91k | void *newbuf; |
112 | 4.91k | int res; |
113 | | |
114 | 4.91k | ENFORCE_STATE(state, CONNECTION_STATE_IDLE); |
115 | | |
116 | 4.91k | if (frame_max < AMQP_FRAME_MIN_SIZE) { |
117 | 0 | frame_max = AMQP_FRAME_MIN_SIZE; |
118 | 0 | } |
119 | | |
120 | 4.91k | state->channel_max = channel_max; |
121 | 4.91k | state->frame_max = frame_max; |
122 | | |
123 | 4.91k | state->heartbeat = heartbeat; |
124 | 4.91k | if (0 > state->heartbeat) { |
125 | 0 | state->heartbeat = 0; |
126 | 0 | } |
127 | | |
128 | 4.91k | res = amqp_time_s_from_now(&state->next_send_heartbeat, |
129 | 4.91k | amqp_heartbeat_send(state)); |
130 | 4.91k | if (AMQP_STATUS_OK != res) { |
131 | 0 | return res; |
132 | 0 | } |
133 | 4.91k | res = amqp_time_s_from_now(&state->next_recv_heartbeat, |
134 | 4.91k | amqp_heartbeat_recv(state)); |
135 | 4.91k | if (AMQP_STATUS_OK != res) { |
136 | 0 | return res; |
137 | 0 | } |
138 | | |
139 | 4.91k | state->outbound_buffer.len = frame_max; |
140 | 4.91k | newbuf = realloc(state->outbound_buffer.bytes, frame_max); |
141 | 4.91k | if (newbuf == NULL) { |
142 | 0 | return AMQP_STATUS_NO_MEMORY; |
143 | 0 | } |
144 | 4.91k | state->outbound_buffer.bytes = newbuf; |
145 | | |
146 | 4.91k | return AMQP_STATUS_OK; |
147 | 4.91k | } |
148 | | |
149 | 0 | int amqp_get_channel_max(amqp_connection_state_t state) { |
150 | 0 | return state->channel_max; |
151 | 0 | } |
152 | | |
153 | 0 | int amqp_get_frame_max(amqp_connection_state_t state) { |
154 | 0 | return state->frame_max; |
155 | 0 | } |
156 | | |
157 | 0 | int amqp_get_heartbeat(amqp_connection_state_t state) { |
158 | 0 | return state->heartbeat; |
159 | 0 | } |
160 | | |
161 | 4.91k | int amqp_destroy_connection(amqp_connection_state_t state) { |
162 | 4.91k | int status = AMQP_STATUS_OK; |
163 | 4.91k | if (state) { |
164 | 4.91k | int i; |
165 | 83.5k | for (i = 0; i < POOL_TABLE_SIZE; ++i) { |
166 | 78.6k | amqp_pool_table_entry_t *entry = state->pool_table[i]; |
167 | 84.6k | while (NULL != entry) { |
168 | 6.05k | amqp_pool_table_entry_t *todelete = entry; |
169 | 6.05k | empty_amqp_pool(&entry->pool); |
170 | 6.05k | entry = entry->next; |
171 | 6.05k | free(todelete); |
172 | 6.05k | } |
173 | 78.6k | } |
174 | | |
175 | 4.91k | free(state->outbound_buffer.bytes); |
176 | 4.91k | free(state->sock_inbound_buffer.bytes); |
177 | 4.91k | amqp_socket_delete(state->socket); |
178 | 4.91k | empty_amqp_pool(&state->properties_pool); |
179 | 4.91k | free(state); |
180 | 4.91k | } |
181 | 4.91k | return status; |
182 | 4.91k | } |
183 | | |
184 | 45.9k | static void return_to_idle(amqp_connection_state_t state) { |
185 | 45.9k | state->inbound_buffer.len = sizeof(state->header_buffer); |
186 | 45.9k | state->inbound_buffer.bytes = state->header_buffer; |
187 | 45.9k | state->inbound_offset = 0; |
188 | 45.9k | state->target_size = HEADER_SIZE; |
189 | 45.9k | state->state = CONNECTION_STATE_IDLE; |
190 | 45.9k | } |
191 | | |
192 | | static size_t consume_data(amqp_connection_state_t state, |
193 | 98.4k | amqp_bytes_t *received_data) { |
194 | | /* how much data is available and will fit? */ |
195 | 98.4k | size_t bytes_consumed = state->target_size - state->inbound_offset; |
196 | 98.4k | if (received_data->len < bytes_consumed) { |
197 | 346 | bytes_consumed = received_data->len; |
198 | 346 | } |
199 | | |
200 | 98.4k | memcpy(amqp_offset(state->inbound_buffer.bytes, state->inbound_offset), |
201 | 98.4k | received_data->bytes, bytes_consumed); |
202 | 98.4k | state->inbound_offset += bytes_consumed; |
203 | 98.4k | received_data->bytes = amqp_offset(received_data->bytes, bytes_consumed); |
204 | 98.4k | received_data->len -= bytes_consumed; |
205 | | |
206 | 98.4k | return bytes_consumed; |
207 | 98.4k | } |
208 | | |
209 | | int amqp_handle_input(amqp_connection_state_t state, amqp_bytes_t received_data, |
210 | 49.4k | amqp_frame_t *decoded_frame) { |
211 | 49.4k | size_t bytes_consumed; |
212 | 49.4k | void *raw_frame; |
213 | | |
214 | | /* Returning frame_type of zero indicates either insufficient input, |
215 | | or a complete, ignored frame was read. */ |
216 | 49.4k | decoded_frame->frame_type = 0; |
217 | | |
218 | 49.4k | if (received_data.len == 0) { |
219 | 0 | return AMQP_STATUS_OK; |
220 | 0 | } |
221 | | |
222 | 49.4k | if (state->state == CONNECTION_STATE_IDLE) { |
223 | 44.5k | state->state = CONNECTION_STATE_HEADER; |
224 | 44.5k | } |
225 | | |
226 | 49.4k | bytes_consumed = consume_data(state, &received_data); |
227 | | |
228 | | /* do we have target_size data yet? if not, return with the |
229 | | expectation that more will arrive */ |
230 | 49.4k | if (state->inbound_offset < state->target_size) { |
231 | 228 | return (int)bytes_consumed; |
232 | 228 | } |
233 | | |
234 | 49.2k | raw_frame = state->inbound_buffer.bytes; |
235 | | |
236 | 49.2k | switch (state->state) { |
237 | 4.91k | case CONNECTION_STATE_INITIAL: |
238 | | /* check for a protocol header from the server */ |
239 | 4.91k | if (memcmp(raw_frame, "AMQP", 4) == 0) { |
240 | 30 | decoded_frame->frame_type = AMQP_PSEUDOFRAME_PROTOCOL_HEADER; |
241 | 30 | decoded_frame->channel = 0; |
242 | | |
243 | 30 | decoded_frame->payload.protocol_header.transport_high = |
244 | 30 | amqp_d8(amqp_offset(raw_frame, 4)); |
245 | 30 | decoded_frame->payload.protocol_header.transport_low = |
246 | 30 | amqp_d8(amqp_offset(raw_frame, 5)); |
247 | 30 | decoded_frame->payload.protocol_header.protocol_version_major = |
248 | 30 | amqp_d8(amqp_offset(raw_frame, 6)); |
249 | 30 | decoded_frame->payload.protocol_header.protocol_version_minor = |
250 | 30 | amqp_d8(amqp_offset(raw_frame, 7)); |
251 | | |
252 | 30 | return_to_idle(state); |
253 | 30 | return (int)bytes_consumed; |
254 | 30 | } |
255 | | |
256 | | /* it's not a protocol header; fall through to process it as a |
257 | | regular frame header */ |
258 | | |
259 | 49.1k | case CONNECTION_STATE_HEADER: { |
260 | 49.1k | amqp_channel_t channel; |
261 | 49.1k | amqp_pool_t *channel_pool; |
262 | 49.1k | uint32_t frame_size; |
263 | | |
264 | 49.1k | channel = amqp_d16(amqp_offset(raw_frame, 1)); |
265 | | |
266 | | /* frame length is 3 bytes in */ |
267 | 49.1k | frame_size = amqp_d32(amqp_offset(raw_frame, 3)); |
268 | | /* To prevent the target_size calculation below from overflowing, check |
269 | | * that the stated frame_size is smaller than a signed 32-bit. Given |
270 | | * the library only allows configuring frame_max as an int32_t, and |
271 | | * frame_size is uint32_t, the math below is safe from overflow. */ |
272 | 49.1k | if (frame_size >= INT32_MAX) { |
273 | 46 | return AMQP_STATUS_BAD_AMQP_DATA; |
274 | 46 | } |
275 | | |
276 | 49.1k | frame_size = frame_size + HEADER_SIZE + FOOTER_SIZE; |
277 | 49.1k | if ((size_t)state->frame_max < frame_size) { |
278 | 100 | return AMQP_STATUS_BAD_AMQP_DATA; |
279 | 100 | } |
280 | | |
281 | 49.0k | channel_pool = amqp_get_or_create_channel_pool(state, channel); |
282 | 49.0k | if (NULL == channel_pool) { |
283 | 0 | return AMQP_STATUS_NO_MEMORY; |
284 | 0 | } |
285 | | |
286 | 49.0k | amqp_pool_alloc_bytes(channel_pool, frame_size, &state->inbound_buffer); |
287 | 49.0k | if (NULL == state->inbound_buffer.bytes) { |
288 | 0 | return AMQP_STATUS_NO_MEMORY; |
289 | 0 | } |
290 | 49.0k | memcpy(state->inbound_buffer.bytes, state->header_buffer, HEADER_SIZE); |
291 | 49.0k | raw_frame = state->inbound_buffer.bytes; |
292 | | |
293 | 49.0k | state->state = CONNECTION_STATE_BODY; |
294 | 49.0k | state->target_size = frame_size; |
295 | 49.0k | bytes_consumed += consume_data(state, &received_data); |
296 | | |
297 | | /* do we have target_size data yet? if not, return with the |
298 | | expectation that more will arrive */ |
299 | 49.0k | if (state->inbound_offset < state->target_size) { |
300 | 118 | return (int)bytes_consumed; |
301 | 118 | } |
302 | 49.0k | } |
303 | | /* fall through to process body */ |
304 | | |
305 | 48.9k | case CONNECTION_STATE_BODY: { |
306 | 48.9k | amqp_bytes_t encoded; |
307 | 48.9k | int res; |
308 | 48.9k | amqp_pool_t *channel_pool; |
309 | | |
310 | | /* Check frame end marker (footer) */ |
311 | 48.9k | if (amqp_d8(amqp_offset(raw_frame, state->target_size - 1)) != |
312 | 48.9k | AMQP_FRAME_END) { |
313 | 51 | return AMQP_STATUS_BAD_AMQP_DATA; |
314 | 51 | } |
315 | | |
316 | 48.8k | decoded_frame->frame_type = amqp_d8(amqp_offset(raw_frame, 0)); |
317 | 48.8k | decoded_frame->channel = amqp_d16(amqp_offset(raw_frame, 1)); |
318 | | |
319 | 48.8k | channel_pool = |
320 | 48.8k | amqp_get_or_create_channel_pool(state, decoded_frame->channel); |
321 | 48.8k | if (NULL == channel_pool) { |
322 | 0 | return AMQP_STATUS_NO_MEMORY; |
323 | 0 | } |
324 | | |
325 | 48.8k | switch (decoded_frame->frame_type) { |
326 | 17.8k | case AMQP_FRAME_METHOD: |
327 | | /* A METHOD frame body must contain at least the 4-byte method id. |
328 | | * Reject undersized frames before subtracting from target_size to |
329 | | * avoid an unsigned underflow that would yield a huge encoded.len |
330 | | * and cause out-of-bounds reads in amqp_decode_method(). */ |
331 | 17.8k | if (state->target_size < HEADER_SIZE + 4 + FOOTER_SIZE) { |
332 | 4 | return AMQP_STATUS_BAD_AMQP_DATA; |
333 | 4 | } |
334 | 17.8k | decoded_frame->payload.method.id = |
335 | 17.8k | amqp_d32(amqp_offset(raw_frame, HEADER_SIZE)); |
336 | 17.8k | encoded.bytes = amqp_offset(raw_frame, HEADER_SIZE + 4); |
337 | 17.8k | encoded.len = state->target_size - HEADER_SIZE - 4 - FOOTER_SIZE; |
338 | | |
339 | 17.8k | res = amqp_decode_method(decoded_frame->payload.method.id, |
340 | 17.8k | channel_pool, encoded, |
341 | 17.8k | &decoded_frame->payload.method.decoded); |
342 | 17.8k | if (res < 0) { |
343 | 2.57k | return res; |
344 | 2.57k | } |
345 | | |
346 | 15.3k | break; |
347 | | |
348 | 15.3k | case AMQP_FRAME_HEADER: |
349 | | /* A HEADER frame body must contain at least 12 bytes (class_id, |
350 | | * weight, body_size). Reject undersized frames before subtracting |
351 | | * from target_size to avoid an unsigned underflow that would yield |
352 | | * a huge encoded.len and cause out-of-bounds reads in |
353 | | * amqp_decode_properties() / the table decoder |
354 | | * (CVE: GHSA-9mmv-r8g3-qp46). */ |
355 | 4.46k | if (state->target_size < HEADER_SIZE + 12 + FOOTER_SIZE) { |
356 | 7 | return AMQP_STATUS_BAD_AMQP_DATA; |
357 | 7 | } |
358 | 4.45k | decoded_frame->payload.properties.class_id = |
359 | 4.45k | amqp_d16(amqp_offset(raw_frame, HEADER_SIZE)); |
360 | | /* unused 2-byte weight field goes here */ |
361 | 4.45k | decoded_frame->payload.properties.body_size = |
362 | 4.45k | amqp_d64(amqp_offset(raw_frame, HEADER_SIZE + 4)); |
363 | 4.45k | encoded.bytes = amqp_offset(raw_frame, HEADER_SIZE + 12); |
364 | 4.45k | encoded.len = state->target_size - HEADER_SIZE - 12 - FOOTER_SIZE; |
365 | 4.45k | decoded_frame->payload.properties.raw = encoded; |
366 | | |
367 | 4.45k | res = amqp_decode_properties( |
368 | 4.45k | decoded_frame->payload.properties.class_id, channel_pool, encoded, |
369 | 4.45k | &decoded_frame->payload.properties.decoded); |
370 | 4.45k | if (res < 0) { |
371 | 391 | return res; |
372 | 391 | } |
373 | | |
374 | 4.06k | break; |
375 | | |
376 | 4.06k | case AMQP_FRAME_BODY: |
377 | 2.13k | if (state->target_size < HEADER_SIZE + FOOTER_SIZE) { |
378 | 0 | return AMQP_STATUS_BAD_AMQP_DATA; |
379 | 0 | } |
380 | 2.13k | decoded_frame->payload.body_fragment.len = |
381 | 2.13k | state->target_size - HEADER_SIZE - FOOTER_SIZE; |
382 | 2.13k | decoded_frame->payload.body_fragment.bytes = |
383 | 2.13k | amqp_offset(raw_frame, HEADER_SIZE); |
384 | 2.13k | break; |
385 | | |
386 | 428 | case AMQP_FRAME_HEARTBEAT: |
387 | 428 | break; |
388 | | |
389 | 23.9k | default: |
390 | | /* Ignore the frame */ |
391 | 23.9k | decoded_frame->frame_type = 0; |
392 | 23.9k | break; |
393 | 48.8k | } |
394 | | |
395 | 45.8k | return_to_idle(state); |
396 | 45.8k | return (int)bytes_consumed; |
397 | 48.8k | } |
398 | | |
399 | 0 | default: |
400 | 0 | amqp_abort("Internal error: invalid amqp_connection_state_t->state %d", |
401 | 0 | state->state); |
402 | 49.2k | } |
403 | 49.2k | } |
404 | | |
405 | 0 | amqp_boolean_t amqp_release_buffers_ok(amqp_connection_state_t state) { |
406 | 0 | return (state->state == CONNECTION_STATE_IDLE); |
407 | 0 | } |
408 | | |
409 | 0 | void amqp_release_buffers(amqp_connection_state_t state) { |
410 | 0 | int i; |
411 | 0 | ENFORCE_STATE(state, CONNECTION_STATE_IDLE); |
412 | |
|
413 | 0 | for (i = 0; i < POOL_TABLE_SIZE; ++i) { |
414 | 0 | amqp_pool_table_entry_t *entry = state->pool_table[i]; |
415 | |
|
416 | 0 | for (; NULL != entry; entry = entry->next) { |
417 | 0 | amqp_maybe_release_buffers_on_channel(state, entry->channel); |
418 | 0 | } |
419 | 0 | } |
420 | 0 | } |
421 | | |
422 | 0 | void amqp_maybe_release_buffers(amqp_connection_state_t state) { |
423 | 0 | if (amqp_release_buffers_ok(state)) { |
424 | 0 | amqp_release_buffers(state); |
425 | 0 | } |
426 | 0 | } |
427 | | |
428 | | void amqp_maybe_release_buffers_on_channel(amqp_connection_state_t state, |
429 | 21.9k | amqp_channel_t channel) { |
430 | 21.9k | amqp_link_t *queued_link; |
431 | 21.9k | amqp_pool_t *pool; |
432 | 21.9k | if (CONNECTION_STATE_IDLE != state->state) { |
433 | 0 | return; |
434 | 0 | } |
435 | | |
436 | 21.9k | queued_link = state->first_queued_frame; |
437 | | |
438 | 21.9k | while (NULL != queued_link) { |
439 | 0 | amqp_frame_t *frame = queued_link->data; |
440 | 0 | if (channel == frame->channel) { |
441 | 0 | return; |
442 | 0 | } |
443 | | |
444 | 0 | queued_link = queued_link->next; |
445 | 0 | } |
446 | | |
447 | 21.9k | pool = amqp_get_channel_pool(state, channel); |
448 | | |
449 | 21.9k | if (pool != NULL) { |
450 | 21.9k | recycle_amqp_pool(pool); |
451 | 21.9k | } |
452 | 21.9k | } |
453 | | |
454 | | static int amqp_frame_to_bytes(const amqp_frame_t *frame, amqp_bytes_t buffer, |
455 | 0 | amqp_bytes_t *encoded) { |
456 | 0 | void *out_frame = buffer.bytes; |
457 | 0 | size_t out_frame_len; |
458 | 0 | int res; |
459 | |
|
460 | 0 | amqp_e8(frame->frame_type, amqp_offset(out_frame, 0)); |
461 | 0 | amqp_e16(frame->channel, amqp_offset(out_frame, 1)); |
462 | |
|
463 | 0 | switch (frame->frame_type) { |
464 | 0 | case AMQP_FRAME_BODY: { |
465 | 0 | const amqp_bytes_t *body = &frame->payload.body_fragment; |
466 | | |
467 | | /* Ensure the body fragment fits within the outbound buffer, leaving |
468 | | * room for the frame header and footer. Without this check an |
469 | | * oversized body fragment would overflow the heap-allocated buffer. */ |
470 | 0 | if (buffer.len < HEADER_SIZE + FOOTER_SIZE || |
471 | 0 | body->len > buffer.len - (HEADER_SIZE + FOOTER_SIZE)) { |
472 | 0 | return AMQP_STATUS_BAD_AMQP_DATA; |
473 | 0 | } |
474 | | |
475 | 0 | memcpy(amqp_offset(out_frame, HEADER_SIZE), body->bytes, body->len); |
476 | |
|
477 | 0 | out_frame_len = body->len; |
478 | 0 | break; |
479 | 0 | } |
480 | 0 | case AMQP_FRAME_METHOD: { |
481 | 0 | amqp_bytes_t method_encoded; |
482 | |
|
483 | 0 | amqp_e32(frame->payload.method.id, amqp_offset(out_frame, HEADER_SIZE)); |
484 | |
|
485 | 0 | method_encoded.bytes = amqp_offset(out_frame, HEADER_SIZE + 4); |
486 | 0 | method_encoded.len = buffer.len - HEADER_SIZE - 4 - FOOTER_SIZE; |
487 | |
|
488 | 0 | res = amqp_encode_method(frame->payload.method.id, |
489 | 0 | frame->payload.method.decoded, method_encoded); |
490 | 0 | if (res < 0) { |
491 | 0 | return res; |
492 | 0 | } |
493 | | |
494 | 0 | out_frame_len = res + 4; |
495 | 0 | break; |
496 | 0 | } |
497 | | |
498 | 0 | case AMQP_FRAME_HEADER: { |
499 | 0 | amqp_bytes_t properties_encoded; |
500 | |
|
501 | 0 | amqp_e16(frame->payload.properties.class_id, |
502 | 0 | amqp_offset(out_frame, HEADER_SIZE)); |
503 | 0 | amqp_e16(0, amqp_offset(out_frame, HEADER_SIZE + 2)); /* "weight" */ |
504 | 0 | amqp_e64(frame->payload.properties.body_size, |
505 | 0 | amqp_offset(out_frame, HEADER_SIZE + 4)); |
506 | |
|
507 | 0 | properties_encoded.bytes = amqp_offset(out_frame, HEADER_SIZE + 12); |
508 | 0 | properties_encoded.len = buffer.len - HEADER_SIZE - 12 - FOOTER_SIZE; |
509 | |
|
510 | 0 | res = amqp_encode_properties(frame->payload.properties.class_id, |
511 | 0 | frame->payload.properties.decoded, |
512 | 0 | properties_encoded); |
513 | 0 | if (res < 0) { |
514 | 0 | return res; |
515 | 0 | } |
516 | | |
517 | 0 | out_frame_len = res + 12; |
518 | 0 | break; |
519 | 0 | } |
520 | | |
521 | 0 | case AMQP_FRAME_HEARTBEAT: |
522 | 0 | out_frame_len = 0; |
523 | 0 | break; |
524 | | |
525 | 0 | default: |
526 | 0 | return AMQP_STATUS_INVALID_PARAMETER; |
527 | 0 | } |
528 | | |
529 | 0 | amqp_e32((uint32_t)out_frame_len, amqp_offset(out_frame, 3)); |
530 | 0 | amqp_e8(AMQP_FRAME_END, amqp_offset(out_frame, HEADER_SIZE + out_frame_len)); |
531 | |
|
532 | 0 | encoded->bytes = out_frame; |
533 | 0 | encoded->len = out_frame_len + HEADER_SIZE + FOOTER_SIZE; |
534 | |
|
535 | 0 | return AMQP_STATUS_OK; |
536 | 0 | } |
537 | | |
538 | 0 | int amqp_send_frame(amqp_connection_state_t state, const amqp_frame_t *frame) { |
539 | 0 | return amqp_send_frame_inner(state, frame, AMQP_SF_NONE, |
540 | 0 | amqp_time_infinite()); |
541 | 0 | } |
542 | | |
543 | | int amqp_send_frame_inner(amqp_connection_state_t state, |
544 | | const amqp_frame_t *frame, int flags, |
545 | 0 | amqp_time_t deadline) { |
546 | 0 | int res; |
547 | 0 | ssize_t sent; |
548 | 0 | amqp_bytes_t encoded; |
549 | 0 | amqp_time_t next_timeout; |
550 | | |
551 | | /* TODO: if the AMQP_SF_MORE socket optimization can be shown to work |
552 | | * correctly, then this could be un-done so that body-frames are sent as 3 |
553 | | * send calls, getting rid of the copy of the body content, some testing |
554 | | * would need to be done to see if this would actually a win for performance. |
555 | | * */ |
556 | 0 | res = amqp_frame_to_bytes(frame, state->outbound_buffer, &encoded); |
557 | 0 | if (AMQP_STATUS_OK != res) { |
558 | 0 | return res; |
559 | 0 | } |
560 | | |
561 | 0 | start_send: |
562 | |
|
563 | 0 | next_timeout = amqp_time_first(deadline, state->next_recv_heartbeat); |
564 | |
|
565 | 0 | sent = amqp_try_send(state, encoded.bytes, encoded.len, next_timeout, flags); |
566 | 0 | if (0 > sent) { |
567 | 0 | return (int)sent; |
568 | 0 | } |
569 | | |
570 | | /* A partial send has occurred, because of a heartbeat timeout (so try recv |
571 | | * something) or common timeout (so return AMQP_STATUS_TIMEOUT) */ |
572 | 0 | if ((ssize_t)encoded.len != sent) { |
573 | 0 | if (amqp_time_equal(next_timeout, deadline)) { |
574 | | /* timeout of method was received, so return from method*/ |
575 | 0 | return AMQP_STATUS_TIMEOUT; |
576 | 0 | } |
577 | | |
578 | 0 | res = amqp_try_recv(state); |
579 | |
|
580 | 0 | if (AMQP_STATUS_TIMEOUT == res) { |
581 | 0 | return AMQP_STATUS_HEARTBEAT_TIMEOUT; |
582 | 0 | } else if (AMQP_STATUS_OK != res) { |
583 | 0 | return res; |
584 | 0 | } |
585 | | |
586 | 0 | encoded.bytes = (uint8_t *)encoded.bytes + sent; |
587 | 0 | encoded.len -= sent; |
588 | 0 | goto start_send; |
589 | 0 | } |
590 | | |
591 | 0 | res = amqp_time_s_from_now(&state->next_send_heartbeat, |
592 | 0 | amqp_heartbeat_send(state)); |
593 | 0 | return res; |
594 | 0 | } |
595 | | |
596 | 0 | amqp_table_t *amqp_get_server_properties(amqp_connection_state_t state) { |
597 | 0 | return &state->server_properties; |
598 | 0 | } |
599 | | |
600 | 0 | amqp_table_t *amqp_get_client_properties(amqp_connection_state_t state) { |
601 | 0 | return &state->client_properties; |
602 | 0 | } |