static char *test_parser_fixed_scalars(void *context) { int idx = 0; static char error[1024]; while (fs_vectors[idx].data) { qd_field_iterator_t *field = qd_field_iterator_binary(fs_vectors[idx].data, fs_vectors[idx].length); qd_parsed_field_t *parsed = qd_parse(field); if (!qd_parse_ok(parsed)) return "Unexpected Parse Error"; if (qd_parse_tag(parsed) != fs_vectors[idx].expected_tag) { sprintf(error, "(%d) Tag: Expected %02x, Got %02x", idx, fs_vectors[idx].expected_tag, qd_parse_tag(parsed)); return error; } if (fs_vectors[idx].check_uint && qd_parse_as_uint(parsed) != fs_vectors[idx].expected_ulong) { sprintf(error, "(%d) UINT: Expected %"PRIx64", Got %"PRIx32, idx, fs_vectors[idx].expected_ulong, qd_parse_as_uint(parsed)); return error; } if (fs_vectors[idx].check_ulong && qd_parse_as_ulong(parsed) != fs_vectors[idx].expected_ulong) { sprintf(error, "(%d) ULONG: Expected %"PRIx64", Got %"PRIx64, idx, fs_vectors[idx].expected_ulong, qd_parse_as_ulong(parsed)); return error; } if (fs_vectors[idx].check_int && qd_parse_as_int(parsed) != fs_vectors[idx].expected_long) { sprintf(error, "(%d) INT: Expected %"PRIx64", Got %"PRIx32, idx, fs_vectors[idx].expected_long, qd_parse_as_int(parsed)); return error; } if (fs_vectors[idx].check_long && qd_parse_as_long(parsed) != fs_vectors[idx].expected_long) { sprintf(error, "(%d) LONG: Expected %"PRIx64", Got %"PRIx64, idx, fs_vectors[idx].expected_long, qd_parse_as_long(parsed)); return error; } idx++; qd_field_iterator_free(field); qd_parse_free(parsed); } return 0; }
void qdra_config_auto_link_create_CT(qdr_core_t *core, qd_field_iterator_t *name, qdr_query_t *query, qd_parsed_field_t *in_body) { while (true) { // // Ensure there isn't a duplicate name and that the body is a map // qdr_auto_link_t *al = DEQ_HEAD(core->auto_links); while (al) { if (name && al->name && qd_field_iterator_equal(name, (const unsigned char*) al->name)) break; al = DEQ_NEXT(al); } if (!!al) { query->status = QD_AMQP_BAD_REQUEST; query->status.description = "Name conflicts with an existing entity"; qd_log(core->agent_log, QD_LOG_ERROR, "Error performing CREATE of %s: %s", CONFIG_AUTOLINK_TYPE, query->status.description); break; } if (!qd_parse_is_map(in_body)) { query->status = QD_AMQP_BAD_REQUEST; query->status.description = "Body of request must be a map"; qd_log(core->agent_log, QD_LOG_ERROR, "Error performing CREATE of %s: %s", CONFIG_AUTOLINK_TYPE, query->status.description); break; } // // Extract the fields from the request // qd_parsed_field_t *addr_field = qd_parse_value_by_key(in_body, qdr_config_auto_link_columns[QDR_CONFIG_AUTO_LINK_ADDR]); qd_parsed_field_t *dir_field = qd_parse_value_by_key(in_body, qdr_config_auto_link_columns[QDR_CONFIG_AUTO_LINK_DIR]); qd_parsed_field_t *phase_field = qd_parse_value_by_key(in_body, qdr_config_auto_link_columns[QDR_CONFIG_AUTO_LINK_PHASE]); qd_parsed_field_t *connection_field = qd_parse_value_by_key(in_body, qdr_config_auto_link_columns[QDR_CONFIG_AUTO_LINK_CONNECTION]); qd_parsed_field_t *container_field = qd_parse_value_by_key(in_body, qdr_config_auto_link_columns[QDR_CONFIG_AUTO_LINK_CONTAINER_ID]); // // Addr and dir fields are mandatory. Fail if they're not both here. // if (!addr_field || !dir_field) { query->status = QD_AMQP_BAD_REQUEST; query->status.description = "addr and dir fields are mandatory"; qd_log(core->agent_log, QD_LOG_ERROR, "Error performing CREATE of %s: %s", CONFIG_AUTOLINK_TYPE, query->status.description); break; } qd_direction_t dir; const char *error = qdra_auto_link_direction_CT(dir_field, &dir); if (error) { query->status = QD_AMQP_BAD_REQUEST; query->status.description = error; qd_log(core->agent_log, QD_LOG_ERROR, "Error performing CREATE of %s: %s", CONFIG_AUTOLINK_TYPE, query->status.description); break; } // // Use the specified phase if present. Otherwise default based on the direction: // Phase 0 for outgoing links and phase 1 for incoming links. // int phase = phase_field ? qd_parse_as_int(phase_field) : (dir == QD_OUTGOING ? 0 : 1); // // Validate the phase // if (phase < 0 || phase > 9) { query->status = QD_AMQP_BAD_REQUEST; query->status.description = "autoLink phase must be between 0 and 9"; qd_log(core->agent_log, QD_LOG_ERROR, "Error performing CREATE of %s: %s", CONFIG_AUTOLINK_TYPE, query->status.description); break; } // // The request is good. Create the entity. // bool is_container = !!container_field; qd_parsed_field_t *in_use_conn = is_container ? container_field : connection_field; al = qdr_route_add_auto_link_CT(core, name, addr_field, dir, phase, in_use_conn, is_container); // // Compose the result map for the response. // if (query->body) { qd_compose_start_map(query->body); for (int col = 0; col < QDR_CONFIG_AUTO_LINK_COLUMN_COUNT; col++) qdr_config_auto_link_insert_column_CT(al, col, query->body, true); qd_compose_end_map(query->body); } query->status = QD_AMQP_CREATED; break; } // // Enqueue the response if there is a body. If there is no body, this is a management // operation created internally by the configuration file parser. // if (query->body) { // // If there was an error in processing the create, insert a NULL value into the body. // if (query->status.status / 100 > 2) qd_compose_insert_null(query->body); qdr_agent_enqueue_response_CT(core, query); } else { if (query->status.status / 100 > 2) qd_log(core->log, QD_LOG_ERROR, "Error configuring linkRoute: %s", query->status.description); qdr_query_free(query); } }
static char *test_parser_fixed_scalars(void *context) { int idx = 0; qd_iterator_t *field = NULL; qd_parsed_field_t *parsed = NULL; static char error[1024]; error[0] = 0; while (fs_vectors[idx].data) { field = qd_iterator_binary(fs_vectors[idx].data, fs_vectors[idx].length, ITER_VIEW_ALL); parsed = qd_parse(field); qd_iterator_t *typed_iter = qd_parse_typed(parsed); int length = qd_iterator_length(typed_iter); if (length != fs_vectors[idx].length) { strcpy(error, "Length of typed iterator does not match actual length"); break; } if (!qd_parse_ok(parsed)) { strcpy(error, "Unexpected Parse Error"); break; } if (qd_parse_tag(parsed) != fs_vectors[idx].expected_tag) { sprintf(error, "(%d) Tag: Expected %02x, Got %02x", idx, fs_vectors[idx].expected_tag, qd_parse_tag(parsed)); break; } if (fs_vectors[idx].check_uint && qd_parse_as_uint(parsed) != fs_vectors[idx].expected_ulong) { sprintf(error, "(%d) UINT: Expected %"PRIx64", Got %"PRIx32, idx, fs_vectors[idx].expected_ulong, qd_parse_as_uint(parsed)); break; } if (fs_vectors[idx].check_ulong && qd_parse_as_ulong(parsed) != fs_vectors[idx].expected_ulong) { sprintf(error, "(%d) ULONG: Expected %"PRIx64", Got %"PRIx64, idx, fs_vectors[idx].expected_ulong, qd_parse_as_ulong(parsed)); break; } if (fs_vectors[idx].check_int && qd_parse_as_int(parsed) != fs_vectors[idx].expected_long) { sprintf(error, "(%d) INT: Expected %"PRIx64", Got %"PRIx32, idx, fs_vectors[idx].expected_long, qd_parse_as_int(parsed)); break; } if (fs_vectors[idx].check_long && qd_parse_as_long(parsed) != fs_vectors[idx].expected_long) { sprintf(error, "(%d) LONG: Expected %"PRIx64", Got %"PRIx64, idx, fs_vectors[idx].expected_long, qd_parse_as_long(parsed)); break; } idx++; qd_iterator_free(field); field = 0; qd_parse_free(parsed); parsed = 0; } qd_iterator_free(field); qd_parse_free(parsed); return *error ? error : 0; }
static char *test_integer_conversion(void *context) { const struct fs_vector_t { const char *data; int length; uint8_t parse_as; bool expect_fail; int64_t expected_int; uint64_t expected_uint; } fs_vectors[] = { // can successfully convert 64 bit values that are valid in the 32bit range {"\x80\x00\x00\x00\x00\xff\xff\xff\xff", 9, QD_AMQP_UINT, false, 0, UINT32_MAX}, {"\x80\x00\x00\x00\x00\x00\x00\x00\x00", 9, QD_AMQP_UINT, false, 0, 0}, {"\x80\x00\x00\x00\x00\x00\x00\x00\x01", 9, QD_AMQP_UINT, false, 0, 1}, {"\x81\x00\x00\x00\x00\x7f\xff\xff\xff", 9, QD_AMQP_INT, false, INT32_MAX, 0}, {"\x81\xFF\xFF\xFF\xFF\x80\x00\x00\x00", 9, QD_AMQP_INT, false, INT32_MIN, 0}, {"\x81\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF", 9, QD_AMQP_INT, false, -1, 0}, // signed/unsigned conversions {"\x70\x7F\xFF\xFF\xFF", 5, QD_AMQP_INT, false, INT32_MAX, 0}, {"\x71\x7F\xFF\xFF\xFF", 5, QD_AMQP_UINT, false, 0, INT32_MAX}, {"\x80\x7F\xFF\xFF\xFF\xFF\xFF\xFF\xFF", 9, QD_AMQP_LONG, false, INT64_MAX, 0}, {"\x81\x7F\xFF\xFF\xFF\xFF\xFF\xFF\xFF", 9, QD_AMQP_ULONG, false,0, INT64_MAX}, {"\x50\x7F", 2, QD_AMQP_INT, false, INT8_MAX, 0}, {"\x60\x7F\xFF", 3, QD_AMQP_INT, false, INT16_MAX, 0}, {"\x53\x7F", 2, QD_AMQP_INT, false, INT8_MAX, 0}, {"\x55\x7F", 2, QD_AMQP_UINT, false, 0, INT8_MAX}, {"\x51\x7F", 2, QD_AMQP_UINT, false, 0, INT8_MAX}, {"\x61\x7F\xFF", 3, QD_AMQP_UINT, false, 0, INT16_MAX}, // strings {"\xa1\x02 1", 4, QD_AMQP_UINT, false, 0, 1}, {"\xa1\x02-1", 4, QD_AMQP_INT, false, -1, 0}, {"\xa1\x14" "18446744073709551615", 22, QD_AMQP_ULONG,false, 0, UINT64_MAX}, {"\xa1\x14" "-9223372036854775808", 22, QD_AMQP_LONG, false, INT64_MIN, 0}, {"\xa1\x13" "9223372036854775807", 21, QD_AMQP_LONG, false, INT64_MAX, 0}, {"\xa3\x13" "9223372036854775807", 21, QD_AMQP_LONG, false, INT64_MAX, 0}, // cannot convert 64 bit values that are outside the 32bit range as int32 {"\x80\x00\x00\x00\x01\x00\x00\x00\x00", 9, QD_AMQP_UINT, true, 0, 0}, {"\x81\x00\x00\x00\x00\x80\x00\x00\x00", 9, QD_AMQP_INT, true, 0, 0}, {"\x81\xFF\xFF\xFF\xFF\x7F\xFF\xFF\xFF", 9, QD_AMQP_INT, true, 0, 0}, // bad signed/unsigned conversions {"\x80\x80\x00\x00\x00\x00\x00\x00\x00", 9, QD_AMQP_LONG, true, 0, 0}, {"\x81\x80\x00\x00\x00\x00\x00\x00\x00", 9, QD_AMQP_ULONG, true, 0, 0}, {"\x70\x80\x00\x00\x00", 5, QD_AMQP_LONG, true, 0, 0}, {"\x71\x80\x00\x00\x00", 5, QD_AMQP_ULONG, true, 0, 0}, {"\x55\x80", 2, QD_AMQP_UINT, true, 0, 0}, {"\x51\x80", 2, QD_AMQP_UINT, true, 0, 0}, {"\x54\x80", 2, QD_AMQP_UINT, true, 0, 0}, {"\x61\x80\x00", 3, QD_AMQP_UINT, true, 0, 0}, {"\x53\x80", 2, QD_AMQP_INT, true, 0, 0}, {"\x52\x80", 2, QD_AMQP_INT, true, 0, 0}, {"\x50\x80", 2, QD_AMQP_LONG, true, 0, 0}, {"\x60\x80", 2, QD_AMQP_LONG, true, 0, 0}, {NULL}, }; char *error = NULL; for (int i = 0; fs_vectors[i].data && !error; ++i) { qd_iterator_t *data_iter = qd_iterator_binary(fs_vectors[i].data, fs_vectors[i].length, ITER_VIEW_ALL); qd_parsed_field_t *field = qd_parse(data_iter); if (!qd_parse_ok(field)) { error = "unexpected parse error"; qd_iterator_free(data_iter); qd_parse_free(field); break; } bool equal = false; switch (fs_vectors[i].parse_as) { case QD_AMQP_UINT: { uint32_t tmp = qd_parse_as_uint(field); equal = (tmp == fs_vectors[i].expected_uint); break; } case QD_AMQP_ULONG: { uint64_t tmp = qd_parse_as_ulong(field); equal = (tmp == fs_vectors[i].expected_uint); break; } case QD_AMQP_INT: { int32_t tmp = qd_parse_as_int(field); equal = (tmp == fs_vectors[i].expected_int); break; } case QD_AMQP_LONG: { int64_t tmp = qd_parse_as_long(field); equal = (tmp == fs_vectors[i].expected_int); break; } } if (!qd_parse_ok(field)) { if (!fs_vectors[i].expect_fail) { error = "unexpected conversion/parse error"; } } else if (fs_vectors[i].expect_fail) { error = "Conversion did not fail as expected"; } else if (!equal) { error = "unexpected converted value"; } qd_iterator_free(data_iter); qd_parse_free(field); } return error; }
/** * Checks the content of the message to see if this can be handled by the C-management agent. If this agent cannot handle it, it will be * forwarded to the Python agent. */ static bool qd_can_handle_request(qd_parsed_field_t *properties_fld, qd_router_entity_type_t *entity_type, qd_router_operation_type_t *operation_type, qd_field_iterator_t **identity_iter, qd_field_iterator_t **name_iter, int *count, int *offset) { // The must be a property field and that property field should be a AMQP map. This is true for QUERY but I need // to check if it true for CREATE, UPDATE and DELETE if (properties_fld == 0 || !qd_parse_is_map(properties_fld)) return false; // // Only certain entity types can be handled by this agent. // 'entityType': 'org.apache.qpid.dispatch.router.address // 'entityType': 'org.apache.qpid.dispatch.router.link' // TODO - Add more entity types here. The above is not a complete list. qd_parsed_field_t *parsed_field = qd_parse_value_by_key(properties_fld, IDENTITY); if (parsed_field!=0) { *identity_iter = qd_parse_raw(parsed_field); } parsed_field = qd_parse_value_by_key(properties_fld, NAME); if (parsed_field!=0) { *name_iter = qd_parse_raw(parsed_field); } parsed_field = qd_parse_value_by_key(properties_fld, ENTITY); if (parsed_field == 0) { // Sometimes there is no 'entityType' but 'type' might be available. parsed_field = qd_parse_value_by_key(properties_fld, TYPE); if (parsed_field == 0) return false; } if (qd_field_iterator_equal(qd_parse_raw(parsed_field), address_entity_type)) *entity_type = QD_ROUTER_ADDRESS; else if (qd_field_iterator_equal(qd_parse_raw(parsed_field), link_entity_type)) *entity_type = QD_ROUTER_LINK; else if (qd_field_iterator_equal(qd_parse_raw(parsed_field), config_address_entity_type)) *entity_type = QD_ROUTER_CONFIG_ADDRESS; else if (qd_field_iterator_equal(qd_parse_raw(parsed_field), link_route_entity_type)) *entity_type = QD_ROUTER_CONFIG_LINK_ROUTE; else if (qd_field_iterator_equal(qd_parse_raw(parsed_field), auto_link_entity_type)) *entity_type = QD_ROUTER_CONFIG_AUTO_LINK; else if (qd_field_iterator_equal(qd_parse_raw(parsed_field), console_entity_type)) *entity_type = QD_ROUTER_FORBIDDEN; else return false; parsed_field = qd_parse_value_by_key(properties_fld, OPERATION); if (parsed_field == 0) return false; if (qd_field_iterator_equal(qd_parse_raw(parsed_field), MANAGEMENT_QUERY)) (*operation_type) = QD_ROUTER_OPERATION_QUERY; else if (qd_field_iterator_equal(qd_parse_raw(parsed_field), MANAGEMENT_CREATE)) (*operation_type) = QD_ROUTER_OPERATION_CREATE; else if (qd_field_iterator_equal(qd_parse_raw(parsed_field), MANAGEMENT_READ)) (*operation_type) = QD_ROUTER_OPERATION_READ; else if (qd_field_iterator_equal(qd_parse_raw(parsed_field), MANAGEMENT_UPDATE)) (*operation_type) = QD_ROUTER_OPERATION_UPDATE; else if (qd_field_iterator_equal(qd_parse_raw(parsed_field), MANAGEMENT_DELETE)) (*operation_type) = QD_ROUTER_OPERATION_DELETE; else // This is an unknown operation type. cannot be handled, return false. return false; // Obtain the count and offset. parsed_field = qd_parse_value_by_key(properties_fld, COUNT); if (parsed_field) (*count) = qd_parse_as_int(parsed_field); else (*count) = -1; parsed_field = qd_parse_value_by_key(properties_fld, OFFSET); if (parsed_field) (*offset) = qd_parse_as_int(parsed_field); else (*offset) = 0; return true; }
static qd_iterator_t *router_annotate_message(qd_router_t *router, qd_parsed_field_t *in_ma, qd_message_t *msg, qd_bitmask_t **link_exclusions, bool strip_inbound_annotations) { qd_iterator_t *ingress_iter = 0; qd_parsed_field_t *trace = 0; qd_parsed_field_t *ingress = 0; qd_parsed_field_t *to = 0; qd_parsed_field_t *phase = 0; *link_exclusions = 0; if (in_ma && !strip_inbound_annotations) { uint32_t count = qd_parse_sub_count(in_ma); bool done = false; for (uint32_t idx = 0; idx < count && !done; idx++) { qd_parsed_field_t *sub = qd_parse_sub_key(in_ma, idx); if (!sub) continue; qd_iterator_t *iter = qd_parse_raw(sub); if (!iter) continue; if (qd_iterator_equal(iter, (unsigned char*) QD_MA_TRACE)) { trace = qd_parse_sub_value(in_ma, idx); } else if (qd_iterator_equal(iter, (unsigned char*) QD_MA_INGRESS)) { ingress = qd_parse_sub_value(in_ma, idx); } else if (qd_iterator_equal(iter, (unsigned char*) QD_MA_TO)) { to = qd_parse_sub_value(in_ma, idx); } else if (qd_iterator_equal(iter, (unsigned char*) QD_MA_PHASE)) { phase = qd_parse_sub_value(in_ma, idx); } done = trace && ingress && to && phase; } } // // QD_MA_TRACE: // If there is a trace field, append this router's ID to the trace. // If the router ID is already in the trace the msg has looped. // qd_composed_field_t *trace_field = qd_compose_subfield(0); qd_compose_start_list(trace_field); if (trace) { if (qd_parse_is_list(trace)) { // // Create a link-exclusion map for the items in the trace. This map will // contain a one-bit for each link that leads to a neighbor router that // the message has already passed through. // *link_exclusions = qd_tracemask_create(router->tracemask, trace); // // Append this router's ID to the trace. // uint32_t idx = 0; qd_parsed_field_t *trace_item = qd_parse_sub_value(trace, idx); while (trace_item) { qd_iterator_t *iter = qd_parse_raw(trace_item); qd_iterator_reset_view(iter, ITER_VIEW_ALL); qd_compose_insert_string_iterator(trace_field, iter); idx++; trace_item = qd_parse_sub_value(trace, idx); } } } qd_compose_insert_string(trace_field, node_id); qd_compose_end_list(trace_field); qd_message_set_trace_annotation(msg, trace_field); // // QD_MA_TO: // Preserve the existing value. // if (to) { qd_composed_field_t *to_field = qd_compose_subfield(0); qd_compose_insert_string_iterator(to_field, qd_parse_raw(to)); qd_message_set_to_override_annotation(msg, to_field); } // // QD_MA_PHASE: // Preserve the existing value. // if (phase) { int phase_val = qd_parse_as_int(phase); qd_message_set_phase_annotation(msg, phase_val); } // // QD_MA_INGRESS: // If there is no ingress field, annotate the ingress as // this router else keep the original field. // qd_composed_field_t *ingress_field = qd_compose_subfield(0); if (ingress && qd_parse_is_scalar(ingress)) { ingress_iter = qd_parse_raw(ingress); qd_compose_insert_string_iterator(ingress_field, ingress_iter); } else qd_compose_insert_string(ingress_field, node_id); qd_message_set_ingress_annotation(msg, ingress_field); // // Return the iterator to the ingress field _if_ it was present. // If we added the ingress, return NULL. // return ingress_iter; }
PyObject *qd_field_to_py(qd_parsed_field_t *field) { qd_python_check_lock(); PyObject *result = 0; uint8_t tag = qd_parse_tag(field); switch (tag) { case QD_AMQP_NULL: Py_INCREF(Py_None); result = Py_None; break; case QD_AMQP_BOOLEAN: case QD_AMQP_TRUE: case QD_AMQP_FALSE: result = qd_parse_as_uint(field) ? Py_True : Py_False; break; case QD_AMQP_UBYTE: case QD_AMQP_USHORT: case QD_AMQP_UINT: case QD_AMQP_SMALLUINT: case QD_AMQP_UINT0: result = PyInt_FromLong((long) qd_parse_as_uint(field)); break; case QD_AMQP_ULONG: case QD_AMQP_SMALLULONG: case QD_AMQP_ULONG0: case QD_AMQP_TIMESTAMP: result = PyLong_FromUnsignedLongLong((unsigned PY_LONG_LONG) qd_parse_as_ulong(field)); break; case QD_AMQP_BYTE: case QD_AMQP_SHORT: case QD_AMQP_INT: case QD_AMQP_SMALLINT: result = PyInt_FromLong((long) qd_parse_as_int(field)); break; case QD_AMQP_LONG: case QD_AMQP_SMALLLONG: result = PyLong_FromLongLong((PY_LONG_LONG)qd_parse_as_long(field)); break; case QD_AMQP_FLOAT: case QD_AMQP_DOUBLE: case QD_AMQP_DECIMAL32: case QD_AMQP_DECIMAL64: case QD_AMQP_DECIMAL128: case QD_AMQP_UTF32: case QD_AMQP_UUID: break; case QD_AMQP_VBIN8: case QD_AMQP_VBIN32: case QD_AMQP_STR8_UTF8: case QD_AMQP_STR32_UTF8: case QD_AMQP_SYM8: case QD_AMQP_SYM32: result = parsed_to_py_string(field); break; case QD_AMQP_LIST0: case QD_AMQP_LIST8: case QD_AMQP_LIST32: { uint32_t count = qd_parse_sub_count(field); result = PyList_New(count); for (uint32_t idx = 0; idx < count; idx++) { qd_parsed_field_t *sub = qd_parse_sub_value(field, idx); PyObject *pysub = qd_field_to_py(sub); if (pysub == 0) return 0; PyList_SetItem(result, idx, pysub); } break; } case QD_AMQP_MAP8: case QD_AMQP_MAP32: { uint32_t count = qd_parse_sub_count(field); result = PyDict_New(); for (uint32_t idx = 0; idx < count; idx++) { qd_parsed_field_t *key = qd_parse_sub_key(field, idx); qd_parsed_field_t *val = qd_parse_sub_value(field, idx); PyObject *pykey = parsed_to_py_string(key); PyObject *pyval = qd_field_to_py(val); if (pyval == 0) return 0; PyDict_SetItem(result, pykey, pyval); Py_DECREF(pykey); Py_DECREF(pyval); } break; } case QD_AMQP_ARRAY8: case QD_AMQP_ARRAY32: break; } if (!result) Py_RETURN_NONE; return result; }