This is an automated email from the ASF dual-hosted git repository. asf-gitbox-commits pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/qpid-proton.git
commit 70c1c58da2f348030f5f4ea9ace476d0f6b30fa6 Author: Andrew Stitcher <[email protected]> AuthorDate: Fri Jun 19 14:19:21 2026 +0100 PROTON-2954: Limit memory use in decoding performative properties * Introduce a new pn_data_set_decode_limits API which limits the number of nodes and the amount of buffer memory that can be used for decoding an AMQP value. * The default on making a new pn_data directly is not to limit the memory use. This is because the users application has full control of any pn_data made like this and is in full control of its value. If this pn_data is going to be used for values coming from an unknown source then its memory use can be limited explicitly. * When the API itself creates pn_data instances to decode protocol fields and pass them to the user then it will impose memory limits to ensure application robustness: - The amount of buffer memory that can be used when decoding property values in performatives to the size of the raw memory in the property list. The number of nodes is limited to 1024 - For a value read directly from a message again the buffer memory is limited by the raw data size being decoded, but there is no limit to the number of nodes. --- c/include/proton/codec.h | 24 +++++++++++ c/src/core/codec.c | 62 +++++++++++++++++++++++--- c/src/core/data.h | 16 ++++++- c/src/core/engine.c | 49 ++++++++++++++------- c/src/core/message.c | 14 ++++-- c/src/core/util.h | 6 ++- c/tests/data_test.cpp | 110 ++++++++++++++++++++++++++++++++++++++++++++++- 7 files changed, 250 insertions(+), 31 deletions(-) diff --git a/c/include/proton/codec.h b/c/include/proton/codec.h index 6a2fb0945..808c3745f 100644 --- a/c/include/proton/codec.h +++ b/c/include/proton/codec.h @@ -411,6 +411,30 @@ PN_EXTERN int pn_data_errno(pn_data_t *data); */ PN_EXTERN pn_error_t *pn_data_error(pn_data_t *data); +/** + * Set decode limits on a pn_data_t object. + * + * Limits the number of nodes and the size of the interned string buffer that + * pn_data_decode() may allocate into this object. When a limit is exceeded + * during decoding, ::pn_data_decode() returns ::PN_OUT_OF_MEMORY and the + * error is set on the object (readable via ::pn_data_error()). + * + * Set either limit to 0 to remove it entirely. + * + * A freshly constructed pn_data_t has both limits set to 0 (unlimited). + * + * Limits survive a call to ::pn_data_clear() — only the decoded data is + * cleared, not the protection settings. + * + * @note The library applies conservative limits internally when decoding peer + * data using @code pn_data_set_decode_limits(data, ...); @endcode + * + * @param data a pn_data_t object + * @param max_nodes maximum number of nodes (0 = no limit beyond the hard ceiling of 65535) + * @param max_buf maximum bytes of interned string/binary data (0 = no limit) + */ +PN_EXTERN void pn_data_set_decode_limits(pn_data_t *data, size_t max_nodes, size_t max_buf); + /** * @cond INTERNAL */ diff --git a/c/src/core/codec.c b/c/src/core/codec.c index bb68ecaed..08ede0c6a 100644 --- a/c/src/core/codec.c +++ b/c/src/core/codec.c @@ -400,9 +400,39 @@ pn_data_t *pn_data(size_t capacity) data->base_parent = 0; data->base_current = 0; data->error = NULL; + /* No limits by default: pn_data_t is a trusted local object. + * Decode-time limits are applied by pni_switch_to_data() at each + * specific call site according to what kind of peer data is being + * decoded. */ + data->max_nid = 0; + data->max_buf_size = 0; return data; } +void pn_data_set_decode_limits(pn_data_t *data, size_t max_nodes, size_t max_buf) +{ + if (!data) return; + /* Clamp the caller's size_t down to pni_nid_t range on entry. */ + data->max_nid = (pni_nid_t) pn_min(max_nodes, PNI_NID_MAX); + data->max_buf_size = max_buf; + + /* Shrink the backing array to max(live nodes, max_nid) if it is now + * oversized. A failed realloc-to-smaller is harmless: the limit is still + * enforced by the size >= max_nid check in pni_data_new(). */ + if (data->max_nid > 0 && data->nodes) { + pni_nid_t new_cap = pn_max(data->size, data->max_nid); + if (new_cap < data->capacity) { + pni_node_t *trimmed = (pni_node_t *) pni_mem_subreallocate( + pn_class(data), data, data->nodes, new_cap * sizeof(pni_node_t)); + if (trimmed) { + data->nodes = trimmed; + data->capacity = new_cap; + } + /* If realloc failed: keep the existing buffer; limit still enforced. */ + } + } +} + void pn_data_free(pn_data_t *data) { pn_free(data); @@ -438,10 +468,16 @@ void pn_data_clear(pn_data_t *data) static int pni_data_grow(pn_data_t *data) { - size_t capacity = data->capacity ? data->capacity : 2; - if (capacity >= PNI_NID_MAX) return PN_OUT_OF_MEMORY; - else if (capacity < PNI_NID_MAX/2) capacity *= 2; - else capacity = PNI_NID_MAX; + /* Resolve the effective ceiling: the user-set limit, or the hard uint16 max. */ + pni_nid_t effective_max = data->max_nid ? data->max_nid : PNI_NID_MAX; + + /* The limit is on logical nodes in use (size), not pre-allocated capacity. + * pni_data_new() is the only caller and it only calls us when size >= capacity, + * so data->size is the count of nodes that will exist after the next add. */ + if (data->size >= effective_max) return PN_OUT_OF_MEMORY; + + /* Double current capacity, starting from 2 if still at 0, capped at effective_max. */ + pni_nid_t capacity = pn_min(data->capacity ? data->capacity * 2 : 2, effective_max); pni_node_t *new_nodes = (pni_node_t *) pni_mem_subreallocate(pn_class(data), data, data->nodes, capacity * sizeof(pni_node_t)); if (new_nodes == NULL) return PN_OUT_OF_MEMORY; @@ -453,6 +489,7 @@ static int pni_data_grow(pn_data_t *data) static ssize_t pni_data_intern(pn_data_t *data, const char *start, size_t size) { size_t offset = pn_buffer_size(data->buf); + if (data->max_buf_size > 0 && offset + size > data->max_buf_size) return PN_OUT_OF_MEMORY; int err = pn_buffer_append(data->buf, start, size); if (err) return err; err = pn_buffer_append(data->buf, "\0", 1); @@ -494,7 +531,10 @@ static int pni_data_intern_node(pn_data_t *data, pni_node_t *node) } size_t oldcap = pn_buffer_capacity(data->buf); ssize_t offset = pni_data_intern(data, bytes->start, bytes->size); - if (offset < 0) return offset; + if (offset < 0) { + pn_error_set(pni_data_error(data), PN_OUT_OF_MEMORY, "pn_data string buffer limit exceeded"); + return offset; + } node->data = true; node->data_offset = offset; node->data_size = bytes->size; @@ -1263,7 +1303,15 @@ static size_t pni_data_id(pn_data_t *data, pni_node_t *node) static pni_node_t *pni_data_new(pn_data_t *data) { - if ((data->capacity <= data->size) && (pni_data_grow(data) != 0)) return NULL; + /* Enforce max_nid limit on logical node count, regardless of pre-allocated capacity. */ + if (data->max_nid > 0 && data->size >= data->max_nid) { + pn_error_set(pni_data_error(data), PN_OUT_OF_MEMORY, "pn_data node limit exceeded"); + return NULL; + } + if ((data->capacity <= data->size) && (pni_data_grow(data) != 0)) { + pn_error_set(pni_data_error(data), PN_OUT_OF_MEMORY, "pn_data node limit exceeded"); + return NULL; + } pni_node_t *node = pn_data_node(data, ++(data->size)); node->next = 0; node->down = 0; @@ -1499,7 +1547,7 @@ void pn_data_dump(pn_data_t *data) pn_fixed_string_t str = pn_fixed_string(buf, sizeof(buf)); pni_inspect_atom((pn_atom_t *) &node->atom, &str); pn_fixed_string_terminate(&str); - printf("Node %u: prev=%" PN_ZU ", next=%" PN_ZU ", parent=%" PN_ZU ", down=%" PN_ZU + printf("Node %u: prev=%" PN_ZU ", next=%" PN_ZU ", parent=%" PN_ZU ", down=%" PN_ZU ", children=%" PN_ZU ", type=%s (%s)\n", i + 1, (size_t) node->prev, (size_t) node->next, diff --git a/c/src/core/data.h b/c/src/core/data.h index 97315b495..8b1f5a61e 100644 --- a/c/src/core/data.h +++ b/c/src/core/data.h @@ -52,6 +52,8 @@ struct pn_data_t { pni_node_t *nodes; pn_buffer_t *buf; pn_error_t *error; + size_t max_buf_size; /* intern buffer limit during decode; 0 = unlimited */ + pni_nid_t max_nid; /* node count limit during decode; 0 = unlimited */ pni_nid_t capacity; pni_nid_t size; pni_nid_t parent; @@ -60,7 +62,19 @@ struct pn_data_t { pni_nid_t base_current; }; -static inline pni_node_t * pn_data_node(pn_data_t *data, pni_nid_t nd) +/* Node-count limits for pni_switch_to_data(). + * 0-width elements (e.g. PNE_NULL) consume a node but no bytes, so bytes->size + * alone does not bound node count — hence a separate constant is needed. + * + * DEFAULT (1024): performative fields (properties, capabilities, annotations, + * condition info, disposition data). + * BODY (0 = unlimited): message body — application data whose node count is + * only bounded by the uint16 hard ceiling of PNI_NID_MAX. + */ +#define PNI_DATA_DEFAULT_MAX_NODES 1024 +#define PNI_DATA_BODY_MAX_NODES 0 + +static inline pni_node_t * pn_data_node(pn_data_t *data, pni_nid_t nd) { return nd ? (data->nodes + nd - 1) : NULL; } diff --git a/c/src/core/engine.c b/c/src/core/engine.c index dc8cf65a4..6e1ea6eeb 100644 --- a/c/src/core/engine.c +++ b/c/src/core/engine.c @@ -23,6 +23,7 @@ #define PN_USE_DEPRECATED_API 1 #include "engine-internal.h" +#include "data.h" #include "consumers.h" #include "core/frame_consumers.h" @@ -669,21 +670,24 @@ void pn_connection_set_password(pn_connection_t *connection, const char *passwor pn_data_t *pn_connection_offered_capabilities(pn_connection_t *connection) { assert(connection); - pni_switch_to_data(&connection->offered_capabilities_raw, &connection->offered_capabilities); + pni_switch_to_data(&connection->offered_capabilities_raw, &connection->offered_capabilities, + PNI_DATA_DEFAULT_MAX_NODES); return connection->offered_capabilities; } pn_data_t *pn_connection_desired_capabilities(pn_connection_t *connection) { assert(connection); - pni_switch_to_data(&connection->desired_capabilities_raw, &connection->desired_capabilities); + pni_switch_to_data(&connection->desired_capabilities_raw, &connection->desired_capabilities, + PNI_DATA_DEFAULT_MAX_NODES); return connection->desired_capabilities; } pn_data_t *pn_connection_properties(pn_connection_t *connection) { assert(connection); - pni_switch_to_data(&connection->properties_raw, &connection->properties); + pni_switch_to_data(&connection->properties_raw, &connection->properties, + PNI_DATA_DEFAULT_MAX_NODES); return connection->properties; } @@ -692,7 +696,8 @@ pn_data_t *pn_connection_remote_offered_capabilities(pn_connection_t *connection assert(connection); if (!connection->transport) return NULL; - pni_switch_to_data(&connection->transport->remote_offered_capabilities_raw, &connection->remote_offered_capabilities); + pni_switch_to_data(&connection->transport->remote_offered_capabilities_raw, &connection->remote_offered_capabilities, + PNI_DATA_DEFAULT_MAX_NODES); return connection->remote_offered_capabilities; } @@ -701,7 +706,8 @@ pn_data_t *pn_connection_remote_desired_capabilities(pn_connection_t *connection assert(connection); if (!connection->transport) return NULL; - pni_switch_to_data(&connection->transport->remote_desired_capabilities_raw, &connection->remote_desired_capabilities); + pni_switch_to_data(&connection->transport->remote_desired_capabilities_raw, &connection->remote_desired_capabilities, + PNI_DATA_DEFAULT_MAX_NODES); return connection->remote_desired_capabilities; } @@ -710,7 +716,8 @@ pn_data_t *pn_connection_remote_properties(pn_connection_t *connection) assert(connection); if (!connection->transport) return NULL; - pni_switch_to_data(&connection->transport->remote_properties_raw, &connection->remote_properties); + pni_switch_to_data(&connection->transport->remote_properties_raw, &connection->remote_properties, + PNI_DATA_DEFAULT_MAX_NODES); return connection->remote_properties; } @@ -1442,7 +1449,8 @@ pn_data_t *pn_terminus_properties(pn_terminus_t *terminus) { if (!terminus) return NULL; - pni_switch_to_data(&terminus->properties_raw, &terminus->properties); + pni_switch_to_data(&terminus->properties_raw, &terminus->properties, + PNI_DATA_DEFAULT_MAX_NODES); return terminus->properties; } @@ -1450,7 +1458,8 @@ pn_data_t *pn_terminus_capabilities(pn_terminus_t *terminus) { if (!terminus) return NULL; - pni_switch_to_data(&terminus->capabilities_raw, &terminus->capabilities); + pni_switch_to_data(&terminus->capabilities_raw, &terminus->capabilities, + PNI_DATA_DEFAULT_MAX_NODES); return terminus->capabilities; } @@ -1458,7 +1467,8 @@ pn_data_t *pn_terminus_outcomes(pn_terminus_t *terminus) { if (!terminus) return NULL; - pni_switch_to_data(&terminus->outcomes_raw, &terminus->outcomes); + pni_switch_to_data(&terminus->outcomes_raw, &terminus->outcomes, + PNI_DATA_DEFAULT_MAX_NODES); return terminus->outcomes; } @@ -1466,7 +1476,8 @@ pn_data_t *pn_terminus_filter(pn_terminus_t *terminus) { if (!terminus) return NULL; - pni_switch_to_data(&terminus->filter_raw, &terminus->filter); + pni_switch_to_data(&terminus->filter_raw, &terminus->filter, + PNI_DATA_DEFAULT_MAX_NODES); return terminus->filter; } @@ -1896,7 +1907,8 @@ pn_data_t *pn_disposition_data(pn_disposition_t *disposition) if (disposition->type != PN_DISP_CUSTOM) { pni_disposition_to_raw(disposition); } - pni_switch_to_data(&disposition->u.s_custom.data_raw, &disposition->u.s_custom.data); + pni_switch_to_data(&disposition->u.s_custom.data_raw, &disposition->u.s_custom.data, + PNI_DATA_DEFAULT_MAX_NODES); return disposition->u.s_custom.data; } @@ -1975,7 +1987,8 @@ pn_data_t *pn_disposition_annotations(pn_disposition_t *disposition) pn_disposition_clear(disposition); disposition->type = PN_DISP_MODIFIED; } - pni_switch_to_data(&disposition->u.s_modified.annotations_raw, &disposition->u.s_modified.annotations); + pni_switch_to_data(&disposition->u.s_modified.annotations_raw, &disposition->u.s_modified.annotations, + PNI_DATA_DEFAULT_MAX_NODES); return disposition->u.s_modified.annotations; } @@ -2033,7 +2046,8 @@ pn_transactional_disposition_t *pn_transactional_disposition(pn_disposition_t *d pn_data_t *pn_custom_disposition_data(pn_custom_disposition_t *disposition) { assert(disposition); - pni_switch_to_data(&disposition->data_raw, &disposition->data); + pni_switch_to_data(&disposition->data_raw, &disposition->data, + PNI_DATA_DEFAULT_MAX_NODES); return disposition->data; } @@ -2105,7 +2119,8 @@ void pn_modified_disposition_set_undeliverable(pn_modified_disposition_t *dispos pn_data_t *pn_modified_disposition_annotations(pn_modified_disposition_t *disposition) { assert(disposition); - pni_switch_to_data(&disposition->annotations_raw, &disposition->annotations); + pni_switch_to_data(&disposition->annotations_raw, &disposition->annotations, + PNI_DATA_DEFAULT_MAX_NODES); return disposition->annotations; } @@ -2447,7 +2462,8 @@ pn_data_t *pn_link_remote_properties(pn_link_t *link) assert(link); // Annoying inconsistency: nearly everywhere else you *HAVE* to return an empty pn_data_t not NULL if (link->remote_properties_raw.size) { - pni_switch_to_data(&link->remote_properties_raw, &link->remote_properties); + pni_switch_to_data(&link->remote_properties_raw, &link->remote_properties, + PNI_DATA_DEFAULT_MAX_NODES); } return link->remote_properties; } @@ -2705,7 +2721,8 @@ int pn_condition_format(pn_condition_t *condition, const char *name, PN_PRINTF_F pn_data_t *pn_condition_info(pn_condition_t *condition) { assert(condition); - pni_switch_to_data(&condition->info_raw, &condition->info); + pni_switch_to_data(&condition->info_raw, &condition->info, + PNI_DATA_DEFAULT_MAX_NODES); return condition->info; } diff --git a/c/src/core/message.c b/c/src/core/message.c index 98f0d9864..94740aa8e 100644 --- a/c/src/core/message.c +++ b/c/src/core/message.c @@ -1058,28 +1058,34 @@ int pn_message_data(pn_message_t *msg, pn_data_t *data) pn_data_t *pn_message_instructions(pn_message_t *msg) { if (!msg) return NULL; - pni_switch_to_data(&msg->instructions_raw, &msg->instructions_deprecated); + pni_switch_to_data(&msg->instructions_raw, &msg->instructions_deprecated, + PNI_DATA_DEFAULT_MAX_NODES); return msg->instructions_deprecated; } pn_data_t *pn_message_annotations(pn_message_t *msg) { if (!msg) return NULL; - pni_switch_to_data(&msg->annotations_raw, &msg->annotations_deprecated); + pni_switch_to_data(&msg->annotations_raw, &msg->annotations_deprecated, + PNI_DATA_DEFAULT_MAX_NODES); return msg->annotations_deprecated; } pn_data_t *pn_message_properties(pn_message_t *msg) { if (!msg) return NULL; - pni_switch_to_data(&msg->properties_raw, &msg->properties_deprecated); + pni_switch_to_data(&msg->properties_raw, &msg->properties_deprecated, + PNI_DATA_DEFAULT_MAX_NODES); return msg->properties_deprecated; } pn_data_t *pn_message_body(pn_message_t *msg) { if (!msg) return NULL; - pni_switch_to_data(&msg->body_raw, &msg->body_deprecated); + /* No node limit - application body values could be complex, + and the backing buffer is already limited*/ + pni_switch_to_data(&msg->body_raw, &msg->body_deprecated, + PNI_DATA_BODY_MAX_NODES); return msg->body_deprecated; } diff --git a/c/src/core/util.h b/c/src/core/util.h index df4f67964..29aef3170 100644 --- a/c/src/core/util.h +++ b/c/src/core/util.h @@ -66,12 +66,16 @@ static inline void pn_bytes_free(pn_bytes_t in) { free((void*)in.start); } -static inline void pni_switch_to_data(pn_bytes_t *bytes, pn_data_t **data) { +static inline void pni_switch_to_data(pn_bytes_t *bytes, pn_data_t **data, + size_t max_nodes) { if (*data == NULL) { *data = pn_data(0); } if (bytes->start) { pn_data_clear(*data); + /* Intern buffer capped at bytes->size: the interned bytes must come from + the input bytes so tight 1:1 bound without an artificial ceiling. */ + pn_data_set_decode_limits(*data, max_nodes, bytes->size); pn_data_decode(*data, bytes->start, bytes->size); pn_data_rewind(*data); diff --git a/c/tests/data_test.cpp b/c/tests/data_test.cpp index 31afbfd85..cf2ae8244 100644 --- a/c/tests/data_test.cpp +++ b/c/tests/data_test.cpp @@ -30,10 +30,116 @@ using namespace pn_test; -// Make sure we can grow the capacity of a pn_data_t all the way to the max and -// we stop there. +// Check that pn_data_set_decode_limits() enforces a node-count cap. +TEST_CASE("data_decode_node_limit") { + auto_free<pn_data_t, pn_data_free> data(pn_data(0)); + + // Tighten the limit to 4 nodes + pn_data_set_decode_limits(data, 4, 0); + + // Build and encode a list of 4 ints (should fit exactly) + auto_free<pn_data_t, pn_data_free> src(pn_data(0)); + pn_data_put_list(src); + pn_data_enter(src); + for (int i = 0; i < 4; i++) pn_data_put_int(src, i); + pn_data_exit(src); + + char buf[256]; + int enc = pn_data_encode(src, buf, sizeof(buf)); + REQUIRE(enc > 0); + + // Should decode successfully (4 nodes: 1 list + 4 ints, but list itself is 1 + // node and the 4 ints are children — total 5 nodes needed; lower to 5) + pn_data_set_decode_limits(data, 5, 0); + ssize_t r = pn_data_decode(data, buf, enc); + CHECK(r == enc); + CHECK(pn_data_errno(data) == 0); + + // Now tighten so the same data overflows + pn_data_clear(data); + pn_data_set_decode_limits(data, 3, 0); // too few for list + 4 ints + r = pn_data_decode(data, buf, enc); + CHECK(r == PN_OUT_OF_MEMORY); + CHECK(pn_data_errno(data) == PN_OUT_OF_MEMORY); + + // Limits survive pn_data_clear() + pn_data_clear(data); + CHECK(pn_data_errno(data) == 0); // error cleared + // limits still in effect: re-decode should still fail + r = pn_data_decode(data, buf, enc); + CHECK(r == PN_OUT_OF_MEMORY); +} + +// Check that pn_data_set_decode_limits() shrinks the backing node allocation +// when the new max_nid is lower than the current capacity. +TEST_CASE("data_decode_limit_shrinks_capacity") { + // Pre-allocate a data object with a large capacity. + auto_free<pn_data_t, pn_data_free> data(pn_data(64)); + pn_data_t *d = data; // raw pointer for struct-field access + // capacity should now be 64 (or the pre-allocated hint). + CHECK(d->capacity == 64); + + // Lower the limit to 8. The backing array must shrink to 8. + pn_data_set_decode_limits(data, 8, 0); + CHECK(d->max_nid == 8); + CHECK(d->capacity == 8); // realloc-to-smaller must have happened + + // Lowering below the number of live nodes must clamp to size, not max_nid. + // Put 4 nodes in, then try to lower the limit to 2. + for (int i = 0; i < 4; i++) pn_data_put_int(data, i); + CHECK(pn_data_size(data) == 4); + pn_data_set_decode_limits(data, 2, 0); + CHECK(d->max_nid == 2); + // capacity must not have been reduced below the 4 live nodes. + CHECK(d->capacity >= 4); + // And the existing nodes must still be intact. + CHECK(pn_data_size(data) == 4); + + // Setting max_nid = 0 (unlimited) must NOT shrink to zero nodes. + pn_data_set_decode_limits(data, 0, 0); + CHECK(d->capacity >= 4); // live nodes still accessible + CHECK(pn_data_size(data) == 4); +} + +// Check that pn_data_set_decode_limits() enforces a string-buffer cap. +TEST_CASE("data_decode_buf_limit") { + auto_free<pn_data_t, pn_data_free> data(pn_data(0)); + + // Encode a symbol of 20 bytes + auto_free<pn_data_t, pn_data_free> src(pn_data(0)); + pn_data_put_symbol(src, pn_bytes("12345678901234567890")); + + char buf[256]; + int enc = pn_data_encode(src, buf, sizeof(buf)); + REQUIRE(enc > 0); + + // Allow plenty of nodes but only 10 bytes of string buffer — should fail + pn_data_set_decode_limits(data, 0, 10); + ssize_t r = pn_data_decode(data, buf, enc); + CHECK(r == PN_OUT_OF_MEMORY); + CHECK(pn_data_errno(data) == PN_OUT_OF_MEMORY); + + // Raise the buf limit enough — should succeed + pn_data_clear(data); + pn_data_set_decode_limits(data, 0, 64); + r = pn_data_decode(data, buf, enc); + CHECK(r == enc); + CHECK(pn_data_errno(data) == 0); + + // Disable both limits (0 = no limit) — should always succeed + pn_data_clear(data); + pn_data_set_decode_limits(data, 0, 0); + r = pn_data_decode(data, buf, enc); + CHECK(r == enc); + CHECK(pn_data_errno(data) == 0); +} + +// Make sure we can grow the capacity of a pn_data_t all the way to the hard +// PNI_NID_MAX ceiling and we stop there (decode-limits disabled for this test). TEST_CASE("data_grow") { auto_free<pn_data_t, pn_data_free> data(pn_data(0)); + // Disable the decode-limits so we can exercise the absolute uint16 ceiling. + pn_data_set_decode_limits(data, 0, 0); int code = 0; while (pn_data_size(data) < PNI_NID_MAX && !code) { code = pn_data_put_int(data, 1); --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
