| /* |
| * Driver O/S-independent utility routines |
| * |
| * Copyright (C) 1999-2019, Broadcom. |
| * |
| * Unless you and Broadcom execute a separate written software license |
| * agreement governing use of this software, this software is licensed to you |
| * under the terms of the GNU General Public License version 2 (the "GPL"), |
| * available at http://www.broadcom.com/licenses/GPLv2.php, with the |
| * following added to such license: |
| * |
| * As a special exception, the copyright holders of this software give you |
| * permission to link this software with independent modules, and to copy and |
| * distribute the resulting executable under terms of your choice, provided that |
| * you also meet, for each linked independent module, the terms and conditions of |
| * the license of that module. An independent module is a module which is not |
| * derived from this software. The special exception does not apply to any |
| * modifications of the software. |
| * |
| * Notwithstanding the above, under no circumstances may you combine this |
| * software in any way with any other Broadcom software provided under a license |
| * other than the GPL, without Broadcom's express prior written consent. |
| * |
| * |
| * <<Broadcom-WL-IPTag/Open:>> |
| * |
| * $Id: bcmxtlv.c 788740 2018-11-13 21:45:01Z $ |
| */ |
| |
| #include <bcm_cfg.h> |
| |
| #include <typedefs.h> |
| #include <bcmdefs.h> |
| |
| #include <stdarg.h> |
| |
| #ifdef BCMDRIVER |
| #include <osl.h> |
| #else /* !BCMDRIVER */ |
| #include <stdio.h> |
| #include <string.h> |
| #include <stdlib.h> |
| #ifndef ASSERT |
| #define ASSERT(exp) |
| #endif // endif |
| #endif /* !BCMDRIVER */ |
| |
| #include <bcmtlv.h> |
| #include <bcmendian.h> |
| #include <bcmutils.h> |
| |
| int |
| bcm_xtlv_hdr_size(bcm_xtlv_opts_t opts) |
| { |
| int len = (int)OFFSETOF(bcm_xtlv_t, data); /* nominal */ |
| if (opts & BCM_XTLV_OPTION_LENU8) --len; |
| if (opts & BCM_XTLV_OPTION_IDU8) --len; |
| |
| return len; |
| } |
| |
| bool |
| bcm_valid_xtlv(const bcm_xtlv_t *elt, int buf_len, bcm_xtlv_opts_t opts) |
| { |
| return elt != NULL && |
| buf_len >= bcm_xtlv_hdr_size(opts) && |
| buf_len >= bcm_xtlv_size(elt, opts); |
| } |
| |
| int |
| bcm_xtlv_size_for_data(int dlen, bcm_xtlv_opts_t opts) |
| { |
| int hsz; |
| |
| hsz = bcm_xtlv_hdr_size(opts); |
| return ((opts & BCM_XTLV_OPTION_ALIGN32) ? ALIGN_SIZE(dlen + hsz, 4) |
| : (dlen + hsz)); |
| } |
| |
| int |
| bcm_xtlv_size(const bcm_xtlv_t *elt, bcm_xtlv_opts_t opts) |
| { |
| int size; /* size including header, data, and any pad */ |
| int len; /* length wthout padding */ |
| |
| len = BCM_XTLV_LEN_EX(elt, opts); |
| size = bcm_xtlv_size_for_data(len, opts); |
| return size; |
| } |
| |
| int |
| bcm_xtlv_len(const bcm_xtlv_t *elt, bcm_xtlv_opts_t opts) |
| { |
| const uint8 *lenp; |
| int len; |
| |
| lenp = (const uint8 *)&elt->len; /* nominal */ |
| if (opts & BCM_XTLV_OPTION_IDU8) { |
| --lenp; |
| } |
| |
| if (opts & BCM_XTLV_OPTION_LENU8) { |
| len = *lenp; |
| } else if (opts & BCM_XTLV_OPTION_LENBE) { |
| len = (uint32)hton16(elt->len); |
| } else { |
| len = ltoh16_ua(lenp); |
| } |
| |
| return len; |
| } |
| |
| int |
| bcm_xtlv_id(const bcm_xtlv_t *elt, bcm_xtlv_opts_t opts) |
| { |
| int id = 0; |
| if (opts & BCM_XTLV_OPTION_IDU8) { |
| id = *(const uint8 *)elt; |
| } else if (opts & BCM_XTLV_OPTION_IDBE) { |
| id = (uint32)hton16(elt->id); |
| } else { |
| id = ltoh16_ua((const uint8 *)elt); |
| } |
| |
| return id; |
| } |
| |
| bcm_xtlv_t * |
| bcm_next_xtlv(const bcm_xtlv_t *elt, int *buflen, bcm_xtlv_opts_t opts) |
| { |
| int sz; |
| /* advance to next elt */ |
| sz = BCM_XTLV_SIZE_EX(elt, opts); |
| elt = (const bcm_xtlv_t*)((const uint8 *)elt + sz); |
| *buflen -= sz; |
| |
| /* validate next elt */ |
| if (!bcm_valid_xtlv(elt, *buflen, opts)) |
| return NULL; |
| |
| GCC_DIAGNOSTIC_PUSH_SUPPRESS_CAST(); |
| return (bcm_xtlv_t *)(elt); |
| GCC_DIAGNOSTIC_POP(); |
| } |
| |
| int |
| bcm_xtlv_buf_init(bcm_xtlvbuf_t *tlv_buf, uint8 *buf, uint16 len, bcm_xtlv_opts_t opts) |
| { |
| if (!tlv_buf || !buf || !len) |
| return BCME_BADARG; |
| |
| tlv_buf->opts = opts; |
| tlv_buf->size = len; |
| tlv_buf->head = buf; |
| tlv_buf->buf = buf; |
| return BCME_OK; |
| } |
| |
| uint16 |
| bcm_xtlv_buf_len(bcm_xtlvbuf_t *tbuf) |
| { |
| uint16 len; |
| |
| if (tbuf) |
| len = (uint16)(tbuf->buf - tbuf->head); |
| else |
| len = 0; |
| |
| return len; |
| } |
| |
| uint16 |
| bcm_xtlv_buf_rlen(bcm_xtlvbuf_t *tbuf) |
| { |
| uint16 rlen; |
| if (tbuf) |
| rlen = tbuf->size - bcm_xtlv_buf_len(tbuf); |
| else |
| rlen = 0; |
| |
| return rlen; |
| } |
| |
| uint8 * |
| bcm_xtlv_buf(bcm_xtlvbuf_t *tbuf) |
| { |
| return tbuf ? tbuf->buf : NULL; |
| } |
| |
| uint8 * |
| bcm_xtlv_head(bcm_xtlvbuf_t *tbuf) |
| { |
| return tbuf ? tbuf->head : NULL; |
| } |
| |
| void |
| bcm_xtlv_pack_xtlv(bcm_xtlv_t *xtlv, uint16 type, uint16 len, const uint8 *data, |
| bcm_xtlv_opts_t opts) |
| { |
| uint8 *data_buf; |
| bcm_xtlv_opts_t mask = BCM_XTLV_OPTION_IDU8 | BCM_XTLV_OPTION_LENU8; |
| |
| if (!(opts & mask)) { /* default */ |
| uint8 *idp = (uint8 *)xtlv; |
| uint8 *lenp = idp + sizeof(xtlv->id); |
| htol16_ua_store(type, idp); |
| htol16_ua_store(len, lenp); |
| data_buf = lenp + sizeof(uint16); |
| } else if ((opts & mask) == mask) { /* u8 id and u8 len */ |
| uint8 *idp = (uint8 *)xtlv; |
| uint8 *lenp = idp + 1; |
| *idp = (uint8)type; |
| *lenp = (uint8)len; |
| data_buf = lenp + sizeof(uint8); |
| } else if (opts & BCM_XTLV_OPTION_IDU8) { /* u8 id, u16 len */ |
| uint8 *idp = (uint8 *)xtlv; |
| uint8 *lenp = idp + 1; |
| *idp = (uint8)type; |
| htol16_ua_store(len, lenp); |
| data_buf = lenp + sizeof(uint16); |
| } else if (opts & BCM_XTLV_OPTION_LENU8) { /* u16 id, u8 len */ |
| uint8 *idp = (uint8 *)xtlv; |
| uint8 *lenp = idp + sizeof(uint16); |
| htol16_ua_store(type, idp); |
| *lenp = (uint8)len; |
| data_buf = lenp + sizeof(uint8); |
| } else { |
| bool Unexpected_xtlv_option = TRUE; |
| BCM_REFERENCE(Unexpected_xtlv_option); |
| ASSERT(!Unexpected_xtlv_option); |
| return; |
| } |
| |
| if (opts & BCM_XTLV_OPTION_LENU8) { |
| ASSERT(len <= 0x00ff); |
| len &= 0xff; |
| } |
| |
| if (data != NULL) |
| memcpy(data_buf, data, len); |
| } |
| |
| /* xtlv header is always packed in LE order */ |
| void |
| bcm_xtlv_unpack_xtlv(const bcm_xtlv_t *xtlv, uint16 *type, uint16 *len, |
| const uint8 **data, bcm_xtlv_opts_t opts) |
| { |
| if (type) |
| *type = (uint16)bcm_xtlv_id(xtlv, opts); |
| if (len) |
| *len = (uint16)bcm_xtlv_len(xtlv, opts); |
| if (data) |
| *data = (const uint8 *)xtlv + BCM_XTLV_HDR_SIZE_EX(opts); |
| } |
| |
| int |
| bcm_xtlv_put_data(bcm_xtlvbuf_t *tbuf, uint16 type, const uint8 *data, int n) |
| { |
| bcm_xtlv_t *xtlv; |
| int size; |
| |
| if (tbuf == NULL) |
| return BCME_BADARG; |
| |
| size = bcm_xtlv_size_for_data(n, tbuf->opts); |
| if (bcm_xtlv_buf_rlen(tbuf) < size) |
| return BCME_NOMEM; |
| |
| xtlv = (bcm_xtlv_t *)bcm_xtlv_buf(tbuf); |
| bcm_xtlv_pack_xtlv(xtlv, type, (uint16)n, data, tbuf->opts); |
| tbuf->buf += size; /* note: data may be NULL, reserves space */ |
| return BCME_OK; |
| } |
| |
| static int |
| bcm_xtlv_put_int(bcm_xtlvbuf_t *tbuf, uint16 type, const uint8 *data, int n, int int_sz) |
| { |
| bcm_xtlv_t *xtlv; |
| int xtlv_len; |
| uint8 *xtlv_data; |
| int err = BCME_OK; |
| |
| if (tbuf == NULL) { |
| err = BCME_BADARG; |
| goto done; |
| } |
| |
| xtlv = (bcm_xtlv_t *)bcm_xtlv_buf(tbuf); |
| |
| /* put type and length in xtlv and reserve data space */ |
| xtlv_len = n * int_sz; |
| err = bcm_xtlv_put_data(tbuf, type, NULL, xtlv_len); |
| if (err != BCME_OK) |
| goto done; |
| |
| xtlv_data = (uint8 *)xtlv + bcm_xtlv_hdr_size(tbuf->opts); |
| |
| /* write data w/ little-endianness into buffer - single loop, aligned access */ |
| for (; n != 0; --n, xtlv_data += int_sz, data += int_sz) { |
| switch (int_sz) { |
| case sizeof(uint8): |
| break; |
| case sizeof(uint16): |
| { |
| uint16 v = load16_ua(data); |
| htol16_ua_store(v, xtlv_data); |
| break; |
| } |
| case sizeof(uint32): |
| { |
| uint32 v = load32_ua(data); |
| htol32_ua_store(v, xtlv_data); |
| break; |
| } |
| case sizeof(uint64): |
| { |
| uint64 v = load64_ua(data); |
| htol64_ua_store(v, xtlv_data); |
| break; |
| } |
| default: |
| err = BCME_UNSUPPORTED; |
| goto done; |
| } |
| } |
| |
| done: |
| return err; |
| } |
| |
| int |
| bcm_xtlv_put16(bcm_xtlvbuf_t *tbuf, uint16 type, const uint16 *data, int n) |
| { |
| return bcm_xtlv_put_int(tbuf, type, (const uint8 *)data, n, sizeof(uint16)); |
| } |
| |
| int |
| bcm_xtlv_put32(bcm_xtlvbuf_t *tbuf, uint16 type, const uint32 *data, int n) |
| { |
| return bcm_xtlv_put_int(tbuf, type, (const uint8 *)data, n, sizeof(uint32)); |
| } |
| |
| int |
| bcm_xtlv_put64(bcm_xtlvbuf_t *tbuf, uint16 type, const uint64 *data, int n) |
| { |
| return bcm_xtlv_put_int(tbuf, type, (const uint8 *)data, n, sizeof(uint64)); |
| } |
| |
| /* |
| * upacks xtlv record from buf checks the type |
| * copies data to callers buffer |
| * advances tlv pointer to next record |
| * caller's resposible for dst space check |
| */ |
| int |
| bcm_unpack_xtlv_entry(const uint8 **tlv_buf, uint16 xpct_type, uint16 xpct_len, |
| uint8 *dst_data, bcm_xtlv_opts_t opts) |
| { |
| const bcm_xtlv_t *ptlv = (const bcm_xtlv_t *)*tlv_buf; |
| uint16 len; |
| uint16 type; |
| const uint8 *data; |
| |
| ASSERT(ptlv); |
| |
| bcm_xtlv_unpack_xtlv(ptlv, &type, &len, &data, opts); |
| if (len) { |
| if ((type != xpct_type) || (len > xpct_len)) |
| return BCME_BADARG; |
| if (dst_data && data) |
| memcpy(dst_data, data, len); /* copy data to dst */ |
| } |
| |
| *tlv_buf += BCM_XTLV_SIZE_EX(ptlv, opts); |
| return BCME_OK; |
| } |
| |
| /* |
| * packs user data into tlv record and advances tlv pointer to next xtlv slot |
| * buflen is used for tlv_buf space check |
| */ |
| int |
| bcm_pack_xtlv_entry(uint8 **tlv_buf, uint16 *buflen, uint16 type, uint16 len, |
| const uint8 *src_data, bcm_xtlv_opts_t opts) |
| { |
| bcm_xtlv_t *ptlv = (bcm_xtlv_t *)*tlv_buf; |
| int size; |
| |
| ASSERT(ptlv); |
| |
| size = bcm_xtlv_size_for_data(len, opts); |
| |
| /* copy data from tlv buffer to dst provided by user */ |
| if (size > *buflen) |
| return BCME_BADLEN; |
| |
| bcm_xtlv_pack_xtlv(ptlv, type, len, src_data, opts); |
| |
| /* advance callers pointer to tlv buff */ |
| *tlv_buf = (uint8*)(*tlv_buf) + size; |
| /* decrement the len */ |
| *buflen -= (uint16)size; |
| return BCME_OK; |
| } |
| |
| /* |
| * unpack all xtlv records from the issue a callback |
| * to set function one call per found tlv record |
| */ |
| int |
| bcm_unpack_xtlv_buf(void *ctx, const uint8 *tlv_buf, uint16 buflen, bcm_xtlv_opts_t opts, |
| bcm_xtlv_unpack_cbfn_t *cbfn) |
| { |
| uint16 len; |
| uint16 type; |
| int res = BCME_OK; |
| int size; |
| const bcm_xtlv_t *ptlv; |
| int sbuflen = buflen; |
| const uint8 *data; |
| int hdr_size; |
| |
| ASSERT(!buflen || tlv_buf); |
| ASSERT(!buflen || cbfn); |
| |
| hdr_size = BCM_XTLV_HDR_SIZE_EX(opts); |
| while (sbuflen >= hdr_size) { |
| ptlv = (const bcm_xtlv_t *)tlv_buf; |
| |
| bcm_xtlv_unpack_xtlv(ptlv, &type, &len, &data, opts); |
| size = bcm_xtlv_size_for_data(len, opts); |
| |
| sbuflen -= size; |
| if (sbuflen < 0) /* check for buffer overrun */ |
| break; |
| |
| if ((res = cbfn(ctx, data, type, len)) != BCME_OK) |
| break; |
| tlv_buf += size; |
| } |
| return res; |
| } |
| |
| int |
| bcm_pack_xtlv_buf(void *ctx, uint8 *tlv_buf, uint16 buflen, bcm_xtlv_opts_t opts, |
| bcm_pack_xtlv_next_info_cbfn_t get_next, bcm_pack_xtlv_pack_next_cbfn_t pack_next, |
| int *outlen) |
| { |
| int res = BCME_OK; |
| uint16 tlv_id; |
| uint16 tlv_len; |
| uint8 *startp; |
| uint8 *endp; |
| uint8 *buf; |
| bool more; |
| int size; |
| int hdr_size; |
| |
| ASSERT(get_next && pack_next); |
| |
| buf = tlv_buf; |
| startp = buf; |
| endp = (uint8 *)buf + buflen; |
| more = TRUE; |
| hdr_size = BCM_XTLV_HDR_SIZE_EX(opts); |
| |
| while (more && (buf < endp)) { |
| more = get_next(ctx, &tlv_id, &tlv_len); |
| size = bcm_xtlv_size_for_data(tlv_len, opts); |
| if ((buf + size) > endp) { |
| res = BCME_BUFTOOSHORT; |
| goto done; |
| } |
| |
| bcm_xtlv_pack_xtlv((bcm_xtlv_t *)buf, tlv_id, tlv_len, NULL, opts); |
| pack_next(ctx, tlv_id, tlv_len, buf + hdr_size); |
| buf += size; |
| } |
| |
| if (more) |
| res = BCME_BUFTOOSHORT; |
| |
| done: |
| if (outlen) { |
| *outlen = (int)(buf - startp); |
| } |
| return res; |
| } |
| |
| /* |
| * pack xtlv buffer from memory according to xtlv_desc_t |
| */ |
| int |
| bcm_pack_xtlv_buf_from_mem(uint8 **tlv_buf, uint16 *buflen, const xtlv_desc_t *items, |
| bcm_xtlv_opts_t opts) |
| { |
| int res = BCME_OK; |
| uint8 *ptlv = *tlv_buf; |
| |
| while (items->type != 0) { |
| if (items->len && items->ptr) { |
| res = bcm_pack_xtlv_entry(&ptlv, buflen, items->type, |
| items->len, items->ptr, opts); |
| if (res != BCME_OK) |
| break; |
| } |
| items++; |
| } |
| |
| *tlv_buf = ptlv; /* update the external pointer */ |
| return res; |
| } |
| |
| /* |
| * unpack xtlv buffer to memory according to xtlv_desc_t |
| * |
| */ |
| int |
| bcm_unpack_xtlv_buf_to_mem(uint8 *tlv_buf, int *buflen, xtlv_desc_t *items, |
| bcm_xtlv_opts_t opts) |
| { |
| int res = BCME_OK; |
| bcm_xtlv_t *elt; |
| |
| elt = bcm_valid_xtlv((bcm_xtlv_t *)tlv_buf, *buflen, opts) ? (bcm_xtlv_t *)tlv_buf : NULL; |
| if (!elt || !items) { |
| res = BCME_BADARG; |
| return res; |
| } |
| |
| for (; elt != NULL && res == BCME_OK; elt = bcm_next_xtlv(elt, buflen, opts)) { |
| /* find matches in desc_t items */ |
| xtlv_desc_t *dst_desc = items; |
| uint16 len, type; |
| const uint8 *data; |
| |
| bcm_xtlv_unpack_xtlv(elt, &type, &len, &data, opts); |
| while (dst_desc->type != 0) { |
| if (type == dst_desc->type) { |
| if (len != dst_desc->len) { |
| res = BCME_BADLEN; |
| } else { |
| memcpy(dst_desc->ptr, data, len); |
| } |
| break; |
| } |
| dst_desc++; |
| } |
| } |
| |
| if (res == BCME_OK && *buflen != 0) |
| res = BCME_BUFTOOSHORT; |
| |
| return res; |
| } |
| |
| /* |
| * return data pointer of a given ID from xtlv buffer. |
| * If the specified xTLV ID is found, on return *datalen will contain |
| * the the data length of the xTLV ID. |
| */ |
| const uint8* |
| bcm_get_data_from_xtlv_buf(const uint8 *tlv_buf, uint16 buflen, uint16 id, |
| uint16 *datalen, bcm_xtlv_opts_t opts) |
| { |
| const uint8 *retptr = NULL; |
| uint16 type, len; |
| int size; |
| const bcm_xtlv_t *ptlv; |
| int sbuflen = buflen; |
| const uint8 *data; |
| int hdr_size; |
| |
| hdr_size = BCM_XTLV_HDR_SIZE_EX(opts); |
| |
| /* Init the datalength */ |
| if (datalen) { |
| *datalen = 0; |
| } |
| while (sbuflen >= hdr_size) { |
| ptlv = (const bcm_xtlv_t *)tlv_buf; |
| bcm_xtlv_unpack_xtlv(ptlv, &type, &len, &data, opts); |
| |
| size = bcm_xtlv_size_for_data(len, opts); |
| sbuflen -= size; |
| if (sbuflen < 0) /* buffer overrun? */ |
| break; |
| |
| if (id == type) { |
| retptr = data; |
| if (datalen) |
| *datalen = len; |
| break; |
| } |
| |
| tlv_buf += size; |
| } |
| |
| return retptr; |
| } |
| |
| bcm_xtlv_t* |
| bcm_xtlv_bcopy(const bcm_xtlv_t *src, bcm_xtlv_t *dst, |
| int src_buf_len, int dst_buf_len, bcm_xtlv_opts_t opts) |
| { |
| bcm_xtlv_t *dst_next = NULL; |
| src = (src && bcm_valid_xtlv(src, src_buf_len, opts)) ? src : NULL; |
| if (src && dst) { |
| uint16 type; |
| uint16 len; |
| const uint8 *data; |
| int size; |
| bcm_xtlv_unpack_xtlv(src, &type, &len, &data, opts); |
| size = bcm_xtlv_size_for_data(len, opts); |
| if (size <= dst_buf_len) { |
| bcm_xtlv_pack_xtlv(dst, type, len, data, opts); |
| dst_next = (bcm_xtlv_t *)((uint8 *)dst + size); |
| } |
| } |
| |
| return dst_next; |
| } |