kcdata_core.m   [plain text]

/*
* Copyright (c) 2015 Apple Inc. All rights reserved.
*
* @APPLE_OSREFERENCE_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. The rights granted to you under the License
* may not be used to create, or enable the creation or redistribution of,
* unlawful or unlicensed copies of an Apple operating system, or to
* circumvent, violate, or enable the circumvention or violation of, any
* terms of an Apple operating system software license agreement.
*
* 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_OSREFERENCE_LICENSE_HEADER_END@
*/

#include <kcdata.h>
#import <Foundation/Foundation.h>
#import "kdd.h"
#import "KCDBasicTypeDescription.h"
#import "KCDStructTypeDescription.h"
#import "KCDEmbeddedBufferDescription.h"

#define LIB_KCD_ERR_DOMAIN @"KCDataError"

#define GEN_ERROR(code, msg) gen_error(__LINE__, code, @msg)
#define GEN_ERRORF(code, msg, ...) gen_error(__LINE__, code, [NSString stringWithFormat:@msg, __VA_ARGS__])

#define MAX_KCDATATYPE_BUFFER_SIZE 2048
extern struct kcdata_type_definition * kcdata_get_typedescription(unsigned type_id, uint8_t * buffer, uint32_t buffer_size);

BOOL setKCDataTypeForID(uint32_t newTypeID, KCDataType *newTypeObj);

static NSError *
gen_error(int line, NSInteger code, NSString *message)
{
return [NSError errorWithDomain:LIB_KCD_ERR_DOMAIN
code:code
userInfo:@{ @"line": @(line), @"message": message }];
}

static BOOL
mergedict(NSMutableDictionary * container, NSDictionary * object, NSError ** error)
{
for (id key in object) {
id existing = container[key];
id new = object[key];
if (existing) {
if ([existing isKindOfClass:[NSMutableArray class]] && [new isKindOfClass:[ NSArray class ]]) {
[existing addObjectsFromArray:new];
} else {
if (error) {
*error = GEN_ERRORF(KERN_INVALID_OBJECT, "repeated key: %@", key);
}
return FALSE;
}
} else {
[container setValue:new forKey:key];
}
}
return TRUE;
}

/*!
* @function getTypeFromTypeDef
*
* @abstract
* Build a KCDataType from a type definition.
*
* @param typeDef
* A pointer to kcdata_type_definition_t that specifies the type fields and has subtype definitions
* in the memory immediately following the type_definition.
*
* @return KCDataType * type object which can be used to parse data into dictionaries.
* This may return nil if it finds the data to be invalid.
*
* @discussion
* This routine tries to decode the typeDef structure and create either a basic type (KCDBasicTypeDescription)
* or a struct type.
*/
static KCDataType * getTypeFromTypeDef(struct kcdata_type_definition * typeDef);

static KCDataType *
getTypeFromTypeDef(struct kcdata_type_definition * typeDef)
{
if (typeDef == NULL) {
return nil;
}
NSString * kct_name = [NSString stringWithFormat:@"%s", typeDef->kct_name];
if (typeDef->kct_num_elements == 1 && !(typeDef->kct_elements[0].kcs_flags & KCS_SUBTYPE_FLAGS_STRUCT)) {
KCDBasicTypeDescription * retval = [[KCDBasicTypeDescription alloc] initWithKCTypeDesc:&typeDef->kct_elements[0]];
return retval;
} else {
KCDStructTypeDescription * retval =
[[KCDStructTypeDescription alloc] initWithType:typeDef->kct_type_identifier withName:kct_name];
/* need to do work here to get the array of elements setup here */
KCDBasicTypeDescription * curField = nil;
for (unsigned int i = 0; i < typeDef->kct_num_elements; i++) {
curField = [[KCDBasicTypeDescription alloc] initWithKCTypeDesc:&typeDef->kct_elements[i]];
[retval addFieldBasicType:curField];
if (typeDef->kct_elements[i].kcs_flags & KCS_SUBTYPE_FLAGS_MERGE) {
[retval setFlagsRequestedMerge];
}
}
return retval;
}
return nil;
}

