CFBinaryPList.c   [plain text]


/*
 * Copyright (c) 2005 Apple Computer, Inc. All rights reserved.
 *
 * @APPLE_LICENSE_HEADER_START@
 * 
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this
 * file.
 * 
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 * 
 * @APPLE_LICENSE_HEADER_END@
 */
/*	CFBinaryPList.c
	Copyright 2000-2002, Apple, Inc. All rights reserved.
	Responsibility: Christopher Kane
*/

#include <CoreFoundation/CFBase.h>
#include <CoreFoundation/CFString.h>
#include <CoreFoundation/CFNumber.h>
#include <CoreFoundation/CFDate.h>
#include <CoreFoundation/CFData.h>
#include <CoreFoundation/CFArray.h>
#include <CoreFoundation/CFDictionary.h>
#include <CoreFoundation/CFSet.h>
#include <CoreFoundation/CFPropertyList.h>
#include <CoreFoundation/CFByteOrder.h>
#include <CoreFoundation/CFRuntime.h>
#include <CoreFoundation/CFStream.h>
#include <stdio.h>
#include <limits.h>
#include <string.h>
#include "CFInternal.h"


CF_INLINE CFTypeID __CFGenericTypeID_genericobj_inline(const void *cf) {
    CFTypeID typeID = __CFBitfieldGetValue(((const CFRuntimeBase *)cf)->_info, 15, 8);
    return CF_IS_OBJC(typeID, cf) ? CFGetTypeID(cf) : typeID;
}

struct __CFKeyedArchiverUID {
    CFRuntimeBase _base;
    uint32_t _value;
};

static CFStringRef __CFKeyedArchiverUIDCopyDescription(CFTypeRef cf) {
    CFKeyedArchiverUIDRef uid = (CFKeyedArchiverUIDRef)cf;
    return CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("<CFKeyedArchiverUID %p [%p]>{value = %u}"), cf, CFGetAllocator(cf), uid->_value);
}

static CFStringRef __CFKeyedArchiverUIDCopyFormattingDescription(CFTypeRef cf, CFDictionaryRef formatOptions) {
    CFKeyedArchiverUIDRef uid = (CFKeyedArchiverUIDRef)cf;
    return CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("@%u@"), uid->_value);
}

static CFTypeID __kCFKeyedArchiverUIDTypeID = _kCFRuntimeNotATypeID;

static const CFRuntimeClass __CFKeyedArchiverUIDClass = {
    0,
    "CFKeyedArchiverUID",
    NULL,	// init
    NULL,	// copy
    NULL,	// finalize
    NULL,	// equal -- pointer equality only
    NULL,	// hash -- pointer hashing only
    __CFKeyedArchiverUIDCopyFormattingDescription,
    __CFKeyedArchiverUIDCopyDescription
};

__private_extern__ void __CFKeyedArchiverUIDInitialize(void) {
    __kCFKeyedArchiverUIDTypeID = _CFRuntimeRegisterClass(&__CFKeyedArchiverUIDClass);
}

CFTypeID _CFKeyedArchiverUIDGetTypeID(void) {
    return __kCFKeyedArchiverUIDTypeID;
}

CFKeyedArchiverUIDRef _CFKeyedArchiverUIDCreate(CFAllocatorRef allocator, uint32_t value) {
    CFKeyedArchiverUIDRef uid;
    uid = (CFKeyedArchiverUIDRef)_CFRuntimeCreateInstance(allocator, __kCFKeyedArchiverUIDTypeID, sizeof(struct __CFKeyedArchiverUID) - sizeof(CFRuntimeBase), NULL);
    if (NULL == uid) {
	return NULL;
    }
    ((struct __CFKeyedArchiverUID *)uid)->_value = value;
    return uid;
}


uint32_t _CFKeyedArchiverUIDGetValue(CFKeyedArchiverUIDRef uid) {
    return uid->_value;
}


typedef struct {
    CFTypeRef stream;
    bool streamIsData;
    uint64_t written;
    int32_t used;
    uint8_t buffer[8192 - 16];
} __CFBinaryPlistWriteBuffer;

CF_INLINE void writeBytes(__CFBinaryPlistWriteBuffer *buf, const UInt8 *bytes, CFIndex length) {
    if (buf->streamIsData) {
        CFDataAppendBytes((CFMutableDataRef)buf->stream, bytes, length);
    } else {
        CFWriteStreamWrite((CFWriteStreamRef)buf->stream, bytes, length);
    }
}

static void bufferWrite(__CFBinaryPlistWriteBuffer *buf, const uint8_t *buffer, CFIndex count) {
    CFIndex copyLen;
    if ((CFIndex)sizeof(buf->buffer) <= count) {
	writeBytes(buf, buf->buffer, buf->used);
	buf->written += buf->used;
	buf->used = 0;
	writeBytes(buf, buffer, count);
	buf->written += count;
	return;
    }
    copyLen = __CFMin(count, (CFIndex)sizeof(buf->buffer) - buf->used);
    memmove(buf->buffer + buf->used, buffer, copyLen);
    buf->used += copyLen;
    if (sizeof(buf->buffer) == buf->used) {
	writeBytes(buf, buf->buffer, sizeof(buf->buffer));
	buf->written += sizeof(buf->buffer);
	memmove(buf->buffer, buffer + copyLen, count - copyLen);
	buf->used = count - copyLen;
    }
}

static void bufferFlush(__CFBinaryPlistWriteBuffer *buf) {
    writeBytes(buf, buf->buffer, buf->used);
    buf->written += buf->used;
    buf->used = 0;
}

