X-Git-Url: https://git.saurik.com/apple/security.git/blobdiff_plain/72a12576750f52947eb043106ba5c12c0d07decf..b1ab9ed8d0e0f1c3b66d7daa8fd5564444c56195:/libsecurity_transform/lib/Transform.cpp diff --git a/libsecurity_transform/lib/Transform.cpp b/libsecurity_transform/lib/Transform.cpp new file mode 100644 index 00000000..4d7910f7 --- /dev/null +++ b/libsecurity_transform/lib/Transform.cpp @@ -0,0 +1,1842 @@ +#include +#include +#include +#include +#include "Transform.h" +#include "StreamSource.h" +#include "SingleShotSource.h" +#include "Monitor.h" +#include "Utilities.h" +#include "c++utils.h" +#include "misc.h" +#include "SecTransformInternal.h" +#include "GroupTransform.h" +#include "GroupTransform.h" + +static const int kMaxPendingTransactions = 20; + +static CFTypeID internalID = _kCFRuntimeNotATypeID; + +// Use &dispatchQueueToTransformKey as a key to dispatch_get_specific to map from +// a transforms master, activation, or any attribute queue to the Transform* +static unsigned char dispatchQueueToTransformKey; + +static char RandomChar() +{ + return arc4random() % 26 + 'A'; // good enough +} + + +static CFStringRef ah_set_describe(const void *v) { + transform_attribute *ta = ah2ta(static_cast(const_cast(v))); + return CFStringCreateWithFormat(NULL, NULL, CFSTR("%@/%@=%@ (conn: %@)"), ta->transform->GetName(), ta->name, ta->value ? ta->value : CFSTR("NULL"), ta->connections ? static_cast(ta->connections) : static_cast(CFSTR("NONE"))); +} + +static CFStringRef AttributeHandleFormat(CFTypeRef ah, CFDictionaryRef dict) { + transform_attribute *ta = ah2ta(ah); + return CFStringCreateWithFormat(NULL, NULL, CFSTR("%@/%@"), ta->transform->GetName(), ta->name); +} + +static CFStringRef AttributeHandleDebugFormat(CFTypeRef ah) { + transform_attribute *ta = ah2ta(ah); + return CFStringCreateWithFormat(NULL, NULL, CFSTR("%@/%@ (%p)"), ta->transform->GetName(), ta->name, ta); +} + +static void AttributeHandleFinalize(CFTypeRef ah) +{ + transform_attribute *ta = ah2ta(ah); + if (!ta) + { + return; + } + + if (ta->transform) + { + // When we release AH's we clear out the transform pointer, so if we get here with transform!=NULL somebody + // has released an AH we are still using and we will crash very very soon (in our code) if we don't abort here + syslog(LOG_ERR, "over release of SecTransformAttributeRef at %p\n", ah); + abort(); + } + + if (ta->value) + { + CFRelease(ta->value); + } + + // ta->q already released + + if (ta->connections) + { + CFRelease(ta->connections); + } + + if (ta->semaphore) + { + dispatch_release(ta->semaphore); + } + + if (ta->attribute_changed_block) + { + Block_release(ta->attribute_changed_block); + } + + if (ta->attribute_validate_block) + { + Block_release(ta->attribute_validate_block); + } + + free(ta); +} + + + +static CFHashCode ah_set_hash(const void *v) { + return CFHash(ah2ta(static_cast(const_cast(v)))->name); +} + +static Boolean ah_set_equal(const void *v1, const void *v2) { + return CFEqual(ah2ta(static_cast(const_cast(v1)))->name, ah2ta(static_cast(const_cast(v2)))->name); +} + +CFTypeID transform_attribute::cftype; + +SecTransformAttributeRef Transform::makeAH(transform_attribute *ta) { + if (ta) { + SecTransformAttributeRef ah = _CFRuntimeCreateInstance(NULL, transform_attribute::cftype, sizeof(struct transform_attribute*), NULL); + if (!ah) { + return NULL; + } + *(struct transform_attribute **)(1 + (CFRuntimeBase*)ah) = ta; + return ah; + } else { + return NULL; + } +} + +static pthread_key_t ah_search_key_slot; + +static void destroy_ah_search_key(void *ah) { + CFRelease(ah); + pthread_setspecific(ah_search_key_slot, NULL); +} + + + +SecTransformAttributeRef Transform::getAH(SecTransformStringOrAttributeRef attrib, bool create_ok, bool create_underscore_ok) +{ + if (CFGetTypeID(attrib) == transform_attribute::cftype) + { + return (SecTransformAttributeRef)attrib; + } + + CFStringRef label = (CFStringRef)attrib; + static dispatch_once_t once = 0; + const char *name = (const char *)"SecTransformAttributeRef"; + static CFRuntimeClass ahclass; + static CFSetCallBacks tasetcb; + + dispatch_once(&once, ^{ + ahclass.className = name; + ahclass.copyFormattingDesc = AttributeHandleFormat; + ahclass.copyDebugDesc = AttributeHandleDebugFormat; + ahclass.finalize = AttributeHandleFinalize; + transform_attribute::cftype = _CFRuntimeRegisterClass(&ahclass); + if (transform_attribute::cftype == _kCFRuntimeNotATypeID) { + abort(); + } + + tasetcb.equal = ah_set_equal; + tasetcb.hash = ah_set_hash; + tasetcb.copyDescription = ah_set_describe; + + pthread_key_create(&ah_search_key_slot, destroy_ah_search_key); + }); + + SecTransformAttributeRef search_for = pthread_getspecific(ah_search_key_slot); + if (!search_for) + { + search_for = makeAH((transform_attribute*)malloc(sizeof(transform_attribute))); + if (!search_for) + { + return NULL; + } + + bzero(ah2ta(search_for), sizeof(transform_attribute)); + pthread_setspecific(ah_search_key_slot, search_for); + } + + if (!mAttributes) + { + mAttributes = CFSetCreateMutable(NULL, 0, &tasetcb); + } + + ah2ta(search_for)->name = label; + SecTransformAttributeRef ah = static_cast(const_cast(CFSetGetValue(mAttributes, search_for))); + if (ah == NULL && create_ok) + { + if (CFStringGetLength(label) && L'_' == CFStringGetCharacterAtIndex(label, 0) && !create_underscore_ok) + { + // Attributes with a leading _ belong to the Transform system only, not to random 3rd party transforms. + return NULL; + } + + transform_attribute *ta = static_cast(malloc(sizeof(transform_attribute))); + ah = makeAH(ta); + if (!ah) + { + return NULL; + } + + ta->name = CFStringCreateCopy(NULL, label); + if (!ta->name) + { + free(ta); + return NULL; + } + CFIndex cnt = CFSetGetCount(mAttributes); + CFSetAddValue(mAttributes, ah); + if (CFSetGetCount(mAttributes) != cnt+1) + { + CFRelease(ta->name); + free(ta); + return NULL; + } + + CFStringRef qname = CFStringCreateWithFormat(NULL, NULL, CFSTR("%s/%@"), dispatch_queue_get_label(this->mDispatchQueue), label); + CFIndex used, sz = 1+CFStringGetMaximumSizeForEncoding(CFStringGetLength(qname), kCFStringEncodingUTF8); + UInt8 *qnbuf = (UInt8 *)alloca(sz); + CFStringGetBytes(qname, CFRangeMake(0, CFStringGetLength(qname)), kCFStringEncodingUTF8, '?', FALSE, qnbuf, sz, &used); + qnbuf[used] = '\0'; + ta->q = dispatch_queue_create((char*)qnbuf, NULL); + CFRelease(qname); + ta->semaphore = dispatch_semaphore_create(kMaxPendingTransactions); + + + ta->pushback_state = transform_attribute::pb_empty; + ta->pushback_value = NULL; + ta->value = NULL; + ta->connections = NULL; + ta->transform = this; + + dispatch_set_target_queue(ta->q, mDispatchQueue); + ta->required = 0; + ta->requires_outbound_connection = 0; + ta->deferred = 0; + ta->stream = 0; + ta->ignore_while_externalizing = 0; + ta->has_incoming_connection = 0; + ta->direct_error_handling = 0; + ta->allow_external_sets = 0; + ta->has_been_deferred = 0; + ta->attribute_changed_block = NULL; + ta->attribute_validate_block = NULL; + } + + return ah; +} + +transform_attribute *Transform::getTA(SecTransformStringOrAttributeRef attrib, bool create_ok) +{ + SecTransformAttributeRef ah = getAH(attrib, create_ok); + if (ah) + { + return ah2ta(ah); + } + else + { + return NULL; + } +} + + + +void Transform::TAGetAll(transform_attribute **attributes) { + CFSetGetValues(mAttributes, (const void**)attributes); + CFIndex i, n = CFSetGetCount(mAttributes); + for(i = 0; i < n; ++i) { + attributes[i] = ah2ta(attributes[i]); + } +} + + + +bool Transform::HasNoOutboundConnections() +{ + // make an array big enough to hold all of the attributes + CFIndex numAttributes = CFSetGetCount(mAttributes); + transform_attribute* attributes[numAttributes]; + + TAGetAll(attributes); + + // check all of the attributes + CFIndex i; + for (i = 0; i < numAttributes; ++i) + { + if (attributes[i]->connections && CFArrayGetCount(attributes[i]->connections) != 0) + { + return false; + } + } + + return true; +} + + + +bool Transform::HasNoInboundConnections() +{ + // make an array big enough to hold all of the attributes + CFIndex numAttributes = CFSetGetCount(mAttributes); + transform_attribute* attributes[numAttributes]; + + TAGetAll(attributes); + + // check all of the attributes + CFIndex i; + for (i = 0; i < numAttributes; ++i) + { + if (attributes[i]->has_incoming_connection) + { + return false; + } + } + + return true; +} + + + +CFIndex Transform::GetAttributeCount() +{ + return CFSetGetCount(mAttributes); +} + +Transform::Transform(CFStringRef transformType, CFStringRef CFobjectType) : + CoreFoundationObject(CFobjectType), + mIsActive(false), + mIsFinalizing(false), + mAlwaysSelfNotify(false), + mGroup(NULL), + mAbortError(NULL), + mTypeName(CFStringCreateCopy(NULL, transformType)) +{ + mAttributes = NULL; + mPushedback = NULL; + mProcessingPushbacks = FALSE; + + if (internalID == _kCFRuntimeNotATypeID) { + (void)SecTransformNoData(); + internalID = CoreFoundationObject::FindObjectType(gInternalCFObjectName); + } + + // create a name for the transform + char rname[10]; + unsigned i; + for (i = 0; i < sizeof(rname) - 1; ++i) + { + rname[i] = RandomChar(); + } + + rname[i] = 0; + + char *tname = const_cast(CFStringGetCStringPtr(transformType, kCFStringEncodingUTF8)); + if (!tname) { + CFIndex sz = 1+CFStringGetMaximumSizeForEncoding(CFStringGetLength(transformType), kCFStringEncodingUTF8); + tname = static_cast(alloca(sz)); + if (tname) { + CFStringGetCString(transformType, tname, sz, kCFStringEncodingUTF8); + } else { + tname = const_cast("-"); + } + } + + char* name; + asprintf(&name, "%s-%s", rname, tname); + + char *dqName; + asprintf(&dqName, "%s-%s", rname, tname); + + char *aqName; + asprintf(&aqName, "aq-%s-%s", rname, tname); + + mDispatchQueue = dispatch_queue_create(dqName, NULL); + dispatch_queue_set_specific(mDispatchQueue, &dispatchQueueToTransformKey, this, NULL); + // mActivationQueue's job in life is to be suspended until just after this transform is made active. + // It's primary use is when a value flowing across a connection isn't sure if the target transform is active yet. + mActivationQueue = dispatch_queue_create(aqName, NULL); + dispatch_set_target_queue(mActivationQueue, mDispatchQueue); + dispatch_suspend(mActivationQueue); + mActivationPending = dispatch_group_create(); + + // set up points for ABORT, DEBUG, INPUT, and OUTPUT + AbortAH = getAH(kSecTransformAbortAttributeName, true); + transform_attribute *ta = ah2ta(AbortAH); + ta->ignore_while_externalizing = 1; + CFStringRef attributeName = CFStringCreateWithCStringNoCopy(NULL, name, 0, kCFAllocatorMalloc); + SetAttributeNoCallback(kSecTransformTransformName, attributeName); + CFRelease(attributeName); + + free(dqName); + free(aqName); + + DebugAH = getAH(kSecTransformDebugAttributeName, true); + ah2ta(DebugAH)->ignore_while_externalizing = 1; + + ta = getTA(kSecTransformInputAttributeName, true); + ta->required = ta->deferred = ta->stream = 1; + ta->allow_external_sets = 0; + ta->value = NULL; + ta->has_been_deferred = 0; + ta = getTA(kSecTransformOutputAttributeName, true); + ta->requires_outbound_connection = ta->stream = 1; +} + +static void run_and_release_finalizer(void *finalizer_block) +{ + ((dispatch_block_t)finalizer_block)(); + Block_release(finalizer_block); +} + +static void set_dispatch_finalizer(dispatch_object_t object, dispatch_block_t finalizer) +{ + finalizer = Block_copy(finalizer); + dispatch_set_context(object, finalizer); + dispatch_set_finalizer_f(object, run_and_release_finalizer); +} + +void Transform::FinalizePhase2() +{ + delete this; +} + +void Transform::FinalizeForClang() +{ + CFIndex numAttributes = CFSetGetCount(mAttributes); + SecTransformAttributeRef handles[numAttributes]; + CFSetGetValues(mAttributes, (const void**)&handles); + + for(CFIndex i = 0; i < numAttributes; ++i) { + SecTransformAttributeRef ah = handles[i]; + transform_attribute *ta = ah2ta(ah); + + set_dispatch_finalizer(ta->q, ^{ + // NOTE: not done until all pending use of the attribute queue has ended AND retain count is zero + ta->transform = NULL; + CFRelease(ah); + }); + // If there is a pending pushback the attribute queue will be suspended, and needs a kick before it can be destructed. + if (__sync_bool_compare_and_swap(&ta->pushback_state, transform_attribute::pb_value, transform_attribute::pb_discard)) { + dispatch_resume(ta->q); + } + dispatch_release(ta->q); + } + + // We might be finalizing a transform as it is being activated, make sure that is complete before we do the rest + dispatch_group_notify(mActivationPending, mDispatchQueue, ^{ + if (mActivationQueue != NULL) { + // This transform has not been activated (and does not have a activation pending), so we need to resume to activation queue before we can release it + dispatch_resume(mActivationQueue); + dispatch_release(mActivationQueue); + } + + set_dispatch_finalizer(mDispatchQueue, ^{ + // NOTE: delayed until all pending work items on the transform's queue are complete, and all of the attribute queues have been finalized, and the retain count is zero + FinalizePhase2(); + }); + dispatch_release(mDispatchQueue); + }); +} + +void Transform::Finalize() +{ + // When _all_ transforms in the group have been marked as finalizing we can tear down our own context without anyone else in the group sending us values + // (NOTE: moved block into member function as clang hits an internal error and declines to compile) + dispatch_block_t continue_finalization = ^{ this->FinalizeForClang(); }; + dispatch_block_t mark_as_finalizing = ^{ this->mIsFinalizing = true; }; + + // Mark the transform as "finalizing" so it knows not to propagate values across connections + if (this == dispatch_get_specific(&dispatchQueueToTransformKey)) { + mark_as_finalizing(); + } else { + dispatch_sync(mDispatchQueue, mark_as_finalizing); + } + + if (mGroup) { + (void)transforms_assume(mGroup->mIsFinalizing); // under retain? + mGroup->AddAllChildrenFinalizedCallback(mDispatchQueue, continue_finalization); + mGroup->ChildStartedFinalization(this); + } else { + // a "bare" transform (normally itself a group) still needs to be deconstructed + dispatch_async(mDispatchQueue, continue_finalization); + } +} + +Transform::~Transform() +{ + CFRelease(mAttributes); + if (mAbortError) { + CFRelease(mAbortError); + mAbortError = NULL; + } + + // See if we can catch anything using us after our death + mDispatchQueue = (dispatch_queue_t)0xdeadbeef; + + CFRelease(mTypeName); + + if (NULL != mPushedback) + { + CFRelease(mPushedback); + } + dispatch_release(mActivationPending); +} + +CFStringRef Transform::GetName() { + return (CFStringRef)GetAttribute(kSecTransformTransformName); +} + +CFTypeID Transform::GetCFTypeID() +{ + return CoreFoundationObject::FindObjectType(CFSTR("SecTransform")); +} + +std::string Transform::DebugDescription() +{ + return CoreFoundationObject::DebugDescription() + "|SecTransform|" + StringFromCFString(this->GetName()); +} + +CFErrorRef Transform::SendMetaAttribute(SecTransformStringOrAttributeRef key, SecTransformMetaAttributeType type, CFTypeRef value) +{ + SecTransformAttributeRef ah = getAH(key, true); + transform_attribute *ta = ah2ta(ah); + switch (type) + { + case kSecTransformMetaAttributeRequired: + ta->required = CFBooleanGetValue((CFBooleanRef)value) ? 1 : 0; + break; + + case kSecTransformMetaAttributeRequiresOutboundConnection: + ta->requires_outbound_connection = CFBooleanGetValue((CFBooleanRef)value) ? 1 : 0; + break; + + case kSecTransformMetaAttributeDeferred: + ta->deferred = CFBooleanGetValue((CFBooleanRef)value) ? 1 : 0; + break; + + case kSecTransformMetaAttributeStream: + ta->stream = CFBooleanGetValue((CFBooleanRef)value) ? 1 : 0; + break; + + case kSecTransformMetaAttributeHasOutboundConnections: + return CreateSecTransformErrorRef(kSecTransformErrorInvalidOperation, "Can't set kSecTransformMetaAttributeHasOutboundConnections for %@ (or any other attribute)", ah); + + case kSecTransformMetaAttributeHasInboundConnection: + return CreateSecTransformErrorRef(kSecTransformErrorInvalidOperation, "Can't set kSecTransformMetaAttributeHasInboundConnection for %@ (or any other attribute)", ah); + + case kSecTransformMetaAttributeCanCycle: + return CreateSecTransformErrorRef(kSecTransformErrorInvalidOperation, "kSecTransformMetaAttributeCanCycle not yet supported (%@)", ah); + + case kSecTransformMetaAttributeExternalize: + ta->ignore_while_externalizing = CFBooleanGetValue((CFBooleanRef)value) ? 0 : 1; + break; + + case kSecTransformMetaAttributeValue: + return SetAttributeNoCallback(ah, value); + + case kSecTransformMetaAttributeRef: + return CreateSecTransformErrorRef(kSecTransformErrorInvalidOperation, "Can't set kSecTransformMetaAttributeRef for %@ (or any other attribute)", ah); + + case kSecTransformMetaAttributeName: + return CreateSecTransformErrorRef(kSecTransformErrorInvalidOperation, "Can't set kSecTransformMetaAttributeName for %@ (or any other attribute)", ah); + + default: + return CreateSecTransformErrorRef(kSecTransformErrorInvalidOperation, "Can't set unknown meta attribute #%d to %@ on %@", type, value, key); + } + + return NULL; +} + +CFTypeRef Transform::GetMetaAttribute(SecTransformStringOrAttributeRef key, SecTransformMetaAttributeType type) { + SecTransformAttributeRef ah = getAH(key, true); + transform_attribute *ta = ah2ta(ah); + switch (type) { + case kSecTransformMetaAttributeRequired: + return (CFTypeRef)(ta->required ? kCFBooleanTrue : kCFBooleanFalse); + case kSecTransformMetaAttributeRequiresOutboundConnection: + return (CFTypeRef)(ta->requires_outbound_connection ? kCFBooleanTrue : kCFBooleanFalse); + case kSecTransformMetaAttributeDeferred: + return (CFTypeRef)(ta->deferred ? kCFBooleanTrue : kCFBooleanFalse); + case kSecTransformMetaAttributeStream: + return (CFTypeRef)(ta->stream ? kCFBooleanTrue : kCFBooleanFalse); + case kSecTransformMetaAttributeHasOutboundConnections: + return (CFTypeRef)((ta->connections && CFArrayGetCount(ta->connections)) ? kCFBooleanTrue : kCFBooleanFalse); + case kSecTransformMetaAttributeHasInboundConnection: + return (CFTypeRef)(ta->has_incoming_connection ? kCFBooleanTrue : kCFBooleanFalse); + case kSecTransformMetaAttributeCanCycle: + return (CFTypeRef)kCFBooleanFalse; + case kSecTransformMetaAttributeExternalize: + return (CFTypeRef)(ta->ignore_while_externalizing ? kCFBooleanFalse : kCFBooleanTrue); + case kSecTransformMetaAttributeRef: + return ah; + case kSecTransformMetaAttributeValue: + return ta->value; + case kSecTransformMetaAttributeName: + return ta->name; + default: + return CreateSecTransformErrorRef(kSecTransformErrorInvalidOperation, "Can't get unknown meta attribute #%d from %@", type, key); + break; + } + + return NULL; +} + + + +CFErrorRef Transform::RefactorErrorToIncludeAbortingTransform(CFErrorRef sourceError) +{ + // pull apart the error + CFIndex code = CFErrorGetCode(sourceError); + CFStringRef domain = CFErrorGetDomain(sourceError); + CFDictionaryRef oldUserInfo = CFErrorCopyUserInfo(sourceError); + CFMutableDictionaryRef userInfo = CFDictionaryCreateMutableCopy(NULL, 0, oldUserInfo); + CFRelease(oldUserInfo); + + // add the new key and value to the dictionary + CFDictionaryAddValue(userInfo, kSecTransformAbortOriginatorKey, GetCFObject()); + + // make a new CFError + CFErrorRef newError = CFErrorCreate(NULL, domain, code, userInfo); + CFRelease(userInfo); + return newError; +} + +// NOTE: If called prior to execution will schedule a later call to AbortAllTransforms +void Transform::AbortJustThisTransform(CFErrorRef abortErr) +{ + (void)transforms_assume(abortErr); + (void)transforms_assume(dispatch_get_specific(&dispatchQueueToTransformKey) == this); + + Boolean wasActive = mIsActive; + + if (OSAtomicCompareAndSwapPtr(NULL, abortErr, (void**)&mAbortError)) { + // send an abort message to the attribute so that it can shut down + // note that this bypasses the normal processes. The message sent is a notification + // that things aren't working well any more, the transform cannot make any other assumption. + + // mAbortError is released in the destructor which is triggered (in part) + // by the dispatch queue finalizer so we don't need a retain/release of + // abortErr for the abortAction block, but we do need to retain it + // here to match with the release by the destructor. + CFRetain(abortErr); + + dispatch_block_t abortAction = ^{ + // This actually makes the abort happen, it needs to run on the transform's queue while the + // transform is executing. + + if (!wasActive) { + // When this abort was first processed we were not executing, so + // additional transforms may have been added to our group (indeed, + // we may not have had a group at all), so we need to let everyone + // know about the problem. This will end up letting ourself (and + // maybe some others) know an additional time, but the CompareAndSwap + // prevents that from being an issue. + this->AbortAllTransforms(abortErr); + } + + SecTransformAttributeRef GCC_BUG_WORKAROUND inputAttributeHandle = getAH(kSecTransformInputAttributeName, false); + // Calling AttributeChanged directly lets an error "skip ahead" of the input queue, + // and even execute if the input queue is suspended pending pushback retries. + AttributeChanged(inputAttributeHandle, abortErr); + try_pushbacks(); + }; + + if (mIsActive) { + // This transform is running, so we use the normal queue (which we are + // already executing on) + abortAction(); + } else { + // This transform hasn't run yet, do the work on the activation queue + // so it happens as soon as the transforms starts executing. + dispatch_async(mActivationQueue, abortAction); + } + } else { + Debug("%@ set to %@ while processing ABORT=%@, this extra set will be ignored", AbortAH, abortErr, mAbortError); + } +} + +// abort all transforms in the root group & below +void Transform::AbortAllTransforms(CFTypeRef err) +{ + Debug("%@ set to %@, aborting\n", AbortAH, err); + CFErrorRef error = NULL; + + CFTypeRef replacementErr = NULL; + + if (CFGetTypeID(err) != CFErrorGetTypeID()) + { + replacementErr = err = CreateSecTransformErrorRef(kSecTransformErrorInvalidType, "ABORT set to a %@ (%@) not a %@", CFCopyTypeIDDescription(CFGetTypeID(err)), err, CFCopyTypeIDDescription(CFErrorGetTypeID())); + } + + error = RefactorErrorToIncludeAbortingTransform((CFErrorRef)err); + + if (replacementErr) + { + CFRelease(replacementErr); + } + + GroupTransform *root = GetRootGroup(); + if (root) + { + // tell everyone in the (root) group to "AbortJustThisTransform" + dispatch_group_t all_aborted = dispatch_group_create(); + root->ForAllNodesAsync(false, all_aborted, ^(Transform* t){ + t->AbortJustThisTransform(error); + }); + dispatch_group_notify(all_aborted, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^(void) { + CFRelease(error); + dispatch_release(all_aborted); + }); + } + else + { + // We are everyone so we AbortJustThisTransform "directly" + // NOTE: this can only happen prior to execution (execution always happens in a group) + (void)transforms_assume_zero(mIsActive); + this->AbortJustThisTransform(error); + } +} + + + +CFErrorRef Transform::Disconnect(Transform* destinationTransform, CFStringRef myKey, CFStringRef hisKey) +{ + //CFTypeRef thisTransform = (SecTransformRef) GetCFObject(); + + // find this transform in the backlinks for the destination + CFIndex i; + + // now remove the link in the transform dictionary + transform_attribute *src = getTA(myKey, true); + SecTransformAttributeRef dst = destinationTransform->getAH(hisKey); + + if (src->connections == NULL) + { + return CreateSecTransformErrorRef(kSecTransformErrorInvalidOperation, "Cannot find transform in destination."); + } + + CFIndex numConnections = CFArrayGetCount(src->connections); + for (i = 0; i < numConnections; ++i) + { + if (CFArrayGetValueAtIndex(src->connections, i) == dst) + { + CFArrayRemoveValueAtIndex(src->connections, i); + numConnections = CFArrayGetCount(src->connections); + } + + // clear the has_incoming_connection bit in the destination. We can do this because inputs can have only one connection. + transform_attribute* dstTA = ah2ta(dst); + dstTA->has_incoming_connection = false; + } + + if (HasNoInboundConnections() && HasNoOutboundConnections()) + { + // we have been orphaned, just remove us + mGroup->RemoveMemberFromGroup(GetCFObject()); + mGroup = NULL; + } + + return NULL; +} + + + +CFErrorRef Transform::Connect(GroupTransform *group, Transform* destinationTransform, CFStringRef destAttr, CFStringRef srcAttr) +{ + if (group == NULL) + { + CFErrorRef err = CreateSecTransformErrorRef(kSecTransformErrorInvalidConnection, "Can not make connections without a specific group (do not call with group = NULL)"); + return err; + } + + GroupTransform *newSourceGroup = mGroup; + GroupTransform *newDestinationGroup = destinationTransform->mGroup; + + if (mGroup == NULL || mGroup == this) + { + newSourceGroup = group; + } + + if (destinationTransform->mGroup == NULL || destinationTransform->mGroup == destinationTransform) + { + newDestinationGroup = group; + } + + if (newSourceGroup != newDestinationGroup && mGroup) + { + CFErrorRef err = CreateSecTransformErrorRef(kSecTransformErrorInvalidConnection, "Can not make connections between transforms in different groups (%@ is in %@, %@ is in %@)", GetName(), newSourceGroup->GetName(), destinationTransform->GetName(), newDestinationGroup->GetName()); + return err; + } + + if (!validConnectionPoint(srcAttr)) { + CFErrorRef err = CreateSecTransformErrorRef(kSecTransformErrorInvalidConnection, "Can not make a connection from non-exported attribute %@ of %@", srcAttr, this->GetName()); + return err; + } + if (!destinationTransform->validConnectionPoint(destAttr)) { + CFErrorRef err = CreateSecTransformErrorRef(kSecTransformErrorInvalidConnection, "Can not make a connection to non-exported attribute %@ of %@", destAttr, destinationTransform->GetName()); + return err; + } + + mGroup = newSourceGroup; + destinationTransform->mGroup = newDestinationGroup; + + // NOTE: this fails on OOM + group->AddMemberToGroup(this->GetCFObject()); + group->AddMemberToGroup(destinationTransform->GetCFObject()); + + transform_attribute *src = this->getTA(srcAttr, true); + SecTransformAttributeRef dst = destinationTransform->getAH(destAttr); + + if (!src->connections) + { + src->connections = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); + } + CFArrayAppendValue(src->connections, dst); + + ah2ta(dst)->has_incoming_connection = 1; + + return NULL; +} + + +bool Transform::validConnectionPoint(CFStringRef attributeName) +{ + return true; +} + +// NoCallback == don't call this transform's Do function, but DO call the Do functions of connected attributes +// SetAttribute eventually calls SetAttributeNoCallback +CFErrorRef Transform::SetAttributeNoCallback(SecTransformStringOrAttributeRef key, CFTypeRef value) +{ + SecTransformAttributeRef ah = getAH(key, true); + if (!ah) + { + abort(); + } + transform_attribute *ta = ah2ta(ah); + + if (ah == AbortAH && value && (mIsActive || !ta->deferred)) + { + AbortAllTransforms(value); + return CreateSecTransformErrorRef(kSecTransformErrorAbortInProgress, "Abort started"); + } + + bool do_propagate = true; + + if (!ta->has_been_deferred) + { + bool doNotRetain = false; + + if (value) + { + CFStringRef name = ta->name; + if (CFGetTypeID(value) == CFReadStreamGetTypeID()) + { + CFTypeRef src = StreamSource::Make((CFReadStreamRef) value, this, name); + value = src; + do_propagate = false; + ta->has_been_deferred = 1; + doNotRetain = true; + } + else if (ta->deferred && !mIsActive) + { + if (ta->deferred) + { + Debug("%@ deferred value=%p\n", ah, value); + } + + CFTypeRef src = SingleShotSource::Make(value, this, name); + ta->has_been_deferred = 1; + + // the old value will be release when Transform::Do terminates + + value = src; + do_propagate = false; + doNotRetain = true; + } + else + { + ta->has_been_deferred = 0; + } + } + + if (ta->value != value) { + if (value && !doNotRetain) { + CFRetain(value); + } + if (ta->value) { + CFRelease(ta->value); + } + } + + ta->value = value; + } + + // propagate the changes out to all connections + if (ta->connections && mIsActive && do_propagate && !(mAbortError || mIsFinalizing)) + { + Debug("Propagating from %@ to %@\n", ah, ta->connections); + CFIndex i, numConnections = CFArrayGetCount(ta->connections); + for(i = 0; i < numConnections; ++i) { + SecTransformAttributeRef ah = static_cast(const_cast(CFArrayGetValueAtIndex(ta->connections, i))); + Transform *tt = ah2ta(ah)->transform; + if (NULL != tt) + { + if (tt->mIsActive) + { + tt->SetAttribute(ah, value); + } + else + { + dispatch_block_t setAttribute = ^{ + tt->SetAttribute(ah, value); + }; + // Here the target queue might not be activated yet, we can't + // look directly at the other transform's ActivationQueue as + // it might activate (or Finalize!) as we look, so just ask + // the other transform to deal with it. + dispatch_async(ah2ta(ah)->q, ^(void) { + // This time we are on the right queue to know this is the real deal + if (tt->mIsActive) { + setAttribute(); + } else { + dispatch_async(ah2ta(ah)->transform->mActivationQueue, setAttribute); + } + }); + } + } + } + } + + return NULL; +} + +// external sets normally fail if the transform is running +CFErrorRef Transform::ExternalSetAttribute(CFTypeRef key, CFTypeRef value) +{ + if (!mIsActive) + { + return this->SetAttribute(key, value); + } + else + { + SecTransformAttributeRef ah = getAH(key, false); + if (ah != NULL && ah2ta(ah)->allow_external_sets) + { + return this->SetAttribute(static_cast(ah), value); + } + else + { + return CreateSecTransformErrorRef(kSecTransformTransformIsExecuting, "%@ can not be set while %@ is executing", ah, this->GetName()); + } + } +} + + +// queue up the setting of the key and value +CFErrorRef Transform::SetAttribute(CFTypeRef key, CFTypeRef value) +{ + if (mAbortError) + { + return CreateSecTransformErrorRef(kSecTransformErrorAborted, "ABORT has been sent to the transform (%@)", mAbortError); + } + + // queue up the setting of the key and value + SecTransformAttributeRef ah; + if (CFGetTypeID(key) == transform_attribute::cftype) + { + ah = key; + } + else if (CFGetTypeID(key) == CFStringGetTypeID()) + { + ah = getAH(static_cast(key)); + if (!ah) + { + return CreateSecTransformErrorRef(kSecTransformErrorUnsupportedAttribute, "Can't set attribute %@ in transform %@", key, GetName()); + } + } + else + { + return CreateSecTransformErrorRef(kSecTransformErrorInvalidType, "Transform::SetAttribute called with %@, requires a string or an AttributeHandle", key); + } + + // Do this after the error check above so we don't leak + if (value != NULL) + { + CFRetain(value); // if we use dispatch_async we need to own the value (the matching release is in the set block) + } + + + transform_attribute *ta = ah2ta(ah); + + dispatch_block_t set = ^{ + Do(ah, value); + + dispatch_semaphore_signal(ta->semaphore); + + if (value != NULL) + { + CFRelease(value); + } + }; + + + // when the transform is active, set attributes asynchronously. Otherwise, we are doing + // initialization and must wait for the operation to complete. + if (mIsActive) + { + dispatch_async(ta->q, set); + } + else + { + dispatch_sync(ta->q, set); + } + if (dispatch_semaphore_wait(ta->semaphore, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC))) { + Debug("Send from %@ to %@ is still waiting\n", GetName(), ah); + dispatch_semaphore_wait(ta->semaphore, DISPATCH_TIME_FOREVER); + } + + // Return the best available status (which will be NULL if we haven't aborted, or stated an + // intent to abort when execution starts) + // + // The value of the ABORT attribute can differ from mAbortError, first if a transform is aborted + // prior to running the general abort mechanic is deferred until execution. Second during + // execution the abort logic avoids most of the normal processing. Third, and most importantly + // during an abort the exact error that gets generated will differ from the value sent to ABORT + // (for example if a non-CFError was sent...plus even if it was a CFError we annotate that error). + + return mAbortError; +} + +CFErrorRef Transform::SendAttribute(SecTransformStringOrAttributeRef key, CFTypeRef value) +{ + return SetAttributeNoCallback(key, value); +} + + + +CFTypeRef Transform::GetAttribute(SecTransformStringOrAttributeRef key) +{ + struct transform_attribute *ta = getTA(key, false); + if (ta == NULL || ta->value == NULL) { + return NULL; + } + + if (CFGetTypeID(ta->value) == internalID) + { + // this is one of our internal objects, so get the value from it + Source* source = (Source*) CoreFoundationHolder::ObjectFromCFType(ta->value); + return source->GetValue(); + } + else + { + return ta->value; + } +} + +CFErrorRef Transform::Pushback(SecTransformAttributeRef ah, CFTypeRef value) +{ + CFErrorRef result = NULL; + transform_attribute *ta = ah2ta(ah); + if (!(ta->pushback_state == transform_attribute::pb_empty || ta->pushback_state == transform_attribute::pb_repush)) + { + CFErrorRef error = fancy_error(kSecTransformErrorDomain, kSecTransformErrorInvalidOperation, CFSTR("Can not pushback new value until old value has been processed")); + SetAttribute(kSecTransformAbortAttributeName, error); + return error; + } + if (value == NULL && ta->pushback_value == NULL && ta->pushback_state == transform_attribute::pb_repush) + { + ta->pushback_state = transform_attribute::pb_presented_once; + } else + { + ta->pushback_state = transform_attribute::pb_value; + } + if (value) + { + CFRetain(value); + } + ta->pushback_value = value; + dispatch_suspend(ta->q); + if (!mPushedback) + { + mPushedback = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); + } + CFArrayAppendValue(mPushedback, ah); + return result; +} + +void Transform::try_pushbacks() { + if (!mPushedback || !CFArrayGetCount(mPushedback)) { + mProcessingPushbacks = FALSE; + return; + } + + CFArrayRef pb = (CFArrayRef)mPushedback; + mPushedback = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); + CFIndex i, n = CFArrayGetCount(pb); + int succeeded = 0; + for(i = 0; i < n; ++i) + { + SecTransformAttributeRef ah = CFArrayGetValueAtIndex(pb, i); + transform_attribute *ta = ah2ta(ah); + ta->pushback_state = transform_attribute::pb_repush; + CFTypeRef v = ta->pushback_value; + ta->pushback_value = NULL; + Do(ah, v); + if (v) + { + CFRelease(v); + } + if (ta->pushback_state == transform_attribute::pb_repush) { + ta->pushback_state = transform_attribute::pb_empty; + succeeded++; + } + // NOTE: a successful repush needs the queue unsuspended so it can run. + // A failed repush has suspended the queue an additional time, so we + // still need to resume it. + dispatch_resume(ta->q); + } + + CFRelease(pb); + + if (succeeded && CFArrayGetCount(mPushedback)) { + // some attribute changed while we proceeded the last batch of pushbacks, so any "new" pushbacks are eligible to run again. + // In theory the ones that were pushed after the last success don't need to be re-run but that isn't a big deal. + dispatch_async(mDispatchQueue, ^{ try_pushbacks(); }); + } else { + mProcessingPushbacks = FALSE; + } +} + +void Transform::Debug(const char *cfmt, ...) { + CFTypeRef d = ah2ta(DebugAH)->value; + if (d) { + CFWriteStreamRef out = NULL; + if (CFGetTypeID(d) == CFWriteStreamGetTypeID()) { + out = (CFWriteStreamRef)d; + } else { + static dispatch_once_t once; + static CFWriteStreamRef StdErrWriteStream; + dispatch_once(&once, ^{ + auto GCC_BUG_WORKAROUND CFURLRef GCC_BUG_WORKAROUND p = CFURLCreateWithFileSystemPath(NULL, CFSTR("/dev/stderr"), kCFURLPOSIXPathStyle, FALSE); + StdErrWriteStream = CFWriteStreamCreateWithFile(NULL, p); + CFWriteStreamOpen(StdErrWriteStream); + CFRelease(p); + }); + out = StdErrWriteStream; + } + + va_list ap; + va_start(ap, cfmt); + + CFStringRef fmt = CFStringCreateWithCString(NULL, cfmt, kCFStringEncodingUTF8); + CFStringRef str = CFStringCreateWithFormatAndArguments(NULL, NULL, fmt, ap); + CFRelease(fmt); + va_end(ap); + + + CFIndex sz = CFStringGetMaximumSizeForEncoding(CFStringGetLength(str), kCFStringEncodingUTF8); + sz += 1; + CFIndex used = 0; + unsigned char *buf; + bool needs_free = true; + buf = (unsigned char*)malloc(sz); + if (buf) { + CFStringGetBytes(str, CFRangeMake(0, CFStringGetLength(str)), kCFStringEncodingUTF8, '?', FALSE, buf, sz, &used); + } else { + buf = (unsigned char *)"malloc failure during Transform::Debug\n"; + needs_free = false; + } + + static dispatch_once_t once; + static dispatch_queue_t print_q; + dispatch_once(&once, ^{ + print_q = dispatch_queue_create("com.apple.security.debug.print_queue", 0); + dispatch_set_target_queue((dispatch_object_t)print_q, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0)); + }); + + dispatch_async(print_q, ^{ + CFWriteStreamWrite(out, buf, used); + if (needs_free) { + free(buf); + } + }); + + CFRelease(str); + } +} + +void Transform::Do(SecTransformAttributeRef ah, CFTypeRef value) +{ + transform_attribute *ta = ah2ta(ah); + if (ta->pushback_state == transform_attribute::pb_discard) + { + return; + } + (void)transforms_assume(dispatch_get_current_queue() == ((ta->pushback_state == transform_attribute::pb_repush) ? mDispatchQueue : ta->q)); + + if (mIsFinalizing) + { + Debug("Ignoring value %p sent to %@ (on queue %s) during finalization", value, ah, dispatch_queue_get_label(dispatch_get_current_queue())); + return; + } + + SetAttributeNoCallback(ah, value); + // While an abort is in progress things can get into bad + // states if we allow normal processing so we throw anything + // on the floor except CFErrorRef or NULL vales sent to + // ABORT or INPUT (we need to process them to let the + // transform shut down correctly) + if (mAbortError && (!(ah == this->AbortAH || ah == getTA(CFSTR("INPUT"), true)) && (value == NULL || CFGetTypeID(value) != CFErrorGetTypeID()))) + { + if (value) { + Debug("Ignoring value (%@) sent to %@ during abort\n", value, ah); + } else { + Debug("Ignoring NULL sent to %@ during abort\n", ah); + } + return; + } + + if (mIsActive || (mAlwaysSelfNotify && !ta->deferred)) + { + Debug("AttributeChanged: %@ (%s) = %@\n", ah, mIsActive ? "is executing" : "self notify set", value ? value : (CFTypeRef)CFSTR("(NULL)")); + AttributeChanged(ah, value); + } + + if (mPushedback && CFArrayGetCount(mPushedback) && !mProcessingPushbacks) + { + Debug("will process pushbacks (%@) later\n", mPushedback); + mProcessingPushbacks = TRUE; + dispatch_async(mDispatchQueue, ^{ try_pushbacks(); }); + } + + return; +} + + +void Transform::AttributeChanged(CFStringRef name, CFTypeRef value) +{ +} + +void Transform::AttributeChanged(SecTransformAttributeRef ah, CFTypeRef value) +{ + AttributeChanged(ah2ta(ah)->name, value); +} + +CFArrayRef Transform::GetAllAH() { + CFIndex cnt = CFSetGetCount(mAttributes); + const void **values = (const void **)alloca(sizeof(void*)*cnt); + CFSetGetValues(mAttributes, values); + return CFArrayCreate(NULL, values, cnt, &kCFTypeArrayCallBacks); +} + +CFTypeRef Transform::Execute(dispatch_queue_t deliveryQueue, SecMessageBlock deliveryBlock, CFErrorRef* errorRef) +{ + if (!mGroup) + { + CFTypeRef g = GroupTransform::Make(); + mGroup = (GroupTransform*)CoreFoundationHolder::ObjectFromCFType(g); + mGroup->AddMemberToGroup(this->GetCFObject()); + SecMessageBlock smb = ^(CFTypeRef message, CFErrorRef error, Boolean isFinal) + { + deliveryBlock(message, error, isFinal); + if (isFinal) + { + dispatch_async(this->mDispatchQueue, ^{ + CFRelease(g); + }); + } + }; + + CFTypeRef ret = this->Execute(deliveryQueue, deliveryBlock ? smb : (SecMessageBlock) NULL, errorRef); + + if (!deliveryBlock) + { + CFRelease(g); + } + + return ret; + } + + if (mIsActive) + { + if (errorRef) + { + *errorRef = CreateSecTransformErrorRef(kSecTransformTransformIsExecuting, "The %@ transform has already executed, it may not be executed again.", GetName()); + } + + return NULL; + } + + // Do a retain on our parent since we are using it + GroupTransform *rootGroup = GetRootGroup(); + CFRetain(rootGroup->GetCFObject()); + + CFTypeRef result = NULL; + + CFTypeRef monitorRef = BlockMonitor::Make(deliveryQueue, deliveryBlock); + + __block CFStringRef outputAttached = NULL; + + dispatch_queue_t p2 = dispatch_queue_create("activate phase2", NULL); + dispatch_queue_t p3 = dispatch_queue_create("activate phase3", NULL); + dispatch_suspend(p2); + dispatch_suspend(p3); + // walk the transform, doing phase1 activating as we go, and queueing phase2 and phase3 work + CFErrorRef temp = TraverseTransform(NULL, ^(Transform *t){ + return t->ExecuteOperation(outputAttached, (SecMonitorRef)monitorRef, p2, p3); + }); + // ExecuteOperation is not called for the outer group, so we need to manually set mISActive for it. + rootGroup->mIsActive = true; + rootGroup->StartingExecutionInGroup(); + dispatch_resume(p2); + dispatch_sync(p2, ^{ dispatch_resume(p3); }); + dispatch_sync(p3, ^{ dispatch_release(p2); }); + dispatch_release(p3); + + if (errorRef) + { + *errorRef = temp; + } + if (temp) { + // It is safe to keep the monitors attached, because it is invalid to try to execute again, BUT + // we do need to release the reference to the group that the monitor would normally release + // when it processes the final message. + CFRelease(rootGroup->GetCFObject()); + CFRelease(monitorRef); + rootGroup->StartedExecutionInGroup(false); + return NULL; + } + + dispatch_group_t initialized = dispatch_group_create(); + rootGroup->ForAllNodesAsync(true, initialized, ^(Transform*t) { + t->Initialize(); + }); + + dispatch_group_notify(initialized, rootGroup->mDispatchQueue, ^{ + dispatch_release(initialized); + dispatch_group_t activated = dispatch_group_create(); + dispatch_group_enter(activated); + dispatch_async(rootGroup->mDispatchQueue, ^{ + rootGroup->ForAllNodesAsync(true, activated, ^(Transform*t) { + t->ActivateInputs(); + }); + dispatch_group_leave(activated); + }); + dispatch_group_notify(activated, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + dispatch_release(activated); + // once we have been activated (but not before!), the monitor belongs to the group, and we can drop our claim + CFRelease(monitorRef); + rootGroup->StartedExecutionInGroup(true); + }); + }); + + return result; +} + + +void Transform::Initialize() +{ +} + +static void ActivateInputs_set(const void *v, void *unused) { + transform_attribute *ta = static_cast(ah2ta(const_cast(v))); + if (ta->value && internalID == CFGetTypeID(ta->value)) { + Source* s = (Source*) CoreFoundationHolder::ObjectFromCFType(ta->value); + s->Activate(); + } +} + +void Transform::ActivateInputs() +{ + (void)transforms_assume_zero(mIsActive && this != dispatch_get_specific(&dispatchQueueToTransformKey)); + + // now run all of the forward links + if (!mIsFinalizing) { + CFSetApplyFunction(mAttributes, ActivateInputs_set, NULL); + } +} + +CFErrorRef Transform::ForAllNodes(bool parallel, bool includeOwningGroup, Transform::TransformOperation op) +{ + GroupTransform *g = GetRootGroup(); + if (g) { + return g->ForAllNodes(parallel, includeOwningGroup, op); + } else { + return op(this); + } +} + +CFErrorRef Transform::TraverseTransform(CFMutableSetRef visited, TransformOperation t) +{ + return ForAllNodes(true, true, t); +} + +CFErrorRef Transform::ExecuteOperation(CFStringRef &outputAttached, SecMonitorRef output, dispatch_queue_t phase2, dispatch_queue_t phase3) +{ + if (!mGroup) { + // top level groups are special, and don't go through this path. + return NULL; + } + + if (!TransformCanExecute()) + { + // oops, this transform isn't ready to go + return CreateSecTransformErrorRef(kSecTransformErrorNotInitializedCorrectly, "The transform %@ was not ready for execution.", GetName()); + } + + // check to see if required attributes are connected or set + CFIndex i, numAttributes = CFSetGetCount(mAttributes); + transform_attribute **attributes = (transform_attribute **)alloca(numAttributes * sizeof(transform_attribute *)); + TAGetAll(attributes); + CFMutableArrayRef still_need = NULL; + for(i = 0; i < numAttributes; ++i) { + transform_attribute *ta = attributes[i]; + if (ta->required && ta->value == NULL && !ta->has_incoming_connection) { + if (!still_need) { + still_need = CFArrayCreateMutable(NULL, i, &kCFTypeArrayCallBacks); + } + CFArrayAppendValue(still_need, ta->name); + } + } + if (still_need) { + CFStringRef elist = CFStringCreateByCombiningStrings(NULL, still_need, CFSTR(", ")); + CFErrorRef err = CreateSecTransformErrorRef(kSecTransformErrorMissingParameter, "Can not execute %@, missing required attributes: %@", GetName(), elist); + CFRelease(elist); + CFRelease(still_need); + return err; + } + + // see if we can attach our output here (note mAttributes may have changed) + numAttributes = CFSetGetCount(mAttributes); + attributes = (transform_attribute **)alloca(numAttributes * sizeof(transform_attribute *)); + TAGetAll(attributes); + for (i = 0; i < numAttributes; ++i) + { + transform_attribute *ta = attributes[i]; + int arraySize = ta->connections ? CFArrayGetCount(ta->connections) : 0; + if (arraySize == 0 && ta->requires_outbound_connection) + { + if (CFStringCompare(ta->name, kSecTransformOutputAttributeName, 0) == kCFCompareEqualTo) { + // this is a place where we can hook up our output -- maybe + if (outputAttached) + { + // oops, we've already done that. + return CreateSecTransformErrorRef(kSecTransformErrorMoreThanOneOutput, "Both %@ and %@ have loose outputs, attach one to something", outputAttached, ta->transform->GetName()); + } + // Delay the connect until after ForAllNodes returns + dispatch_async(phase2, ^{ + SecTransformConnectTransformsInternal(mGroup->GetCFObject(), + GetCFObject(), kSecTransformOutputAttributeName, + output, kSecTransformInputAttributeName); + }); + outputAttached = ta->transform->GetName(); + + // activate the attached monitor + Monitor* m = (Monitor*) CoreFoundationHolder::ObjectFromCFType(output); + m->mIsActive = true; + + // add the monitor to the output so that it doesn't get activated twice + } else { + return CreateSecTransformErrorRef(kSecTransformErrorNotInitializedCorrectly, "Attribute %@ (in %@) requires an outbound connection and doesn't have one", ta->name, GetName()); + } + + break; + } + } + + // Delay activation until after the Monitor is connected + dispatch_async(phase3, ^{ + phase3Activation(); + }); + + return NULL; +} + + + +void Transform::DoPhase3Activation() +{ + this->mIsActive = true; + // execution has now truly started ("mIsActive is true") + CFErrorRef initError = TransformStartingExecution(); + if (initError) + { + // Oops, now execution is about to grind to a halt + this->SendAttribute(AbortAH, initError); + } + + dispatch_resume(this->mActivationQueue); + dispatch_group_async(this->mActivationPending, this->mActivationQueue, ^{ + dispatch_release(this->mActivationQueue); + this->mActivationQueue = NULL; + }); +} + + + +// This would be best expressed as a block, but we seem to run into compiler errors +void Transform::phase3Activation() +{ + dispatch_async(this->mDispatchQueue, ^ + { + DoPhase3Activation(); + }); +} + + +Boolean Transform::TransformCanExecute() +{ + return true; +} + + + +CFErrorRef Transform::TransformStartingExecution() +{ + return NULL; +} + + + +bool Transform::IsExternalizable() +{ + return true; +} + +static const void *CFTypeOrNULLRetain(CFAllocatorRef allocator, const void *value) { + if (value != NULL) { + return CFRetain(value); + } else { + return value; + } +} + +static void CFTypeOrNULLRelease(CFAllocatorRef allocator, const void *value) { + if (value != NULL) { + CFRelease(value); + } +} + +static CFStringRef CFTypeOrNULLCopyDescription (const void *value) { + if (value != NULL) { + return CFCopyDescription(value); + } else { + return CFSTR("NULL"); + } +} + +static Boolean CFTypeOrNULLEqual(const void *value1, const void *value2) { + if (value1 == NULL && value2 == NULL) { + return TRUE; + } else { + if (value1 == NULL || value2 == NULL) { + return FALSE; + } else { + return CFEqual(value1, value2); + } + } +} + +CFHashCode CFTypeOrNULLHash(const void *value) { + if (value != NULL) { + return CFHash(value); + } else { + return 42; + } +} + + +// Returns a dictionary of all the meta attributes that will need to be reset on a RestoreState +CFDictionaryRef Transform::GetAHDictForSaveState(SecTransformStringOrAttributeRef key) +{ + SecTransformMetaAttributeType types[] = + { + kSecTransformMetaAttributeRequired, + kSecTransformMetaAttributeRequiresOutboundConnection, + kSecTransformMetaAttributeDeferred, + kSecTransformMetaAttributeStream, + kSecTransformMetaAttributeCanCycle, + kSecTransformMetaAttributeValue + }; + + CFIndex i, cnt = sizeof(types)/sizeof(SecTransformMetaAttributeType); + CFTypeRef values[cnt]; + CFNumberRef keys[cnt]; + key = getAH(key); + + // NOTE: we save meta attributes that are in their "default" state on purpose because the + // default may change in the future and we definitely want to restore the default values at + // time of save (i.e. if "stream=1" is the 10.7 default, but "stream=0" becomes the 10.8 + // default we want to load all old transforms with stream=1, the simplest way to do that is + // to store all values, not just non-default values) + for(i = 0; i < cnt; ++i) + { + values[i] = GetMetaAttribute(key, types[i]); + int tmp = (int)types[i]; + keys[i] = CFNumberCreate(NULL, kCFNumberIntType, &tmp); + } + + static CFDictionaryValueCallBacks CFTypeOrNULL; + static dispatch_once_t once; + dispatch_block_t b = + ^{ + CFTypeOrNULL.version = 0; + CFTypeOrNULL.retain = CFTypeOrNULLRetain; + CFTypeOrNULL.release = CFTypeOrNULLRelease; + CFTypeOrNULL.copyDescription = CFTypeOrNULLCopyDescription; + CFTypeOrNULL.equal = CFTypeOrNULLEqual; + }; + dispatch_once(&once, b); + + CFDictionaryRef ret = CFDictionaryCreate(NULL, (const void**)&keys, (const void**)&values, cnt, &kCFTypeDictionaryKeyCallBacks, &CFTypeOrNULL); + + for(i = 0; i < cnt; ++i) + { + CFRelease(keys[i]); + } + + return ret; +} + +// return everything that doesn't have ignore_while_externalizing set +CFDictionaryRef Transform::CopyState() +{ + CFIndex i, j, cnt = CFSetGetCount(mAttributes); + transform_attribute *attrs[cnt]; + CFStringRef names[cnt]; + CFDictionaryRef values[cnt]; + TAGetAll(attrs); + for(i = j = 0; i < cnt; ++i) + { + transform_attribute *ta = attrs[i]; + if (!ta->ignore_while_externalizing) + { + names[j] = ta->name; + values[j++] = GetAHDictForSaveState(ta->name); + } + } + + CFDictionaryRef result = CFDictionaryCreate(NULL, (const void**)&names, (const void**)&values, j, &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + + for(i = j = 0; i < cnt; ++i) + { + transform_attribute *ta = attrs[i]; + if (!ta->ignore_while_externalizing) + { + CFRelease(values[j++]); + } + } + + return result; +} + + + +void Transform::RestoreState(CFDictionaryRef state) +{ + CFIndex i, cnt = CFDictionaryGetCount(state); + const void + **keys = (const void **)alloca(sizeof(void*)*cnt), + **values = (const void **)alloca(sizeof(void*)*cnt); + + CFDictionaryGetKeysAndValues(state, keys, values); + + // Open issue -- do we need to do anything to values that are already set, but are not in "state"? + // this isn't an issue right now, which is only used on the SecTransformCopyExternalRepresentation path which starts with brand new objects, + // it only becomes an issue if we add a ResetFromState, or use it internally in that role. + + for(i = 0; i < cnt; i++) + { + SecTransformAttributeRef ah = getAH(keys[i]); + + if (NULL == ah) + { + continue; + } + + CFIndex j, meta_cnt = CFDictionaryGetCount((CFDictionaryRef)values[i]); + const void **types = (const void**)alloca(sizeof(void*)*meta_cnt), **meta_values = (const void**)alloca(sizeof(void*)*meta_cnt); + CFDictionaryGetKeysAndValues((CFDictionaryRef)values[i], types, meta_values); + + int t; + for(j = 0; j < meta_cnt; ++j) + { + CFNumberGetValue((CFNumberRef)types[j], kCFNumberIntType, &t); + if (t == kSecTransformMetaAttributeValue) + { + if (meta_values[j]) { + // SendMetaAttribute doesn't activate the callbacks + SetAttribute(ah, meta_values[j]); + } + } + else + { + CFErrorRef result = SendMetaAttribute(ah, (SecTransformMetaAttributeType)t, meta_values[j]); + if (result) + { + CFRelease(result); // see Transform::RestoreState is ignoring error returns + } + } + } + + CFErrorRef result = SendMetaAttribute(ah, kSecTransformMetaAttributeExternalize, kCFBooleanTrue); + if (result) + { + CFRelease(result); // see Transform::RestoreState is ignoring error returns + } + } +} + +GroupTransform* Transform::GetRootGroup() +{ + GroupTransform *g = mGroup; + if (g) { + while (g->mGroup) { + g = g->mGroup; + } + } else { + if (CFGetTypeID(this->GetCFObject()) == SecGroupTransformGetTypeID()) { + return (GroupTransform *)this; + } + } + return g; +} + +CFDictionaryRef Transform::GetCustomExternalData() +{ + return NULL; +} + +void Transform::SetCustomExternalData(CFDictionaryRef customData) +{ + return; +} + +CFDictionaryRef Transform::Externalize(CFErrorRef* error) +{ + if (mIsActive) + { + return (CFDictionaryRef)CreateSecTransformErrorRef(kSecTransformTransformIsExecuting, "The %@ transform is executing, you need to externalize it prior to execution", GetName()); + } + + // make arrays to hold the transforms and the connections + __block CFMutableArrayRef transforms = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); + __block CFMutableArrayRef connections = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); + GroupTransform *root = GetRootGroup(); + + CFErrorRef err = ForAllNodes(false, true, ^(Transform *t) { + if (t != root) { + return t->ProcessExternalize(transforms, connections); + } + return (CFErrorRef)NULL; + }); + + if (NULL != err) + { + // Really? This just seems like a bad idea + if (NULL != error) + { + *error = err; + } + return NULL; + + } + + // make a dictionary to hold the output + CFMutableDictionaryRef output = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + CFDictionaryAddValue(output, EXTERN_TRANSFORM_TRANSFORM_ARRAY, transforms); + CFDictionaryAddValue(output, EXTERN_TRANSFORM_CONNECTION_ARRAY, connections); + + // clean up + CFRelease(connections); + CFRelease(transforms); + + return output; +} + +CFErrorRef Transform::ProcessExternalize(CFMutableArrayRef transforms, CFMutableArrayRef connections) +{ + if (!IsExternalizable()) { + return NULL; + } + + CFDictionaryRef state = CopyState(); + if (state && CFGetTypeID(state) == CFErrorGetTypeID()) { + return (CFErrorRef)state; + } + + // make a dictionary to hold the name, type, and state of this node + CFMutableDictionaryRef node = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + CFDictionaryAddValue(node, EXTERN_TRANSFORM_NAME, GetName()); + + CFTypeRef type = CFStringCreateCopy(NULL, mTypeName); + CFDictionaryAddValue(node, EXTERN_TRANSFORM_TYPE, type); + CFRelease(type); + + if (state != NULL) + { + CFDictionaryAddValue(node, EXTERN_TRANSFORM_STATE, state); + CFRelease(state); + } + + CFDictionaryRef customItems = GetCustomExternalData(); + if (NULL != customItems) + { + CFDictionaryAddValue(node, EXTERN_TRANSFORM_CUSTOM_EXPORTS_DICTIONARY, customItems); + CFRelease(customItems); + } + + // append the resulting dictionary to the node list + CFArrayAppendValue(transforms, node); + CFRelease(node); + + // now walk the attribute list + CFIndex numAttributes = CFSetGetCount(mAttributes); + transform_attribute *attributes[numAttributes]; + TAGetAll(attributes); + + CFIndex i; + + // walk the forward links + for (i = 0; i < numAttributes; ++i) + { + int arraySize = attributes[i]->connections ? CFArrayGetCount(attributes[i]->connections) : 0; + if (arraySize != 0) + { + CFIndex j; + for (j = 0; j < arraySize; ++j) + { + transform_attribute *ta = ah2ta((SecTransformAttributeRef)CFArrayGetValueAtIndex(attributes[i]->connections, j)); + + if (!ta->transform->IsExternalizable()) { + // just pretend non-externalizable transforms don't even exist. Don't write out connections, and don't talk to them about externalizing. + continue; + } + + // add this forward connection to the array -- make a dictionary + CFMutableDictionaryRef connection = + CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + + CFDictionaryAddValue(connection, EXTERN_TRANSFORM_FROM_NAME, GetName()); + CFDictionaryAddValue(connection, EXTERN_TRANSFORM_FROM_ATTRIBUTE, attributes[i]->name); + CFDictionaryAddValue(connection, EXTERN_TRANSFORM_TO_NAME, ta->transform->GetName()); + CFDictionaryAddValue(connection, EXTERN_TRANSFORM_TO_ATTRIBUTE, ta->name); + + CFArrayAppendValue(connections, connection); + CFRelease(connection); + } + } + } + + return NULL; +}