static dispatch_once_t onceToken;
static NSMutableDictionary * knownTypes = nil;

KCDataType *
getKCDataTypeForID(uint32_t typeID)
{
dispatch_once(&onceToken, ^{
if (!knownTypes) {
knownTypes = [[NSMutableDictionary alloc] init];
}
});

NSNumber * type = [NSNumber numberWithUnsignedInt:typeID];
if (!knownTypes[type]) {
if (typeID == KCDATA_TYPE_NESTED_KCDATA) {
knownTypes[type] = [[KCDEmbeddedBufferDescription alloc] init];
return knownTypes[type];
}
/* code to query system for type information */
uint8_t buffer[MAX_KCDATATYPE_BUFFER_SIZE];
struct kcdata_type_definition * sys_def = kcdata_get_typedescription(typeID, buffer, MAX_KCDATATYPE_BUFFER_SIZE);
if (sys_def == NULL) {
knownTypes[type] = [[KCDBasicTypeDescription alloc] createDefaultForType:typeID];
} else {
knownTypes[type] = getTypeFromTypeDef(sys_def);
}
}
assert(knownTypes[type] != nil);
return knownTypes[type];
}

BOOL
setKCDataTypeForID(uint32_t newTypeID, KCDataType *newTypeObj) {
if (newTypeObj == NULL || newTypeID == 0) {
return FALSE;
}

dispatch_once(&onceToken, ^{
if (!knownTypes) {
knownTypes = [[NSMutableDictionary alloc] init];
}
});

NSNumber * type = [NSNumber numberWithUnsignedInt:newTypeID];

if (!knownTypes[type]) {
knownTypes[type] = newTypeObj;
return TRUE;
}

return FALSE;
}

NSString *
KCDataTypeNameForID(uint32_t typeID)
{
NSString * retval = [NSString stringWithFormat:@"%u", typeID];
KCDataType * t = getKCDataTypeForID(typeID);

if (![[t name] containsString:@"Type_"]) {
retval = [t name];
}
return retval;
}

NSMutableDictionary *
parseKCDataArray(kcdata_iter_t iter, NSError **error)
{
if (!kcdata_iter_array_valid(iter)) {
if (error)
*error = GEN_ERROR(KERN_INVALID_OBJECT, "invalid array");
return NULL;
}

uint32_t typeID = kcdata_iter_array_elem_type(iter);
uint32_t count = kcdata_iter_array_elem_count(iter);
uint32_t size = kcdata_iter_array_elem_size(iter);
uint8_t * buffer = (uint8_t *)kcdata_iter_payload(iter);
KCDataType * datatype = getKCDataTypeForID(typeID);
NSMutableDictionary * retval = [[NSMutableDictionary alloc] initWithCapacity:1];
NSMutableArray * arr = [[NSMutableArray alloc] initWithCapacity:count];
retval[[datatype name]] = arr;
NSDictionary * tmpdict = NULL;
for (uint32_t i = 0; i < count; i++) {
tmpdict = [datatype parseData:(void *)&buffer[i * size] ofLength:size];
if (!tmpdict) {
if (error)
*error = GEN_ERRORF(KERN_INVALID_OBJECT, "failed to parse array element. type=0x%x", (int)typeID);
return NULL;
}
if ([datatype shouldMergeData]) {
assert([tmpdict count] == 1);
[arr addObject: [tmpdict allValues][0]];
} else {
[arr addObject:tmpdict];
}
}
return retval;
}

