/*
 * Copyright (c) 2000-2004 Apple Computer, Inc. All Rights Reserved.
 * 
 * @APPLE_LICENSE_HEADER_START@
 * 
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this
 * file.
 * 
 * The 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.
 * 
 * @APPLE_LICENSE_HEADER_END@
 */


//
// connection - manage connections to clients.
//
// Note that Connection objects correspond to client process threads, and are
// thus inherently single-threaded. It is physically impossible for multiple
// requests to come in for the same Connection, unless the client side is
// illegally messing with the IPC protocol (for which we check below).
// It is still necessary to take the object lock for a Connection because there
// are times when we want to manipulate a busy Connection from another securityd
// thread (say, in response to a DPN).
//
#include "connection.h"
#include "key.h"
#include "server.h"
#include "session.h"
#include <security_cdsa_client/keyclient.h>
#include <security_cdsa_client/genkey.h>
#include <security_cdsa_client/wrapkey.h>
#include <security_cdsa_client/signclient.h>
#include <security_cdsa_client/macclient.h>
#include <security_cdsa_client/cryptoclient.h>


//
// Construct a Connection object.
//
Connection::Connection(Process &proc, Port rPort)
 : mClientPort(rPort), state(idle), agentWait(NULL),
   aclUpdateTrigger(NULL)
{
	parent(proc);
	
	// bump the send-rights count on the reply port so we keep the right after replying
	mClientPort.modRefs(MACH_PORT_RIGHT_SEND, +1);
	
	secdebug("SS", "New connection %p for process %d clientport=%d",
		this, process().pid(), int(rPort));
}


//
// When a Connection's destructor executes, the connection must already have been
// terminated. All we have to do here is clean up a bit.
//
Connection::~Connection()
{
	secdebug("SS", "Connection %p destroyed", this);
	assert(!agentWait);
}


//
// Terminate a Connection normally.
// This is assumed to be properly sequenced, so no thread races are possible.
//
void Connection::terminate()
{
	// cleanly discard port rights
	assert(state == idle);
	mClientPort.modRefs(MACH_PORT_RIGHT_SEND, -1);	// discard surplus send right
	assert(mClientPort.getRefs(MACH_PORT_RIGHT_SEND) == 1);	// one left for final reply
	secdebug("SS", "Connection %p terminated", this);
}


//
// Abort a Connection.
// This may be called from thread A while thread B is working a request for the Connection,
// so we must be careful.
//
void Connection::abort(bool keepReplyPort)
{
	StLock<Mutex> _(*this);
    if (!keepReplyPort)
        mClientPort.destroy();		// dead as a doornail already
	switch (state) {
	case idle:
		secdebug("SS", "Connection %p aborted", this);
		break;
	case busy:
		state = dying;				// shoot me soon, please
		if (agentWait)
			agentWait->destroy();
		secdebug("SS", "Connection %p abort deferred (busy)", this);
		break;
	default:
		assert(false);				// impossible (we hope)
		break;
	}
}


//
// Service request framing.
// These are here so "hanging" connection service threads don't fall
// into the Big Bad Void as Connections and processes drop out from
// under them.
//
void Connection::beginWork()
{
	switch (state) {
	case idle:
		state = busy;
		break;
	case busy:
		secdebug("SS", "Attempt to re-enter connection %p(port %d)", this, mClientPort.port());
		CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR);	//@@@ some state-error code instead?
	default:
		assert(false);
	}
}

void Connection::checkWork()
{
	StLock<Mutex> _(*this);
	switch (state) {
	case busy:
		return;
	case dying:
		agentWait = NULL;	// obviously we're not waiting on this
		throw this;
	default:
		assert(false);
	}
}

void Connection::endWork()
{
	switch (state) {
	case busy:
		// process the n-step aclUpdateTrigger
		if (aclUpdateTrigger) {
            if (--aclUpdateTriggerCount == 0) {
                aclUpdateTrigger = NULL;
                secdebug("kcacl", "acl update trigger expires");
            } else
                secdebug("kcacl", "acl update trigger armed for %d calls",
                    aclUpdateTriggerCount);
        }
		// end involvement
		state = idle;
		return;
	case dying:
		secdebug("SS", "Connection %p abort resuming", this);
		return;
	default:
		assert(false);
		return;	// placebo
	}
}
