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


//
// Encapsulate the callback mechanism of CSSM.
//
#ifdef __MWERKS__
#define _CPP_CALLBACK
#endif
#include <Security/callback.h>


//
// Invoke a callback
//
void ModuleCallback::operator () (CSSM_MODULE_EVENT event,
                                  const Guid &guid, uint32 subId,
                                  CSSM_SERVICE_TYPE serviceType) const
{
    if (mCallback)
        if (CSSM_RETURN err = mCallback(&guid, mContext, subId, serviceType, event))
            CssmError::throwMe(err);
}


//
// Manage Callback sets.
// THREADS: Caller is ensuring single-thread access on these calls.
//
void ModuleCallbackSet::insert(const ModuleCallback &newCallback)
{
    callbacks.insert(CallbackMap::value_type(newCallback, new CountingMutex));
}

void ModuleCallbackSet::erase(const ModuleCallback &oldCallback)
{
    CallbackMap::iterator it = callbacks.find(oldCallback);
    if (it == callbacks.end())	// not registered; fail
        CssmError::throwMe(CSSMERR_CSSM_INVALID_ADDIN_HANDLE);
    CountingMutex *counter = it->second;
    {
        StLock<Mutex> _(*counter);
        if (!counter->isIdle()) // callbacks are scheduled against this
            CssmError::throwMe(CSSM_ERRCODE_FUNCTION_FAILED);	// @#module is busy
    }
    // counter is zero (idle), and we hold the entry lock (via our caller)
    delete counter;
    callbacks.erase(it);
}


//
// Invoke an entire callback set.
// THREADS: Caller is ensuring  single-thread access on these calls.
//
void ModuleCallbackSet::operator () (CSSM_MODULE_EVENT event,
                                     const Guid &guid, uint32 subId,
                                     CSSM_SERVICE_TYPE serviceType) const
{
    if (callbacks.empty())	// nothing to do; quick exit
        return;

#if _USE_THREADS == _USE_NO_THREADS || defined(SYNCHRONOUS_CALLBACKS)
    // no threading model supported - we HAVE to do this right here
    // note that the user better not re-enter CSSM too much,
    // or we might deadlock...
    for (CallbackMap::const_iterator it = callbacks.begin();
         it != callbacks.end(); it++) {
        it->first(event, guid, subId, serviceType);
    }
#else // real threads available
    // lock down all callback elements - still protected by global lock (via caller)
    for (CallbackMap::iterator it = callbacks.begin();
         it != callbacks.end(); it++)
        it->second->enter();

    // get out of this thread - now!
    (new Runner(callbacks, event, guid, subId, serviceType))->run();
#endif
}

void ModuleCallbackSet::Runner::action()
{
    //
    // NOTE WELL: Our callbacks map shares (pointed-to) values with the ModuleCallbackSet
    // we were created from. Some of these values may be dangling pointers since they have
    // been destroyed by other threads, but only *after* we are done with them, since
    // we must call exit() on them before they become eligible for destruction.
    // In all cases, it is the responsibility of other threads to destroy those mutexi.
    //
    // @@@ Could also fan out to multiple callback threads in parallel.
    for (CallbackMap::iterator it = callbacks.begin();
         it != callbacks.end(); it++) {
        //@@@ safety vs. convenience - recheck
        it->first(event, guid, subserviceId, serviceType);
        it->second->exit();
    }
}