NSMutableDictionary *
parseKCDataContainer(kcdata_iter_t *iter_p, NSError **error)
{
kcdata_iter_t iter = *iter_p;

if (!kcdata_iter_container_valid(iter)) {
if (error)
*error = GEN_ERROR(KERN_INVALID_OBJECT, "invalid container");
return NULL;
}
uint64_t containerID = kcdata_iter_container_id(iter);

/* setup collection object for sub containers */
NSMutableDictionary * sub_containers = [[NSMutableDictionary alloc] init];
NSMutableDictionary * retval = [[NSMutableDictionary alloc] init];
NSMutableDictionary * container = [[NSMutableDictionary alloc] init];

KCDataType * tmptype;
uint32_t _t;
void * _d;
BOOL ok;
NSDictionary * tmpdict;
BOOL found_end = FALSE;
retval[KCDataTypeNameForID(kcdata_iter_container_type(iter))] = container;

iter = kcdata_iter_next(iter);

KCDATA_ITER_FOREACH(iter)
{
_t = kcdata_iter_type(iter);
_d = kcdata_iter_payload(iter);
if (_t == KCDATA_TYPE_CONTAINER_END) {
if (kcdata_iter_container_id(iter) != containerID) {
if (error)
*error = GEN_ERROR(KERN_INVALID_ARGUMENT, "container marker mismatch");
return NULL;
}
found_end = TRUE;
break;
}

if (_t == KCDATA_TYPE_ARRAY) {
tmpdict = parseKCDataArray(iter, error);
if (!tmpdict)
return NULL;

ok = mergedict(container, tmpdict, error);
if (!ok)
return NULL;

continue;
}

if (_t == KCDATA_TYPE_CONTAINER_BEGIN) {
NSString * subcontainerID = [NSString stringWithFormat:@"%llu", kcdata_iter_container_id(iter)];
tmpdict = parseKCDataContainer(&iter, error);
if (!tmpdict)
return NULL;
assert([tmpdict count] == 1);
for (NSString * k in [tmpdict keyEnumerator]) {
if (sub_containers[k] == nil) {
sub_containers[k] = [[NSMutableDictionary alloc] init];
}
if (sub_containers[k][subcontainerID] != nil) {
if (error)
*error = GEN_ERRORF(KERN_INVALID_OBJECT, "repeated container id: %@", subcontainerID);
return NULL;
}
sub_containers[k][subcontainerID] = tmpdict[k];
}
continue;
}

tmptype = getKCDataTypeForID(_t);
tmpdict = [tmptype parseData:_d ofLength:kcdata_iter_size(iter)];
if (!tmpdict) {
if (error)
*error = GEN_ERRORF(KERN_INVALID_OBJECT, "failed to parse. type=0x%x", (int)_t);
return NULL;
}
if (![tmptype shouldMergeData]) {
tmpdict = @{[tmptype name] : tmpdict};
}
ok = mergedict(container, tmpdict, error);
if (!ok)
return NULL;
}

if (!found_end) {
if (error)
*error = GEN_ERROR(KERN_INVALID_ARGUMENT, "missing container end");
return NULL;
}

ok = mergedict(container, sub_containers, error);
if (!ok)
return NULL;

*iter_p = iter;
return retval;
}

