SecTransform.cpp   [plain text]


#include "SecTransform.h"
#include "SecTransformInternal.h"

#include "Transform.h"
#include "Utilities.h"
#include "TransformFactory.h"
#include "GroupTransform.h"
#include "c++utils.h"
#include "SecCollectTransform.h"


#include <string>

using namespace std;

const CFStringRef kSecTransformInputAttributeName = CFSTR("INPUT");
const CFStringRef kSecTransformOutputAttributeName = CFSTR("OUTPUT");
const CFStringRef kSecTransformDebugAttributeName = CFSTR("DEBUG");
const CFStringRef kSecTransformTransformName = CFSTR("NAME");
//const CFStringRef kSecTransformErrorTransform = CFSTR("TRANSFORM");
const CFStringRef kSecTransformErrorDomain = CFSTR("com.apple.security.transforms.error");
const CFStringRef kSecTransformAbortAttributeName = CFSTR("ABORT");
		
CFErrorRef SecTransformConnectTransformsInternal(SecGroupTransformRef groupRef,
							        SecTransformRef sourceTransformRef, 
									CFStringRef sourceAttributeName,
									SecTransformRef destinationTransformRef, 
									CFStringRef destinationAttributeName)
{
	Transform* destination = (Transform*) CoreFoundationHolder::ObjectFromCFType(destinationTransformRef);
	Transform* source = (Transform*) CoreFoundationHolder::ObjectFromCFType(sourceTransformRef);
	
	GroupTransform* group = (GroupTransform*) CoreFoundationHolder::ObjectFromCFType(groupRef);
	CFErrorRef temp = source->Connect(group, destination, destinationAttributeName, sourceAttributeName);
	return temp;
}


CFErrorRef SecTransformDisconnectTransforms(SecTransformRef sourceTransformRef, CFStringRef sourceAttributeName,
											SecTransformRef destinationTransformRef, CFStringRef destinationAttributeName)
{
	Transform* destination = (Transform*) CoreFoundationHolder::ObjectFromCFType(destinationTransformRef);
	Transform* source = (Transform*) CoreFoundationHolder::ObjectFromCFType(sourceTransformRef);
	return source->Disconnect(destination, sourceAttributeName, destinationAttributeName);
}

SecGroupTransformRef SecTransformCreateGroupTransform()
{
	return (SecGroupTransformRef) GroupTransform::Make();
}

SecGroupTransformRef SecTransformConnectTransforms(SecTransformRef sourceTransformRef,
						   CFStringRef sourceAttributeName,
						   SecTransformRef destinationTransformRef,
				 		   CFStringRef destinationAttributeName,
						   SecGroupTransformRef group,
						   CFErrorRef *error)
{
	if (group == NULL)
	{
		if (error)
		{
			*error = CreateSecTransformErrorRef(kSecTransformErrorMissingParameter, "Group must not be NULL.");
		}
		
		return NULL;
	}
	
	if (destinationAttributeName == NULL)
	{
		destinationAttributeName = kSecTransformInputAttributeName;
	}
	
	if (sourceAttributeName == NULL)
	{
		sourceAttributeName = kSecTransformOutputAttributeName;
	}
	
	GroupTransform* gtr = (GroupTransform*) CoreFoundationHolder::ObjectFromCFType(group);
	
	CFErrorRef temp = SecTransformConnectTransformsInternal(gtr->GetCFObject(), 
			sourceTransformRef, sourceAttributeName,
			destinationTransformRef, destinationAttributeName);
	
	if (error)
	{
		*error = temp;
	}
	
	if (temp) // an error happened?
	{
		return NULL;
	}
	else
	{
		return group;
	}
}
										