/*
HEADER
	magic number ("bplist")
	file format version

OBJECT TABLE
	variable-sized objects

	Object Formats (marker byte followed by additional info in some cases)
	null	0000 0000
	bool	0000 1000			// false
	bool	0000 1001			// true
	fill	0000 1111			// fill byte
	int	0001 nnnn	...		// # of bytes is 2^nnnn, big-endian bytes
	real	0010 nnnn	...		// # of bytes is 2^nnnn, big-endian bytes
	date	0011 0011	...		// 8 byte float follows, big-endian bytes
	data	0100 nnnn	[int]	...	// nnnn is number of bytes unless 1111 then int count follows, followed by bytes
	string	0101 nnnn	[int]	...	// ASCII string, nnnn is # of chars, else 1111 then int count, then bytes
	string	0110 nnnn	[int]	...	// Unicode string, nnnn is # of chars, else 1111 then int count, then big-endian 2-byte uint16_t
		0111 xxxx			// unused
	uid	1000 nnnn	...		// nnnn+1 is # of bytes
		1001 xxxx			// unused
	array	1010 nnnn	[int]	objref*	// nnnn is count, unless '1111', then int count follows
		1011 xxxx			// unused
		1100 xxxx			// unused
	dict	1101 nnnn	[int]	keyref* objref*	// nnnn is count, unless '1111', then int count follows
		1110 xxxx			// unused
		1111 xxxx			// unused

OFFSET TABLE
	list of ints, byte size of which is given in trailer
	-- these are the byte offsets into the file
	-- number of these is in the trailer

TRAILER
	byte size of offset ints in offset table
	byte size of object refs in arrays and dicts
	number of offsets in offset table (also is number of objects)
	element # in offset table which is top level object

*/


static CFTypeID stringtype = -1, datatype = -1, numbertype = -1, datetype = -1;
static CFTypeID booltype = -1, dicttype = -1, arraytype = -1;

static void _appendInt(__CFBinaryPlistWriteBuffer *buf, uint64_t bigint) {
    uint8_t marker;
    uint8_t *bytes;
    CFIndex nbytes;
    if (bigint <= (uint64_t)0xff) {
	nbytes = 1;
	marker = kCFBinaryPlistMarkerInt | 0;
    } else if (bigint <= (uint64_t)0xffff) {
	nbytes = 2;
	marker = kCFBinaryPlistMarkerInt | 1;
    } else if (bigint <= (uint64_t)0xffffffff) {
	nbytes = 4;
	marker = kCFBinaryPlistMarkerInt | 2;
    } else {
	nbytes = 8;
	marker = kCFBinaryPlistMarkerInt | 3;
    }
    bigint = CFSwapInt64HostToBig(bigint);
    bytes = (uint8_t *)&bigint + sizeof(bigint) - nbytes;
    bufferWrite(buf, &marker, 1);
    bufferWrite(buf, bytes, nbytes);
}

static void _appendUID(__CFBinaryPlistWriteBuffer *buf, CFKeyedArchiverUIDRef uid) {
    uint8_t marker;
    uint8_t *bytes;
    CFIndex nbytes;
    uint64_t bigint = _CFKeyedArchiverUIDGetValue(uid);
    if (bigint <= (uint64_t)0xff) {
	nbytes = 1;
    } else if (bigint <= (uint64_t)0xffff) {
	nbytes = 2;
    } else if (bigint <= (uint64_t)0xffffffff) {
	nbytes = 4;
    } else {
	nbytes = 8;
    }
    marker = kCFBinaryPlistMarkerUID | (nbytes - 1);
    bigint = CFSwapInt64HostToBig(bigint);
    bytes = (uint8_t *)&bigint + sizeof(bigint) - nbytes;
    bufferWrite(buf, &marker, 1);
    bufferWrite(buf, bytes, nbytes);
}

static Boolean __plistUniquingEqual(CFTypeRef cf1, CFTypeRef cf2) {
    // As long as this equals function is more restrictive than the
    // existing one, for any given type, the hash function need not
    // also be provided for the uniquing set.
    if (__CFGenericTypeID_genericobj_inline(cf1) != __CFGenericTypeID_genericobj_inline(cf2)) return false;
    if (__CFGenericTypeID_genericobj_inline(cf1) == numbertype) {
	if (CFNumberIsFloatType(cf1) != CFNumberIsFloatType(cf2)) return false;
	return CFEqual(cf1, cf2);
    }
    return CFEqual(cf1, cf2);
}

