Return-Path: MIME-Version: 1.0 In-Reply-To: References: <1415839007-21196-1-git-send-email-armansito@chromium.org> <1415839007-21196-2-git-send-email-armansito@chromium.org> Date: Thu, 13 Nov 2014 16:58:39 -0800 Message-ID: Subject: Re: [PATCH BlueZ v1 1/7] shared/att: Add API for encoding ATT error response. From: Arman Uguray To: Luiz Augusto von Dentz Cc: Arman Uguray , "linux-bluetooth@vger.kernel.org" Content-Type: text/plain; charset=UTF-8 Sender: linux-bluetooth-owner@vger.kernel.org List-ID: Hi Luiz, > On Thu, Nov 13, 2014 at 8:10 AM, Luiz Augusto von Dentz wrote: > Hi Arman, > > On Thu, Nov 13, 2014 at 2:36 AM, Arman Uguray wrote: >> This patch adds bt_att_encode_error_rsp which allows encoding an ATT >> protocol error response by correctly converting system errnos to ATT >> protocol error codes. struct bt_att_pdu_error_rsp is introduced to >> represent an error response PDU which can be sent directly over the >> wire once properly encoded in network order. >> --- >> src/shared/att-types.h | 12 ++++ >> src/shared/att.c | 58 +++++++++++++++++- >> src/shared/att.h | 3 + >> src/shared/gatt-client.c | 8 ++- >> src/shared/gatt-helpers.c | 8 ++- >> src/shared/gatt-server.c | 153 +++++++++++++++++++++++----------------------- >> 6 files changed, 160 insertions(+), 82 deletions(-) >> >> diff --git a/src/shared/att-types.h b/src/shared/att-types.h >> index 24bf3da..3d8829a 100644 >> --- a/src/shared/att-types.h >> +++ b/src/shared/att-types.h >> @@ -23,6 +23,10 @@ >> >> #include >> >> +#ifndef __packed >> +#define __packed __attribute__((packed)) >> +#endif >> + >> #define BT_ATT_DEFAULT_LE_MTU 23 >> >> /* ATT protocol opcodes */ >> @@ -55,6 +59,14 @@ >> #define BT_ATT_OP_HANDLE_VAL_IND 0x1D >> #define BT_ATT_OP_HANDLE_VAL_CONF 0x1E >> >> +/* Packed struct definitions for ATT protocol PDUs */ >> +/* TODO: Complete these definitions for all opcodes */ >> +struct bt_att_pdu_error_rsp { >> + uint8_t opcode; >> + uint16_t handle; >> + uint8_t ecode; >> +} __packed; >> + >> /* Special opcode to receive all requests (legacy servers) */ >> #define BT_ATT_ALL_REQUESTS 0x00 >> >> diff --git a/src/shared/att.c b/src/shared/att.c >> index 6e1e538..e831078 100644 >> --- a/src/shared/att.c >> +++ b/src/shared/att.c >> @@ -35,13 +35,22 @@ >> #include "src/shared/timeout.h" >> #include "lib/uuid.h" >> #include "src/shared/att.h" >> -#include "src/shared/att-types.h" >> >> #define ATT_MIN_PDU_LEN 1 /* At least 1 byte for the opcode. */ >> #define ATT_OP_CMD_MASK 0x40 >> #define ATT_OP_SIGNED_MASK 0x80 >> #define ATT_TIMEOUT_INTERVAL 30000 /* 30000 ms */ >> >> +/* >> + * Common Profile and Service Error Code descriptions (see Supplement to the >> + * Bluetooth Core Specification, sections 1.2 and 2). The error codes within >> + * 0xE0-0xFC are reserved for future use. The remaining 3 are defined as the >> + * following: >> + */ >> +#define BT_ERROR_CCC_IMPROPERLY_CONFIGURED 0xfd >> +#define BT_ERROR_ALREADY_IN_PROGRESS 0xfe >> +#define BT_ERROR_OUT_OF_RANGE 0xff >> + >> struct att_send_op; >> >> struct bt_att { >> @@ -1218,3 +1227,50 @@ bool bt_att_unregister_all(struct bt_att *att) >> >> return true; >> } >> + >> +static uint8_t att_ecode_from_error(int err) >> +{ >> + /* >> + * If the error fits in a single byte, treat it as an ATT protocol >> + * error as is. Since "0" is not a valid ATT protocol error code, we map >> + * that to UNLIKELY below. >> + */ >> + if (err > 0 && err < UINT8_MAX) >> + return err; >> + >> + /* >> + * Since we allow UNIX errnos, map them to appropriate ATT protocol >> + * and "Common Profile and Service" error codes. >> + */ >> + switch (err) { >> + case -ENOENT: >> + return BT_ATT_ERROR_INVALID_HANDLE; >> + case -ENOMEM: >> + return BT_ATT_ERROR_INSUFFICIENT_RESOURCES; >> + case -EALREADY: >> + return BT_ERROR_ALREADY_IN_PROGRESS; >> + case -EOVERFLOW: >> + return BT_ERROR_OUT_OF_RANGE; >> + } >> + >> + return BT_ATT_ERROR_UNLIKELY; >> +} >> + >> +bool bt_att_encode_error_rsp(uint8_t opcode, uint16_t handle, int error, >> + struct bt_att_pdu_error_rsp *pdu) >> +{ >> + uint8_t ecode; >> + >> + if (!pdu) >> + return false; >> + >> + ecode = att_ecode_from_error(error); >> + >> + memset(pdu, 0, sizeof(*pdu)); >> + >> + pdu->opcode = opcode; >> + put_le16(handle, &pdu->handle); >> + pdu->ecode = ecode; >> + >> + return true; >> +} >> diff --git a/src/shared/att.h b/src/shared/att.h >> index 1063021..a1a4821 100644 >> --- a/src/shared/att.h >> +++ b/src/shared/att.h >> @@ -76,3 +76,6 @@ unsigned int bt_att_register_disconnect(struct bt_att *att, >> bool bt_att_unregister_disconnect(struct bt_att *att, unsigned int id); >> >> bool bt_att_unregister_all(struct bt_att *att); >> + >> +bool bt_att_encode_error_rsp(uint8_t opcode, uint16_t handle, int error, >> + struct bt_att_pdu_error_rsp *pdu); > > Lets drop the encode part from the name, it should be implicit that it > will encode before sending. Also Im not sure why you have chosen to > pass the parameter as a struct, you could have passed separately by > value so encoding does not affect the caller stack, also I was > assuming that it would send the PDU after encoding it. > Ah, I see, so you're saying let's just have a bt_att_send_error_rsp that encodes and sends it. Yeah, I like that idea better, I'll upload this in a follow up. In the meantime though, I don't think this is really blocking the tools/btgatt-server patches, so if you're fine with the latest patch set, please apply them and I'll send the revised version of this patch seperately. >> diff --git a/src/shared/gatt-client.c b/src/shared/gatt-client.c >> index 30b271e..1f82048 100644 >> --- a/src/shared/gatt-client.c >> +++ b/src/shared/gatt-client.c >> @@ -1285,10 +1285,14 @@ static bool notify_data_write_ccc(struct notify_data *notify_data, bool enable, >> >> static uint8_t process_error(const void *pdu, uint16_t length) >> { >> - if (!pdu || length != 4) >> + const struct bt_att_pdu_error_rsp *error_pdu; >> + >> + if (!pdu || length != sizeof(struct bt_att_pdu_error_rsp)) >> return 0; >> >> - return ((uint8_t *) pdu)[3]; >> + error_pdu = pdu; >> + >> + return error_pdu->ecode; >> } >> >> static void enable_ccc_callback(uint8_t opcode, const void *pdu, >> diff --git a/src/shared/gatt-helpers.c b/src/shared/gatt-helpers.c >> index 220d0eb..9c8a3ba 100644 >> --- a/src/shared/gatt-helpers.c >> +++ b/src/shared/gatt-helpers.c >> @@ -430,10 +430,14 @@ static void destroy_mtu_op(void *user_data) >> >> static uint8_t process_error(const void *pdu, uint16_t length) >> { >> - if (!pdu || length != 4) >> + const struct bt_att_pdu_error_rsp *error_pdu; >> + >> + if (!pdu || length != sizeof(struct bt_att_pdu_error_rsp)) >> return 0; >> >> - return ((uint8_t *) pdu)[3]; >> + error_pdu = pdu; >> + >> + return error_pdu->ecode; >> } >> >> static void mtu_cb(uint8_t opcode, const void *pdu, uint16_t length, >> diff --git a/src/shared/gatt-server.c b/src/shared/gatt-server.c >> index 2ca318a..3262992 100644 >> --- a/src/shared/gatt-server.c >> +++ b/src/shared/gatt-server.c >> @@ -22,6 +22,7 @@ >> */ >> >> #include >> +#include >> >> #include "src/shared/att.h" >> #include "lib/uuid.h" >> @@ -29,7 +30,6 @@ >> #include "src/shared/gatt-db.h" >> #include "src/shared/gatt-server.h" >> #include "src/shared/gatt-helpers.h" >> -#include "src/shared/att-types.h" >> #include "src/shared/util.h" >> >> #ifndef MAX >> @@ -133,22 +133,6 @@ static void bt_gatt_server_free(struct bt_gatt_server *server) >> free(server); >> } >> >> -static uint8_t att_ecode_from_error(int err) >> -{ >> - if (err < 0 || err > UINT8_MAX) >> - return 0xff; >> - >> - return err; >> -} >> - >> -static void encode_error_rsp(uint8_t opcode, uint16_t handle, uint8_t ecode, >> - uint8_t pdu[4]) >> -{ >> - pdu[0] = opcode; >> - pdu[3] = ecode; >> - put_le16(handle, pdu + 1); >> -} >> - >> static bool get_uuid_le(const uint8_t *uuid, size_t len, bt_uuid_t *out_uuid) >> { >> uint128_t u128; >> @@ -315,8 +299,9 @@ static void read_by_grp_type_cb(uint8_t opcode, const void *pdu, >> >> error: >> rsp_opcode = BT_ATT_OP_ERROR_RSP; >> - rsp_len = 4; >> - encode_error_rsp(opcode, ehandle, ecode, rsp_pdu); >> + rsp_len = sizeof(struct bt_att_pdu_error_rsp); >> + bt_att_encode_error_rsp(opcode, ehandle, ecode, >> + (struct bt_att_pdu_error_rsp *) rsp_pdu); >> >> done: >> queue_destroy(q, NULL); >> @@ -355,13 +340,13 @@ static void read_by_type_read_complete_cb(struct gatt_db_attribute *attr, >> >> /* Terminate the operation if there was an error */ >> if (err) { >> - uint8_t pdu[4]; >> - uint8_t att_ecode = att_ecode_from_error(err); >> + struct bt_att_pdu_error_rsp error_pdu; >> >> - encode_error_rsp(BT_ATT_OP_READ_BY_TYPE_REQ, handle, att_ecode, >> - pdu); >> - bt_att_send(server->att, BT_ATT_OP_ERROR_RSP, pdu, 4, NULL, >> - NULL, NULL); >> + bt_att_encode_error_rsp(BT_ATT_OP_READ_BY_TYPE_REQ, handle, err, >> + &error_pdu); >> + bt_att_send(server->att, BT_ATT_OP_ERROR_RSP, &error_pdu, >> + sizeof(error_pdu), >> + NULL, NULL, NULL); >> async_read_op_destroy(op); >> return; >> } >> @@ -437,8 +422,9 @@ static void process_read_by_type(struct async_read_op *op) >> error: >> ehandle = gatt_db_attribute_get_handle(attr); >> rsp_opcode = BT_ATT_OP_ERROR_RSP; >> - rsp_len = 4; >> - encode_error_rsp(BT_ATT_OP_READ_BY_TYPE_REQ, ehandle, ecode, op->pdu); >> + rsp_len = sizeof(struct bt_att_pdu_error_rsp); >> + bt_att_encode_error_rsp(BT_ATT_OP_READ_BY_TYPE_REQ, ehandle, ecode, >> + (struct bt_att_pdu_error_rsp *) op->pdu); >> >> done: >> bt_att_send(server->att, rsp_opcode, op->pdu, rsp_len, NULL, >> @@ -452,7 +438,7 @@ static void read_by_type_cb(uint8_t opcode, const void *pdu, >> struct bt_gatt_server *server = user_data; >> uint16_t start, end; >> bt_uuid_t type; >> - uint8_t rsp_pdu[4]; >> + struct bt_att_pdu_error_rsp error_pdu; >> uint16_t ehandle = 0; >> uint8_t ecode; >> struct queue *q = NULL; >> @@ -524,9 +510,10 @@ static void read_by_type_cb(uint8_t opcode, const void *pdu, >> return; >> >> error: >> - encode_error_rsp(opcode, ehandle, ecode, rsp_pdu); >> + bt_att_encode_error_rsp(opcode, ehandle, ecode, &error_pdu); >> queue_destroy(q, NULL); >> - bt_att_send(server->att, BT_ATT_OP_ERROR_RSP, rsp_pdu, 4, >> + bt_att_send(server->att, BT_ATT_OP_ERROR_RSP, &error_pdu, >> + sizeof(error_pdu), >> NULL, NULL, NULL); >> } >> >> @@ -665,8 +652,9 @@ static void find_info_cb(uint8_t opcode, const void *pdu, >> >> error: >> rsp_opcode = BT_ATT_OP_ERROR_RSP; >> - rsp_len = 4; >> - encode_error_rsp(opcode, ehandle, ecode, rsp_pdu); >> + rsp_len = sizeof(struct bt_att_pdu_error_rsp); >> + bt_att_encode_error_rsp(opcode, ehandle, ecode, >> + (struct bt_att_pdu_error_rsp *) rsp_pdu); >> >> done: >> queue_destroy(q, NULL); >> @@ -697,11 +685,11 @@ static void write_complete_cb(struct gatt_db_attribute *attr, int err, >> handle = gatt_db_attribute_get_handle(attr); >> >> if (err) { >> - uint8_t rsp_pdu[4]; >> - uint8_t att_ecode = att_ecode_from_error(err); >> + struct bt_att_pdu_error_rsp error_pdu; >> >> - encode_error_rsp(op->opcode, handle, att_ecode, rsp_pdu); >> - bt_att_send(server->att, BT_ATT_OP_ERROR_RSP, rsp_pdu, 4, >> + bt_att_encode_error_rsp(op->opcode, handle, err, &error_pdu); >> + bt_att_send(server->att, BT_ATT_OP_ERROR_RSP, &error_pdu, >> + sizeof(error_pdu), >> NULL, NULL, NULL); >> } else { >> bt_att_send(server->att, BT_ATT_OP_WRITE_RSP, NULL, 0, >> @@ -717,7 +705,7 @@ static void write_cb(uint8_t opcode, const void *pdu, >> struct bt_gatt_server *server = user_data; >> struct gatt_db_attribute *attr; >> uint16_t handle = 0; >> - uint8_t rsp_pdu[4]; >> + struct bt_att_pdu_error_rsp error_pdu; >> struct async_write_op *op = NULL; >> uint8_t ecode; >> uint32_t perm; >> @@ -777,8 +765,9 @@ error: >> if (opcode == BT_ATT_OP_WRITE_CMD) >> return; >> >> - encode_error_rsp(opcode, handle, ecode, rsp_pdu); >> - bt_att_send(server->att, BT_ATT_OP_ERROR_RSP, rsp_pdu, 4, >> + bt_att_encode_error_rsp(opcode, handle, ecode, &error_pdu); >> + bt_att_send(server->att, BT_ATT_OP_ERROR_RSP, &error_pdu, >> + sizeof(error_pdu), >> NULL, NULL, NULL); >> } >> >> @@ -824,12 +813,12 @@ static void read_complete_cb(struct gatt_db_attribute *attr, int err, >> handle = gatt_db_attribute_get_handle(attr); >> >> if (err) { >> - uint8_t pdu[4]; >> - uint8_t att_ecode = att_ecode_from_error(err); >> + struct bt_att_pdu_error_rsp error_pdu; >> >> - encode_error_rsp(op->opcode, handle, att_ecode, pdu); >> - bt_att_send(server->att, BT_ATT_OP_ERROR_RSP, pdu, 4, NULL, >> - NULL, NULL); >> + bt_att_encode_error_rsp(op->opcode, handle, err, &error_pdu); >> + bt_att_send(server->att, BT_ATT_OP_ERROR_RSP, &error_pdu, >> + sizeof(error_pdu), >> + NULL, NULL, NULL); >> async_read_op_destroy(op); >> return; >> } >> @@ -846,7 +835,7 @@ static void handle_read_req(struct bt_gatt_server *server, uint8_t opcode, >> uint16_t handle, >> uint16_t offset) >> { >> - uint8_t error_pdu[4]; >> + struct bt_att_pdu_error_rsp error_pdu; >> struct gatt_db_attribute *attr; >> uint8_t ecode; >> uint32_t perm; >> @@ -898,9 +887,10 @@ error: >> if (op) >> async_read_op_destroy(op); >> >> - encode_error_rsp(opcode, handle, ecode, error_pdu); >> - bt_att_send(server->att, BT_ATT_OP_ERROR_RSP, error_pdu, 4, NULL, NULL, >> - NULL); >> + bt_att_encode_error_rsp(opcode, handle, ecode, &error_pdu); >> + bt_att_send(server->att, BT_ATT_OP_ERROR_RSP, &error_pdu, >> + sizeof(error_pdu), >> + NULL, NULL, NULL); >> } >> >> static void read_cb(uint8_t opcode, const void *pdu, >> @@ -910,11 +900,13 @@ static void read_cb(uint8_t opcode, const void *pdu, >> uint16_t handle; >> >> if (length != 2) { >> - uint8_t pdu[4]; >> + struct bt_att_pdu_error_rsp error_pdu; >> >> - encode_error_rsp(opcode, 0, BT_ATT_ERROR_INVALID_PDU, pdu); >> - bt_att_send(server->att, BT_ATT_OP_ERROR_RSP, pdu, 4, NULL, >> - NULL, NULL); >> + bt_att_encode_error_rsp(opcode, 0, BT_ATT_ERROR_INVALID_PDU, >> + &error_pdu); >> + bt_att_send(server->att, BT_ATT_OP_ERROR_RSP, &error_pdu, >> + sizeof(error_pdu), >> + NULL, NULL, NULL); >> return; >> } >> >> @@ -930,11 +922,13 @@ static void read_blob_cb(uint8_t opcode, const void *pdu, >> uint16_t handle, offset; >> >> if (length != 4) { >> - uint8_t pdu[4]; >> + struct bt_att_pdu_error_rsp error_pdu; >> >> - encode_error_rsp(opcode, 0, BT_ATT_ERROR_INVALID_PDU, pdu); >> - bt_att_send(server->att, BT_ATT_OP_ERROR_RSP, pdu, 4, NULL, >> - NULL, NULL); >> + bt_att_encode_error_rsp(opcode, 0, BT_ATT_ERROR_INVALID_PDU, >> + &error_pdu); >> + bt_att_send(server->att, BT_ATT_OP_ERROR_RSP, &error_pdu, >> + sizeof(error_pdu), >> + NULL, NULL, NULL); >> return; >> } >> >> @@ -1031,8 +1025,9 @@ error: >> prep_write_data_destroy(prep_data); >> >> rsp_opcode = BT_ATT_OP_ERROR_RSP; >> - rsp_len = 4; >> - encode_error_rsp(opcode, handle, ecode, rsp_pdu); >> + rsp_len = sizeof(struct bt_att_pdu_error_rsp); >> + bt_att_encode_error_rsp(opcode, handle, ecode, >> + (struct bt_att_pdu_error_rsp *) rsp_pdu); >> >> done: >> bt_att_send(server->att, rsp_opcode, rsp_pdu, rsp_len, NULL, NULL, >> @@ -1040,30 +1035,29 @@ done: >> } >> >> static void exec_next_prep_write(struct bt_gatt_server *server, >> - uint16_t ehandle, uint8_t att_ecode); >> + uint16_t ehandle, int err); >> >> static void exec_write_complete_cb(struct gatt_db_attribute *attr, int err, >> void *user_data) >> { >> struct bt_gatt_server *server = user_data; >> uint16_t handle = gatt_db_attribute_get_handle(attr); >> - uint16_t att_ecode = att_ecode_from_error(err); >> >> - exec_next_prep_write(server, handle, att_ecode); >> + exec_next_prep_write(server, handle, err); >> } >> >> static void exec_next_prep_write(struct bt_gatt_server *server, >> - uint16_t ehandle, uint8_t att_ecode) >> + uint16_t ehandle, int err) >> { >> struct prep_write_data *next = NULL; >> uint8_t rsp_opcode = BT_ATT_OP_EXEC_WRITE_RSP; >> - uint8_t error_pdu[4]; >> + struct bt_att_pdu_error_rsp error_pdu; >> uint8_t *rsp_pdu = NULL; >> uint16_t rsp_len = 0; >> struct gatt_db_attribute *attr; >> bool status; >> >> - if (att_ecode) >> + if (err) >> goto error; >> >> next = queue_pop_head(server->prep_queue); >> @@ -1072,7 +1066,7 @@ static void exec_next_prep_write(struct bt_gatt_server *server, >> >> attr = gatt_db_get_attribute(server->db, next->handle); >> if (!attr) { >> - att_ecode = BT_ATT_ERROR_UNLIKELY; >> + err = BT_ATT_ERROR_UNLIKELY; >> goto error; >> } >> >> @@ -1086,14 +1080,15 @@ static void exec_next_prep_write(struct bt_gatt_server *server, >> if (status) >> return; >> >> - att_ecode = BT_ATT_ERROR_UNLIKELY; >> + err = BT_ATT_ERROR_UNLIKELY; >> >> error: >> rsp_opcode = BT_ATT_OP_ERROR_RSP; >> - rsp_len = 4; >> - rsp_pdu = error_pdu; >> - encode_error_rsp(BT_ATT_OP_EXEC_WRITE_REQ, ehandle, att_ecode, rsp_pdu); >> + rsp_len = sizeof(error_pdu); >> + bt_att_encode_error_rsp(BT_ATT_OP_EXEC_WRITE_REQ, ehandle, err, >> + &error_pdu); >> >> + rsp_pdu = (uint8_t *) &error_pdu; >> done: >> bt_att_send(server->att, rsp_opcode, rsp_pdu, rsp_len, NULL, NULL, >> NULL); >> @@ -1106,7 +1101,7 @@ static void exec_write_cb(uint8_t opcode, const void *pdu, >> uint8_t flags; >> uint8_t ecode; >> uint8_t rsp_opcode; >> - uint8_t error_pdu[4]; >> + struct bt_att_pdu_error_rsp error_pdu; >> uint8_t *rsp_pdu = NULL; >> uint16_t rsp_len = 0; >> bool write; >> @@ -1143,9 +1138,10 @@ static void exec_write_cb(uint8_t opcode, const void *pdu, >> >> error: >> rsp_opcode = BT_ATT_OP_ERROR_RSP; >> - rsp_len = 4; >> - rsp_pdu = error_pdu; >> - encode_error_rsp(opcode, 0, ecode, rsp_pdu); >> + rsp_len = sizeof(error_pdu); >> + bt_att_encode_error_rsp(opcode, 0, ecode, &error_pdu); >> + >> + rsp_pdu = (uint8_t *) &error_pdu; >> >> done: >> bt_att_send(server->att, rsp_opcode, rsp_pdu, rsp_len, NULL, NULL, >> @@ -1158,12 +1154,15 @@ static void exchange_mtu_cb(uint8_t opcode, const void *pdu, >> struct bt_gatt_server *server = user_data; >> uint16_t client_rx_mtu; >> uint16_t final_mtu; >> - uint8_t rsp_pdu[4]; >> + uint8_t rsp_pdu[2]; >> >> if (length != 2) { >> - encode_error_rsp(opcode, 0, BT_ATT_ERROR_INVALID_PDU, rsp_pdu); >> - bt_att_send(server->att, BT_ATT_OP_ERROR_RSP, rsp_pdu, >> - sizeof(rsp_pdu), NULL, NULL, NULL); >> + struct bt_att_pdu_error_rsp error_pdu; >> + >> + bt_att_encode_error_rsp(opcode, 0, BT_ATT_ERROR_INVALID_PDU, >> + &error_pdu); >> + bt_att_send(server->att, BT_ATT_OP_ERROR_RSP, &error_pdu, >> + sizeof(error_pdu), NULL, NULL, NULL); >> return; >> } >> >> -- >> 2.1.0.rc2.206.gedb03e5 >> >> -- >> To unsubscribe from this list: send the line "unsubscribe linux-bluetooth" in >> the body of a message to majordomo@vger.kernel.org >> More majordomo info at http://vger.kernel.org/majordomo-info.html > > > > -- > Luiz Augusto von Dentz > -- > To unsubscribe from this list: send the line "unsubscribe linux-bluetooth" in > the body of a message to majordomo@vger.kernel.org > More majordomo info at http://vger.kernel.org/majordomo-info.html Thanks, Arman