Boolean SecTransformSetAttribute(SecTransformRef transformRef,
								CFStringRef key,
								CFTypeRef value,
								CFErrorRef *error)
{
	Boolean result = false; // Guilty until proven
	Transform* transform = (Transform*) CoreFoundationHolder::ObjectFromCFType(transformRef);
	
	if (CFGetTypeID(transformRef) == GroupTransform::GetCFTypeID() && !transform->getAH(key, false))
	{
		if (error)
		{
			*error = CreateSecTransformErrorRef(kSecTransformOperationNotSupportedOnGroup, "SecTransformSetAttribute on non-exported attribute: %@ (exported attributes are: %@).", key, transform->GetAllAH());
		}
		
		return result;
	}

	// if the caller is setting the abort attribute, a value must be supplied
	if (NULL == value && CFStringCompare(key, kSecTransformAbortAttributeName, 0) == kCFCompareEqualTo)
	{
		if (error)
		{
            // XXX:  "a parameter"?  It has one: NULL.  What it requires is a non-NULL value.
			*error = CreateSecTransformErrorRef(kSecTransformInvalidArgument, "ABORT requires a parameter.");
		}
		
		return result;
	}
	
	CFErrorRef temp = transform->ExternalSetAttribute(key, value);
	result = (temp == NULL);
	if (error)
	{
		*error = temp;
	}
	else
	{
		if (temp)
		{
			CFRelease(temp);
		}
	}

	return result;
}



CFTypeRef SecTransformGetAttribute(SecTransformRef transformRef,
									CFStringRef key)
{
	// if the transform is a group, we really want to operation on its first object
	if (CFGetTypeID(transformRef) == GroupTransform::GetCFTypeID())
	{
		return NULL;
	}

	Transform* transform = (Transform*) CoreFoundationHolder::ObjectFromCFType(transformRef);
	if (transform->mIsActive) {
		return CreateSecTransformErrorRef(kSecTransformTransformIsExecuting, "Can not get the value of attributes during execution (attempt to fetch %@/%@)", transform->GetName(), key);
	}
	return transform->GetAttribute(key);
}



static inline GroupTransform* MakeGroupTransformFromTransformRef(SecTransformRef tr)
{
	GroupTransform* gt = (GroupTransform*) CoreFoundationHolder::ObjectFromCFType(tr);
	return gt;
}

static CFTypeRef InternalSecTransformExecute(SecTransformRef transformRef,
							CFErrorRef* errorRef,
							dispatch_queue_t deliveryQueue,
							SecMessageBlock deliveryBlock)
{
	if (NULL == transformRef || (deliveryBlock && !deliveryQueue))
	{
		CFErrorRef localError = CFErrorCreate(kCFAllocatorDefault, kSecTransformErrorDomain, 
			kSecTransformInvalidArgument, NULL);
			
		if (NULL != errorRef)
		{
			*errorRef = localError;
		}
		else
		{
			CFRelease(localError);
		}
		
		return (CFTypeRef)NULL;
	}
	
	// if our transform is a group, connect to its first member instead
	if (CFGetTypeID(transformRef) == GroupTransform::GetCFTypeID())
	{
		GroupTransform* gtsrc = (GroupTransform*) CoreFoundationHolder::ObjectFromCFType(transformRef);
		transformRef = gtsrc->GetAnyMember();
	}
	
	Transform* transform = (Transform*) CoreFoundationHolder::ObjectFromCFType(transformRef);
	return transform->Execute(deliveryQueue, deliveryBlock, errorRef);
}

