| // SPDX-License-Identifier: GPL-2.0-or-later |
| /* |
| * |
| * BlueZ - Bluetooth protocol stack for Linux |
| * |
| * Copyright (C) 2010 Nokia Corporation |
| * Copyright (C) 2010 Marcel Holtmann <marcel@holtmann.org> |
| * |
| * |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include <config.h> |
| #endif |
| |
| #include <errno.h> |
| #include <stdint.h> |
| #include <stdlib.h> |
| |
| #include <glib.h> |
| |
| #include "lib/bluetooth.h" |
| #include "lib/uuid.h" |
| |
| #include "src/shared/util.h" |
| #include "att.h" |
| |
| static inline void put_uuid_le(const bt_uuid_t *src, void *dst) |
| { |
| if (src->type == BT_UUID16) |
| put_le16(src->value.u16, dst); |
| else |
| /* Convert from 128-bit BE to LE */ |
| bswap_128(&src->value.u128, dst); |
| } |
| |
| const char *att_ecode2str(uint8_t status) |
| { |
| switch (status) { |
| case ATT_ECODE_INVALID_HANDLE: |
| return "Invalid handle"; |
| case ATT_ECODE_READ_NOT_PERM: |
| return "Attribute can't be read"; |
| case ATT_ECODE_WRITE_NOT_PERM: |
| return "Attribute can't be written"; |
| case ATT_ECODE_INVALID_PDU: |
| return "Attribute PDU was invalid"; |
| case ATT_ECODE_AUTHENTICATION: |
| return "Attribute requires authentication before read/write"; |
| case ATT_ECODE_REQ_NOT_SUPP: |
| return "Server doesn't support the request received"; |
| case ATT_ECODE_INVALID_OFFSET: |
| return "Offset past the end of the attribute"; |
| case ATT_ECODE_AUTHORIZATION: |
| return "Attribute requires authorization before read/write"; |
| case ATT_ECODE_PREP_QUEUE_FULL: |
| return "Too many prepare writes have been queued"; |
| case ATT_ECODE_ATTR_NOT_FOUND: |
| return "No attribute found within the given range"; |
| case ATT_ECODE_ATTR_NOT_LONG: |
| return "Attribute can't be read/written using Read Blob Req"; |
| case ATT_ECODE_INSUFF_ENCR_KEY_SIZE: |
| return "Encryption Key Size is insufficient"; |
| case ATT_ECODE_INVAL_ATTR_VALUE_LEN: |
| return "Attribute value length is invalid"; |
| case ATT_ECODE_UNLIKELY: |
| return "Request attribute has encountered an unlikely error"; |
| case ATT_ECODE_INSUFF_ENC: |
| return "Encryption required before read/write"; |
| case ATT_ECODE_UNSUPP_GRP_TYPE: |
| return "Attribute type is not a supported grouping attribute"; |
| case ATT_ECODE_INSUFF_RESOURCES: |
| return "Insufficient Resources to complete the request"; |
| case ATT_ECODE_IO: |
| return "Internal application error: I/O"; |
| case ATT_ECODE_TIMEOUT: |
| return "A timeout occured"; |
| case ATT_ECODE_ABORTED: |
| return "The operation was aborted"; |
| default: |
| return "Unexpected error code"; |
| } |
| } |
| |
| void att_data_list_free(struct att_data_list *list) |
| { |
| if (list == NULL) |
| return; |
| |
| if (list->data) { |
| int i; |
| for (i = 0; i < list->num; i++) |
| g_free(list->data[i]); |
| } |
| |
| g_free(list->data); |
| g_free(list); |
| } |
| |
| struct att_data_list *att_data_list_alloc(uint16_t num, uint16_t len) |
| { |
| struct att_data_list *list; |
| int i; |
| |
| if (len > UINT8_MAX) |
| return NULL; |
| |
| list = g_new0(struct att_data_list, 1); |
| list->len = len; |
| list->num = num; |
| |
| list->data = g_malloc0(sizeof(uint8_t *) * num); |
| |
| for (i = 0; i < num; i++) |
| list->data[i] = g_malloc0(sizeof(uint8_t) * len); |
| |
| return list; |
| } |
| |
| static void get_uuid(uint8_t type, const void *val, bt_uuid_t *uuid) |
| { |
| if (type == BT_UUID16) |
| bt_uuid16_create(uuid, get_le16(val)); |
| else { |
| uint128_t u128; |
| |
| /* Convert from 128-bit LE to BE */ |
| bswap_128(val, &u128); |
| bt_uuid128_create(uuid, u128); |
| } |
| } |
| |
| uint16_t enc_read_by_grp_req(uint16_t start, uint16_t end, bt_uuid_t *uuid, |
| uint8_t *pdu, size_t len) |
| { |
| uint16_t uuid_len; |
| |
| if (!uuid) |
| return 0; |
| |
| if (uuid->type == BT_UUID16) |
| uuid_len = 2; |
| else if (uuid->type == BT_UUID128) |
| uuid_len = 16; |
| else |
| return 0; |
| |
| /* Attribute Opcode (1 octet) */ |
| pdu[0] = ATT_OP_READ_BY_GROUP_REQ; |
| /* Starting Handle (2 octets) */ |
| put_le16(start, &pdu[1]); |
| /* Ending Handle (2 octets) */ |
| put_le16(end, &pdu[3]); |
| /* Attribute Group Type (2 or 16 octet UUID) */ |
| put_uuid_le(uuid, &pdu[5]); |
| |
| return 5 + uuid_len; |
| } |
| |
| uint16_t dec_read_by_grp_req(const uint8_t *pdu, size_t len, uint16_t *start, |
| uint16_t *end, bt_uuid_t *uuid) |
| { |
| const size_t min_len = sizeof(pdu[0]) + sizeof(*start) + sizeof(*end); |
| uint8_t type; |
| |
| if (pdu == NULL) |
| return 0; |
| |
| if (start == NULL || end == NULL || uuid == NULL) |
| return 0; |
| |
| if (pdu[0] != ATT_OP_READ_BY_GROUP_REQ) |
| return 0; |
| |
| if (len == (min_len + 2)) |
| type = BT_UUID16; |
| else if (len == (min_len + 16)) |
| type = BT_UUID128; |
| else |
| return 0; |
| |
| *start = get_le16(&pdu[1]); |
| *end = get_le16(&pdu[3]); |
| |
| get_uuid(type, &pdu[5], uuid); |
| |
| return len; |
| } |
| |
| uint16_t enc_read_by_grp_resp(struct att_data_list *list, uint8_t *pdu, |
| size_t len) |
| { |
| int i; |
| uint16_t w; |
| uint8_t *ptr; |
| |
| if (list == NULL) |
| return 0; |
| |
| if (len < list->len + sizeof(uint8_t) * 2) |
| return 0; |
| |
| pdu[0] = ATT_OP_READ_BY_GROUP_RESP; |
| pdu[1] = list->len; |
| |
| ptr = &pdu[2]; |
| |
| for (i = 0, w = 2; i < list->num && w + list->len <= len; i++) { |
| memcpy(ptr, list->data[i], list->len); |
| ptr += list->len; |
| w += list->len; |
| } |
| |
| return w; |
| } |
| |
| struct att_data_list *dec_read_by_grp_resp(const uint8_t *pdu, size_t len) |
| { |
| struct att_data_list *list; |
| const uint8_t *ptr; |
| uint16_t elen, num; |
| int i; |
| |
| if (pdu[0] != ATT_OP_READ_BY_GROUP_RESP) |
| return NULL; |
| |
| /* PDU must contain at least: |
| * - Attribute Opcode (1 octet) |
| * - Length (1 octet) |
| * - Attribute Data List (at least one entry): |
| * - Attribute Handle (2 octets) |
| * - End Group Handle (2 octets) |
| * - Attribute Value (at least 1 octet) */ |
| if (len < 7) |
| return NULL; |
| |
| elen = pdu[1]; |
| /* Minimum Attribute Data List size */ |
| if (elen < 5) |
| return NULL; |
| |
| /* Reject incomplete Attribute Data List */ |
| if ((len - 2) % elen) |
| return NULL; |
| |
| num = (len - 2) / elen; |
| list = att_data_list_alloc(num, elen); |
| if (list == NULL) |
| return NULL; |
| |
| ptr = &pdu[2]; |
| |
| for (i = 0; i < num; i++) { |
| memcpy(list->data[i], ptr, list->len); |
| ptr += list->len; |
| } |
| |
| return list; |
| } |
| |
| uint16_t enc_find_by_type_req(uint16_t start, uint16_t end, bt_uuid_t *uuid, |
| const uint8_t *value, size_t vlen, |
| uint8_t *pdu, size_t len) |
| { |
| uint16_t min_len = sizeof(pdu[0]) + sizeof(start) + sizeof(end) + |
| sizeof(uint16_t); |
| |
| if (pdu == NULL) |
| return 0; |
| |
| if (!uuid) |
| return 0; |
| |
| if (uuid->type != BT_UUID16) |
| return 0; |
| |
| if (vlen > len - min_len) |
| vlen = len - min_len; |
| |
| pdu[0] = ATT_OP_FIND_BY_TYPE_REQ; |
| put_le16(start, &pdu[1]); |
| put_le16(end, &pdu[3]); |
| put_le16(uuid->value.u16, &pdu[5]); |
| |
| if (vlen > 0) { |
| memcpy(&pdu[7], value, vlen); |
| return min_len + vlen; |
| } |
| |
| return min_len; |
| } |
| |
| uint16_t dec_find_by_type_req(const uint8_t *pdu, size_t len, uint16_t *start, |
| uint16_t *end, bt_uuid_t *uuid, |
| uint8_t *value, size_t *vlen) |
| { |
| if (pdu == NULL) |
| return 0; |
| |
| if (len < 7) |
| return 0; |
| |
| /* Attribute Opcode (1 octet) */ |
| if (pdu[0] != ATT_OP_FIND_BY_TYPE_REQ) |
| return 0; |
| |
| /* First requested handle number (2 octets) */ |
| *start = get_le16(&pdu[1]); |
| /* Last requested handle number (2 octets) */ |
| *end = get_le16(&pdu[3]); |
| /* 16-bit UUID to find (2 octets) */ |
| bt_uuid16_create(uuid, get_le16(&pdu[5])); |
| |
| /* Attribute value to find */ |
| *vlen = len - 7; |
| if (*vlen > 0) |
| memcpy(value, pdu + 7, *vlen); |
| |
| return len; |
| } |
| |
| uint16_t enc_find_by_type_resp(GSList *matches, uint8_t *pdu, size_t len) |
| { |
| GSList *l; |
| uint16_t offset; |
| |
| if (!pdu) |
| return 0; |
| |
| pdu[0] = ATT_OP_FIND_BY_TYPE_RESP; |
| |
| for (l = matches, offset = 1; |
| l && len >= (offset + sizeof(uint16_t) * 2); |
| l = l->next, offset += sizeof(uint16_t) * 2) { |
| struct att_range *range = l->data; |
| |
| put_le16(range->start, &pdu[offset]); |
| put_le16(range->end, &pdu[offset + 2]); |
| } |
| |
| return offset; |
| } |
| |
| GSList *dec_find_by_type_resp(const uint8_t *pdu, size_t len) |
| { |
| struct att_range *range; |
| GSList *matches; |
| off_t offset; |
| |
| /* PDU should contain at least: |
| * - Attribute Opcode (1 octet) |
| * - Handles Information List (at least one entry): |
| * - Found Attribute Handle (2 octets) |
| * - Group End Handle (2 octets) */ |
| if (pdu == NULL || len < 5) |
| return NULL; |
| |
| if (pdu[0] != ATT_OP_FIND_BY_TYPE_RESP) |
| return NULL; |
| |
| /* Reject incomplete Handles Information List */ |
| if ((len - 1) % 4) |
| return NULL; |
| |
| for (offset = 1, matches = NULL; |
| len >= (offset + sizeof(uint16_t) * 2); |
| offset += sizeof(uint16_t) * 2) { |
| range = g_new0(struct att_range, 1); |
| range->start = get_le16(&pdu[offset]); |
| range->end = get_le16(&pdu[offset + 2]); |
| |
| matches = g_slist_append(matches, range); |
| } |
| |
| return matches; |
| } |
| |
| uint16_t enc_read_by_type_req(uint16_t start, uint16_t end, bt_uuid_t *uuid, |
| uint8_t *pdu, size_t len) |
| { |
| uint16_t uuid_len; |
| |
| if (!uuid) |
| return 0; |
| |
| if (uuid->type == BT_UUID16) |
| uuid_len = 2; |
| else if (uuid->type == BT_UUID128) |
| uuid_len = 16; |
| else |
| return 0; |
| |
| /* Attribute Opcode (1 octet) */ |
| pdu[0] = ATT_OP_READ_BY_TYPE_REQ; |
| /* Starting Handle (2 octets) */ |
| put_le16(start, &pdu[1]); |
| /* Ending Handle (2 octets) */ |
| put_le16(end, &pdu[3]); |
| /* Attribute Type (2 or 16 octet UUID) */ |
| put_uuid_le(uuid, &pdu[5]); |
| |
| return 5 + uuid_len; |
| } |
| |
| uint16_t dec_read_by_type_req(const uint8_t *pdu, size_t len, uint16_t *start, |
| uint16_t *end, bt_uuid_t *uuid) |
| { |
| const size_t min_len = sizeof(pdu[0]) + sizeof(*start) + sizeof(*end); |
| uint8_t type; |
| |
| if (pdu == NULL) |
| return 0; |
| |
| if (start == NULL || end == NULL || uuid == NULL) |
| return 0; |
| |
| if (len == (min_len + 2)) |
| type = BT_UUID16; |
| else if (len == (min_len + 16)) |
| type = BT_UUID128; |
| else |
| return 0; |
| |
| if (pdu[0] != ATT_OP_READ_BY_TYPE_REQ) |
| return 0; |
| |
| *start = get_le16(&pdu[1]); |
| *end = get_le16(&pdu[3]); |
| |
| get_uuid(type, &pdu[5], uuid); |
| |
| return len; |
| } |
| |
| uint16_t enc_read_by_type_resp(struct att_data_list *list, uint8_t *pdu, |
| size_t len) |
| { |
| uint8_t *ptr; |
| size_t i, w, l; |
| |
| if (list == NULL) |
| return 0; |
| |
| if (pdu == NULL) |
| return 0; |
| |
| l = MIN(len - 2, list->len); |
| |
| pdu[0] = ATT_OP_READ_BY_TYPE_RESP; |
| pdu[1] = l; |
| ptr = &pdu[2]; |
| |
| for (i = 0, w = 2; i < list->num && w + l <= len; i++) { |
| memcpy(ptr, list->data[i], l); |
| ptr += l; |
| w += l; |
| } |
| |
| return w; |
| } |
| |
| struct att_data_list *dec_read_by_type_resp(const uint8_t *pdu, size_t len) |
| { |
| struct att_data_list *list; |
| const uint8_t *ptr; |
| uint16_t elen, num; |
| int i; |
| |
| if (pdu[0] != ATT_OP_READ_BY_TYPE_RESP) |
| return NULL; |
| |
| /* PDU must contain at least: |
| * - Attribute Opcode (1 octet) |
| * - Length (1 octet) |
| * - Attribute Data List (at least one entry): |
| * - Attribute Handle (2 octets) |
| * - Attribute Value (at least 1 octet) */ |
| if (len < 5) |
| return NULL; |
| |
| elen = pdu[1]; |
| /* Minimum Attribute Data List size */ |
| if (elen < 3) |
| return NULL; |
| |
| /* Reject incomplete Attribute Data List */ |
| if ((len - 2) % elen) |
| return NULL; |
| |
| num = (len - 2) / elen; |
| list = att_data_list_alloc(num, elen); |
| if (list == NULL) |
| return NULL; |
| |
| ptr = &pdu[2]; |
| |
| for (i = 0; i < num; i++) { |
| memcpy(list->data[i], ptr, list->len); |
| ptr += list->len; |
| } |
| |
| return list; |
| } |
| |
| uint16_t enc_write_cmd(uint16_t handle, const uint8_t *value, size_t vlen, |
| uint8_t *pdu, size_t len) |
| { |
| const uint16_t min_len = sizeof(pdu[0]) + sizeof(handle); |
| |
| if (pdu == NULL) |
| return 0; |
| |
| if (vlen > len - min_len) |
| vlen = len - min_len; |
| |
| pdu[0] = ATT_OP_WRITE_CMD; |
| put_le16(handle, &pdu[1]); |
| |
| if (vlen > 0) { |
| memcpy(&pdu[3], value, vlen); |
| return min_len + vlen; |
| } |
| |
| return min_len; |
| } |
| |
| uint16_t dec_write_cmd(const uint8_t *pdu, size_t len, uint16_t *handle, |
| uint8_t *value, size_t *vlen) |
| { |
| const uint16_t min_len = sizeof(pdu[0]) + sizeof(*handle); |
| |
| if (pdu == NULL) |
| return 0; |
| |
| if (value == NULL || vlen == NULL || handle == NULL) |
| return 0; |
| |
| if (len < min_len) |
| return 0; |
| |
| if (pdu[0] != ATT_OP_WRITE_CMD) |
| return 0; |
| |
| *handle = get_le16(&pdu[1]); |
| memcpy(value, pdu + min_len, len - min_len); |
| *vlen = len - min_len; |
| |
| return len; |
| } |
| |
| uint16_t enc_signed_write_cmd(uint16_t handle, const uint8_t *value, |
| size_t vlen, struct bt_crypto *crypto, |
| const uint8_t csrk[16], |
| uint32_t sign_cnt, |
| uint8_t *pdu, size_t len) |
| { |
| const uint16_t hdr_len = sizeof(pdu[0]) + sizeof(handle); |
| const uint16_t min_len = hdr_len + ATT_SIGNATURE_LEN; |
| |
| if (pdu == NULL) |
| return 0; |
| |
| if (vlen > len - min_len) |
| vlen = len - min_len; |
| |
| pdu[0] = ATT_OP_SIGNED_WRITE_CMD; |
| put_le16(handle, &pdu[1]); |
| |
| if (vlen > 0) |
| memcpy(&pdu[hdr_len], value, vlen); |
| |
| if (!bt_crypto_sign_att(crypto, csrk, pdu, hdr_len + vlen, sign_cnt, |
| &pdu[hdr_len + vlen])) |
| return 0; |
| |
| return min_len + vlen; |
| } |
| |
| uint16_t dec_signed_write_cmd(const uint8_t *pdu, size_t len, |
| uint16_t *handle, |
| uint8_t *value, size_t *vlen, |
| uint8_t signature[12]) |
| { |
| const uint16_t hdr_len = sizeof(pdu[0]) + sizeof(*handle); |
| const uint16_t min_len = hdr_len + ATT_SIGNATURE_LEN; |
| |
| |
| if (pdu == NULL) |
| return 0; |
| |
| if (value == NULL || vlen == NULL || handle == NULL) |
| return 0; |
| |
| if (len < min_len) |
| return 0; |
| |
| if (pdu[0] != ATT_OP_SIGNED_WRITE_CMD) |
| return 0; |
| |
| *vlen = len - min_len; |
| *handle = get_le16(&pdu[1]); |
| memcpy(value, pdu + hdr_len, *vlen); |
| |
| memcpy(signature, pdu + hdr_len + *vlen, ATT_SIGNATURE_LEN); |
| |
| return len; |
| } |
| |
| uint16_t enc_write_req(uint16_t handle, const uint8_t *value, size_t vlen, |
| uint8_t *pdu, size_t len) |
| { |
| const uint16_t min_len = sizeof(pdu[0]) + sizeof(handle); |
| |
| if (pdu == NULL) |
| return 0; |
| |
| if (vlen > len - min_len) |
| vlen = len - min_len; |
| |
| pdu[0] = ATT_OP_WRITE_REQ; |
| put_le16(handle, &pdu[1]); |
| |
| if (vlen > 0) { |
| memcpy(&pdu[3], value, vlen); |
| return min_len + vlen; |
| } |
| |
| return min_len; |
| } |
| |
| uint16_t dec_write_req(const uint8_t *pdu, size_t len, uint16_t *handle, |
| uint8_t *value, size_t *vlen) |
| { |
| const uint16_t min_len = sizeof(pdu[0]) + sizeof(*handle); |
| |
| if (pdu == NULL) |
| return 0; |
| |
| if (value == NULL || vlen == NULL || handle == NULL) |
| return 0; |
| |
| if (len < min_len) |
| return 0; |
| |
| if (pdu[0] != ATT_OP_WRITE_REQ) |
| return 0; |
| |
| *handle = get_le16(&pdu[1]); |
| *vlen = len - min_len; |
| if (*vlen > 0) |
| memcpy(value, pdu + min_len, *vlen); |
| |
| return len; |
| } |
| |
| uint16_t enc_write_resp(uint8_t *pdu) |
| { |
| if (pdu == NULL) |
| return 0; |
| |
| pdu[0] = ATT_OP_WRITE_RESP; |
| |
| return sizeof(pdu[0]); |
| } |
| |
| uint16_t dec_write_resp(const uint8_t *pdu, size_t len) |
| { |
| if (pdu == NULL) |
| return 0; |
| |
| if (pdu[0] != ATT_OP_WRITE_RESP) |
| return 0; |
| |
| return len; |
| } |
| |
| uint16_t enc_read_req(uint16_t handle, uint8_t *pdu, size_t len) |
| { |
| if (pdu == NULL) |
| return 0; |
| |
| /* Attribute Opcode (1 octet) */ |
| pdu[0] = ATT_OP_READ_REQ; |
| /* Attribute Handle (2 octets) */ |
| put_le16(handle, &pdu[1]); |
| |
| return 3; |
| } |
| |
| uint16_t enc_read_blob_req(uint16_t handle, uint16_t offset, uint8_t *pdu, |
| size_t len) |
| { |
| if (pdu == NULL) |
| return 0; |
| |
| /* Attribute Opcode (1 octet) */ |
| pdu[0] = ATT_OP_READ_BLOB_REQ; |
| /* Attribute Handle (2 octets) */ |
| put_le16(handle, &pdu[1]); |
| /* Value Offset (2 octets) */ |
| put_le16(offset, &pdu[3]); |
| |
| return 5; |
| } |
| |
| uint16_t dec_read_req(const uint8_t *pdu, size_t len, uint16_t *handle) |
| { |
| const uint16_t min_len = sizeof(pdu[0]) + sizeof(*handle); |
| |
| if (pdu == NULL) |
| return 0; |
| |
| if (handle == NULL) |
| return 0; |
| |
| if (len < min_len) |
| return 0; |
| |
| if (pdu[0] != ATT_OP_READ_REQ) |
| return 0; |
| |
| *handle = get_le16(&pdu[1]); |
| |
| return min_len; |
| } |
| |
| uint16_t dec_read_blob_req(const uint8_t *pdu, size_t len, uint16_t *handle, |
| uint16_t *offset) |
| { |
| const uint16_t min_len = sizeof(pdu[0]) + sizeof(*handle) + |
| sizeof(*offset); |
| |
| if (pdu == NULL) |
| return 0; |
| |
| if (handle == NULL) |
| return 0; |
| |
| if (offset == NULL) |
| return 0; |
| |
| if (len < min_len) |
| return 0; |
| |
| if (pdu[0] != ATT_OP_READ_BLOB_REQ) |
| return 0; |
| |
| *handle = get_le16(&pdu[1]); |
| *offset = get_le16(&pdu[3]); |
| |
| return min_len; |
| } |
| |
| uint16_t enc_read_resp(uint8_t *value, size_t vlen, uint8_t *pdu, size_t len) |
| { |
| if (pdu == NULL) |
| return 0; |
| |
| /* If the attribute value length is longer than the allowed PDU size, |
| * send only the octets that fit on the PDU. The remaining octets can |
| * be requested using the Read Blob Request. */ |
| if (vlen > len - 1) |
| vlen = len - 1; |
| |
| pdu[0] = ATT_OP_READ_RESP; |
| |
| memcpy(pdu + 1, value, vlen); |
| |
| return vlen + 1; |
| } |
| |
| uint16_t enc_read_blob_resp(uint8_t *value, size_t vlen, uint16_t offset, |
| uint8_t *pdu, size_t len) |
| { |
| if (pdu == NULL) |
| return 0; |
| |
| vlen -= offset; |
| if (vlen > len - 1) |
| vlen = len - 1; |
| |
| pdu[0] = ATT_OP_READ_BLOB_RESP; |
| |
| memcpy(pdu + 1, &value[offset], vlen); |
| |
| return vlen + 1; |
| } |
| |
| ssize_t dec_read_resp(const uint8_t *pdu, size_t len, uint8_t *value, |
| size_t vlen) |
| { |
| if (pdu == NULL) |
| return -EINVAL; |
| |
| if (pdu[0] != ATT_OP_READ_RESP) |
| return -EINVAL; |
| |
| if (value == NULL) |
| return len - 1; |
| |
| if (vlen < (len - 1)) |
| return -ENOBUFS; |
| |
| memcpy(value, pdu + 1, len - 1); |
| |
| return len - 1; |
| } |
| |
| uint16_t enc_error_resp(uint8_t opcode, uint16_t handle, uint8_t status, |
| uint8_t *pdu, size_t len) |
| { |
| /* Attribute Opcode (1 octet) */ |
| pdu[0] = ATT_OP_ERROR; |
| /* Request Opcode In Error (1 octet) */ |
| pdu[1] = opcode; |
| /* Attribute Handle In Error (2 octets) */ |
| put_le16(handle, &pdu[2]); |
| /* Error Code (1 octet) */ |
| pdu[4] = status; |
| |
| return 5; |
| } |
| |
| uint16_t enc_find_info_req(uint16_t start, uint16_t end, uint8_t *pdu, |
| size_t len) |
| { |
| if (pdu == NULL) |
| return 0; |
| |
| /* Attribute Opcode (1 octet) */ |
| pdu[0] = ATT_OP_FIND_INFO_REQ; |
| /* Starting Handle (2 octets) */ |
| put_le16(start, &pdu[1]); |
| /* Ending Handle (2 octets) */ |
| put_le16(end, &pdu[3]); |
| |
| return 5; |
| } |
| |
| uint16_t dec_find_info_req(const uint8_t *pdu, size_t len, uint16_t *start, |
| uint16_t *end) |
| { |
| const uint16_t min_len = sizeof(pdu[0]) + sizeof(*start) + sizeof(*end); |
| |
| if (pdu == NULL) |
| return 0; |
| |
| if (len < min_len) |
| return 0; |
| |
| if (start == NULL || end == NULL) |
| return 0; |
| |
| if (pdu[0] != ATT_OP_FIND_INFO_REQ) |
| return 0; |
| |
| *start = get_le16(&pdu[1]); |
| *end = get_le16(&pdu[3]); |
| |
| return min_len; |
| } |
| |
| uint16_t enc_find_info_resp(uint8_t format, struct att_data_list *list, |
| uint8_t *pdu, size_t len) |
| { |
| uint8_t *ptr; |
| size_t i, w; |
| |
| if (pdu == NULL) |
| return 0; |
| |
| if (list == NULL) |
| return 0; |
| |
| if (len < list->len + sizeof(uint8_t) * 2) |
| return 0; |
| |
| pdu[0] = ATT_OP_FIND_INFO_RESP; |
| pdu[1] = format; |
| ptr = (void *) &pdu[2]; |
| |
| for (i = 0, w = 2; i < list->num && w + list->len <= len; i++) { |
| memcpy(ptr, list->data[i], list->len); |
| ptr += list->len; |
| w += list->len; |
| } |
| |
| return w; |
| } |
| |
| struct att_data_list *dec_find_info_resp(const uint8_t *pdu, size_t len, |
| uint8_t *format) |
| { |
| struct att_data_list *list; |
| uint8_t *ptr; |
| uint16_t elen, num; |
| int i; |
| |
| if (pdu == NULL) |
| return 0; |
| |
| if (format == NULL) |
| return 0; |
| |
| if (pdu[0] != ATT_OP_FIND_INFO_RESP) |
| return 0; |
| |
| *format = pdu[1]; |
| elen = sizeof(pdu[0]) + sizeof(*format); |
| if (*format == 0x01) |
| elen += 2; |
| else if (*format == 0x02) |
| elen += 16; |
| |
| num = (len - 2) / elen; |
| |
| ptr = (void *) &pdu[2]; |
| |
| list = att_data_list_alloc(num, elen); |
| if (list == NULL) |
| return NULL; |
| |
| for (i = 0; i < num; i++) { |
| memcpy(list->data[i], ptr, list->len); |
| ptr += list->len; |
| } |
| |
| return list; |
| } |
| |
| uint16_t enc_notification(uint16_t handle, uint8_t *value, size_t vlen, |
| uint8_t *pdu, size_t len) |
| { |
| const uint16_t min_len = sizeof(pdu[0]) + sizeof(uint16_t); |
| |
| if (pdu == NULL) |
| return 0; |
| |
| if (len < (vlen + min_len)) |
| return 0; |
| |
| pdu[0] = ATT_OP_HANDLE_NOTIFY; |
| put_le16(handle, &pdu[1]); |
| memcpy(&pdu[3], value, vlen); |
| |
| return vlen + min_len; |
| } |
| |
| uint16_t enc_indication(uint16_t handle, uint8_t *value, size_t vlen, |
| uint8_t *pdu, size_t len) |
| { |
| const uint16_t min_len = sizeof(pdu[0]) + sizeof(uint16_t); |
| |
| if (pdu == NULL) |
| return 0; |
| |
| if (len < (vlen + min_len)) |
| return 0; |
| |
| pdu[0] = ATT_OP_HANDLE_IND; |
| put_le16(handle, &pdu[1]); |
| memcpy(&pdu[3], value, vlen); |
| |
| return vlen + min_len; |
| } |
| |
| uint16_t dec_indication(const uint8_t *pdu, size_t len, uint16_t *handle, |
| uint8_t *value, size_t vlen) |
| { |
| const uint16_t min_len = sizeof(pdu[0]) + sizeof(uint16_t); |
| uint16_t dlen; |
| |
| if (pdu == NULL) |
| return 0; |
| |
| if (pdu[0] != ATT_OP_HANDLE_IND) |
| return 0; |
| |
| if (len < min_len) |
| return 0; |
| |
| dlen = MIN(len - min_len, vlen); |
| |
| if (handle) |
| *handle = get_le16(&pdu[1]); |
| |
| memcpy(value, &pdu[3], dlen); |
| |
| return dlen; |
| } |
| |
| uint16_t enc_confirmation(uint8_t *pdu, size_t len) |
| { |
| if (pdu == NULL) |
| return 0; |
| |
| /* Attribute Opcode (1 octet) */ |
| pdu[0] = ATT_OP_HANDLE_CNF; |
| |
| return 1; |
| } |
| |
| uint16_t enc_mtu_req(uint16_t mtu, uint8_t *pdu, size_t len) |
| { |
| if (pdu == NULL) |
| return 0; |
| |
| /* Attribute Opcode (1 octet) */ |
| pdu[0] = ATT_OP_MTU_REQ; |
| /* Client Rx MTU (2 octets) */ |
| put_le16(mtu, &pdu[1]); |
| |
| return 3; |
| } |
| |
| uint16_t dec_mtu_req(const uint8_t *pdu, size_t len, uint16_t *mtu) |
| { |
| const uint16_t min_len = sizeof(pdu[0]) + sizeof(*mtu); |
| |
| if (pdu == NULL) |
| return 0; |
| |
| if (mtu == NULL) |
| return 0; |
| |
| if (len < min_len) |
| return 0; |
| |
| if (pdu[0] != ATT_OP_MTU_REQ) |
| return 0; |
| |
| *mtu = get_le16(&pdu[1]); |
| |
| return min_len; |
| } |
| |
| uint16_t enc_mtu_resp(uint16_t mtu, uint8_t *pdu, size_t len) |
| { |
| if (pdu == NULL) |
| return 0; |
| |
| /* Attribute Opcode (1 octet) */ |
| pdu[0] = ATT_OP_MTU_RESP; |
| /* Server Rx MTU (2 octets) */ |
| put_le16(mtu, &pdu[1]); |
| |
| return 3; |
| } |
| |
| uint16_t dec_mtu_resp(const uint8_t *pdu, size_t len, uint16_t *mtu) |
| { |
| const uint16_t min_len = sizeof(pdu[0]) + sizeof(*mtu); |
| |
| if (pdu == NULL) |
| return 0; |
| |
| if (mtu == NULL) |
| return 0; |
| |
| if (len < min_len) |
| return 0; |
| |
| if (pdu[0] != ATT_OP_MTU_RESP) |
| return 0; |
| |
| *mtu = get_le16(&pdu[1]); |
| |
| return min_len; |
| } |
| |
| uint16_t enc_prep_write_req(uint16_t handle, uint16_t offset, |
| const uint8_t *value, size_t vlen, |
| uint8_t *pdu, size_t len) |
| { |
| const uint16_t min_len = sizeof(pdu[0]) + sizeof(handle) + |
| sizeof(offset); |
| |
| if (pdu == NULL) |
| return 0; |
| |
| if (vlen > len - min_len) |
| vlen = len - min_len; |
| |
| pdu[0] = ATT_OP_PREP_WRITE_REQ; |
| put_le16(handle, &pdu[1]); |
| put_le16(offset, &pdu[3]); |
| |
| if (vlen > 0) { |
| memcpy(&pdu[5], value, vlen); |
| return min_len + vlen; |
| } |
| |
| return min_len; |
| } |
| |
| uint16_t dec_prep_write_req(const uint8_t *pdu, size_t len, uint16_t *handle, |
| uint16_t *offset, uint8_t *value, size_t *vlen) |
| { |
| const uint16_t min_len = sizeof(pdu[0]) + sizeof(*handle) + |
| sizeof(*offset); |
| |
| if (pdu == NULL) |
| return 0; |
| |
| if (handle == NULL || offset == NULL || value == NULL || vlen == NULL) |
| return 0; |
| |
| if (len < min_len) |
| return 0; |
| |
| if (pdu[0] != ATT_OP_PREP_WRITE_REQ) |
| return 0; |
| |
| *handle = get_le16(&pdu[1]); |
| *offset = get_le16(&pdu[3]); |
| |
| *vlen = len - min_len; |
| if (*vlen > 0) |
| memcpy(value, pdu + min_len, *vlen); |
| |
| return len; |
| } |
| |
| uint16_t enc_prep_write_resp(uint16_t handle, uint16_t offset, |
| const uint8_t *value, size_t vlen, |
| uint8_t *pdu, size_t len) |
| { |
| const uint16_t min_len = sizeof(pdu[0]) + sizeof(handle) + |
| sizeof(offset); |
| |
| if (pdu == NULL) |
| return 0; |
| |
| if (vlen > len - min_len) |
| vlen = len - min_len; |
| |
| pdu[0] = ATT_OP_PREP_WRITE_RESP; |
| put_le16(handle, &pdu[1]); |
| put_le16(offset, &pdu[3]); |
| |
| if (vlen > 0) { |
| memcpy(&pdu[5], value, vlen); |
| return min_len + vlen; |
| } |
| |
| return min_len; |
| } |
| |
| uint16_t dec_prep_write_resp(const uint8_t *pdu, size_t len, uint16_t *handle, |
| uint16_t *offset, uint8_t *value, size_t *vlen) |
| { |
| const uint16_t min_len = sizeof(pdu[0]) + sizeof(*handle) + |
| sizeof(*offset); |
| |
| if (pdu == NULL) |
| return 0; |
| |
| if (handle == NULL || offset == NULL || value == NULL || vlen == NULL) |
| return 0; |
| |
| if (len < min_len) |
| return 0; |
| |
| if (pdu[0] != ATT_OP_PREP_WRITE_REQ) |
| return 0; |
| |
| *handle = get_le16(&pdu[1]); |
| *offset = get_le16(&pdu[3]); |
| *vlen = len - min_len; |
| if (*vlen > 0) |
| memcpy(value, pdu + min_len, *vlen); |
| |
| return len; |
| } |
| |
| uint16_t enc_exec_write_req(uint8_t flags, uint8_t *pdu, size_t len) |
| { |
| if (pdu == NULL) |
| return 0; |
| |
| if (flags > 1) |
| return 0; |
| |
| /* Attribute Opcode (1 octet) */ |
| pdu[0] = ATT_OP_EXEC_WRITE_REQ; |
| /* Flags (1 octet) */ |
| pdu[1] = flags; |
| |
| return 2; |
| } |
| |
| uint16_t dec_exec_write_req(const uint8_t *pdu, size_t len, uint8_t *flags) |
| { |
| const uint16_t min_len = sizeof(pdu[0]) + sizeof(*flags); |
| |
| if (pdu == NULL) |
| return 0; |
| |
| if (flags == NULL) |
| return 0; |
| |
| if (len < min_len) |
| return 0; |
| |
| if (pdu[0] != ATT_OP_EXEC_WRITE_REQ) |
| return 0; |
| |
| *flags = pdu[1]; |
| |
| return min_len; |
| } |
| |
| uint16_t enc_exec_write_resp(uint8_t *pdu) |
| { |
| if (pdu == NULL) |
| return 0; |
| |
| /* Attribute Opcode (1 octet) */ |
| pdu[0] = ATT_OP_EXEC_WRITE_RESP; |
| |
| return 1; |
| } |
| |
| uint16_t dec_exec_write_resp(const uint8_t *pdu, size_t len) |
| { |
| const uint16_t min_len = sizeof(pdu[0]); |
| |
| if (pdu == NULL) |
| return 0; |
| |
| if (len < min_len) |
| return 0; |
| |
| if (pdu[0] != ATT_OP_EXEC_WRITE_RESP) |
| return 0; |
| |
| return len; |
| } |