/*
 * 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.
 */


//
// cssmclient - common client interface to CSSM and MDS.
//
// Locking Strategy (preliminary):
// XXX This is obsolete update this --mb
// A CssmObject is a CountingMutex. Its count represents the number of children that have registered
// themselves (using addChild/removeChild). The lock controls the internal management fields of the
// various subclasses to protect them against corruption. It does NOT control attribute and argument
// fields and operations, not does it control object-constant fields.
// This means that if you use an object from multiple threads, you (the caller) must lock the object
// during set/get calls of attributes. Note that the CSSM operations themselves are safely multithreaded
// and thus don't need to be interlocked explicitly.
//
#include <Security/cssmclient.h>
#include <Security/cssmerrno.h>


using namespace CssmClient;

//
// Exception model
//
CSSM_RETURN
Error::cssmError() const
{
	//@@@ munge in client-side error codes here?
	return CssmError::cssmError();
}

const char *
Error::what () const throw()
{
	return "CSSM client library error";
}


//
// General utilities
//
void
ObjectImpl::check(CSSM_RETURN status)
{
	if (status != CSSM_OK)
	{
		CssmError::throwMe(status);
	}
}


//
// Common features of Objects
//
ObjectImpl::ObjectImpl() : mParent(), mChildCount(0)
{
	mActive = false;		// not activated
	mAllocator = NULL;		// allocator to be determined
}

ObjectImpl::ObjectImpl(const Object &mommy) : mParent(mommy.mImpl), mChildCount(0)
{
	mActive = false;		// not activated
	mAllocator = NULL;		// allocator to be determined
	if (mParent)
		mParent->addChild();
}

ObjectImpl::~ObjectImpl()
{
	assert(!mActive);	// subclass must have deactivated us
	if (!isIdle())
		Error::throwMe(Error::objectBusy);
		
	// release parent from her obligations (if we still have one)
	if (mParent)
		mParent->removeChild();
}

void
ObjectImpl::addChild()
{
	mChildCount++;		// atomic
}

void
ObjectImpl::removeChild()
{
	mChildCount--;		// atomic
}


//
// Manage allocators in the Object tree
//
CssmAllocator &
ObjectImpl::allocator() const
{
	if (mAllocator == NULL)
	{
		// fix allocator now
		if (mParent)
			mAllocator = &mParent->allocator();
		else
			mAllocator = &CssmAllocator::standard();
	}

	return *mAllocator;
}

void
ObjectImpl::allocator(CssmAllocator &alloc)
{
	assert(mAllocator == NULL);	// cannot redefine allocator once set
	mAllocator = &alloc;
}

// Comparison operators use pointer comparison by default.  Subclasses may override.
bool
ObjectImpl::operator <(const ObjectImpl &other) const
{
	return this < &other;
}

bool
ObjectImpl::operator ==(const ObjectImpl &other) const
{
	return this == &other;
}


//
// CSSMSession objects.
// parent ::= NULL (none)
// active ::= CSSM initialized
//
ModuleNexus<CssmImpl::StandardCssm> CssmImpl::mStandard;

CssmImpl::CssmImpl() : ObjectImpl()
{
	setup();
	mStandard().setCssm(this);
}

CssmImpl::CssmImpl(bool) : ObjectImpl()
{
	setup();
	// implicitly constructed - caller responsible for standard session management
}

CssmImpl::~CssmImpl()
{
    try
    {
		deactivate();
    }
	catch(...) {}

	// this may be the standard session...
    mStandard().unsetCssm(this);
}


void
CssmImpl::setup()
{
	// set default configuration
	mVersion.Major = 2;
	mVersion.Minor = 0;
	mScope = CSSM_PRIVILEGE_SCOPE_PROCESS;
}


Cssm
CssmImpl::standard()
{
    return Cssm(mStandard().get());
}


void
CssmImpl::activate()
{
	if (!mActive)
	{
		// currently, no choices on PVC mode and key hierarchy
		CSSM_PVC_MODE pvc = CSSM_PVC_NONE;
		switch (CSSM_RETURN rc = CSSM_Init(&mVersion,
				mScope, &mCallerGuid,
				CSSM_KEY_HIERARCHY_NONE, &pvc, NULL)) {
		case CSSMERR_CSSM_PVC_ALREADY_CONFIGURED:
		case CSSM_OK:
			break;
		default:
			check(rc);
		}
		mActive = true;
	}
}

void
CssmImpl::deactivate()
{
	if (mActive)
	{
		mActive = false;

		// clear module map (all gone now)
		moduleMap.erase(moduleMap.begin(), moduleMap.end());
	
		// now terminate CSSM
		check(CSSM_Terminate());
	}
}

void
CssmImpl::atExitHandler()
{
    try {
        mStandard.reset();
    } catch (...) {
    }
}