static void _flattenPlist(CFPropertyListRef plist, CFMutableArrayRef objlist, CFMutableDictionaryRef objtable, CFMutableSetRef uniquingsets[]) {
    CFPropertyListRef unique;
    uint32_t refnum;
    CFTypeID type = __CFGenericTypeID_genericobj_inline(plist);
    CFIndex idx;
    CFPropertyListRef *list, buffer[256];

    // Do not unique dictionaries or arrays, because: they
    // are slow to compare, and have poor hash codes.
    // Uniquing bools is unnecessary.
    int which = -1;
    if (stringtype == type) {
	which = 0;
    } else if (numbertype == type) {
	which = 1;
    } else if (datatype == type) {
	which = 2;
    } else if (datetype == type) {
	which = 3;
    }
    if (1 && -1 != which) {
	CFMutableSetRef uniquingset = uniquingsets[which];
	CFIndex before = CFSetGetCount(uniquingset);
	CFSetAddValue(uniquingset, plist);
	CFIndex after = CFSetGetCount(uniquingset);
	if (after == before) {	// already in set
	    unique = CFSetGetValue(uniquingset, plist);
	    if (unique != plist) {
		refnum = (uint32_t)CFDictionaryGetValue(objtable, unique);
		CFDictionaryAddValue(objtable, plist, (const void *)refnum);
	    }
	    return;
	}
    }
    refnum = CFArrayGetCount(objlist);
    CFArrayAppendValue(objlist, plist);
    CFDictionaryAddValue(objtable, plist, (const void *)refnum);
    if (dicttype == type) {
	CFIndex count = CFDictionaryGetCount(plist);
	list = (count <= 128) ? buffer : CFAllocatorAllocate(kCFAllocatorSystemDefault, 2 * count * sizeof(CFTypeRef), 0);
        CFDictionaryGetKeysAndValues(plist, list, list + count);
        for (idx = 0; idx < 2 * count; idx++) {
            _flattenPlist(list[idx], objlist, objtable, uniquingsets);
        }
        if (list != buffer) CFAllocatorDeallocate(kCFAllocatorSystemDefault, list);
    } else if (arraytype == type) {
	CFIndex count = CFArrayGetCount(plist);
	list = (count <= 256) ? buffer : CFAllocatorAllocate(kCFAllocatorSystemDefault, count * sizeof(CFTypeRef), 0);
        CFArrayGetValues(plist, CFRangeMake(0, count), list);
        for (idx = 0; idx < count; idx++) {
            _flattenPlist(list[idx], objlist, objtable, uniquingsets);
        }
        if (list != buffer) CFAllocatorDeallocate(kCFAllocatorSystemDefault, list);
    }
}

