/*
 * Copyright (c) 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@
 *
 */


//
// C++ gate to "Muscle" smartcard interface layer
//
#include "muscle++.h"
#include <security_utilities/debugging.h>


namespace Security {
namespace Muscle {


//
// PCSC domain errors
//
Error::Error(MSC_RV err) : error(err)
{
	IFDEBUG(debugDiagnose(this));
}


const char *Error::what() const throw ()
{
	return msc_error(error);
}


void Error::throwMe(MSC_RV err)
{
	throw Error(err);
}


OSStatus Error::osStatus() const
{
	return -1;	//@@@ preliminary
}

int Error::unixError() const
{
	return EINVAL;  //@@@ preliminary
}

#if !defined(NDEBUG)
void Error::debugDiagnose(const void *id) const
{
    secdebug("exception", "%p Muscle::Error %s (%ld) osStatus %ld",
		id, msc_error(error), error, osStatus());
}
#endif //NDEBUG


//
// Open a connection with PCSC layer information.
// The ReaderState fields required are the slot name and the ATR.
//
Connection::Connection()
	: mIsOpen(false), mCurrentTransaction(NULL)
{
}

Connection::~Connection()
{
	assert(!mCurrentTransaction);
	close();
}

void Connection::open(const PCSC::ReaderState &reader, unsigned share)
{
	// fill in the minimum needed to identify the card
	MSCTokenInfo info;
	
	// set slot name in info
	strncpy(info.slotName, reader.name(), MAX_READERNAME);
	
	// set ATR in info
	assert(reader.length() <= MAX_ATR_SIZE);
	memcpy(info.tokenId, reader.data(), reader.length());
	info.tokenIdLength = reader.length();
	
	// establish Muscle-level connection to card
	Error::check(::MSCEstablishConnection(&info, share, NULL, 0, this));
	mIsOpen = true;
	secdebug("muscle", "%p opened %s", this, info.slotName);
	
	// pull initial status
	updateStatus();
}

void Connection::close()
{
	if (mIsOpen) {
		secdebug("muscle", "%p closing", this);
		Error::check(::MSCReleaseConnection(this, SCARD_LEAVE_CARD));
		mIsOpen = false;
	}
}


void Connection::begin(Transaction *trans)
{
	assert(!mCurrentTransaction);
	Error::check(::MSCBeginTransaction(this));
	secdebug("muscle", "%p start transaction %p", this, trans);
	mCurrentTransaction = trans;
}

void Connection::end(Transaction *trans)
{
	assert(trans == mCurrentTransaction);
	secdebug("muscle", "%p end transaction %p", this, trans);
	Error::check(::MSCEndTransaction(this, SCARD_LEAVE_CARD));
	mCurrentTransaction = NULL;
}


//
// Update card status (cached in the Connection object)
//
void Connection::updateStatus()
{
	Error::check(::MSCGetStatus(this, this));
}


//
// Get all items off the card
//
template <class Info, class Item, MSC_RV (*list)(MSCTokenConnection *, MSCUChar8, Info *)>
static void get(Connection *conn, Connection::ItemSet &items)
{
	Info info;
	MSCUChar8 seq = MSC_SEQUENCE_RESET;
	for (;;) {
		switch (MSC_RV rc = list(conn, seq, &info)) {
		case MSC_SEQUENCE_END:
			return;
		case MSC_SUCCESS:
			items.insert(new Item(info));
			seq = MSC_SEQUENCE_NEXT;
			break;
		default:
			Error::throwMe(rc);
		}
	}
}

void Connection::getItems(ItemSet &result, bool getKeys, bool getOthers)
{
	ItemSet items;
	if (getKeys)
		get<MSCKeyInfo, Key, MSCListKeys>(this, items);
	if (getOthers)
		get<MSCObjectInfo, Object, MSCListObjects>(this, items);
	items.swap(result);
}


//
// Transaction monitors
//
Transaction::Transaction(Connection &con)
    : connection(con)
{
    connection.begin(this);
}

Transaction::~Transaction()
{
    connection.end(this);
}


//
// ACLs (Muscle style)
//
static void aclForm(string &s, MSCUShort16 acl, int offset, char c)
{
	for (int n = 0; n < 5; n++) {
		char p = '-';
		switch (acl) {
		case MSC_AUT_ALL:		p = c; break;
		case MSC_AUT_NONE:		break;
		default:				if (acl & (MSC_AUT_PIN_0 << n)) p = c; break;
		}
		s[3 * n + offset] = p;
	}
}

string ACL::form(char ue) const
{
	string r = "---------------";
	aclForm(r, mRead, 0, 'r');
	aclForm(r, mWrite, 1, 'w');
	aclForm(r, mErase, 2, ue);
	return r;
}


//
// Keys and objects
//
CardItem::~CardItem()
{ /* virtual */ }


Key::Key(const MSCKeyInfo &info)
	: MSCKeyInfo(info)
{
	snprintf(mKeyName, sizeof(mKeyName), "K%d", id());
}


const ACL &Key::acl() const		{ return reinterpret_cast<const ACL &>(keyACL); }
ACL &Key::acl()					{ return reinterpret_cast<ACL &>(keyACL); }

const char *Key::name() const	{ return mKeyName; }
unsigned Key::size() const		{ return keySize; }

void Key::debugDump()
{
	printf("Key %d type %d size %d mode=0x%x dir=0x%x ACL %s\n",
		keyNum, keyType, keySize, mode(), operations(), acl().form('u').c_str());
}

const char *Object::name() const { return objectID; }
unsigned Object::size() const	{ return objectSize; }

const ACL &Object::acl() const	{ return reinterpret_cast<const ACL &>(objectACL); }
ACL &Object::acl()				{ return reinterpret_cast<ACL &>(objectACL); }

void Object::debugDump()
{
	printf("Object %s size %ld ACL %s\n",
		objectID, objectSize, acl().form('e').c_str());
}


}	// namespace Muscle
}	// namespace Security