void
CssmImpl::catchExit()
{
	// @@@ Even though this is the "right thing" to do.  This only causes
	// exceptions during exit and doesn't really help cleanup correctly.
#if 0
	if (::atexit(atExitHandler))
		UnixError::throwMe();
#endif
}


//
// Manage the automatic Cssm object.
// This is a program global.
//
void CssmImpl::StandardCssm::setCssm(CssmImpl *cssm)
{
    StLock<Mutex> _(*this);
    if (mCssm == NULL)
        mCssm = cssm;
}

void CssmImpl::StandardCssm::unsetCssm(CssmImpl *cssm)
{
    StLock<Mutex> _(*this);
    if (mCssm == cssm)
        mCssm = NULL;
}

CssmImpl *CssmImpl::StandardCssm::get()
{
    StLock<Mutex> _(*this);
    if (mCssm == NULL) {	// make the default instance
        mCssm = new CssmImpl(true);
    }
    return mCssm;
}

CssmImpl::StandardCssm::~StandardCssm()
{
    if (mCssm) {
        mCssm->deactivate();
        delete mCssm;
    }
}


//
// Auto-module management
//
Module
CssmImpl::autoModule(const Guid &guid)
{
	StLock<Mutex> _(mapLock);
	ModuleMap::iterator it = moduleMap.find(guid);
	if (it == moduleMap.end())
	{
		// no automodule for this guid yet, create one
		Module module(guid, Cssm(this));
		moduleMap.insert(ModuleMap::value_type(guid, module));
		return module;
	}
	else
	{
		// existing automodule - use it
		return it->second;
	}
}


//
// Module objects.
// parent ::= the session object (usually Cssm::standard)
// active ::= module is loaded.
//
ModuleImpl::ModuleImpl(const Guid &guid) : ObjectImpl(Cssm::standard())
{
	setGuid(guid);
}

ModuleImpl::ModuleImpl(const Guid &guid, const Cssm &session) : ObjectImpl(session)
{
	setGuid(guid);
}

ModuleImpl::~ModuleImpl()
{
	unload();
}


void
ModuleImpl::activate()
{
	if (!mActive)
	{
		session()->init();
		// @@@ install handler here (use central dispatch with override)
		check(CSSM_ModuleLoad(&guid(), CSSM_KEY_HIERARCHY_NONE, NULL, NULL));
		mActive = true;
		session()->catchExit();
	}
}

void
ModuleImpl::deactivate()
{
	if (!isIdle())
		Error::throwMe(Error::objectBusy);
	if (mActive)
	{
		mActive = false;
		check(CSSM_ModuleUnload(&guid(), NULL, NULL));
	}
}

Cssm
ModuleImpl::session() const
{
	return parent<Cssm>();
}


//
// CssmAttachment objects.
// parent ::= the loaded module object.
// active ::= attached.
//
AttachmentImpl::AttachmentImpl(const Guid &guid, CSSM_SERVICE_TYPE subserviceType)
: ObjectImpl(CssmImpl::standard()->autoModule(guid))
{
	make(subserviceType);
}

AttachmentImpl::AttachmentImpl(const Module &module, CSSM_SERVICE_TYPE subserviceType)
: ObjectImpl(module)
{
	make(subserviceType);
}

AttachmentImpl::~AttachmentImpl()
{
	detach();
}

void
AttachmentImpl::make(CSSM_SERVICE_TYPE subserviceType)
{
	// default configuration
	mVersion.Major = 2;
	mVersion.Minor = 0;
	mSubserviceType = subserviceType;
	mSubserviceId = 0;
	mAttachFlags = 0;
}

void
AttachmentImpl::activate()
{
	if (!mActive)
	{
		module()->load();
		mMemoryFunctions = CssmAllocatorMemoryFunctions(allocator());
		check(CSSM_ModuleAttach(&guid(), &mVersion,
			  &mMemoryFunctions,
			  mSubserviceId,
			  mSubserviceType,
			  mAttachFlags,
			  CSSM_KEY_HIERARCHY_NONE,
			  NULL, 0,	// no function pointer table return
			  NULL,		// reserved
			  &mHandle));
		mActive = true;
	}
}

void
AttachmentImpl::deactivate()
{
	if (mActive)
	{
		mActive = false;
		check(CSSM_ModuleDetach(mHandle));
	}
}

CSSM_SERVICE_MASK
AttachmentImpl::subserviceMask() const
{
	return mSubserviceType;
}

void
AttachmentImpl::subserviceId(uint32 id)
{
	mSubserviceId = id;
}

CssmSubserviceUid
AttachmentImpl::subserviceUid() const
{
	return CssmSubserviceUid(guid(), &mVersion, mSubserviceId, subserviceMask());
}

Module
AttachmentImpl::module() const
{
	return parent<Module>();
}
