| /* |
| * Driver O/S-independent utility routines |
| * |
| * Copyright 1999-2016, Broadcom Corporation |
| * All rights reserved, |
| * |
| * Redistribution and use in source and binary forms, with or without modification, |
| * are permitted provided that the following conditions are met: |
| * 1. Redistributions of source code must retain the above copyright notice, |
| * this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright notice, |
| * this list of conditions and the following disclaimer in the documentation |
| * and/or other materials provided with the distribution. |
| * |
| * This software is provided by the copyright holder "as is" and any express or |
| * implied warranties, including, but not limited to, the implied warranties of |
| * merchantability and fitness for a particular purpose are disclaimed. In no event |
| * shall copyright holder be liable for any direct, indirect, incidental, special, |
| * exemplary, or consequential damages (including, but not limited to, procurement |
| * of substitute goods or services; loss of use, data, or profits; or business |
| * interruption) however caused and on any theory of liability, whether in |
| * contract, strict liability, or tort (including negligence or otherwise) arising |
| * in any way out of the use of this software, even if advised of the possibility |
| * of such damage |
| * |
| * |
| * <<Broadcom-WL-IPTag/Open:>> |
| * |
| * $Id: bcmxtlv.c 527361 2015-01-17 01:48:34Z $ |
| */ |
| |
| #include <bcm_cfg.h> |
| |
| #include <typedefs.h> |
| #include <bcmdefs.h> |
| |
| #include <stdarg.h> |
| |
| #ifdef BCMDRIVER |
| #include <osl.h> |
| #else /* !BCMDRIVER */ |
| #include <stdlib.h> /* AS!!! */ |
| #include <stdio.h> |
| #include <string.h> |
| #include <stdlib.h> |
| #ifndef ASSERT |
| #define ASSERT(exp) |
| #endif |
| INLINE void* MALLOCZ(void *o, size_t s) { BCM_REFERENCE(o); return calloc(1, s); } |
| INLINE void MFREE(void *o, void *p, size_t s) { BCM_REFERENCE(o); BCM_REFERENCE(s); free(p); } |
| #endif /* !BCMDRIVER */ |
| |
| #include <bcmendian.h> |
| #include <bcmutils.h> |
| |
| static INLINE int bcm_xtlv_size_for_data(int dlen, bcm_xtlv_opts_t opts) |
| { |
| return ((opts & BCM_XTLV_OPTION_ALIGN32) ? ALIGN_SIZE(dlen + BCM_XTLV_HDR_SIZE, 4) |
| : (dlen + BCM_XTLV_HDR_SIZE)); |
| } |
| |
| bcm_xtlv_t * |
| bcm_next_xtlv(bcm_xtlv_t *elt, int *buflen, bcm_xtlv_opts_t opts) |
| { |
| int sz; |
| /* advance to next elt */ |
| sz = BCM_XTLV_SIZE(elt, opts); |
| elt = (bcm_xtlv_t*)((uint8 *)elt + sz); |
| *buflen -= sz; |
| |
| /* validate next elt */ |
| if (!bcm_valid_xtlv(elt, *buflen, opts)) |
| return NULL; |
| |
| return elt; |
| } |
| |
| 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) |
| { |
| if (tbuf == NULL) return 0; |
| return (uint16)(tbuf->buf - tbuf->head); |
| } |
| uint16 |
| bcm_xtlv_buf_rlen(bcm_xtlvbuf_t *tbuf) |
| { |
| if (tbuf == NULL) return 0; |
| return tbuf->size - bcm_xtlv_buf_len(tbuf); |
| } |
| uint8 * |
| bcm_xtlv_buf(bcm_xtlvbuf_t *tbuf) |
| { |
| if (tbuf == NULL) return NULL; |
| return tbuf->buf; |
| } |
| uint8 * |
| bcm_xtlv_head(bcm_xtlvbuf_t *tbuf) |
| { |
| if (tbuf == NULL) return NULL; |
| return tbuf->head; |
| } |
| int |
| bcm_xtlv_put_data(bcm_xtlvbuf_t *tbuf, uint16 type, const void *data, uint16 dlen) |
| { |
| bcm_xtlv_t *xtlv; |
| int size; |
| |
| if (tbuf == NULL) |
| return BCME_BADARG; |
| size = bcm_xtlv_size_for_data(dlen, tbuf->opts); |
| if (bcm_xtlv_buf_rlen(tbuf) < size) |
| return BCME_NOMEM; |
| xtlv = (bcm_xtlv_t *)bcm_xtlv_buf(tbuf); |
| xtlv->id = htol16(type); |
| xtlv->len = htol16(dlen); |
| memcpy(xtlv->data, data, dlen); |
| tbuf->buf += size; |
| return BCME_OK; |
| } |
| int |
| bcm_xtlv_put_8(bcm_xtlvbuf_t *tbuf, uint16 type, const int8 data) |
| { |
| bcm_xtlv_t *xtlv; |
| int size; |
| |
| if (tbuf == NULL) |
| return BCME_BADARG; |
| size = bcm_xtlv_size_for_data(1, tbuf->opts); |
| if (bcm_xtlv_buf_rlen(tbuf) < size) |
| return BCME_NOMEM; |
| xtlv = (bcm_xtlv_t *)bcm_xtlv_buf(tbuf); |
| xtlv->id = htol16(type); |
| xtlv->len = htol16(sizeof(data)); |
| xtlv->data[0] = data; |
| tbuf->buf += size; |
| return BCME_OK; |
| } |
| int |
| bcm_xtlv_put_16(bcm_xtlvbuf_t *tbuf, uint16 type, const int16 data) |
| { |
| bcm_xtlv_t *xtlv; |
| int size; |
| |
| if (tbuf == NULL) |
| return BCME_BADARG; |
| size = bcm_xtlv_size_for_data(2, tbuf->opts); |
| if (bcm_xtlv_buf_rlen(tbuf) < size) |
| return BCME_NOMEM; |
| |
| xtlv = (bcm_xtlv_t *)bcm_xtlv_buf(tbuf); |
| xtlv->id = htol16(type); |
| xtlv->len = htol16(sizeof(data)); |
| htol16_ua_store(data, xtlv->data); |
| tbuf->buf += size; |
| return BCME_OK; |
| } |
| int |
| bcm_xtlv_put_32(bcm_xtlvbuf_t *tbuf, uint16 type, const int32 data) |
| { |
| bcm_xtlv_t *xtlv; |
| int size; |
| |
| if (tbuf == NULL) |
| return BCME_BADARG; |
| size = bcm_xtlv_size_for_data(4, tbuf->opts); |
| if (bcm_xtlv_buf_rlen(tbuf) < size) |
| return BCME_NOMEM; |
| xtlv = (bcm_xtlv_t *)bcm_xtlv_buf(tbuf); |
| xtlv->id = htol16(type); |
| xtlv->len = htol16(sizeof(data)); |
| htol32_ua_store(data, xtlv->data); |
| tbuf->buf += size; |
| return BCME_OK; |
| } |
| |
| /* |
| * 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(uint8 **tlv_buf, uint16 xpct_type, uint16 xpct_len, void *dst, |
| bcm_xtlv_opts_t opts) |
| { |
| bcm_xtlv_t *ptlv = (bcm_xtlv_t *)*tlv_buf; |
| uint16 len; |
| uint16 type; |
| |
| ASSERT(ptlv); |
| /* tlv headr is always packed in LE order */ |
| len = ltoh16(ptlv->len); |
| type = ltoh16(ptlv->id); |
| if (len == 0) { |
| /* z-len tlv headers: allow, but don't process */ |
| printf("z-len, skip unpack\n"); |
| } else { |
| if ((type != xpct_type) || |
| (len > xpct_len)) { |
| printf("xtlv_unpack Error: found[type:%d,len:%d] != xpct[type:%d,len:%d]\n", |
| type, len, xpct_type, xpct_len); |
| return BCME_BADARG; |
| } |
| /* copy tlv record to caller's buffer */ |
| memcpy(dst, ptlv->data, ptlv->len); |
| } |
| *tlv_buf += BCM_XTLV_SIZE(ptlv, opts); |
| return BCME_OK; |
| } |
| |
| /* |
| * packs user data into tlv record |
| * 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, void *src, |
| bcm_xtlv_opts_t opts) |
| { |
| bcm_xtlv_t *ptlv = (bcm_xtlv_t *)*tlv_buf; |
| int size; |
| |
| ASSERT(ptlv); |
| ASSERT(src); |
| |
| size = bcm_xtlv_size_for_data(len, opts); |
| |
| /* copy data from tlv buffer to dst provided by user */ |
| if (size > *buflen) { |
| printf("bcm_pack_xtlv_entry: no space tlv_buf: requested:%d, available:%d\n", |
| size, *buflen); |
| return BCME_BADLEN; |
| } |
| ptlv->id = htol16(type); |
| ptlv->len = htol16(len); |
| |
| /* copy callers data */ |
| memcpy(ptlv->data, src, len); |
| |
| /* advance callers pointer to tlv buff */ |
| *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, 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; |
| bcm_xtlv_t *ptlv; |
| int sbuflen = buflen; |
| |
| ASSERT(!buflen || tlv_buf); |
| ASSERT(!buflen || cbfn); |
| |
| while (sbuflen >= (int)BCM_XTLV_HDR_SIZE) { |
| ptlv = (bcm_xtlv_t *)tlv_buf; |
| |
| /* tlv header is always packed in LE order */ |
| len = ltoh16(ptlv->len); |
| type = ltoh16(ptlv->id); |
| |
| size = bcm_xtlv_size_for_data(len, opts); |
| |
| sbuflen -= size; |
| /* check for possible buffer overrun */ |
| if (sbuflen < 0) |
| break; |
| |
| if ((res = cbfn(ctx, ptlv->data, type, len)) != BCME_OK) |
| break; |
| tlv_buf += size; |
| } |
| return res; |
| } |
| |
| int |
| bcm_pack_xtlv_buf(void *ctx, void *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; |
| |
| ASSERT(get_next && pack_next); |
| |
| buf = (uint8 *)tlv_buf; |
| startp = buf; |
| endp = (uint8 *)buf + buflen; |
| more = TRUE; |
| 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; |
| } |
| |
| htol16_ua_store(tlv_id, buf); |
| htol16_ua_store(tlv_len, buf + sizeof(tlv_id)); |
| pack_next(ctx, tlv_id, tlv_len, buf + BCM_XTLV_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(void **tlv_buf, uint16 *buflen, xtlv_desc_t *items, |
| bcm_xtlv_opts_t opts) |
| { |
| int res = BCME_OK; |
| uint8 *ptlv = (uint8 *)*tlv_buf; |
| |
| while (items->type != 0) { |
| if ((res = bcm_pack_xtlv_entry(&ptlv, |
| buflen, items->type, |
| items->len, items->ptr, opts) != 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(void *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 = ltoh16(elt->len); |
| |
| while (dst_desc->type != 0) { |
| if (ltoh16(elt->id) == dst_desc->type) { |
| if (len != dst_desc->len) { |
| res = BCME_BADLEN; |
| } else { |
| memcpy(dst_desc->ptr, elt->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 *data_len_out will contain |
| * the the data length of the xTLV ID. |
| */ |
| void * |
| bcm_get_data_from_xtlv_buf(uint8 *tlv_buf, uint16 buflen, uint16 id, |
| uint16 *datalen_out, bcm_xtlv_opts_t opts) |
| { |
| void *retptr = NULL; |
| uint16 type, len; |
| int size; |
| bcm_xtlv_t *ptlv; |
| int sbuflen = buflen; |
| |
| while (sbuflen >= (int)BCM_XTLV_HDR_SIZE) { |
| ptlv = (bcm_xtlv_t *)tlv_buf; |
| |
| /* tlv header is always packed in LE order */ |
| type = ltoh16(ptlv->id); |
| len = ltoh16(ptlv->len); |
| size = bcm_xtlv_size_for_data(len, opts); |
| |
| sbuflen -= size; |
| /* check for possible buffer overrun */ |
| if (sbuflen < 0) { |
| printf("%s %d: Invalid sbuflen %d\n", |
| __FUNCTION__, __LINE__, sbuflen); |
| break; |
| } |
| |
| if (id == type) { |
| retptr = ptlv->data; |
| if (datalen_out) { |
| *datalen_out = len; |
| } |
| break; |
| } |
| tlv_buf += size; |
| } |
| |
| return retptr; |
| } |
| |
| int bcm_xtlv_size(const bcm_xtlv_t *elt, bcm_xtlv_opts_t opts) |
| { |
| int size; /* entire size of the XTLV including header, data, and optional padding */ |
| int len; /* XTLV's value real length wthout padding */ |
| |
| len = BCM_XTLV_LEN(elt); |
| |
| size = bcm_xtlv_size_for_data(len, opts); |
| |
| return size; |
| } |