// stream can be a CFWriteStreamRef or a CFMutableDataRef
CFIndex __CFBinaryPlistWriteToStream(CFPropertyListRef plist, CFTypeRef stream) {
    CFMutableDictionaryRef objtable;
    CFMutableArrayRef objlist;
    CFBinaryPlistTrailer trailer;
    uint64_t *offsets, length_so_far;
    uint64_t mask, refnum;
    int64_t idx, idx2, cnt;
    __CFBinaryPlistWriteBuffer *buf;
    CFSetCallBacks cb = kCFTypeSetCallBacks;

    if ((CFTypeID)-1 == stringtype) {
	stringtype = CFStringGetTypeID();
	datatype = CFDataGetTypeID();
	numbertype = CFNumberGetTypeID();
	booltype = CFBooleanGetTypeID();
	datetype = CFDateGetTypeID();
	dicttype = CFDictionaryGetTypeID();
	arraytype = CFArrayGetTypeID();
    }
    objtable = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, NULL);
    _CFDictionarySetCapacity(objtable, 640);
    objlist = CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, NULL);
    _CFArraySetCapacity(objlist, 640);
    cb.equal = __plistUniquingEqual;
    CFMutableSetRef uniquingsets[4];
    uniquingsets[0] = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &cb);
    _CFSetSetCapacity(uniquingsets[0], 1000);
    uniquingsets[1] = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &cb);
    _CFSetSetCapacity(uniquingsets[1], 500);
    uniquingsets[2] = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &cb);
    _CFSetSetCapacity(uniquingsets[2], 250);
    uniquingsets[3] = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &cb);
    _CFSetSetCapacity(uniquingsets[3], 250);

    _flattenPlist(plist, objlist, objtable, uniquingsets);
    
    CFRelease(uniquingsets[0]);
    CFRelease(uniquingsets[1]);
    CFRelease(uniquingsets[2]);
    CFRelease(uniquingsets[3]);

    cnt = CFArrayGetCount(objlist);
    offsets = CFAllocatorAllocate(kCFAllocatorSystemDefault, cnt * sizeof(*offsets), 0);

    buf = CFAllocatorAllocate(kCFAllocatorSystemDefault, sizeof(__CFBinaryPlistWriteBuffer), 0);
    buf->stream = stream;
    buf->streamIsData = (CFGetTypeID(stream) == CFDataGetTypeID());
    buf->written = 0;
    buf->used = 0;
    bufferWrite(buf, "bplist00", 8);	// header

    memset(&trailer, 0, sizeof(trailer));
    trailer._numObjects = CFSwapInt64HostToBig(cnt);
    trailer._topObject = 0;	// true for this implementation
    mask = ~(uint64_t)0;
    while (cnt & mask) {
	trailer._objectRefSize++;
	mask = mask << 8;
    }

    for (idx = 0; idx < cnt; idx++) {
	CFPropertyListRef obj = CFArrayGetValueAtIndex(objlist, idx);
	CFTypeID type = CFGetTypeID(obj);
	offsets[idx] = buf->written + buf->used;
	if (stringtype == type) {
	    CFIndex ret, count = CFStringGetLength(obj);
	    CFIndex needed;
	    uint8_t *bytes, buffer[1024];
	    bytes = (count <= 1024) ? buffer : CFAllocatorAllocate(kCFAllocatorDefault, count, 0);
	    // presumption, believed to be true, is that ASCII encoding may need
	    // less bytes, but will not need greater, than the # of unichars
	    ret = CFStringGetBytes(obj, CFRangeMake(0, count), kCFStringEncodingASCII, 0, false, bytes, count, &needed);
	    if (ret == count) {
		uint8_t marker = kCFBinaryPlistMarkerASCIIString | (needed < 15 ? needed : 0xf);
		bufferWrite(buf, &marker, 1);
		if (15 <= needed) {
		    _appendInt(buf, (uint64_t)needed);
		}
		bufferWrite(buf, bytes, needed);
	    } else {
		UniChar *chars;
		uint8_t marker = kCFBinaryPlistMarkerUnicode16String | (count < 15 ? count : 0xf);
		bufferWrite(buf, &marker, 1);
		if (15 <= count) {
		    _appendInt(buf, (uint64_t)count);
		}
		chars = CFAllocatorAllocate(kCFAllocatorDefault, count * sizeof(UniChar), 0);
		CFStringGetCharacters(obj, CFRangeMake(0, count), chars);
		for (idx2 = 0; idx2 < count; idx2++) {
		    chars[idx2] = CFSwapInt16HostToBig(chars[idx2]);
		}
		bufferWrite(buf, (uint8_t *)chars, count * sizeof(UniChar));
		CFAllocatorDeallocate(kCFAllocatorDefault, chars);
	    }
	    if (bytes != buffer) CFAllocatorDeallocate(kCFAllocatorDefault, bytes);
	} else if (numbertype == type) {
	    uint8_t marker;
	    CFSwappedFloat64 swapped64;
	    CFSwappedFloat32 swapped32;
	    uint64_t bigint;
	    uint8_t *bytes;
	    CFIndex nbytes;
	    if (CFNumberIsFloatType(obj)) {
		if (CFNumberGetByteSize(obj) <= (CFIndex)sizeof(float)) {
		    float v;
		    CFNumberGetValue(obj, kCFNumberFloat32Type, &v);
		    swapped32 = CFConvertFloat32HostToSwapped(v);
		    bytes = (uint8_t *)&swapped32;
		    nbytes = sizeof(float);
		    marker = kCFBinaryPlistMarkerReal | 2;
		} else {
		    double v;
		    CFNumberGetValue(obj, kCFNumberFloat64Type, &v);
		    swapped64 = CFConvertFloat64HostToSwapped(v);
		    bytes = (uint8_t *)&swapped64;
		    nbytes = sizeof(double);
		    marker = kCFBinaryPlistMarkerReal | 3;
		}
		bufferWrite(buf, &marker, 1);
		bufferWrite(buf, bytes, nbytes);
	    } else {
		CFNumberGetValue(obj, kCFNumberSInt64Type, &bigint);
		_appendInt(buf, bigint);
	    }
	} else if (_CFKeyedArchiverUIDGetTypeID() == type) {
	    _appendUID(buf, (CFKeyedArchiverUIDRef)obj);
	} else if (booltype == type) {
	    uint8_t marker = CFBooleanGetValue(obj) ? kCFBinaryPlistMarkerTrue : kCFBinaryPlistMarkerFalse;
	    bufferWrite(buf, &marker, 1);
	} else if (datatype == type) {
	    CFIndex count = CFDataGetLength(obj);
	    uint8_t marker = kCFBinaryPlistMarkerData | (count < 15 ? count : 0xf);
	    bufferWrite(buf, &marker, 1);
	    if (15 <= count) {
		_appendInt(buf, (uint64_t)count);
	    }
	    bufferWrite(buf, CFDataGetBytePtr(obj), count);
	} else if (datetype == type) {
	    CFSwappedFloat64 swapped;
	    uint8_t marker = kCFBinaryPlistMarkerDate;
	    bufferWrite(buf, &marker, 1);
	    swapped = CFConvertFloat64HostToSwapped(CFDateGetAbsoluteTime(obj));
	    bufferWrite(buf, (uint8_t *)&swapped, sizeof(swapped));
	} else if (dicttype == type) {
	    CFIndex count = CFDictionaryGetCount(obj);
	    CFPropertyListRef *list, buffer[512];
	    uint8_t marker = kCFBinaryPlistMarkerDict | (count < 15 ? count : 0xf);
	    bufferWrite(buf, &marker, 1);
	    if (15 <= count) {
		_appendInt(buf, (uint64_t)count);
	    }
	    list = (count <= 256) ? buffer : CFAllocatorAllocate(kCFAllocatorSystemDefault, 2 * count * sizeof(CFTypeRef), 0);
	    CFDictionaryGetKeysAndValues(obj, list, list + count);
	    for (idx2 = 0; idx2 < 2 * count; idx2++) {
		CFPropertyListRef value = list[idx2];
		uint32_t swapped = 0;
		uint8_t *source = (uint8_t *)&swapped;
                refnum = (uint32_t)CFDictionaryGetValue(objtable, value);
                swapped = CFSwapInt32HostToBig(refnum);
		bufferWrite(buf, source + sizeof(swapped) - trailer._objectRefSize, trailer._objectRefSize);
	    }
	    if (list != buffer) CFAllocatorDeallocate(kCFAllocatorSystemDefault, list);
	} else if (arraytype == type) {
	    CFIndex count = CFArrayGetCount(obj);
	    CFPropertyListRef *list, buffer[256];
	    uint8_t marker = kCFBinaryPlistMarkerArray | (count < 15 ? count : 0xf);
	    bufferWrite(buf, &marker, 1);
	    if (15 <= count) {
		_appendInt(buf, (uint64_t)count);
	    }
	    list = (count <= 256) ? buffer : CFAllocatorAllocate(kCFAllocatorSystemDefault, count * sizeof(CFTypeRef), 0);
	    CFArrayGetValues(obj, CFRangeMake(0, count), list);
	    for (idx2 = 0; idx2 < count; idx2++) {
		CFPropertyListRef value = list[idx2];
		uint32_t swapped = 0;
		uint8_t *source = (uint8_t *)&swapped;
                refnum = (uint32_t)CFDictionaryGetValue(objtable, value);
                swapped = CFSwapInt32HostToBig(refnum);
		bufferWrite(buf, source + sizeof(swapped) - trailer._objectRefSize, trailer._objectRefSize);
	    }
	    if (list != buffer) CFAllocatorDeallocate(kCFAllocatorSystemDefault, list);
	} else {
	    CFRelease(objtable);
	    CFRelease(objlist);
	    CFAllocatorDeallocate(kCFAllocatorSystemDefault, buf);
            CFAllocatorDeallocate(kCFAllocatorSystemDefault, offsets);
	    return 0;
	}
    }
    CFRelease(objtable);
    CFRelease(objlist);

    length_so_far = buf->written + buf->used;
    trailer._offsetTableOffset = CFSwapInt64HostToBig(length_so_far);
    trailer._offsetIntSize = 0;
    mask = ~(uint64_t)0;
    while (length_so_far & mask) {
	trailer._offsetIntSize++;
	mask = mask << 8;
    }

    for (idx = 0; idx < cnt; idx++) {
	uint64_t swapped = CFSwapInt64HostToBig(offsets[idx]);
	uint8_t *source = (uint8_t *)&swapped;
	bufferWrite(buf, source + sizeof(*offsets) - trailer._offsetIntSize, trailer._offsetIntSize);
    }
    length_so_far += cnt * trailer._offsetIntSize;

    bufferWrite(buf, (uint8_t *)&trailer, sizeof(trailer));
    bufferFlush(buf);
    length_so_far += sizeof(trailer);
    CFAllocatorDeallocate(kCFAllocatorSystemDefault, buf);
    CFAllocatorDeallocate(kCFAllocatorSystemDefault, offsets);
    return (CFIndex)length_so_far;
}

