/* * * oFono - Open Source Telephony * * Copyright (C) 2017 Intel Corporation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include "mbim-message.h" #include "mbim-private.h" #define MAX_NESTING 2 /* a(uss) */ #define HEADER_SIZE (sizeof(struct mbim_message_header) + \ sizeof(struct mbim_fragment_header)) static const char CONTAINER_TYPE_ARRAY = 'a'; static const char CONTAINER_TYPE_STRUCT = 'r'; static const char CONTAINER_TYPE_DATABUF = 'd'; static const char *simple_types = "syqut"; struct mbim_message { int ref_count; uint8_t header[HEADER_SIZE]; struct iovec *frags; uint32_t n_frags; uint8_t uuid[16]; uint32_t cid; union { uint32_t status; uint32_t command_type; }; uint32_t info_buf_len; bool sealed : 1; }; static const char *_signature_end(const char *signature) { const char *ptr = signature; unsigned int indent = 0; char expect; switch (*signature) { case '(': expect = ')'; break; case 'a': return _signature_end(signature + 1); case '0' ... '9': expect = 'y'; break; default: return signature; } for (ptr = signature; *ptr != '\0'; ptr++) { if (*ptr == *signature) indent++; else if (*ptr == expect) if (!--indent) return ptr; } return NULL; } static int get_alignment(const char type) { switch (type) { case 'y': return 1; case 'q': return 2; case 'u': case 's': return 4; case 't': return 4; case 'a': return 4; case 'v': return 4; default: return 0; } } static int get_basic_size(const char type) { switch (type) { case 'y': return 1; case 'q': return 2; case 'u': return 4; case 't': return 8; default: return 0; } } static bool is_fixed_size(const char *sig_start, const char *sig_end) { while (sig_start <= sig_end) { if (*sig_start == 'a' || *sig_start == 's' || *sig_start == 'v') return false; sig_start++; } return true; } static inline const void *_iter_get_data(struct mbim_message_iter *iter, size_t pos) { pos = iter->base_offset + pos; while (pos >= iter->cur_iov_offset + iter->iov[iter->cur_iov].iov_len) { iter->cur_iov_offset += iter->iov[iter->cur_iov].iov_len; iter->cur_iov += 1; } return iter->iov[iter->cur_iov].iov_base + pos - iter->cur_iov_offset; } static bool _iter_copy_string(struct mbim_message_iter *iter, uint32_t offset, uint32_t len, char **out) { uint8_t buf[len]; uint8_t *dest = buf; uint32_t remaining = len; uint32_t iov_start = 0; uint32_t i = 0; uint32_t tocopy; if (!len) { *out = NULL; return true; } if (offset + len > iter->len) return false; offset += iter->base_offset; while (offset >= iov_start + iter->iov[i].iov_len) iov_start += iter->iov[i++].iov_len; tocopy = iter->iov[i].iov_len - (offset - iov_start); if (tocopy > remaining) tocopy = remaining; memcpy(dest, iter->iov[i].iov_base + offset - iov_start, tocopy); remaining -= tocopy; dest += tocopy; i += 1; while (remaining) { tocopy = remaining; if (remaining > iter->iov[i].iov_len) tocopy = iter->iov[i].iov_len; memcpy(dest, iter->iov[i].iov_base, tocopy); remaining -= tocopy; dest += tocopy; } /* Strings are in UTF16-LE, so convert to UTF16-CPU first if needed */ if (L_CPU_TO_LE16(0x8000) != 0x8000) { uint16_t *le = (uint16_t *) buf; for (i = 0; i < len; i+= 2) le[i] = __builtin_bswap16(le[i]); } *out = l_utf8_from_utf16(buf, len); return true; } static inline void _iter_init_internal(struct mbim_message_iter *iter, char container_type, const char *sig_start, const char *sig_end, const struct iovec *iov, uint32_t n_iov, size_t len, size_t base_offset, size_t pos, uint32_t n_elem) { size_t sig_len; if (sig_end) sig_len = sig_end - sig_start; else sig_len = strlen(sig_start); iter->sig_start = sig_start; iter->sig_len = sig_len; iter->sig_pos = 0; iter->iov = iov; iter->n_iov = n_iov; iter->cur_iov = 0; iter->cur_iov_offset = 0; iter->len = len; iter->base_offset = base_offset; iter->pos = pos; iter->n_elem = n_elem; iter->container_type = container_type; } static bool _iter_next_entry_basic(struct mbim_message_iter *iter, char type, void *out) { uint8_t uint8_val; uint16_t uint16_val; uint32_t uint32_val; uint64_t uint64_val; uint32_t offset, length; const void *data; size_t pos; if (iter->container_type == CONTAINER_TYPE_ARRAY && !iter->n_elem) return false; if (iter->pos >= iter->len) return false; pos = align_len(iter->pos, get_alignment(type)); switch (type) { case 'y': if (pos + 1 > iter->len) return false; data = _iter_get_data(iter, pos); uint8_val = l_get_u8(data); *(uint8_t *) out = uint8_val; iter->pos = pos + 1; break; case 'q': if (pos + 2 > iter->len) return false; data = _iter_get_data(iter, pos); uint16_val = l_get_le16(data); *(uint16_t *) out = uint16_val; iter->pos = pos + 2; break; case 'u': if (pos + 4 > iter->len) return false; data = _iter_get_data(iter, pos); uint32_val = l_get_le32(data); *(uint32_t *) out = uint32_val; iter->pos = pos + 4; break; case 't': if (pos + 8 > iter->len) return false; data = _iter_get_data(iter, pos); uint64_val = l_get_le64(data); *(uint64_t *) out = uint64_val; iter->pos = pos + 8; break; case 's': /* * String consists of two uint32_t values: * offset followed by length */ if (pos + 8 > iter->len) return false; data = _iter_get_data(iter, pos); offset = l_get_le32(data); data = _iter_get_data(iter, pos + 4); length = l_get_le32(data); if (!_iter_copy_string(iter, offset, length, out)) return false; iter->pos = pos + 8; break; default: return false; } if (iter->container_type != CONTAINER_TYPE_ARRAY) iter->sig_pos += 1; return true; } static bool _iter_enter_array(struct mbim_message_iter *iter, struct mbim_message_iter *array) { size_t pos; uint32_t n_elem; const char *sig_start; const char *sig_end; const void *data; bool fixed; uint32_t offset; if (iter->container_type == CONTAINER_TYPE_ARRAY && !iter->n_elem) return false; if (iter->sig_start[iter->sig_pos] != 'a') return false; sig_start = iter->sig_start + iter->sig_pos + 1; sig_end = _signature_end(sig_start) + 1; /* * Two possibilities: * 1. Element Count, followed by OL_PAIR_LIST * 2. Offset, followed by element length or size for raw buffers */ fixed = is_fixed_size(sig_start, sig_end); if (fixed) { pos = align_len(iter->pos, 4); if (pos + 4 > iter->len) return false; data = _iter_get_data(iter, pos); offset = l_get_le32(data); iter->pos += 4; } pos = align_len(iter->pos, 4); if (pos + 4 > iter->len) return false; data = _iter_get_data(iter, pos); n_elem = l_get_le32(data); pos += 4; if (iter->container_type != CONTAINER_TYPE_ARRAY) iter->sig_pos += sig_end - sig_start + 1; if (fixed) { _iter_init_internal(array, CONTAINER_TYPE_ARRAY, sig_start, sig_end, iter->iov, iter->n_iov, iter->len, iter->base_offset, offset, n_elem); return true; } _iter_init_internal(array, CONTAINER_TYPE_ARRAY, sig_start, sig_end, iter->iov, iter->n_iov, iter->len, iter->base_offset, pos, n_elem); iter->pos = pos + 8 * n_elem; return true; } static bool _iter_enter_struct(struct mbim_message_iter *iter, struct mbim_message_iter *structure) { size_t offset; size_t len; size_t pos; const char *sig_start; const char *sig_end; const void *data; if (iter->container_type == CONTAINER_TYPE_ARRAY && !iter->n_elem) return false; if (iter->sig_start[iter->sig_pos] != '(') return false; sig_start = iter->sig_start + iter->sig_pos + 1; sig_end = _signature_end(iter->sig_start + iter->sig_pos); /* TODO: support fixed size structures */ if (is_fixed_size(sig_start, sig_end)) return false; pos = align_len(iter->pos, 4); if (pos + 8 > iter->len) return false; data = _iter_get_data(iter, pos); offset = l_get_le32(data); pos += 4; data = _iter_get_data(iter, pos); len = l_get_le32(data); _iter_init_internal(structure, CONTAINER_TYPE_STRUCT, sig_start, sig_end, iter->iov, iter->n_iov, len, iter->base_offset + offset, 0, 0); if (iter->container_type != CONTAINER_TYPE_ARRAY) iter->sig_pos += sig_end - sig_start + 2; iter->pos = pos + 4; return true; } static bool _iter_enter_databuf(struct mbim_message_iter *iter, const char *signature, struct mbim_message_iter *databuf) { if (iter->container_type != CONTAINER_TYPE_STRUCT) return false; _iter_init_internal(databuf, CONTAINER_TYPE_DATABUF, signature, NULL, iter->iov, iter->n_iov, iter->len - iter->pos, iter->base_offset + iter->pos, 0, 0); iter->pos = iter->len; return true; } static bool message_iter_next_entry_valist(struct mbim_message_iter *orig, va_list args) { struct mbim_message_iter *iter = orig; const char *signature = orig->sig_start + orig->sig_pos; const char *end; uint32_t *out_n_elem; struct mbim_message_iter *sub_iter; struct mbim_message_iter stack[MAX_NESTING]; unsigned int indent = 0; void *arg; while (signature < orig->sig_start + orig->sig_len) { if (strchr(simple_types, *signature)) { arg = va_arg(args, void *); if (!_iter_next_entry_basic(iter, *signature, arg)) return false; signature += 1; continue; } switch (*signature) { case '0' ... '9': { uint32_t i; uint32_t n_elem; size_t pos; const void *src; if (iter->pos >= iter->len) return false; pos = align_len(iter->pos, 4); end = _signature_end(signature); n_elem = strtol(signature, NULL, 10); if (pos + n_elem > iter->len) return false; arg = va_arg(args, uint8_t *); for (i = 0; i + 4 < n_elem; i += 4) { src = _iter_get_data(iter, pos + i); memcpy(arg + i, src, 4); } src = _iter_get_data(iter, pos + i); memcpy(arg + i, src, n_elem - i); iter->pos = pos + n_elem; signature = end + 1; break; } case '(': signature += 1; indent += 1; if (unlikely(indent > MAX_NESTING)) return false; if (!_iter_enter_struct(iter, &stack[indent - 1])) return false; iter = &stack[indent - 1]; break; case ')': if (unlikely(indent == 0)) return false; signature += 1; indent -= 1; if (indent == 0) iter = orig; else iter = &stack[indent - 1]; break; case 'a': out_n_elem = va_arg(args, uint32_t *); sub_iter = va_arg(args, void *); if (!_iter_enter_array(iter, sub_iter)) return false; *out_n_elem = sub_iter->n_elem; end = _signature_end(signature + 1); signature = end + 1; break; case 'd': { const char *s = va_arg(args, const char *); sub_iter = va_arg(args, void *); if (!_iter_enter_databuf(iter, s, sub_iter)) return false; signature += 1; break; } default: return false; } } if (iter->container_type == CONTAINER_TYPE_ARRAY) iter->n_elem -= 1; return true; } bool mbim_message_iter_next_entry(struct mbim_message_iter *iter, ...) { va_list args; bool result; if (unlikely(!iter)) return false; va_start(args, iter); result = message_iter_next_entry_valist(iter, args); va_end(args); return result; } uint32_t _mbim_information_buffer_offset(uint32_t type) { switch (type) { case MBIM_COMMAND_MSG: case MBIM_COMMAND_DONE: return 28; case MBIM_INDICATE_STATUS_MSG: return 24; } return 0; } static struct mbim_message *_mbim_message_new_common(uint32_t type, const uint8_t *uuid, uint32_t cid) { struct mbim_message *msg; struct mbim_message_header *hdr; struct mbim_fragment_header *frag; msg = l_new(struct mbim_message, 1); hdr = (struct mbim_message_header *) msg->header; hdr->type = L_CPU_TO_LE32(type); frag = (struct mbim_fragment_header *) (msg->header + sizeof(*hdr)); frag->num_frags = L_CPU_TO_LE32(1); frag->cur_frag = L_CPU_TO_LE32(0); memcpy(msg->uuid, uuid, 16); msg->cid = cid; return mbim_message_ref(msg); } struct mbim_message *_mbim_message_new_command_done(const uint8_t *uuid, uint32_t cid, uint32_t status) { struct mbim_message *message = _mbim_message_new_common(MBIM_COMMAND_DONE, uuid, cid); if (!message) return NULL; message->status = status; return message; } void _mbim_message_set_tid(struct mbim_message *message, uint32_t tid) { struct mbim_message_header *hdr = (struct mbim_message_header *) message->header; hdr->tid = L_CPU_TO_LE32(tid); } void *_mbim_message_to_bytearray(struct mbim_message *message, size_t *out_len) { unsigned int i; struct mbim_message_header *hdr; void *binary; size_t pos; size_t len; if (!message->sealed) return NULL; hdr = (struct mbim_message_header *) message->header; len = L_LE32_TO_CPU(hdr->len); binary = l_malloc(len); memcpy(binary, message->header, HEADER_SIZE); pos = HEADER_SIZE; for (i = 0; i < message->n_frags; i++) { memcpy(binary + pos, message->frags[i].iov_base, message->frags[i].iov_len); pos += message->frags[i].iov_len; } if (out_len) *out_len = len; return binary; } struct mbim_message *mbim_message_new(const uint8_t *uuid, uint32_t cid, enum mbim_command_type type) { struct mbim_message *message = _mbim_message_new_common(MBIM_COMMAND_MSG, uuid, cid); if (!message) return NULL; message->command_type = type; return message; } struct mbim_message *mbim_message_ref(struct mbim_message *msg) { if (unlikely(!msg)) return NULL; __sync_fetch_and_add(&msg->ref_count, 1); return msg; } void mbim_message_unref(struct mbim_message *msg) { unsigned int i; if (unlikely(!msg)) return; if (__sync_sub_and_fetch(&msg->ref_count, 1)) return; for (i = 0; i < msg->n_frags; i++) l_free(msg->frags[i].iov_base); l_free(msg->frags); l_free(msg); } struct mbim_message *_mbim_message_build(const void *header, struct iovec *frags, uint32_t n_frags) { struct mbim_message *msg; struct mbim_message_header *hdr = (struct mbim_message_header *) header; struct mbim_message_iter iter; bool r = false; msg = l_new(struct mbim_message, 1); msg->ref_count = 1; memcpy(msg->header, header, HEADER_SIZE); msg->frags = frags; msg->n_frags = n_frags; msg->sealed = true; switch (L_LE32_TO_CPU(hdr->type)) { case MBIM_COMMAND_DONE: _iter_init_internal(&iter, CONTAINER_TYPE_STRUCT, "16yuuu", NULL, frags, n_frags, frags[0].iov_len, 0, 0, 0); r = mbim_message_iter_next_entry(&iter, msg->uuid, &msg->cid, &msg->status, &msg->info_buf_len); break; case MBIM_COMMAND_MSG: _iter_init_internal(&iter, CONTAINER_TYPE_STRUCT, "16yuuu", NULL, frags, n_frags, frags[0].iov_len, 0, 0, 0); r = mbim_message_iter_next_entry(&iter, msg->uuid, &msg->cid, &msg->command_type, &msg->info_buf_len); break; case MBIM_INDICATE_STATUS_MSG: _iter_init_internal(&iter, CONTAINER_TYPE_STRUCT, "16yuu", NULL, frags, n_frags, frags[0].iov_len, 0, 0, 0); r = mbim_message_iter_next_entry(&iter, msg->uuid, &msg->cid, &msg->info_buf_len); break; default: break; } if (!r) { l_free(msg); msg = NULL; } return msg; } uint32_t mbim_message_get_error(struct mbim_message *message) { struct mbim_message_header *hdr; if (unlikely(!message)) return false; if (unlikely(!message->sealed)) return false; hdr = (struct mbim_message_header *) message->header; if (L_LE32_TO_CPU(hdr->type) != MBIM_COMMAND_DONE) return 0; return message->status; } uint32_t mbim_message_get_cid(struct mbim_message *message) { if (unlikely(!message)) return false; return message->cid; } const uint8_t *mbim_message_get_uuid(struct mbim_message *message) { if (unlikely(!message)) return false; return message->uuid; } bool mbim_message_get_arguments(struct mbim_message *message, const char *signature, ...) { struct mbim_message_iter iter; va_list args; bool result; struct mbim_message_header *hdr; uint32_t type; size_t begin; if (unlikely(!message)) return false; if (unlikely(!message->sealed)) return false; hdr = (struct mbim_message_header *) message->header; type = L_LE32_TO_CPU(hdr->type); begin = _mbim_information_buffer_offset(type); _iter_init_internal(&iter, CONTAINER_TYPE_STRUCT, signature, NULL, message->frags, message->n_frags, message->info_buf_len, begin, 0, 0); va_start(args, signature); result = message_iter_next_entry_valist(&iter, args); va_end(args); return result; } static bool _mbim_message_get_data(struct mbim_message *message, uint32_t offset, void *dest, size_t len) { struct mbim_message_iter iter; struct mbim_message_header *hdr; uint32_t type; size_t begin; const void *src; size_t pos; uint32_t i; if (unlikely(!message)) return false; if (unlikely(!message->sealed)) return false; hdr = (struct mbim_message_header *) message->header; type = L_LE32_TO_CPU(hdr->type); begin = _mbim_information_buffer_offset(type); _iter_init_internal(&iter, CONTAINER_TYPE_STRUCT, "", NULL, message->frags, message->n_frags, message->info_buf_len, begin, offset, 0); pos = align_len(iter.pos, 4); if (pos + len > iter.len) return false; for (i = 0; i + 4 < len; i += 4) { src = _iter_get_data(&iter, pos + i); memcpy(dest + i, src, 4); } src = _iter_get_data(&iter, pos + i); memcpy(dest + i, src, len - i); return true; } bool mbim_message_get_ipv4_address(struct mbim_message *message, uint32_t offset, struct in_addr *addr) { return _mbim_message_get_data(message, offset, &addr->s_addr, 4); } bool mbim_message_get_ipv4_element(struct mbim_message *message, uint32_t offset, uint32_t *prefix_len, struct in_addr *addr) { uint8_t buf[8]; if (!_mbim_message_get_data(message, offset, buf, 8)) return false; *prefix_len = l_get_le32(buf); memcpy(&addr->s_addr, buf + 4, 4); return true; } bool mbim_message_get_ipv6_address(struct mbim_message *message, uint32_t offset, struct in6_addr *addr) { return _mbim_message_get_data(message, offset, addr->s6_addr, 16); } bool mbim_message_get_ipv6_element(struct mbim_message *message, uint32_t offset, uint32_t *prefix_len, struct in6_addr *addr) { uint8_t buf[20]; if (!_mbim_message_get_data(message, offset, buf, 20)) return false; *prefix_len = l_get_le32(buf); memcpy(&addr->s6_addr, buf + 4, 16); return true; } struct container { void *sbuf; /* static buffer */ size_t sbuf_size; size_t sbuf_pos; void *dbuf; /* data buffer */ size_t dbuf_size; size_t dbuf_pos; void *obuf; /* offset buffer */ size_t obuf_size; size_t obuf_pos; char container_type; char signature[64]; uint8_t sigindex; uint32_t base_offset; uint32_t array_start; }; static void container_update_offsets(struct container *container) { size_t i; if (!container->obuf) return; for (i = 0; i < container->obuf_pos; i += 4) { uint32_t sbuf_offset = l_get_u32(container->obuf + i); uint32_t dbuf_offset = l_get_u32(container->sbuf + sbuf_offset); dbuf_offset += container->sbuf_pos - container->base_offset; l_put_le32(dbuf_offset, container->sbuf + sbuf_offset); } l_free(container->obuf); container->obuf = NULL; container->obuf_pos = 0; container->obuf_size = 0; } struct mbim_message_builder { struct mbim_message *message; struct container stack[MAX_NESTING + 1]; uint32_t index; }; static inline size_t grow_buf(void **buf, size_t *buf_size, size_t *pos, size_t len, unsigned int alignment) { size_t size = align_len(*pos, alignment); if (size + len > *buf_size) { *buf = l_realloc(*buf, size + len); *buf_size = size + len; } if (size - *pos > 0) memset(*buf + *pos, 0, size - *pos); *pos = size + len; return size; } #define GROW_SBUF(c, len, alignment) \ grow_buf(&c->sbuf, &c->sbuf_size, &c->sbuf_pos, \ len, alignment) #define GROW_DBUF(c, len, alignment) \ grow_buf(&c->dbuf, &c->dbuf_size, &c->dbuf_pos, \ len, alignment) #define GROW_OBUF(c) \ grow_buf(&c->obuf, &c->obuf_size, &c->obuf_pos, 4, 4) static void add_offset_and_length(struct container *container, uint32_t offset, uint32_t len) { size_t start; /* * note the relative offset in the data buffer. Store it in native * endian order for now. It will be fixed up later once we finalize * the structure */ start = GROW_SBUF(container, 8, 4); l_put_u32(offset, container->sbuf + start); l_put_le32(len, container->sbuf + start + 4); /* Make a note in offset buffer to update the offset at this position */ offset = start; start = GROW_OBUF(container); l_put_u32(offset, container->obuf + start); } struct mbim_message_builder *mbim_message_builder_new(struct mbim_message *msg) { struct mbim_message_builder *ret; struct mbim_message_header *hdr; uint32_t type; struct container *container; if (unlikely(!msg)) return NULL; if (msg->sealed) return NULL; hdr = (struct mbim_message_header *) msg->header; type = L_LE32_TO_CPU(hdr->type); ret = l_new(struct mbim_message_builder, 1); ret->message = mbim_message_ref(msg); /* Reserve space in the static buffer for UUID, CID, Status, etc */ container = &ret->stack[ret->index]; container->base_offset = _mbim_information_buffer_offset(type); container->container_type = CONTAINER_TYPE_STRUCT; GROW_SBUF(container, container->base_offset, 0); return ret; } void mbim_message_builder_free(struct mbim_message_builder *builder) { uint32_t i; if (unlikely(!builder)) return; mbim_message_unref(builder->message); for (i = 0; i <= builder->index; i++) { if (builder->stack[i].container_type == CONTAINER_TYPE_ARRAY) continue; l_free(builder->stack[i].sbuf); l_free(builder->stack[i].dbuf); l_free(builder->stack[i].obuf); } l_free(builder); } bool mbim_message_builder_append_basic(struct mbim_message_builder *builder, char type, const void *value) { struct container *container = &builder->stack[builder->index]; struct container *array = NULL; size_t start; unsigned int alignment; size_t len; uint16_t *utf16; if (unlikely(!builder)) return false; if (unlikely(!strchr(simple_types, type))) return false; alignment = get_alignment(type); if (!alignment) return false; if (builder->index > 0 && container->signature[container->sigindex] != type) return false; len = get_basic_size(type); if (container->container_type == CONTAINER_TYPE_ARRAY) { array = container; container = &builder->stack[builder->index - 1]; } if (len) { uint16_t swapped_u16; uint32_t swapped_u32; uint64_t swapped_u64; switch (len) { case 2: swapped_u16 = L_CPU_TO_LE16(l_get_u16(value)); value = &swapped_u16; break; case 4: swapped_u32 = L_CPU_TO_LE32(l_get_u32(value)); value = &swapped_u32; break; case 8: swapped_u64 = L_CPU_TO_LE64(l_get_u64(value)); value = &swapped_u64; break; } if (array) { uint32_t n_elem = l_get_le32(container->sbuf + array->array_start + 4); start = GROW_DBUF(container, len, alignment); memcpy(container->dbuf + start, value, len); l_put_le32(n_elem + 1, container->sbuf + array->array_start + 4); } else { start = GROW_SBUF(container, len, alignment); memcpy(container->sbuf + start, value, len); } goto done; } /* Null string? */ if (!value) { start = GROW_SBUF(container, 8, 4); l_put_le32(0, container->sbuf + start); l_put_le32(0, container->sbuf + start + 4); goto done; } utf16 = l_utf8_to_utf16(value, &len); if (!utf16) return false; /* Strings are in UTF16-LE, so convert if needed */ if (L_CPU_TO_LE16(0x8000) != 0x8000) { size_t i; for (i = 0; i < len - 2; i += 2) utf16[i] = __builtin_bswap16(utf16[i]); } /* * First grow the data buffer. * MBIM v1.0-errata1, Section 10.3: * "If the size of the payload in the variable field is not a multiple * of 4 bytes, the field shall be padded up to the next 4 byte multiple. * This shall be true even for the last payload in DataBuffer." */ start = GROW_DBUF(container, len - 2, 4); memcpy(container->dbuf + start, utf16, len - 2); l_free(utf16); add_offset_and_length(container, start, len - 2); if (array) { uint32_t n_elem = l_get_le32(container->sbuf + array->array_start); l_put_le32(n_elem + 1, container->sbuf + array->array_start); } done: if (!array) container->sigindex += 1; return true; } bool mbim_message_builder_append_bytes(struct mbim_message_builder *builder, size_t len, const uint8_t *bytes) { struct container *container = &builder->stack[builder->index]; size_t start; if (unlikely(!builder)) return false; if (container->container_type == CONTAINER_TYPE_ARRAY) { struct container *array; if (unlikely(container->sigindex != 0)) return false; if (unlikely(container->signature[container->sigindex] != 'y')) return false; array = container; container = &builder->stack[builder->index - 1]; start = GROW_DBUF(container, len, 1); memcpy(container->dbuf + start, bytes, len); l_put_le32(len, container->sbuf + array->array_start + 4); return true; } else if (container->container_type == CONTAINER_TYPE_STRUCT) { if (builder->index > 0) { unsigned int i = container->sigindex; const char *sig = container->signature + i; size_t n_elem; const char *sigend; if (*sig < '0' || *sig > '9') return false; n_elem = strtol(sig, NULL, 10); if (n_elem != len) return false; sigend = _signature_end(sig); if (!sigend) return false; container->sigindex += sigend - sig + 1; } start = GROW_SBUF(container, len, 1); memcpy(container->sbuf + start, bytes, len); return true; } return false; } bool mbim_message_builder_enter_struct(struct mbim_message_builder *builder, const char *signature) { struct container *container; if (strlen(signature) > sizeof(((struct container *) 0)->signature) - 1) return false; if (builder->index == L_ARRAY_SIZE(builder->stack) - 1) return false; builder->index += 1; container = &builder->stack[builder->index]; memset(container, 0, sizeof(*container)); strcpy(container->signature, signature); container->sigindex = 0; container->container_type = CONTAINER_TYPE_STRUCT; return true; } bool mbim_message_builder_leave_struct(struct mbim_message_builder *builder) { struct container *container; struct container *parent; struct container *array = NULL; size_t start; if (unlikely(builder->index == 0)) return false; container = &builder->stack[builder->index]; if (unlikely(container->container_type != CONTAINER_TYPE_STRUCT)) return false; builder->index -= 1; parent = &builder->stack[builder->index]; GROW_DBUF(container, 0, 4); container_update_offsets(container); if (parent->container_type == CONTAINER_TYPE_ARRAY) { array = parent; parent = &builder->stack[builder->index - 1]; } /* * Copy the structure buffers into parent's buffers */ start = GROW_DBUF(parent, container->sbuf_pos + container->dbuf_pos, 4); memcpy(parent->dbuf + start, container->sbuf, container->sbuf_pos); memcpy(parent->dbuf + start + container->sbuf_pos, container->dbuf, container->dbuf_pos); l_free(container->sbuf); l_free(container->dbuf); add_offset_and_length(parent, start, container->sbuf_pos + container->dbuf_pos); if (array) { uint32_t n_elem = l_get_le32(parent->sbuf + array->array_start); l_put_le32(n_elem + 1, parent->sbuf + array->array_start); } memset(container, 0, sizeof(*container)); return true; } bool mbim_message_builder_enter_array(struct mbim_message_builder *builder, const char *signature) { struct container *parent; struct container *container; if (strlen(signature) > sizeof(((struct container *) 0)->signature) - 1) return false; if (builder->index == L_ARRAY_SIZE(builder->stack) - 1) return false; /* * TODO: validate that arrays consist of a single simple type or * a single struct */ parent = &builder->stack[builder->index++]; container = &builder->stack[builder->index]; /* Arrays add on to the parent's buffers */ container->container_type = CONTAINER_TYPE_ARRAY; strcpy(container->signature, signature); container->sigindex = 0; /* First grow the body enough to cover preceding length */ container->array_start = GROW_SBUF(parent, 4, 4); l_put_le32(0, parent->sbuf + container->array_start); /* For arrays of fixed-size elements, it is offset followed by length */ if (is_fixed_size(container->signature, _signature_end(container->signature))) { /* Note down offset into the data buffer */ size_t start = GROW_DBUF(parent, 0, 4); l_put_u32(start, parent->sbuf + container->array_start); /* Set length to 0 */ start = GROW_SBUF(parent, 4, 4); l_put_le32(0, parent->sbuf + start); /* Note down offset position to recalculate */ start = GROW_OBUF(parent); l_put_u32(container->array_start, parent->obuf + start); } return true; } bool mbim_message_builder_leave_array(struct mbim_message_builder *builder) { struct container *container; if (unlikely(builder->index == 0)) return false; container = &builder->stack[builder->index]; if (unlikely(container->container_type != CONTAINER_TYPE_ARRAY)) return false; builder->index -= 1; memset(container, 0, sizeof(*container)); return true; } bool mbim_message_builder_enter_databuf(struct mbim_message_builder *builder, const char *signature) { struct container *container; if (strlen(signature) > sizeof(((struct container *) 0)->signature) - 1) return false; if (builder->index != 0) return false; builder->index += 1; container = &builder->stack[builder->index]; memset(container, 0, sizeof(*container)); strcpy(container->signature, signature); container->sigindex = 0; container->container_type = CONTAINER_TYPE_DATABUF; return true; } bool mbim_message_builder_leave_databuf(struct mbim_message_builder *builder) { struct container *container; struct container *parent; size_t start; if (unlikely(builder->index == 0)) return false; container = &builder->stack[builder->index]; if (unlikely(container->container_type != CONTAINER_TYPE_DATABUF)) return false; builder->index -= 1; parent = &builder->stack[builder->index]; GROW_DBUF(container, 0, 4); container_update_offsets(container); /* * Copy the structure buffers into parent's buffers */ start = GROW_SBUF(parent, container->sbuf_pos + container->dbuf_pos, 4); memcpy(parent->sbuf + start, container->sbuf, container->sbuf_pos); memcpy(parent->sbuf + start + container->sbuf_pos, container->dbuf, container->dbuf_pos); l_free(container->sbuf); l_free(container->dbuf); memset(container, 0, sizeof(*container)); return true; } struct mbim_message *mbim_message_builder_finalize( struct mbim_message_builder *builder) { struct container *root; struct mbim_message_header *hdr; if (unlikely(!builder)) return NULL; if (builder->index != 0) return NULL; hdr = (struct mbim_message_header *) builder->message->header; root = &builder->stack[0]; GROW_DBUF(root, 0, 4); container_update_offsets(root); memcpy(root->sbuf, builder->message->uuid, 16); l_put_le32(builder->message->cid, root->sbuf + 16); switch (L_LE32_TO_CPU(hdr->type)) { case MBIM_COMMAND_DONE: l_put_le32(builder->message->status, root->sbuf + 20); break; case MBIM_COMMAND_MSG: l_put_le32(builder->message->command_type, root->sbuf + 20); break; default: break; } builder->message->info_buf_len = root->dbuf_pos + root->sbuf_pos - root->base_offset; l_put_le32(builder->message->info_buf_len, root->sbuf + root->base_offset - 4); builder->message->n_frags = 2; builder->message->frags = l_new(struct iovec, 2); builder->message->frags[0].iov_base = root->sbuf; builder->message->frags[0].iov_len = root->sbuf_pos; builder->message->frags[1].iov_base = root->dbuf; builder->message->frags[1].iov_len = root->dbuf_pos; root->sbuf = NULL; root->dbuf = NULL; hdr->len = L_CPU_TO_LE32(HEADER_SIZE + root->dbuf_pos + root->sbuf_pos); builder->message->sealed = true; return builder->message; } static bool append_arguments(struct mbim_message *message, const char *signature, va_list args) { struct mbim_message_builder *builder; char subsig[64]; const char *sigend; struct { char type; const char *sig_start; const char *sig_end; unsigned int n_items; } stack[MAX_NESTING + 1]; unsigned int stack_index = 0; if (strlen(signature) > sizeof(subsig) - 1) return false; builder = mbim_message_builder_new(message); stack[stack_index].type = CONTAINER_TYPE_STRUCT; stack[stack_index].sig_start = signature; stack[stack_index].sig_end = signature + strlen(signature); stack[stack_index].n_items = 0; while (stack_index != 0 || stack[0].sig_start != stack[0].sig_end) { const char *s; const char *str; if (stack[stack_index].type == CONTAINER_TYPE_ARRAY && stack[stack_index].n_items == 0) stack[stack_index].sig_start = stack[stack_index].sig_end; if (stack[stack_index].sig_start == stack[stack_index].sig_end) { bool r = false; if (stack_index == 0) goto error; if (stack[stack_index].type == CONTAINER_TYPE_ARRAY) r = mbim_message_builder_leave_array(builder); if (stack[stack_index].type == CONTAINER_TYPE_STRUCT) r = mbim_message_builder_leave_struct(builder); if (stack[stack_index].type == CONTAINER_TYPE_DATABUF) r = mbim_message_builder_leave_databuf(builder); if (!r) goto error; stack_index -= 1; continue; } s = stack[stack_index].sig_start; if (stack[stack_index].type != CONTAINER_TYPE_ARRAY) stack[stack_index].sig_start += 1; else stack[stack_index].n_items -= 1; switch (*s) { case '0' ... '9': { uint32_t n_elem = strtol(s, NULL, 10); const uint8_t *arg = va_arg(args, const uint8_t *); sigend = _signature_end(s); if (!sigend) goto error; if (!mbim_message_builder_append_bytes(builder, n_elem, arg)) goto error; stack[stack_index].sig_start = sigend + 1; break; } case 's': str = va_arg(args, const char *); if (!mbim_message_builder_append_basic(builder, *s, str)) goto error; break; case 'y': { uint8_t y = (uint8_t) va_arg(args, int); if (!mbim_message_builder_append_basic(builder, *s, &y)) goto error; break; } case 'q': { uint16_t n = (uint16_t) va_arg(args, int); if (!mbim_message_builder_append_basic(builder, *s, &n)) goto error; break; } case 'u': { uint32_t u = va_arg(args, uint32_t); if (!mbim_message_builder_append_basic(builder, *s, &u)) goto error; break; } case 't': { uint64_t u = va_arg(args, uint64_t); if (!mbim_message_builder_append_basic(builder, *s, &u)) goto error; break; } case 'v': /* Structure with variable signature */ { if (stack_index == MAX_NESTING) goto error; str = va_arg(args, const char *); if (!str) goto error; if (!mbim_message_builder_enter_struct(builder, str)) goto error; stack_index += 1; stack[stack_index].sig_start = str; stack[stack_index].sig_end = str + strlen(str); stack[stack_index].n_items = 0; stack[stack_index].type = CONTAINER_TYPE_STRUCT; break; } case 'd': { if (stack_index == MAX_NESTING) goto error; str = va_arg(args, const char *); if (!str) goto error; if (!mbim_message_builder_enter_databuf(builder, str)) goto error; stack_index += 1; stack[stack_index].sig_start = str; stack[stack_index].sig_end = str + strlen(str); stack[stack_index].n_items = 0; stack[stack_index].type = CONTAINER_TYPE_DATABUF; break; } case '(': if (stack_index == MAX_NESTING) goto error; sigend = _signature_end(s); memcpy(subsig, s + 1, sigend - s - 1); subsig[sigend - s - 1] = '\0'; if (!mbim_message_builder_enter_struct(builder, subsig)) goto error; if (stack[stack_index].type != CONTAINER_TYPE_ARRAY) stack[stack_index].sig_start = sigend + 1; stack_index += 1; stack[stack_index].sig_start = s + 1; stack[stack_index].sig_end = sigend; stack[stack_index].n_items = 0; stack[stack_index].type = CONTAINER_TYPE_STRUCT; break; case 'a': if (stack_index == MAX_NESTING) goto error; sigend = _signature_end(s + 1) + 1; memcpy(subsig, s + 1, sigend - s - 1); subsig[sigend - s - 1] = '\0'; if (!mbim_message_builder_enter_array(builder, subsig)) goto error; if (stack[stack_index].type != CONTAINER_TYPE_ARRAY) stack[stack_index].sig_start = sigend; stack_index += 1; stack[stack_index].sig_start = s + 1; stack[stack_index].sig_end = sigend; stack[stack_index].n_items = va_arg(args, unsigned int); stack[stack_index].type = CONTAINER_TYPE_ARRAY; /* Special case of byte arrays, just copy the data */ if (!strcmp(subsig, "y")) { const uint8_t *bytes = va_arg(args, const uint8_t *); if (!mbim_message_builder_append_bytes(builder, stack[stack_index].n_items, bytes)) goto error; stack[stack_index].n_items = 0; } break; default: goto error; } } mbim_message_builder_finalize(builder); mbim_message_builder_free(builder); return true; error: mbim_message_builder_free(builder); return false; } bool mbim_message_set_arguments(struct mbim_message *message, const char *signature, ...) { va_list args; bool result; if (unlikely(!message)) return false; if (unlikely(message->sealed)) return false; if (!signature) return true; va_start(args, signature); result = append_arguments(message, signature, args); va_end(args); return result; } void *_mbim_message_get_header(struct mbim_message *message, size_t *out_len) { if (out_len) *out_len = HEADER_SIZE; return message->header; } struct iovec *_mbim_message_get_body(struct mbim_message *message, size_t *out_n_iov, size_t *out_len) { if (out_len) *out_len = message->info_buf_len; if (out_n_iov) *out_n_iov = message->info_buf_len ? message->n_frags : message->n_frags - 1; return message->frags; }