NSDictionary *
parseKCDataBuffer(void * dataBuffer, uint32_t size, NSError ** error)
{
if (dataBuffer == NULL) {
if (error)
*error = GEN_ERROR(KERN_INVALID_ARGUMENT, "buffer is null");
return NULL;
}

uint32_t _type = (size >= sizeof(uint32_t)) ? *(uint32_t*)dataBuffer : 0;
uint32_t _size = 0;
uint64_t _flags = 0;
void * _datap = NULL;
KCDataType * kcd_type = NULL;
NSString * rootKey = NULL;
uint32_t rootType = _type;
BOOL ok;

/* validate begin tag and get root key */
switch (_type) {
case KCDATA_BUFFER_BEGIN_CRASHINFO:
rootKey = @"kcdata_crashinfo";
break;
case KCDATA_BUFFER_BEGIN_STACKSHOT:
rootKey = @"kcdata_stackshot";
break;
case KCDATA_BUFFER_BEGIN_DELTA_STACKSHOT:
rootKey = @"kcdata_delta_stackshot";
break;
case KCDATA_BUFFER_BEGIN_OS_REASON:
rootKey = @"kcdata_reason";
break;
case KCDATA_BUFFER_BEGIN_XNUPOST_CONFIG:
rootKey = @"xnupost_testconfig";
break;
default: {
if (error)
*error = GEN_ERROR(KERN_INVALID_VALUE, "invalid magic number");
return NULL;
break;
}
}
assert(rootKey != NULL);

kcdata_iter_t iter = kcdata_iter(dataBuffer, size);

if (!kcdata_iter_valid(iter)) {
if (error) {
*error = GEN_ERROR(KERN_INVALID_OBJECT, "initial item is invalid");
}
return NULL;
}

NSMutableDictionary * rootObject = [NSMutableDictionary dictionary];
NSDictionary * retval = [NSMutableDictionary dictionaryWithObject:rootObject forKey:rootKey];

/* iterate over each kcdata item */
KCDATA_ITER_FOREACH(iter)
{
_type = kcdata_iter_type(iter);
_size = kcdata_iter_size(iter);
_flags = kcdata_iter_flags(iter);
_datap = kcdata_iter_payload(iter);

if (_type == rootType)
continue;

if (_type == KCDATA_TYPE_ARRAY) {
NSDictionary * dict = parseKCDataArray(iter, error);
if (!dict)
return nil;

ok = mergedict(rootObject, dict, error);
if (!ok)
return NULL;

continue;
}

if (_type == KCDATA_TYPE_CONTAINER_BEGIN) {
NSString * containerID = [NSString stringWithFormat:@"%llu", kcdata_iter_container_id(iter)];
NSMutableDictionary *container = parseKCDataContainer(&iter, error);
if (!container)
return nil;
assert([container count] == 1);
for (NSString * k in [container keyEnumerator]) {
if (rootObject[k] == nil) {
rootObject[k] = [[NSMutableDictionary alloc] init];
}
if (rootObject[k][containerID] != nil) {
if (error)
*error = GEN_ERRORF(KERN_INVALID_OBJECT, "repeated container id: %@", containerID);
return NULL;
}
rootObject[k][containerID] = container[k];
}
continue;
}

if (_type == KCDATA_TYPE_TYPEDEFINTION) {
KCDataType *new_type = getTypeFromTypeDef((struct kcdata_type_definition *)_datap);
if (new_type != NULL) {
setKCDataTypeForID([new_type typeID], new_type);
kcd_type = getKCDataTypeForID(_type);
NSDictionary * tmpdict = [kcd_type parseData:_datap ofLength:_size];
if (!tmpdict) {
if (error)
*error = GEN_ERRORF(KERN_INVALID_OBJECT, "failed to parse. type=0x%x", (int)_type);
return NULL;
}
NSString *k = [NSString stringWithFormat:@"typedef[%@]", [new_type name]];
rootObject[k] = tmpdict;
}else {
if (error)
*error = GEN_ERRORF(KERN_INVALID_OBJECT, "Failed to parse type definition for type %u", _type);
return NULL;
}
continue;
}

kcd_type = getKCDataTypeForID(_type);
NSDictionary * tmpdict = [kcd_type parseData:_datap ofLength:_size];
if (!tmpdict) {
if (error)
*error = GEN_ERRORF(KERN_INVALID_OBJECT, "failed to parse. type=0x%x", (int)_type);
return NULL;
}
if (![kcd_type shouldMergeData]) {
tmpdict = @{[kcd_type name] : tmpdict};
}
ok = mergedict(rootObject, tmpdict, error);
if (!ok)
return NULL;
}

if (KCDATA_ITER_FOREACH_FAILED(iter)) {
retval = nil;
if (error) {
*error = GEN_ERROR(KERN_INVALID_OBJECT, "invalid item or missing buffer end marker");
}
}

return retval;
}