--- /dev/null
+#include <CoreServices/CoreServices.h>
+#include <Block.h>
+#include <libkern/OSAtomic.h>
+#include <syslog.h>
+#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"
+#include <pthread.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<SecTransformAttributeRef>(const_cast<void*>(v)));
+ return CFStringCreateWithFormat(NULL, NULL, CFSTR("%@/%@=%@ (conn: %@)"), ta->transform->GetName(), ta->name, ta->value ? ta->value : CFSTR("NULL"), ta->connections ? static_cast<CFTypeRef>(ta->connections) : static_cast<CFTypeRef>(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<SecTransformAttributeRef>(const_cast<void*>(v)))->name);
+}
+
+static Boolean ah_set_equal(const void *v1, const void *v2) {
+ return CFEqual(ah2ta(static_cast<SecTransformAttributeRef>(const_cast<void*>(v1)))->name, ah2ta(static_cast<SecTransformAttributeRef>(const_cast<void*>(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<SecTransformAttributeRef>(const_cast<void*>(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<transform_attribute *>(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<char*>(CFStringGetCStringPtr(transformType, kCFStringEncodingUTF8));
+ if (!tname) {
+ CFIndex sz = 1+CFStringGetMaximumSizeForEncoding(CFStringGetLength(transformType), kCFStringEncodingUTF8);
+ tname = static_cast<typeof(tname)>(alloca(sz));
+ if (tname) {
+ CFStringGetCString(transformType, tname, sz, kCFStringEncodingUTF8);
+ } else {
+ tname = const_cast<char*>("-");
+ }
+ }
+
+ 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, (void *)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<SecTransformAttributeRef>(const_cast<void *>(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<CFTypeRef>(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<CFStringRef>(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<transform_attribute *>(ah2ta(const_cast<void *>(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];
+ CFIndex 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);
+ }
+ }
+}
+
+// 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 <rdar://problem/8741628> Transform::RestoreState is ignoring error returns
+ }
+ }
+ }
+
+ CFErrorRef result = SendMetaAttribute(ah, kSecTransformMetaAttributeExternalize, kCFBooleanTrue);
+ if (result)
+ {
+ CFRelease(result); // see <rdar://problem/8741628> 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)
+ {
+ CFIndex 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;
+}