1 /* 2 * Copyright (c) 2005, 2006, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package sun.security.smartcardio; 27 28 import java.util.*; 29 import java.lang.ref.*; 30 31 import javax.smartcardio.*; 32 import static javax.smartcardio.CardTerminals.State.*; 33 34 import static sun.security.smartcardio.PCSC.*; 35 36 /** 37 * TerminalFactorySpi implementation class. 38 * 39 * @since 1.6 40 * @author Andreas Sterbenz 41 */ 42 final class PCSCTerminals extends CardTerminals { 43 44 // SCARDCONTEXT, currently shared between all threads/terminals 45 private static long contextId; 46 47 // terminal state used by waitForCard() 48 private Map<String,ReaderState> stateMap; 49 PCSCTerminals()50 PCSCTerminals() { 51 // empty 52 } 53 initContext()54 static synchronized void initContext() throws PCSCException { 55 if (contextId == 0) { 56 contextId = SCardEstablishContext(SCARD_SCOPE_USER); 57 } 58 } 59 60 private static final Map<String,Reference<TerminalImpl>> terminals 61 = new HashMap<String,Reference<TerminalImpl>>(); 62 implGetTerminal(String name)63 private static synchronized TerminalImpl implGetTerminal(String name) { 64 Reference<TerminalImpl> ref = terminals.get(name); 65 TerminalImpl terminal = (ref != null) ? ref.get() : null; 66 if (terminal != null) { 67 return terminal; 68 } 69 terminal = new TerminalImpl(contextId, name); 70 terminals.put(name, new WeakReference<TerminalImpl>(terminal)); 71 return terminal; 72 73 } 74 list(State state)75 public synchronized List<CardTerminal> list(State state) throws CardException { 76 if (state == null) { 77 throw new NullPointerException(); 78 } 79 try { 80 String[] readerNames = SCardListReaders(contextId); 81 List<CardTerminal> list = new ArrayList<CardTerminal>(readerNames.length); 82 if (stateMap == null) { 83 // If waitForChange() has never been called, treat event 84 // queries as status queries. 85 if (state == CARD_INSERTION) { 86 state = CARD_PRESENT; 87 } else if (state == CARD_REMOVAL) { 88 state = CARD_ABSENT; 89 } 90 } 91 for (String readerName : readerNames) { 92 CardTerminal terminal = implGetTerminal(readerName); 93 ReaderState readerState; 94 switch (state) { 95 case ALL: 96 list.add(terminal); 97 break; 98 case CARD_PRESENT: 99 if (terminal.isCardPresent()) { 100 list.add(terminal); 101 } 102 break; 103 case CARD_ABSENT: 104 if (terminal.isCardPresent() == false) { 105 list.add(terminal); 106 } 107 break; 108 case CARD_INSERTION: 109 readerState = stateMap.get(readerName); 110 if ((readerState != null) && readerState.isInsertion()) { 111 list.add(terminal); 112 } 113 break; 114 case CARD_REMOVAL: 115 readerState = stateMap.get(readerName); 116 if ((readerState != null) && readerState.isRemoval()) { 117 list.add(terminal); 118 } 119 break; 120 default: 121 throw new CardException("Unknown state: " + state); 122 } 123 } 124 return Collections.unmodifiableList(list); 125 } catch (PCSCException e) { 126 throw new CardException("list() failed", e); 127 } 128 } 129 130 private static class ReaderState { 131 private int current, previous; ReaderState()132 ReaderState() { 133 current = SCARD_STATE_UNAWARE; 134 previous = SCARD_STATE_UNAWARE; 135 } get()136 int get() { 137 return current; 138 } update(int newState)139 void update(int newState) { 140 previous = current; 141 current = newState; 142 } isInsertion()143 boolean isInsertion() { 144 return !present(previous) && present(current); 145 } isRemoval()146 boolean isRemoval() { 147 return present(previous) && !present(current); 148 } present(int state)149 static boolean present(int state) { 150 return (state & SCARD_STATE_PRESENT) != 0; 151 } 152 } 153 waitForChange(long timeout)154 public synchronized boolean waitForChange(long timeout) throws CardException { 155 if (timeout < 0) { 156 throw new IllegalArgumentException 157 ("Timeout must not be negative: " + timeout); 158 } 159 if (stateMap == null) { 160 // We need to initialize the state database. 161 // Do that with a recursive call, which will return immediately 162 // because we pass SCARD_STATE_UNAWARE. 163 // After that, proceed with the real call. 164 stateMap = new HashMap<String,ReaderState>(); 165 waitForChange(0); 166 } 167 if (timeout == 0) { 168 timeout = TIMEOUT_INFINITE; 169 } 170 try { 171 String[] readerNames = SCardListReaders(contextId); 172 int n = readerNames.length; 173 if (n == 0) { 174 throw new IllegalStateException("No terminals available"); 175 } 176 int[] status = new int[n]; 177 ReaderState[] readerStates = new ReaderState[n]; 178 for (int i = 0; i < readerNames.length; i++) { 179 String name = readerNames[i]; 180 ReaderState state = stateMap.get(name); 181 if (state == null) { 182 state = new ReaderState(); 183 } 184 readerStates[i] = state; 185 status[i] = state.get(); 186 } 187 status = SCardGetStatusChange(contextId, timeout, status, readerNames); 188 stateMap.clear(); // remove any readers that are no longer available 189 for (int i = 0; i < n; i++) { 190 ReaderState state = readerStates[i]; 191 state.update(status[i]); 192 stateMap.put(readerNames[i], state); 193 } 194 return true; 195 } catch (PCSCException e) { 196 if (e.code == SCARD_E_TIMEOUT) { 197 return false; 198 } else { 199 throw new CardException("waitForChange() failed", e); 200 } 201 } 202 } 203 waitForCards(List<? extends CardTerminal> terminals, long timeout, boolean wantPresent)204 static List<CardTerminal> waitForCards(List<? extends CardTerminal> terminals, 205 long timeout, boolean wantPresent) throws CardException { 206 // the argument sanity checks are performed in 207 // javax.smartcardio.TerminalFactory or TerminalImpl 208 209 long thisTimeout; 210 if (timeout == 0) { 211 timeout = TIMEOUT_INFINITE; 212 thisTimeout = TIMEOUT_INFINITE; 213 } else { 214 // if timeout is not infinite, do the initial call that retrieves 215 // the status with a 0 timeout. Otherwise, we might get incorrect 216 // timeout exceptions (seen on Solaris with PC/SC shim) 217 thisTimeout = 0; 218 } 219 220 String[] names = new String[terminals.size()]; 221 int i = 0; 222 for (CardTerminal terminal : terminals) { 223 if (terminal instanceof TerminalImpl == false) { 224 throw new IllegalArgumentException 225 ("Invalid terminal type: " + terminal.getClass().getName()); 226 } 227 TerminalImpl impl = (TerminalImpl)terminal; 228 names[i++] = impl.name; 229 } 230 231 int[] status = new int[names.length]; 232 Arrays.fill(status, SCARD_STATE_UNAWARE); 233 234 try { 235 while (true) { 236 // note that we pass "timeout" on each native PC/SC call 237 // that means that if we end up making multiple (more than 2) 238 // calls, we might wait too long. 239 // for now assume that is unlikely and not a problem. 240 status = SCardGetStatusChange(contextId, thisTimeout, status, names); 241 thisTimeout = timeout; 242 243 List<CardTerminal> results = null; 244 for (i = 0; i < names.length; i++) { 245 boolean nowPresent = (status[i] & SCARD_STATE_PRESENT) != 0; 246 if (nowPresent == wantPresent) { 247 if (results == null) { 248 results = new ArrayList<CardTerminal>(); 249 } 250 results.add(implGetTerminal(names[i])); 251 } 252 } 253 254 if (results != null) { 255 return Collections.unmodifiableList(results); 256 } 257 } 258 } catch (PCSCException e) { 259 if (e.code == SCARD_E_TIMEOUT) { 260 return Collections.emptyList(); 261 } else { 262 throw new CardException("waitForCard() failed", e); 263 } 264 } 265 } 266 267 } 268