bool __CFBinaryPlistGetTopLevelInfo(const uint8_t *databytes, uint64_t datalen, uint8_t *marker, uint64_t *offset, CFBinaryPlistTrailer *trailer) {
    const uint8_t *bytesptr;
    CFBinaryPlistTrailer trail;
    uint64_t off;
    CFIndex idx;

    if ((CFTypeID)-1 == stringtype) {
	stringtype = CFStringGetTypeID();
	datatype = CFDataGetTypeID();
	numbertype = CFNumberGetTypeID();
	booltype = CFBooleanGetTypeID();
	datetype = CFDateGetTypeID();
	dicttype = CFDictionaryGetTypeID();
	arraytype = CFArrayGetTypeID();
    }
    if (!databytes || datalen < 8 || 0 != memcmp("bplist00", databytes, 8)) return false;
    if (datalen < sizeof(trail) + 8 + 1) return false;
    memmove(&trail, databytes + datalen - sizeof(trail), sizeof(trail));
    trail._numObjects = CFSwapInt64BigToHost(trail._numObjects);
    trail._topObject = CFSwapInt64BigToHost(trail._topObject);
    if (trail._numObjects < trail._topObject) return false;
    trail._offsetTableOffset = CFSwapInt64BigToHost(trail._offsetTableOffset);
    if (datalen < trail._offsetTableOffset + trail._numObjects * trail._offsetIntSize + sizeof(trail)) return false;
    bytesptr = databytes + trail._offsetTableOffset + trail._topObject * trail._offsetIntSize;
    off = 0;
    for (idx = 0; idx < trail._offsetIntSize; idx++) {
	off = (off << 8) + bytesptr[idx];
    }
    if (trail._offsetTableOffset <= off) return false;
    if (trailer) *trailer = trail;
    if (offset) *offset = off;
    if (marker) *marker = *(databytes + off);
    return true;
}

static bool _readInt(const uint8_t *ptr, uint64_t *bigint, const uint8_t **newptr) {
    uint8_t marker;
    CFIndex idx, cnt;
    marker = *ptr++;
    if ((marker & 0xf0) != kCFBinaryPlistMarkerInt) return false;
    cnt = 1 << (marker & 0xf);
    *bigint = 0;
    for (idx = 0; idx < cnt; idx++) {
	*bigint = (*bigint << 8) + *ptr++;
    }
    if (newptr) *newptr = ptr;
    return true;
}

static uint64_t _getOffsetOfRefAt(const uint8_t *databytes, const uint8_t *bytesptr, const CFBinaryPlistTrailer *trailer) {
    uint64_t ref = 0, off = 0;
    CFIndex idx;
    for (idx = 0; idx < trailer->_objectRefSize; idx++) {
	ref = (ref << 8) + bytesptr[idx];
    }
    bytesptr = databytes + trailer->_offsetTableOffset + ref * trailer->_offsetIntSize;
    for (idx = 0; idx < trailer->_offsetIntSize; idx++) {
	off = (off << 8) + bytesptr[idx];
    }
    return off;
}

bool __CFBinaryPlistGetOffsetForValueFromArray(const uint8_t *databytes, uint64_t datalen, uint64_t startOffset, const CFBinaryPlistTrailer *trailer, CFIndex idx, uint64_t *offset) {
    const uint8_t *bytesptr;
    uint8_t marker;
    CFIndex cnt;
    uint64_t off;

    marker = *(databytes + startOffset);
    if ((marker & 0xf0) != kCFBinaryPlistMarkerArray) return false;
    cnt = (marker & 0x0f);
    if (cnt < 15 && cnt <= idx) return false;
    bytesptr = databytes + startOffset + 1;
    if (0xf == cnt) {
	uint64_t bigint;
	if (!_readInt(bytesptr, &bigint, &bytesptr)) return false;
	if (INT_MAX < bigint) return false;
	cnt = (CFIndex)bigint;
    }
    if (cnt <= idx) return false;
    off = _getOffsetOfRefAt(databytes, bytesptr + idx * trailer->_objectRefSize, trailer);
    if (datalen <= off) return false;
    if (offset) *offset = off;
    return true;
}