CFTypeRef SecTransformExecute(SecTransformRef transformRef, CFErrorRef* errorRef)
{
	if (NULL == transformRef)
	{
		if (errorRef)
		{
			*errorRef = CreateSecTransformErrorRef(kSecTransformInvalidArgument, "NULL transform can not be executed");
		}
		return NULL;
	}
	
	Transform* transform = (Transform*) CoreFoundationHolder::ObjectFromCFType(transformRef);
	
	// transform->Execute will check this, but by then we have attached a collector which causes all manner of issues.
	if (transform->mIsActive)
	{
		if (errorRef)
		{
			*errorRef = CreateSecTransformErrorRef(kSecTransformTransformIsExecuting, "The %@ transform has already executed, it may not be executed again.", transform->GetName());
		}
		return NULL;
	}
	
	SecTransformRef collectTransform = transforms_assume(SecCreateCollectTransform(errorRef));
	SecGroupTransformRef theGroup = NULL;
	Boolean releaseTheGroup = false;
	GroupTransform* myGroup = NULL;
	Boolean needConnection = true;
	
	// Sniff the type of the transformRef to see if it is a group
	if (SecGroupTransformGetTypeID() == CFGetTypeID(transformRef))
	{
		theGroup = (SecGroupTransformRef)transformRef;
	}
	else
	{
		// Ok TransformRef is a TransformRef so get's it group
		
		myGroup = transform->mGroup;
		
		if (NULL == myGroup)
		{
			theGroup = SecTransformCreateGroupTransform();
			if (NULL == theGroup)
			{
				if (NULL != errorRef)
				{
					*errorRef = GetNoMemoryErrorAndRetain();
				}
				
				return (CFTypeRef)NULL;
				
			}
			
			releaseTheGroup = true;	
			
			SecGroupTransformRef connectResult = 
				SecTransformConnectTransforms(transformRef, 
										  kSecTransformOutputAttributeName,
										  collectTransform, 
										  kSecTransformInputAttributeName,
										  theGroup, errorRef);
			
			if (NULL == connectResult)
			{
				return (CFTypeRef)NULL;
			}
			
			needConnection = false;
			
		}
		else 
		{
			theGroup = (SecGroupTransformRef)myGroup->GetCFObject();
		}
	}
		
	if (NULL == theGroup || (SecGroupTransformGetTypeID() != CFGetTypeID(theGroup)))
	{
		if (NULL != errorRef)
		{
			*errorRef = GetNoMemoryErrorAndRetain();
		}
		
		return (CFTypeRef)NULL;

	}
	
	
	if (needConnection)
	{
		// Connect the collectTransform to the group
		myGroup = ((GroupTransform*)CoreFoundationHolder::ObjectFromCFType(theGroup))->GetRootGroup();
        if (NULL == myGroup)
        {
            if (NULL != errorRef)
            {
                *errorRef = GetNoMemoryErrorAndRetain();
            }
            
            return (CFTypeRef)NULL;
        }
		
		SecTransformRef outputTransform = myGroup->FindLastTransform();						 
		
		SecGroupTransformRef connectResult = 
		SecTransformConnectTransforms(outputTransform, 
									  kSecTransformOutputAttributeName,
									  collectTransform, 
									  kSecTransformInputAttributeName,
									  myGroup->GetCFObject(), errorRef);
		
		if (NULL == connectResult)
		{
			CFRelease(collectTransform);
			if (releaseTheGroup)
			{
				CFRelease(theGroup);
			}
			return (CFTypeRef)NULL;
		}
	}
			
	__block CFTypeRef myResult = NULL;
	dispatch_semaphore_t mySem = dispatch_semaphore_create(0L);
	dispatch_queue_t myQueue = dispatch_queue_create("com.apple.security.sectransfrom.SecTransformExecute", NULL);
	SecMessageBlock myBlock = ^(CFTypeRef message, CFErrorRef error, Boolean isFinal)
	{
		if (NULL != error)
		{
			if (NULL != errorRef)
			{
				CFRetain(error);
				*errorRef = error;
			}
			
			if (NULL != myResult)
			{
				CFRelease(myResult);
				myResult = NULL;
			}
		}
		
		if (NULL != message)
		{
			myResult = message;
			CFRetain(myResult);
		}
		
		if (isFinal)
		{
			dispatch_semaphore_signal(mySem);
		}
	};
	
	SecTransformExecuteAsync(theGroup, myQueue, myBlock);
	dispatch_semaphore_wait(mySem, DISPATCH_TIME_FOREVER);
	dispatch_release(mySem);
	dispatch_release(myQueue);
	
	if (releaseTheGroup)
	{
		CFRelease(theGroup);
		theGroup = NULL;
	}
	CFRelease(collectTransform);
	
	return myResult;	
}

