summaryrefslogtreecommitdiffstats
path: root/src/opack.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/opack.c')
-rw-r--r--src/opack.c476
1 files changed, 476 insertions, 0 deletions
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;
+}