/****************************************************************************
* Copyright (C) 2012 ecsec GmbH.
* All rights reserved.
* Contact: ecsec GmbH (info@ecsec.de)
*
* This file is part of the Open eCard App.
*
* GNU General Public License Usage
* This file may be used under the terms of the GNU General Public
* License version 3.0 as published by the Free Software Foundation
* and appearing in the file LICENSE.GPL included in the packaging of
* this file. Please review the following information to ensure the
* GNU General Public License version 3.0 requirements will be met:
* http://www.gnu.org/copyleft/gpl.html.
*
* Other Usage
* Alternatively, this file may be used in accordance with the terms
* and conditions contained in a signed written agreement between
* you and ecsec GmbH.
*
***************************************************************************/
package org.openecard.common.sal.state;
import iso.std.iso_iec._24727.tech.schema.CardApplicationPathType;
import iso.std.iso_iec._24727.tech.schema.ChannelHandleType;
import iso.std.iso_iec._24727.tech.schema.ConnectionHandleType;
import iso.std.iso_iec._24727.tech.schema.ConnectionHandleType.RecognitionInfo;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentSkipListMap;
import org.openecard.common.util.ByteComparator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
*
* @author Tobias Wich <tobias.wich@ecsec.de>
*/
public class CardStateMap {
private static final Logger _logger = LoggerFactory.getLogger(CardStateMap.class);
private final TreeSet<CardStateEntry> allEntries = new TreeSet<CardStateEntry>();
private final ConcurrentSkipListMap<String,Set<CardStateEntry>> sessionMap = new ConcurrentSkipListMap<String,Set<CardStateEntry>>();
private final ConcurrentSkipListMap<byte[],Set<CardStateEntry>> contextMap = new ConcurrentSkipListMap<byte[],Set<CardStateEntry>>(new ByteComparator());
private final ConcurrentSkipListMap<byte[],Set<CardStateEntry>> slothandleMap = new ConcurrentSkipListMap<byte[],Set<CardStateEntry>>(new ByteComparator());
public synchronized CardStateEntry getEntry(ConnectionHandleType handle) {
return getEntry(handle);
}
public synchronized CardStateEntry getEntry(ConnectionHandleType handle, boolean filterAppId) {
Set<CardStateEntry> entry = getMatchingEntries(handle, filterAppId);
int size = entry.size();
if (size == 1) {
return entry.iterator().next();
} else if (size == 0) {
_logger.warn("No state entry found for the given ConnectionHandle.");
} else {
_logger.warn("More than one state entry found for the given ConnectionHandle.");
}
return null;
}
public synchronized void addEntry(CardStateEntry entry) {
ConnectionHandleType handle = entry.handleCopy();
ChannelHandleType channel = handle.getChannelHandle();
if (channel != null) {
addMapEntry(channel.getSessionIdentifier(), sessionMap, entry);
}
addMapEntry(handle.getContextHandle(), contextMap, entry);
addMapEntry(handle.getSlotHandle(), slothandleMap, entry);
allEntries.add(entry);
}
/**
* Remove all references to the CardStateEntries matching this ConnectionHandle. <br/>
* If more than one entry exists, all occurrences are deleted.
* @param handle
*/
public synchronized void removeEntry(ConnectionHandleType handle) {
Set<CardStateEntry> entries = getMatchingEntries(handle);
Iterator<CardStateEntry> it = entries.iterator();
boolean removeSlotHandles = handle.getSlotHandle() == null;
while (it.hasNext()) {
CardStateEntry entry = it.next();
removeEntry(entry, removeSlotHandles);
}
}
/**
* Remove the entry reference in slotHandle index. <br/>
* This function is needed to update the index in CardApplicationDisconnect.
*
* @param slotHandle SlotHandle for which the entry reference should be deleted.
*/
public synchronized void removeSlotHandleEntry(byte[] slotHandle) {
ConnectionHandleType handle = new ConnectionHandleType();
handle.setSlotHandle(slotHandle);
Set<CardStateEntry> entries = getMatchingEntries(handle);
Iterator<CardStateEntry> it = entries.iterator();
if (it.hasNext()) {
CardStateEntry entry = it.next();
removeMapEntry(handle.getSlotHandle(), slothandleMap, entry);
clearProtocolsForEntry(entry);
}
}
private void clearProtocolsForEntry(CardStateEntry entry) {
Iterator<CardStateEntry> it = allEntries.iterator();
if (it.hasNext()) {
CardStateEntry allEntriesEntry = it.next();
if (entry.equals(allEntriesEntry)) {
allEntriesEntry.removeAllProtocols();
}
}
}
/**
* Remove all references to this CardStateEntry.
* @param entry Entry to delete.
* @param removeSlotHandles When set remove all occurrences of this entry in the slotHandle index.
*/
private synchronized void removeEntry(CardStateEntry entry, boolean removeSlotHandles) {
ConnectionHandleType handle = entry.handleCopy();
ChannelHandleType channel = handle.getChannelHandle();
if (channel != null) {
removeMapEntry(channel.getSessionIdentifier(), sessionMap, entry);
}
removeMapEntry(handle.getContextHandle(), contextMap, entry);
// remove all or just the one a key is given for
if (removeSlotHandles) {
Iterator<byte[]> it = slothandleMap.keySet().iterator();
while (it.hasNext()) {
byte[] key = it.next();
removeMapEntry(key, slothandleMap, entry);
}
} else {
removeMapEntry(handle.getSlotHandle(), slothandleMap, entry);
}
allEntries.remove(entry);
}
private <K> void addMapEntry(K key, ConcurrentSkipListMap<K,Set<CardStateEntry>> map, CardStateEntry entry) {
if (key != null) {
Set<CardStateEntry> entrySet = setFromMap(map, key);
boolean empty = entrySet.isEmpty();
entrySet.add(entry);
if (empty) {
map.put(key, entrySet);
}
}
}
private <K> void removeMapEntry(K key, ConcurrentSkipListMap<K,Set<CardStateEntry>> map, CardStateEntry entry) {
if (key != null) {
if (map.containsKey(key)) {
Set<CardStateEntry> entrySet = map.get(key);
entrySet.remove(entry);
if (entrySet.isEmpty()) {
map.remove(key);
}
}
}
}
public Set<CardStateEntry> getMatchingEntries(ConnectionHandleType cHandle) {
return getMatchingEntries(cHandle, true);
}
public Set<CardStateEntry> getMatchingEntries(ConnectionHandleType cHandle, boolean filterAppId) {
return getMatchingEntries(cHandle, cHandle.getSlotHandle(), cHandle.getRecognitionInfo(), filterAppId);
}
public Set<CardStateEntry> getMatchingEntries(CardApplicationPathType cHandle) {
return getMatchingEntries(cHandle, true);
}
public Set<CardStateEntry> getMatchingEntries(CardApplicationPathType cHandle, boolean filterAppId) {
return getMatchingEntries(cHandle, null, null, filterAppId);
}
private synchronized Set<CardStateEntry> getMatchingEntries(CardApplicationPathType cHandle, byte[] slotHandle,
RecognitionInfo recInfo, boolean filterAppId) {
// extract values from map
ChannelHandleType channel = cHandle.getChannelHandle();
String session = (channel != null) ? channel.getSessionIdentifier() : null;
byte[] ctx = cHandle.getContextHandle();
String ifdname = cHandle.getIFDName();
BigInteger slotIdx = cHandle.getSlotIndex();
byte[] cardApplication = cHandle.getCardApplication();
// when nothing has been specified, return all elements
Set<CardStateEntry> mergedSets;
if (session == null && ctx == null && slotHandle == null) {
mergedSets = new TreeSet<CardStateEntry>(allEntries);
} else {
// fetch applicable lists from maps
Set<CardStateEntry> sessionEntries = setFromMap(sessionMap, session);
Set<CardStateEntry> ctxEntries = setFromMap(contextMap, ctx);
Set<CardStateEntry> slothandleEntries = setFromMap(slothandleMap, slotHandle);
// merge entries
ArrayList<Set<CardStateEntry>> setsToMerge = new ArrayList<Set<CardStateEntry>>(3);
if (session != null) {
setsToMerge.add(sessionEntries);
}
if (ctx != null) {
setsToMerge.add(ctxEntries);
}
if (slotHandle != null) {
setsToMerge.add(slothandleEntries);
}
mergedSets = mergeSets(setsToMerge);
}
// filter maps for slotIndex if any is given
if (slotIdx != null) {
filterIdx(mergedSets, slotIdx);
}
if (ifdname != null) {
filterIfdname(mergedSets, ifdname);
}
if (filterAppId && cardApplication != null) {
filterCardApplication(mergedSets, cardApplication);
} else {
// [TR-03112-4] If no card application is specified, paths to all
// available cards (alpha-card applications) and unused card
// terminal slots are returned.
}
if (recInfo != null && recInfo.getCardType() != null) {
filterCardType(mergedSets, recInfo.getCardType());
}
return mergedSets;
}
/**
* Simplify returning a result from the map.<br/>
* If key is null or no key is present, the empty list is returned.
*
* @param <K>
* @param map
* @param key
* @return
*/
private static <K> Set<CardStateEntry> setFromMap(ConcurrentSkipListMap<K, Set<CardStateEntry>> map, K key) {
Set<CardStateEntry> result = null;
if (key != null) {
result = map.get(key);
}
return (result != null) ? result : new TreeSet<CardStateEntry>();
}
/**
* Remove non matching entries (slotIndex) from given list.
*
* @param entries
* @param idx
*/
private static void filterIdx(Set<CardStateEntry> entries, BigInteger idx) {
Iterator<CardStateEntry> it = entries.iterator();
while (it.hasNext()) {
CardStateEntry next = it.next();
// other index is not equal to this one
if (next.hasSlotIdx() && ! next.matchSlotIdx(idx)) {
it.remove();
}
}
}
/**
* Remove non matching entries (cardApplication) from given list.
*
* @param entries
* @param cardApplication
*/
private static void filterCardApplication(Set<CardStateEntry> entries, byte[] cardApplication) {
Iterator<CardStateEntry> it = entries.iterator();
while (it.hasNext()) {
CardStateEntry next = it.next();
if (! Arrays.equals(next.getCurrentCardApplication().getApplicationIdentifier(), cardApplication)) {
it.remove();
}
}
}
/**
* Remove non matching entries (ifdName) from given list.
*
* @param entries
* @param ifdname
*/
private static void filterIfdname(Set<CardStateEntry> entries, String ifdName) {
Iterator<CardStateEntry> it = entries.iterator();
while (it.hasNext()) {
CardStateEntry next = it.next();
String otherName = next.getIfdName();
// other ifdName is not equal to this one
if (otherName != null && ! otherName.equals(ifdName)) {
it.remove();
}
}
}
/**
* Remove non matching entries (cardType) from given list.
*
* @param entries
* @param cardType
*/
private static void filterCardType(Set<CardStateEntry> entries, String cardType) {
Iterator<CardStateEntry> it = entries.iterator();
while (it.hasNext()) {
CardStateEntry next = it.next();
String otherType = next.getCardType();
// other ifdName is not equal to this one
if (! otherType.equals(cardType)) {
it.remove();
}
}
}
private static Set<CardStateEntry> mergeSets(ArrayList<Set<CardStateEntry>> setsToMerge) {
TreeSet<CardStateEntry> result = new TreeSet<CardStateEntry>();
if (setsToMerge.isEmpty()) {
return Collections.emptySet();
} else {
// add first then try to merge the others
result.addAll(setsToMerge.get(0));
if (setsToMerge.size() >= 2) {
for (Set<CardStateEntry> nextSet : setsToMerge.subList(1, setsToMerge.size())) {
Iterator<CardStateEntry> it = result.iterator();
while (it.hasNext()) {
CardStateEntry nextEntry = it.next();
// remove entry from results if it is not present in the next set
if (! nextSet.contains(nextEntry)) {
it.remove();
}
}
}
}
// done
return result;
}
}
}