void SecTransformExecuteAsync(SecTransformRef transformRef,
							  dispatch_queue_t deliveryQueue,
							  SecMessageBlock deliveryBlock) 
{
	CFErrorRef localError = NULL;
	InternalSecTransformExecute(transformRef, &localError, deliveryQueue, deliveryBlock);
	
	// if we got an error (usually a transform startup error), we must deliver it
	if (localError != NULL)
	{
		// The monitor treats a NULL queue as running on it's own queue, which from an appication's point of view is
		// the same as a default global queue.   Chances are there is no monitor at this point, so it is best to just use the
		//  global queue for this.
		dispatch_queue_t effectiveQueue = deliveryQueue ? deliveryQueue : dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, NULL);		
		dispatch_async(effectiveQueue, ^{
			deliveryBlock(NULL, localError, true);
		});
	}
}

CFDictionaryRef SecTransformCopyExternalRepresentation(SecTransformRef transformRef) 
{	
	
	Transform* tr = (Transform*) CoreFoundationHolder::ObjectFromCFType(transformRef);
	return tr->Externalize(NULL);
}

CFStringRef SecTransformDotForDebugging(SecTransformRef transformRef)
{
    if (CFGetTypeID(transformRef) == SecGroupTransformGetTypeID()) {
        GroupTransform* tr = (GroupTransform*) CoreFoundationHolder::ObjectFromCFType(transformRef);
        return tr->DotForDebugging();
    } else {
        return CFSTR("Can only dot debug a group");
    }
}

