/* * 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 #endif #include #include #include #include #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); uint64_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; } } 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; } 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; }