summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorGravatar Nikias Bassen2022-02-04 04:21:28 +0100
committerGravatar Nikias Bassen2022-02-04 04:21:28 +0100
commit90fc3833203192ff5a6009d339454b0265b4efc1 (patch)
tree36d43304d2849063b4549441eab38f53043cb5b9 /src
parentb5ccf24362f65cacc96f529c33bd2dcf2a50cf1b (diff)
downloadlibimobiledevice-glue-90fc3833203192ff5a6009d339454b0265b4efc1.tar.gz
libimobiledevice-glue-90fc3833203192ff5a6009d339454b0265b4efc1.tar.bz2
Add support for Apple's OPACK encoding and TLV format
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am2
-rw-r--r--src/opack.c476
-rw-r--r--src/tlv.c196
3 files changed, 674 insertions, 0 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
index 38a2349..b709103 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -15,6 +15,8 @@ libimobiledevice_glue_1_0_la_SOURCES = \
collection.c \
termcolors.c \
cbuf.c \
+ opack.c \
+ tlv.c \
common.h
if WIN32
diff --git a/src/opack.c b/src/opack.c
new file mode 100644
index 0000000..9e7fa73
--- /dev/null
+++ b/src/opack.c
@@ -0,0 +1,476 @@
+/*
+ * opack.c
+ * "opack" format encoder/decoder implementation.
+ *
+ * Copyright (c) 2021 Nikias Bassen, All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <stdio.h>
+
+#include "common.h"
+#include "libimobiledevice-glue/cbuf.h"
+#include "libimobiledevice-glue/opack.h"
+#include "endianness.h"
+
+#define MAC_EPOCH 978307200
+
+static void opack_encode_node(plist_t node, struct char_buf* cbuf)
+{
+ plist_type type = plist_get_node_type(node);
+ switch (type) {
+ case PLIST_DICT: {
+ uint32_t count = plist_dict_get_size(node);
+ uint8_t blen = 0xEF;
+ if (count < 15)
+ blen = (uint8_t)count-32;
+ char_buf_append(cbuf, 1, &blen);
+ plist_dict_iter iter = NULL;
+ plist_dict_new_iter(node, &iter);
+ if (iter) {
+ plist_t sub = NULL;
+ do {
+ sub = NULL;
+ plist_dict_next_item(node, iter, NULL, &sub);
+ if (sub) {
+ plist_t key = plist_dict_item_get_key(sub);
+ opack_encode_node(key, cbuf);
+ opack_encode_node(sub, cbuf);
+ }
+ } while (sub);
+ free(iter);
+ if (count > 14) {
+ uint8_t term = 0x03;
+ char_buf_append(cbuf, 1, &term);
+ }
+ }
+ } break;
+ case PLIST_ARRAY: {
+ uint32_t count = plist_array_get_size(node);
+ uint8_t blen = 0xDF;
+ if (count < 15)
+ blen = (uint8_t)(count-48);
+ char_buf_append(cbuf, 1, &blen);
+ plist_array_iter iter = NULL;
+ plist_array_new_iter(node, &iter);
+ if (iter) {
+ plist_t sub = NULL;
+ do {
+ sub = NULL;
+ plist_array_next_item(node, iter, &sub);
+ if (sub) {
+ opack_encode_node(sub, cbuf);
+ }
+ } while (sub);
+ free(iter);
+ if (count > 14) {
+ uint8_t term = 0x03;
+ char_buf_append(cbuf, 1, &term);
+ }
+ }
+ } break;
+ case PLIST_BOOLEAN: {
+ uint8_t bval = 2 - plist_bool_val_is_true(node);
+ char_buf_append(cbuf, 1, &bval);
+ } break;
+ case PLIST_UINT: {
+ uint64_t u64val = 0;
+ plist_get_uint_val(node, &u64val);
+ if ((uint8_t)u64val == u64val) {
+ uint8_t u8val = (uint8_t)u64val;
+ if (u8val > 0x27) {
+ uint8_t blen = 0x30;
+ char_buf_append(cbuf, 1, &blen);
+ char_buf_append(cbuf, 1, &u8val);
+ } else {
+ u8val += 8;
+ char_buf_append(cbuf, 1, &u8val);
+ }
+ } else if ((uint32_t)u64val == u64val) {
+ uint8_t blen = 0x32;
+ char_buf_append(cbuf, 1, &blen);
+ uint32_t u32val = (uint32_t)u64val;
+ u32val = htole32(u32val);
+ char_buf_append(cbuf, 4, (unsigned char*)&u32val);
+ } else {
+ uint8_t blen = 0x33;
+ char_buf_append(cbuf, 1, &blen);
+ u64val = htole64(u64val);
+ char_buf_append(cbuf, 8, (unsigned char*)&u64val);
+ }
+ } break;
+ case PLIST_REAL: {
+ double dval = 0;
+ plist_get_real_val(node, &dval);
+ if ((float)dval == dval) {
+ float fval = (float)dval;
+ uint32_t u32val = 0;
+ memcpy(&u32val, &fval, 4);
+ u32val = float_bswap32(u32val);
+ uint8_t blen = 0x35;
+ char_buf_append(cbuf, 1, &blen);
+ char_buf_append(cbuf, 4, (unsigned char*)&u32val);
+ } else {
+ uint64_t u64val = 0;
+ memcpy(&u64val, &dval, 8);
+ u64val = float_bswap64(u64val);
+ uint8_t blen = 0x36;
+ char_buf_append(cbuf, 1, &blen);
+ char_buf_append(cbuf, 8, (unsigned char*)&u64val);
+ }
+ } break;
+ case PLIST_DATE: {
+ int32_t sec = 0;
+ int32_t usec = 0;
+ plist_get_date_val(node, &sec, &usec);
+ time_t tsec = sec;
+ tsec -= MAC_EPOCH;
+ double dval = (double)tsec + ((double)usec / 1000000);
+ uint8_t blen = 0x06;
+ char_buf_append(cbuf, 1, &blen);
+ uint64_t u64val = 0;
+ memcpy(&u64val, &dval, 8);
+ u64val = float_bswap64(u64val);
+ char_buf_append(cbuf, 8, (unsigned char*)&u64val);
+ } break;
+ case PLIST_STRING:
+ case PLIST_KEY: {
+ uint64_t len = 0;
+ char* str = NULL;
+ if (type == PLIST_KEY) {
+ plist_get_key_val(node, &str);
+ len = strlen(str);
+ } else {
+ str = (char*)plist_get_string_ptr(node, &len);
+ }
+ if (len > 0x20) {
+ if (len > 0xFF) {
+ if (len > 0xFFFF) {
+ if (len >> 32) {
+ uint8_t blen = 0x64;
+ char_buf_append(cbuf, 1, &blen);
+ uint64_t u64val = htole64(len);
+ char_buf_append(cbuf, 8, (unsigned char*)&u64val);
+ } else {
+ uint8_t blen = 0x63;
+ char_buf_append(cbuf, 1, &blen);
+ uint32_t u32val = htole32((uint32_t)len);
+ char_buf_append(cbuf, 4, (unsigned char*)&u32val);
+ }
+ } else {
+ uint8_t blen = 0x62;
+ char_buf_append(cbuf, 1, &blen);
+ uint16_t u16val = htole16((uint16_t)len);
+ char_buf_append(cbuf, 2, (unsigned char*)&u16val);
+ }
+ } else {
+ uint8_t blen = 0x61;
+ char_buf_append(cbuf, 1, &blen);
+ char_buf_append(cbuf, 1, (unsigned char*)&len);
+ }
+ } else {
+ uint8_t blen = 0x40 + len;
+ char_buf_append(cbuf, 1, &blen);
+ }
+ char_buf_append(cbuf, len, (unsigned char*)str);
+ if (type == PLIST_KEY) {
+ free(str);
+ }
+ } break;
+ case PLIST_DATA: {
+ uint64_t len = 0;
+ const char* data = plist_get_data_ptr(node, &len);
+ if (len > 0x20) {
+ if (len > 0xFF) {
+ if (len > 0xFFFF) {
+ if (len >> 32) {
+ uint8_t blen = 0x94;
+ char_buf_append(cbuf, 1, &blen);
+ uint32_t u64val = htole64(len);
+ char_buf_append(cbuf, 8, (unsigned char*)&u64val);
+ } else {
+ uint8_t blen = 0x93;
+ char_buf_append(cbuf, 1, &blen);
+ uint32_t u32val = htole32((uint32_t)len);
+ char_buf_append(cbuf, 4, (unsigned char*)&u32val);
+ }
+ } else {
+ uint8_t blen = 0x92;
+ char_buf_append(cbuf, 1, &blen);
+ uint16_t u16val = htole16((uint16_t)len);
+ char_buf_append(cbuf, 2, (unsigned char*)&u16val);
+ }
+ } else {
+ uint8_t blen = 0x91;
+ char_buf_append(cbuf, 1, &blen);
+ char_buf_append(cbuf, 1, (unsigned char*)&len);
+ }
+ } else {
+ uint8_t blen = 0x70 + len;
+ char_buf_append(cbuf, 1, &blen);
+ }
+ char_buf_append(cbuf, len, (unsigned char*)data);
+ } break;
+ default:
+ fprintf(stderr, "%s: ERROR: Unsupported data type in plist\n", __func__);
+ break;
+ }
+}
+
+LIBIMOBILEDEVICE_GLUE_API void opack_encode_from_plist(plist_t plist, unsigned char** out, unsigned int* out_len)
+{
+ if (!plist || !out || !out_len) {
+ return;
+ }
+ struct char_buf* cbuf = char_buf_new();
+ opack_encode_node(plist, cbuf);
+ *out = cbuf->data;
+ *out_len = cbuf->length;
+ cbuf->data = NULL;
+ char_buf_free(cbuf);
+}
+
+static int opack_decode_obj(unsigned char** p, unsigned char* end, plist_t* plist_out, uint32_t level)
+{
+ uint8_t type = **p;
+ if (type == 0x02) {
+ /* bool: false */
+ *plist_out = plist_new_bool(0);
+ (*p)++;
+ return 0;
+ } else if (type == 0x01) {
+ /* bool: true */
+ *plist_out = plist_new_bool(1);
+ (*p)++;
+ return 0;
+ } else if (type == 0x03) {
+ /* NULL / structured type child node terminator */
+ (*p)++;
+ return -2;
+ } else if (type == 0x06) {
+ /* date type */
+ (*p)++;
+ double value = *(double*)*p;
+ time_t sec = (time_t)value;
+ value -= sec;
+ uint32_t usec = value * 1000000;
+ (*p)+=8;
+ *plist_out = plist_new_date(sec, usec);
+ } else if (type >= 0x08 && type <= 0x36) {
+ /* numerical type */
+ (*p)++;
+ uint64_t value = 0;
+ if (type == 0x36) {
+ /* double */
+ uint64_t u64val = float_bswap64(*(uint64_t*)(*p));
+ (*p)+=8;
+ double dval = 0;
+ memcpy(&dval, &u64val, 8);
+ *plist_out = plist_new_real(dval);
+ return 0;
+ } else if (type == 0x35) {
+ /* float */
+ uint32_t u32val = float_bswap32(*(uint32_t*)(*p));
+ (*p)+=4;
+ float fval = 0;
+ memcpy(&fval, &u32val, 4);
+ *plist_out = plist_new_real((double)fval);
+ return 0;
+ } else if (type < 0x30) {
+ value = type - 8;
+ } else if (type == 0x30) {
+ value = (int8_t)**p;
+ (*p)++;
+ } else if (type == 0x32) {
+ uint32_t u32val = *(uint32_t*)*p;
+ value = (int32_t)le32toh(u32val);
+ (p)+=4;
+ } else if (type == 0x33) {
+ uint64_t u64val = *(uint64_t*)*p;
+ value = le64toh(u64val);
+ (p)+=8;
+ } else {
+ fprintf(stderr, "%s: ERROR: Invalid encoded byte '%02x'\n", __func__, type);
+ *p = end;
+ return -1;
+ }
+ *plist_out = plist_new_uint(value);
+ } else if (type >= 0x40 && type <= 0x64) {
+ /* string */
+ (*p)++;
+ size_t slen = 0;
+ if (type < 0x61) {
+ slen = type - 0x40;
+ } else {
+ if (type == 0x61) {
+ slen = **p;
+ (*p)++;
+ } else if (type == 0x62) {
+ uint16_t u16val = *(uint16_t*)*p;
+ slen = le16toh(u16val);
+ (*p)+=2;
+ } else if (type == 0x63) {
+ uint32_t u32val = *(uint32_t*)*p;
+ slen = le32toh(u32val);
+ (*p)+=4;
+ } else if (type == 0x64) {
+ uint64_t u64val = *(uint64_t*)*p;
+ slen = le64toh(u64val);
+ (*p)+=8;
+ }
+ }
+ if (*p + slen > end) {
+ fprintf(stderr, "%s: ERROR: Size points past end of data\n", __func__);
+ *p = end;
+ return -1;
+ }
+ char* str = malloc(slen+1);
+ strncpy(str, (const char*)*p, slen);
+ str[slen] = '\0';
+ *plist_out = plist_new_string(str);
+ (*p)+=slen;
+ free(str);
+ } else if (type >= 0x70 && type <= 0x94) {
+ /* data */
+ (*p)++;
+ size_t dlen = 0;
+ if (type < 0x91) {
+ dlen = type - 0x70;
+ } else {
+ if (type == 0x91) {
+ dlen = **p;
+ (*p)++;
+ } else if (type == 0x92) {
+ uint16_t u16val = *(uint16_t*)*p;
+ dlen = le16toh(u16val);
+ (*p)+=2;
+ } else if (type == 0x93) {
+ uint32_t u32val = *(uint32_t*)*p;
+ dlen = le32toh(u32val);
+ (*p)+=4;
+ } else if (type == 0x94) {
+ uint64_t u64val = *(uint64_t*)*p;
+ dlen = le64toh(u64val);
+ (*p)+=8;
+ }
+ }
+ if (*p + dlen > end) {
+ fprintf(stderr, "%s: ERROR: Size points past end of data\n", __func__);
+ *p = end;
+ return -1;
+ }
+ *plist_out = plist_new_data((const char*)*p, dlen);
+ (*p)+=dlen;
+ } else if (type >= 0xE0 && type <= 0xEF) {
+ /* dictionary */
+ (*p)++;
+ plist_t dict = plist_new_dict();
+ uint32_t num_children = 0xFFFFFFFF;
+ if (type < 0xEF) {
+ /* explicit number of children */
+ num_children = type - 0xE0;
+ }
+ if (!*plist_out) {
+ *plist_out = dict;
+ }
+ uint32_t i = 0;
+ while (i++ < num_children) {
+ plist_t keynode = NULL;
+ int res = opack_decode_obj(p, end, &keynode, level+1);
+ if (res == -2) {
+ break;
+ } else if (res < 0) {
+ return -1;
+ }
+ if (!PLIST_IS_STRING(keynode)) {
+ plist_free(keynode);
+ fprintf(stderr, "%s: ERROR: Invalid node type for dictionary key node\n", __func__);
+ *p = end;
+ return -1;
+ }
+ char* key = NULL;
+ plist_get_string_val(keynode, &key);
+ plist_free(keynode);
+ plist_t valnode = NULL;
+ if (opack_decode_obj(p, end, &valnode, level+1) < 0) {
+ free(key);
+ return -1;
+ }
+ plist_dict_set_item(dict, key, valnode);
+ }
+ if (level == 0) {
+ *p = end;
+ return 0;
+ }
+ } else if (type >= 0xD0 && type <= 0xDF) {
+ /* array */
+ (*p)++;
+ plist_t array = plist_new_array();
+ if (!*plist_out) {
+ *plist_out = array;
+ }
+ uint32_t num_children = 0xFFFFFFFF;
+ if (type < 0xDF) {
+ /* explicit number of children */
+ num_children = type - 0xD0;
+ }
+ uint32_t i = 0;
+ while (i++ < num_children) {
+ plist_t child = NULL;
+ int res = opack_decode_obj(p, end, &child, level+1);
+ if (res == -2) {
+ if (type < 0xDF) {
+ fprintf(stderr, "%s: ERROR: Expected child node, found terminator\n", __func__);
+ *p = end;
+ return -1;
+ }
+ break;
+ } else if (res < 0) {
+ return -1;
+ }
+ plist_array_append_item(array, child);
+ }
+ if (level == 0) {
+ *p = end;
+ return 0;
+ }
+ } else {
+ fprintf(stderr, "%s: ERROR: Unexpected character '%02x encountered\n", __func__, type);
+ *p = end;
+ return -1;
+ }
+ return 0;
+}
+
+LIBIMOBILEDEVICE_GLUE_API int opack_decode_to_plist(unsigned char* buf, unsigned int buf_len, plist_t* plist_out)
+{
+ if (!buf || buf_len == 0 || !plist_out) {
+ return -1;
+ }
+ unsigned char* p = buf;
+ unsigned char* end = buf + buf_len;
+ while (p < end) {
+ opack_decode_obj(&p, end, plist_out, 0);
+ }
+ return 0;
+}
diff --git a/src/tlv.c b/src/tlv.c
new file mode 100644
index 0000000..d08c8f3
--- /dev/null
+++ b/src/tlv.c
@@ -0,0 +1,196 @@
+/*
+ * tlv.c
+ * Simple TLV implementation.
+ *
+ * Copyright (c) 2021 Nikias Bassen, All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <stdio.h>
+
+#include "common.h"
+#include "libimobiledevice-glue/tlv.h"
+#include "endianness.h"
+
+LIBIMOBILEDEVICE_GLUE_API tlv_buf_t tlv_buf_new()
+{
+ tlv_buf_t tlv = malloc(sizeof(struct tlv_buf));
+ tlv->capacity = 1024;
+ tlv->length = 0;
+ tlv->data = malloc(tlv->capacity);
+ return tlv;
+}
+
+LIBIMOBILEDEVICE_GLUE_API void tlv_buf_free(tlv_buf_t tlv)
+{
+ if (tlv) {
+ free(tlv->data);
+ free(tlv);
+ }
+}
+
+LIBIMOBILEDEVICE_GLUE_API void tlv_buf_append(tlv_buf_t tlv, uint8_t tag, unsigned int length, void* data)
+{
+ if (!tlv || !tlv->data) {
+ return;
+ }
+ unsigned int req_len = (length > 255) ? (length / 255) * 257 + (2 + (length % 255)) : length;
+ if (tlv->length + req_len > tlv->capacity) {
+ unsigned int newcapacity = tlv->capacity + ((req_len / 1024) + 1) * 1024;
+ unsigned char* newdata = realloc(tlv->data, newcapacity);
+ if (!newdata) {
+ fprintf(stderr, "%s: ERROR: Failed to realloc\n", __func__);
+ return;
+ }
+ tlv->data = newdata;
+ tlv->capacity = newcapacity;
+ }
+ unsigned char* p = tlv->data + tlv->length;
+ unsigned int cur = 0;
+ while (length - cur > 0) {
+ if (length - cur > 255) {
+ *(p++) = tag;
+ *(p++) = 0xFF;
+ memcpy(p, (unsigned char*)data + cur, 255);
+ p += 255;
+ cur += 255;
+ } else {
+ uint8_t rem = length - cur;
+ *(p++) = tag;
+ *(p++) = rem;
+ memcpy(p, (unsigned char*)data + cur, rem);
+ p += rem;
+ cur += rem;
+ }
+ }
+ tlv->length = p - tlv->data;
+}
+
+LIBIMOBILEDEVICE_GLUE_API unsigned char* tlv_get_data_ptr(const void* tlv_data, void* tlv_end, uint8_t tag, uint8_t* length)
+{
+ unsigned char* p = (unsigned char*)tlv_data;
+ unsigned char* end = (unsigned char*)tlv_end;
+ while (p < end) {
+ uint8_t cur_tag = *(p++);
+ uint8_t len = *(p++);
+ if (cur_tag == tag) {
+ *length = len;
+ return p;
+ }
+ p+=len;
+ }
+ return NULL;
+}
+
+LIBIMOBILEDEVICE_GLUE_API int tlv_data_get_uint(const void* tlv_data, unsigned int tlv_length, uint8_t tag, uint64_t* value)
+{
+ if (!tlv_data || tlv_length < 2 || !value) {
+ return 0;
+ }
+ uint8_t length = 0;
+ unsigned char* ptr = tlv_get_data_ptr(tlv_data, (unsigned char*)tlv_data+tlv_length, tag, &length);
+ if (!ptr) {
+ return 0;
+ }
+ if (ptr + length > (unsigned char*)tlv_data + tlv_length) {
+ return 0;
+ }
+ if (length == 1) {
+ uint8_t val = *ptr;
+ *value = val;
+ } else if (length == 2) {
+ uint16_t val = *(uint16_t*)ptr;
+ val = le16toh(val);
+ *value = val;
+ } else if (length == 4) {
+ uint32_t val = *(uint32_t*)ptr;
+ val = le32toh(val);
+ *value = val;
+ } else if (length == 8) {
+ uint64_t val = *(uint64_t*)ptr;
+ val = le64toh(val);
+ *value = val;
+ } else {
+ return 0;
+ }
+ return 1;
+}
+
+LIBIMOBILEDEVICE_GLUE_API int tlv_data_get_uint8(const void* tlv_data, unsigned int tlv_length, uint8_t tag, uint8_t* value)
+{
+ if (!tlv_data || tlv_length < 2 || !value) {
+ return 0;
+ }
+ uint8_t length = 0;
+ unsigned char* ptr = tlv_get_data_ptr(tlv_data, (unsigned char*)tlv_data+tlv_length, tag, &length);
+ if (!ptr) {
+ return 0;
+ }
+ if (length != 1) {
+ return 0;
+ }
+ if (ptr + length > (unsigned char*)tlv_data + tlv_length) {
+ return 0;
+ }
+ *value = *ptr;
+ return 1;
+}
+
+LIBIMOBILEDEVICE_GLUE_API int tlv_data_copy_data(const void* tlv_data, unsigned int tlv_length, uint8_t tag, void** out, unsigned int* out_len)
+{
+ if (!tlv_data || tlv_length < 2 || !out || !out_len) {
+ return 0;
+ }
+ *out = NULL;
+ *out_len = 0;
+
+ unsigned char* dest = NULL;
+ unsigned int dest_len = 0;
+
+ unsigned char* ptr = (unsigned char*)tlv_data;
+ unsigned char* end = (unsigned char*)tlv_data + tlv_length;
+ while (ptr < end) {
+ uint8_t length = 0;
+ ptr = tlv_get_data_ptr(ptr, end, tag, &length);
+ if (!ptr) {
+ break;
+ }
+ unsigned char* newdest = realloc(dest, dest_len + length);
+ if (!newdest) {
+ free(dest);
+ return 0;
+ }
+ dest = newdest;
+ memcpy(dest + dest_len, ptr, length);
+ dest_len += length;
+ ptr += length;
+ }
+ if (!dest) {
+ return 0;
+ }
+
+ *out = (void*)dest;
+ *out_len = dest_len;
+
+ return 1;
+}