SecTransformRef SecTransformCreateFromExternalRepresentation(
								CFDictionaryRef dictionary,
								CFErrorRef *error) 
{
	// The incoming dictionary consists of a list of transforms and
	// a list of connections.  We start by making the individual
	// transforms and storing them in a dictionary so that we can
	// efficiently make connections
	
	CFArrayRef transforms = (CFArrayRef) CFDictionaryGetValue(dictionary, EXTERN_TRANSFORM_TRANSFORM_ARRAY);
	if (transforms == NULL)
	{
		// The dictionary we got is massively malformed!
		*error = CreateSecTransformErrorRef(kSecTransformErrorInvalidInputDictionary, "%@ is missing from the dictionary.  The dictionary is malformed.", EXTERN_TRANSFORM_TRANSFORM_ARRAY);
		return NULL;
	}
	
	CFArrayRef connections = (CFArrayRef) CFDictionaryGetValue(dictionary, EXTERN_TRANSFORM_CONNECTION_ARRAY);
	if (connections == NULL)
	{
		// The dictionary we got is massively malformed!
		*error = CreateSecTransformErrorRef(kSecTransformErrorInvalidInputDictionary, "%@ is missing from the dictionary.  The dictionary is malformed.", EXTERN_TRANSFORM_CONNECTION_ARRAY);
		return NULL;
	}

	CFMutableDictionaryRef transformHolder = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
	CFTypeRefHolder _(transformHolder);
	
	CFIndex numTransforms = CFArrayGetCount(transforms);
	CFIndex n;
	
	SecTransformRef aTransform;
	
	for (n = 0; n < numTransforms; ++n)
	{
		// get the basic info we need
		CFDictionaryRef xTransform = (CFDictionaryRef) CFArrayGetValueAtIndex(transforms, n);
		
		CFStringRef xName = (CFStringRef) CFDictionaryGetValue(xTransform, EXTERN_TRANSFORM_NAME);
		
		CFStringRef xType = (CFStringRef) CFDictionaryGetValue(xTransform, EXTERN_TRANSFORM_TYPE);
		
		// reconstruct the transform
		aTransform = TransformFactory::MakeTransformWithType(xType, error);
		SecTransformSetAttribute(aTransform, kSecTransformTransformName, xName, NULL);
		
		// restore the transform state
		Transform* tr = (Transform*) CoreFoundationHolder::ObjectFromCFType(aTransform);
		tr->RestoreState((CFDictionaryRef) CFDictionaryGetValue(xTransform, EXTERN_TRANSFORM_STATE));																																								
		tr->SetCustomExternalData((CFDictionaryRef) CFDictionaryGetValue(xTransform, EXTERN_TRANSFORM_CUSTOM_EXPORTS_DICTIONARY));
		
		CFIndex cnt = CFDictionaryGetCount(transformHolder);
		
		// add the transform to the dictionary
		CFDictionaryAddValue(transformHolder, xName, aTransform);
		
		if (CFDictionaryGetCount(transformHolder) <= cnt) 
		{
			if (error) 
			{
				*error = CreateSecTransformErrorRef(kSecTransformErrorInvalidInputDictionary, 
							"Out of memory, or damaged input dictonary (duplicate label %@?)", xName);
			}
			return NULL;
		}
	}
	
	CFIndex numConnections = CFArrayGetCount(connections);
	if (numConnections == 0)
	{
		return aTransform;
	}
	
	SecGroupTransformRef gt = SecTransformCreateGroupTransform();
	
	for (n = 0; n < numConnections; ++n)
	{
		CFDictionaryRef connection = (CFDictionaryRef) CFArrayGetValueAtIndex(connections, n);
		CFStringRef fromTransformName = (CFStringRef) CFDictionaryGetValue(connection, EXTERN_TRANSFORM_FROM_NAME);
		CFStringRef fromAttribute = (CFStringRef) CFDictionaryGetValue(connection, EXTERN_TRANSFORM_FROM_ATTRIBUTE);
		CFStringRef toTransformName = (CFStringRef) CFDictionaryGetValue(connection, EXTERN_TRANSFORM_TO_NAME);
		CFStringRef toAttribute = (CFStringRef) CFDictionaryGetValue(connection, EXTERN_TRANSFORM_TO_ATTRIBUTE);
		
		SecTransformRef fromTransform = (SecTransformRef) CFDictionaryGetValue(transformHolder, fromTransformName);
		if (!fromTransform) {
			if (error) {
				*error = CreateSecTransformErrorRef(kSecTransformErrorInvalidInputDictionary, "Can't connect %@ to %@ because %@ was not found", fromTransformName, toTransformName, fromTransformName);
			}
			return NULL;
		}
		SecTransformRef toTransform = (SecTransformRef) CFDictionaryGetValue(transformHolder, toTransformName);
		if (!toTransform) {
			if (error) {
				*error = CreateSecTransformErrorRef(kSecTransformErrorInvalidInputDictionary, "Can't connect %@ to %@ because %@ was not found", fromTransformName, toTransformName, toTransformName);
			}
			return NULL;
		}
		
		aTransform = SecTransformConnectTransforms(fromTransform, fromAttribute, toTransform, toAttribute, gt, error);
	}
	
	return gt;
}



SecTransformRef SecTransformFindByName(SecTransformRef transform, CFStringRef name)
{
	Transform *t = (Transform*)CoreFoundationHolder::ObjectFromCFType(transform);
    GroupTransform *g = t->GetRootGroup();
    
    if (g) {
        return g->FindByName(name);
    } else {
        // There is no group, so if transform isn't our guy nobody is.
        return (CFStringCompare(name, t->GetName(), 0) == kCFCompareEqualTo) ? transform : NULL;
    }
}



CFTypeID SecGroupTransformGetTypeID()
{
	return GroupTransform::GetCFTypeID();
}

CFTypeID SecTransformGetTypeID()
{
	// Obviously this is wrong (returns same CFTypeID as SecTransformGetTypeID) Needs to be fixed
	return GroupTransform::GetCFTypeID();
}