bool __CFBinaryPlistGetOffsetForValueFromDictionary(const uint8_t *databytes, uint64_t datalen, uint64_t startOffset, const CFBinaryPlistTrailer *trailer, CFTypeRef key, uint64_t *koffset, uint64_t *voffset) {
    const uint8_t *refsptr, *bytesptr;
    uint64_t off;
    uint8_t marker;
    CFTypeID keytype = CFGetTypeID(key);
    CFIndex idx, keyn, cnt, cnt2;

    marker = *(databytes + startOffset);
    if ((marker & 0xf0) != kCFBinaryPlistMarkerDict) return false;
    cnt = (marker & 0x0f);
    refsptr = databytes + startOffset + 1 + 0;
    if (0xf == cnt) {
	uint64_t bigint;
	if (!_readInt(refsptr, &bigint, &refsptr)) return false;
	if (INT_MAX < bigint) return false;
	cnt = (CFIndex)bigint;
    }
    for (keyn = 0; keyn < cnt; keyn++) {
	off = _getOffsetOfRefAt(databytes, refsptr, trailer);
	if (datalen <= off) return false;
	refsptr += trailer->_objectRefSize;
	bytesptr = databytes + off;
	marker = *bytesptr & 0xf0;
	cnt2 = *bytesptr & 0x0f;
	if (kCFBinaryPlistMarkerASCIIString == marker || kCFBinaryPlistMarkerUnicode16String == marker) {
	    CFStringInlineBuffer strbuf;
	    UniChar uchar;
	    if (keytype != stringtype) goto miss;
            if (0xf == cnt2 && CFStringGetLength(key) < 15) goto miss;
	    bytesptr++;
	    if (0xf == cnt2) {
		uint64_t bigint;
		if (!_readInt(bytesptr, &bigint, &bytesptr)) return false;
		if (INT_MAX < bigint) return false;
		cnt2 = (CFIndex)bigint;
	    }
	    if (cnt2 != CFStringGetLength(key)) goto miss;
	    uchar = (kCFBinaryPlistMarkerASCIIString == marker) ? (UniChar)bytesptr[0] : (UniChar)(bytesptr[0] * 256 + bytesptr[1]);
	    if (uchar != CFStringGetCharacterAtIndex(key, 0)) goto miss;
	    bytesptr += (kCFBinaryPlistMarkerASCIIString == marker) ? 1 : 2;
	    CFStringInitInlineBuffer(key, &strbuf, CFRangeMake(0, cnt2));
	    for (idx = 1; idx < cnt2; idx++) {
		uchar = (kCFBinaryPlistMarkerASCIIString == marker) ? (UniChar)bytesptr[0] : (UniChar)(bytesptr[0] * 256 + bytesptr[1]);
		if (uchar != __CFStringGetCharacterFromInlineBufferQuick(&strbuf, idx)) goto miss;
		bytesptr += (kCFBinaryPlistMarkerASCIIString == marker) ? 1 : 2;
	    }
	    if (koffset) *koffset = off;
	    off = _getOffsetOfRefAt(databytes, refsptr + (cnt - 1) * trailer->_objectRefSize, trailer);
	    if (datalen <= off) return false;
	    if (voffset) *voffset = off;
	    return true;
	} else {
//#warning the other primitive types should be allowed as keys in a binary plist dictionary, I think
	    return false;
	}
	miss: ;
    }
    return false;
}

extern CFArrayRef _CFArrayCreate_ex(CFAllocatorRef allocator, bool mutable, const void **values, CFIndex numValues);

extern CFDictionaryRef _CFDictionaryCreate_ex(CFAllocatorRef allocator, bool mutable, const void **keys, const void **values, CFIndex numValues);

#if 0
static bool _getUIDFromData(const uint8_t *datap, uint64_t *vp) {
    int32_t idx, cnt;
    uint8_t marker = *datap;
    uint64_t bigint;
    if ((marker & 0xf0) != kCFBinaryPlistMarkerUID) return false;
    cnt = (marker & 0x0f) + 1;
    datap++;
    bigint = 0;
    for (idx = 0; idx < cnt; idx++) {
	bigint = (bigint << 8) + *datap++;
    }
    *vp = bigint;
    return true;
}
#endif

static bool _getFloatFromData(const uint8_t *datap, float *vp) {
    CFSwappedFloat32 swapped32;
    if (*datap != (kCFBinaryPlistMarkerReal | 2)) return false;
    datap++;
    memmove(&swapped32, datap, sizeof(swapped32));
    *vp = CFConvertFloat32SwappedToHost(swapped32);
    return true;
}

static bool _getDoubleFromData(const uint8_t *datap, double *vp) {
    CFSwappedFloat64 swapped64;
    if (*datap != (kCFBinaryPlistMarkerReal | 3)) return false;
    datap++;
    memmove(&swapped64, datap, sizeof(swapped64));
    *vp = CFConvertFloat64SwappedToHost(swapped64);
    return true;
}

bool __CFBinaryPlistCreateObject(const uint8_t *databytes, uint64_t datalen, uint64_t startOffset, const CFBinaryPlistTrailer *trailer, CFAllocatorRef allocator, CFOptionFlags mutabilityOption, CFMutableDictionaryRef objects, CFPropertyListRef *plist) {
    const uint8_t *bytesptr;
    uint64_t off;
    uint8_t marker;
    CFIndex idx, cnt;
    uint64_t bigint;
    UniChar *chars;
    CFPropertyListRef *list, buffer[256];
    CFAllocatorRef listAllocator;

    if (objects) {
	*plist = CFDictionaryGetValue(objects, (const void *)(intptr_t)startOffset);
	if (*plist) {
	    CFRetain(*plist);
	    return true;
	}
    }

    marker = *(databytes + startOffset);
    switch (marker & 0xf0) {
    case kCFBinaryPlistMarkerNull:
	switch (marker) {
	case kCFBinaryPlistMarkerNull:
	    *plist = NULL;
	    return true;
	case kCFBinaryPlistMarkerFalse:
	    *plist = CFRetain(kCFBooleanFalse);
	    return true;
	case kCFBinaryPlistMarkerTrue:
	    *plist = CFRetain(kCFBooleanTrue);
	    return true;
	}
	return false;
    case kCFBinaryPlistMarkerInt:
	if (!_readInt(databytes + startOffset, &bigint, NULL)) return false;
	*plist = CFNumberCreate(allocator, kCFNumberSInt64Type, &bigint);
	if (objects) CFDictionarySetValue(objects, (const void *)(intptr_t)startOffset, *plist);
	return (*plist) ? true : false;
    case kCFBinaryPlistMarkerReal:
	cnt = marker & 0x0f;
	if (2 == cnt) {
	    float f;
	    _getFloatFromData(databytes + startOffset, &f);
	    *plist = CFNumberCreate(allocator, kCFNumberFloat32Type, &f);
	    if (objects) CFDictionarySetValue(objects, (const void *)(intptr_t)startOffset, *plist);
	    return (*plist) ? true : false;
	} else if (3 == cnt) {
	    double d;
	    _getDoubleFromData(databytes + startOffset, &d);
	    *plist = CFNumberCreate(allocator, kCFNumberFloat64Type, &d);
	    if (objects) CFDictionarySetValue(objects, (const void *)(intptr_t)startOffset, *plist);
	    return (*plist) ? true : false;
	}
	return false;
    case kCFBinaryPlistMarkerDate & 0xf0: {
	CFSwappedFloat64 swapped64;
	double d;
	cnt = marker & 0x0f;
	if (3 != cnt) return false;
	memmove(&swapped64, databytes + startOffset + 1, sizeof(swapped64));
	d = CFConvertFloat64SwappedToHost(swapped64);
	*plist = CFDateCreate(allocator, d);
	if (objects) CFDictionarySetValue(objects, (const void *)(intptr_t)startOffset, *plist);
	return (*plist) ? true : false;
	}
    case kCFBinaryPlistMarkerData: 
	cnt = marker & 0x0f;
	bytesptr = databytes + startOffset + 1;
	if (0xf == cnt) {
	    if (!_readInt(bytesptr, &bigint, &bytesptr)) return false;
	    if (INT_MAX < bigint) return false;
	    cnt = (CFIndex)bigint;
	}
	if (mutabilityOption == kCFPropertyListMutableContainersAndLeaves) {
	    *plist = CFDataCreateMutable(allocator, 0);
	    CFDataAppendBytes((CFMutableDataRef)*plist, bytesptr, cnt);
	} else {
	    *plist = CFDataCreate(allocator, bytesptr, cnt);
	}
	if (objects) CFDictionarySetValue(objects, (const void *)(intptr_t)startOffset, *plist);
	return (*plist) ? true : false;
    case kCFBinaryPlistMarkerASCIIString:
	cnt = marker & 0x0f;
	bytesptr = databytes + startOffset + 1;
	if (0xf == cnt) {
	    if (!_readInt(bytesptr, &bigint, &bytesptr)) return false;
	    if (INT_MAX < bigint) return false;
	    cnt = (CFIndex)bigint;
	}
	if (mutabilityOption == kCFPropertyListMutableContainersAndLeaves) {
	    CFStringRef str = CFStringCreateWithBytes(allocator, bytesptr, cnt, kCFStringEncodingASCII, false);
	    *plist = CFStringCreateMutableCopy(allocator, 0, str);
	    CFRelease(str);
	} else {
	    *plist = CFStringCreateWithBytes(allocator, bytesptr, cnt, kCFStringEncodingASCII, false);
	}
	if (objects) CFDictionarySetValue(objects, (const void *)(intptr_t)startOffset, *plist);
	return (*plist) ? true : false;
    case kCFBinaryPlistMarkerUnicode16String:
	cnt = marker & 0x0f;
	bytesptr = databytes + startOffset + 1;
	if (0xf == cnt) {
	    if (!_readInt(bytesptr, &bigint, &bytesptr)) return false;
	    if (INT_MAX < bigint) return false;
	    cnt = (CFIndex)bigint;
	}
	chars = CFAllocatorAllocate(allocator, cnt * sizeof(UniChar), 0);
	memmove(chars, bytesptr, cnt * sizeof(UniChar));
	for (idx = 0; idx < cnt; idx++) {
	    chars[idx] = CFSwapInt16BigToHost(chars[idx]);
	}
	if (mutabilityOption == kCFPropertyListMutableContainersAndLeaves) {
	    CFStringRef str = CFStringCreateWithCharactersNoCopy(allocator, chars, cnt, allocator);
	    *plist = CFStringCreateMutableCopy(allocator, 0, str);
	    CFRelease(str);
	} else {
	    *plist = CFStringCreateWithCharactersNoCopy(allocator, chars, cnt, allocator);
	}
	if (objects) CFDictionarySetValue(objects, (const void *)(intptr_t)startOffset, *plist);
	return (*plist) ? true : false;
    case kCFBinaryPlistMarkerUID:
	cnt = (marker & 0x0f) + 1;
	bytesptr = databytes + startOffset + 1;
	bigint = 0;
	for (idx = 0; idx < cnt; idx++) {
	    bigint = (bigint << 8) + *bytesptr++;
	}
	if (UINT_MAX < bigint) return false;
	*plist = _CFKeyedArchiverUIDCreate(allocator, (uint32_t)bigint);
	if (objects) CFDictionarySetValue(objects, (const void *)(intptr_t)startOffset, *plist);
	return (*plist) ? true : false;
    case kCFBinaryPlistMarkerArray:
	cnt = marker & 0x0f;
	bytesptr = databytes + startOffset + 1;
	if (0xf == cnt) {
	    if (!_readInt(bytesptr, &bigint, &bytesptr)) return false;
	    if (INT_MAX < bigint) return false;
	    cnt = (CFIndex)bigint;
	}
	list = (cnt <= 256) ? buffer : CFAllocatorAllocate(kCFAllocatorSystemDefault, sizeof(CFPropertyListRef) * cnt, 0);
	listAllocator = (list == buffer ? kCFAllocatorNull : kCFAllocatorSystemDefault);
	for (idx = 0; idx < cnt; idx++) {
	    CFPropertyListRef pl;
	    off = _getOffsetOfRefAt(databytes, bytesptr, trailer);
	    if (datalen <= off) return false;
	    if (!__CFBinaryPlistCreateObject(databytes, datalen, off, trailer, allocator, mutabilityOption, objects, &pl)) {
		if (!CF_IS_COLLECTABLE_ALLOCATOR(allocator)) {
		    while (idx--) {
			CFRelease(list[idx]);
		    }
		}
		if (list != buffer) CFAllocatorDeallocate(kCFAllocatorSystemDefault, list);
		return false;
	    }
	    if (CF_IS_COLLECTABLE_ALLOCATOR(allocator)) {
		CF_WRITE_BARRIER_BASE_ASSIGN(listAllocator, list, list[idx], CFMakeCollectable(pl));
	    } else {
		list[idx] = pl;
	    }
	    bytesptr += trailer->_objectRefSize;
	}
	*plist = _CFArrayCreate_ex(allocator, (mutabilityOption != kCFPropertyListImmutable), list, cnt);
	if (objects) CFDictionarySetValue(objects, (const void *)(intptr_t)startOffset, *plist);
	if (list != buffer) CFAllocatorDeallocate(kCFAllocatorSystemDefault, list);
	return (*plist) ? true : false;
    case kCFBinaryPlistMarkerDict:
	cnt = marker & 0x0f;
	bytesptr = databytes + startOffset + 1;
	if (0xf == cnt) {
	    if (!_readInt(bytesptr, &bigint, &bytesptr)) return false;
	    if (INT_MAX < bigint) return false;
	    cnt = (CFIndex)bigint;
	}
	cnt *= 2;
	list = (cnt <= 256) ? buffer : CFAllocatorAllocate(kCFAllocatorSystemDefault, sizeof(CFPropertyListRef) * cnt, 0);
	listAllocator = (list == buffer ? kCFAllocatorNull : kCFAllocatorSystemDefault);
	for (idx = 0; idx < cnt; idx++) {
	    CFPropertyListRef pl;
	    off = _getOffsetOfRefAt(databytes, bytesptr, trailer);
	    if (datalen <= off) return false;
	    if (!__CFBinaryPlistCreateObject(databytes, datalen, off, trailer, allocator, mutabilityOption, objects, &pl)) {
		if (!CF_IS_COLLECTABLE_ALLOCATOR(allocator)) {
		    while (idx--) {
			CFRelease(list[idx]);
		    }
		}
		if (list != buffer) CFAllocatorDeallocate(kCFAllocatorSystemDefault, list);
		return false;
	    }
	    if (CF_IS_COLLECTABLE_ALLOCATOR(allocator)) {
		CF_WRITE_BARRIER_BASE_ASSIGN(listAllocator, list, list[idx], CFMakeCollectable(pl));
	    } else {
		list[idx] = pl;
	    }
	    bytesptr += trailer->_objectRefSize;
	}
	*plist = _CFDictionaryCreate_ex(allocator, (mutabilityOption != kCFPropertyListImmutable), list, list + cnt / 2, cnt / 2);
	if (objects) CFDictionarySetValue(objects, (const void *)(intptr_t)startOffset, *plist);
	if (list != buffer) CFAllocatorDeallocate(kCFAllocatorSystemDefault, list);
	return (*plist) ? true : false;
    }
    return false;
}

__private_extern__ bool __CFTryParseBinaryPlist(CFAllocatorRef allocator, CFDataRef data, CFOptionFlags option, CFPropertyListRef *plist, CFStringRef *errorString) {
    uint8_t marker;    
    CFBinaryPlistTrailer trailer;
    uint64_t offset;
    CFPropertyListRef pl;
    const uint8_t *databytes = CFDataGetBytePtr(data);
    uint64_t datalen = CFDataGetLength(data);

    if (8 <= datalen && __CFBinaryPlistGetTopLevelInfo(databytes, datalen, &marker, &offset, &trailer)) {
        if (__CFBinaryPlistCreateObject(databytes, datalen, offset, &trailer, allocator, option, NULL, &pl)) {
	    if (plist) *plist = pl;
        } else {
	    if (plist) *plist = NULL;
            if (errorString) *errorString = CFRetain(CFSTR("binary data is corrupt"));
	}
        return true;
    }
    return false;
}