PCSC4Java
0.2
Library PCSC for Java language.
|
00001 /* 00002 * Copyright (c) 2005, 2006, Oracle and/or its affiliates. All rights reserved. 00003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 00004 * 00005 * This code is free software; you can redistribute it and/or modify it 00006 * under the terms of the GNU General Public License version 2 only, as 00007 * published by the Free Software Foundation. Oracle designates this 00008 * particular file as subject to the "Classpath" exception as provided 00009 * by Oracle in the LICENSE file that accompanied this code. 00010 * 00011 * This code is distributed in the hope that it will be useful, but WITHOUT 00012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 00013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 00014 * version 2 for more details (a copy is included in the LICENSE file that 00015 * accompanied this code). 00016 * 00017 * You should have received a copy of the GNU General Public License version 00018 * 2 along with this work; if not, write to the Free Software Foundation, 00019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 00020 * 00021 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 00022 * or visit www.oracle.com if you need additional information or have any 00023 * questions. 00024 */ 00025 00026 package fr.redbilled.security.pcscforjava; 00027 00028 import java.util.*; 00029 import java.lang.ref.*; 00030 00031 import fr.redbilled.pcscforjava.*; 00032 import static fr.redbilled.pcscforjava.CardTerminals.State.*; 00033 00034 import static fr.redbilled.security.pcscforjava.PCSC.*; 00035 import static fr.redbilled.security.pcscforjava.PCSCDefines.*; 00036 import java.util.logging.Level; 00037 import java.util.logging.Logger; 00038 00045 final class PCSCTerminals extends CardTerminals { 00046 00047 // SCARDCONTEXT, currently shared between all threads/terminals 00048 private static long contextId; 00049 00050 // terminal state used by waitForCard() 00051 private Map<String,ReaderState> stateMap; 00052 00053 // Plug and play thread 00054 static private PCSCPnPThread m_cardTerminalsThread = null; 00055 00056 static private Thread m_thread; 00057 00058 private State m_state; 00059 00060 private List<CardTerminal> list; 00061 00062 private Object m_currentList; 00063 00064 PCSCTerminals() { 00065 // empty 00066 } 00067 00068 static synchronized void initContext() throws PCSCException { 00069 if (contextId == 0) { 00070 contextId = SCardEstablishContext(SCARD_SCOPE_USER); 00071 } 00072 } 00073 00074 static synchronized void releaseContext() throws PCSCException { 00075 SCardReleaseContext(contextId); 00076 contextId = 0; 00077 } 00078 00079 private static final Map<String,Reference<TerminalImpl>> terminals 00080 = new HashMap<String,Reference<TerminalImpl>>(); 00081 00082 private static synchronized TerminalImpl implGetTerminal(String name) { 00083 Reference<TerminalImpl> ref = terminals.get(name); 00084 TerminalImpl terminal = (ref != null) ? ref.get() : null; 00085 if (terminal != null) { 00086 terminal.setContextId(contextId); 00087 return terminal; 00088 } 00089 terminal = new TerminalImpl(contextId, name); 00090 terminals.put(name, new WeakReference<TerminalImpl>(terminal)); 00091 00092 return terminal; 00093 } 00094 00095 public synchronized List<CardTerminal> list(State state) throws CardException { 00096 if (state == null) { 00097 throw new NullPointerException(); 00098 } 00099 00100 try 00101 { 00102 // Be sure that the context is well established 00103 initContext(); 00104 } catch (PCSCException ex) 00105 { 00106 this.startPnPThread(); 00107 this.m_currentList = Collections.emptyList(); 00108 return (List<CardTerminal>) this.m_currentList; 00109 } 00110 00111 if(contextId == 0) 00112 { 00113 this.startPnPThread(); 00114 this.m_currentList = Collections.emptyList(); 00115 return (List<CardTerminal>) this.m_currentList; 00116 } 00117 00118 this.m_state = state; 00119 00120 try { 00121 String[] readerNames = SCardListReaders(contextId); 00122 list = new ArrayList<CardTerminal>(readerNames.length); 00123 if (stateMap == null) { 00124 // If waitForChange() has never been called, treat event 00125 // queries as status queries. 00126 if (state == CARD_INSERTION) { 00127 state = CARD_PRESENT; 00128 } else if (state == CARD_REMOVAL) { 00129 state = CARD_ABSENT; 00130 } 00131 } 00132 00133 for (String readerName : readerNames) { 00134 CardTerminal terminal = implGetTerminal(readerName); 00135 ReaderState readerState; 00136 switch (state) { 00137 case ALL: 00138 list.add(terminal); 00139 // Just to be sure that the card terminal is real 00140 try { terminal.isCardPresent(); } 00141 catch(Exception ex){ list.remove(terminal); } 00142 break; 00143 case CARD_PRESENT: 00144 if (terminal.isCardPresent()) { 00145 list.add(terminal); 00146 } 00147 break; 00148 case CARD_ABSENT: 00149 if (terminal.isCardPresent() == false) { 00150 list.add(terminal); 00151 } 00152 break; 00153 case CARD_INSERTION: 00154 readerState = stateMap.get(readerName); 00155 if ((readerState != null) && readerState.isInsertion()) { 00156 list.add(terminal); 00157 } 00158 break; 00159 case CARD_REMOVAL: 00160 readerState = stateMap.get(readerName); 00161 if ((readerState != null) && readerState.isRemoval()) { 00162 list.add(terminal); 00163 } 00164 break; 00165 default: 00166 throw new CardException("Unknown state: " + state); 00167 } 00168 } 00169 00170 this.startPnPThread(); 00171 this.m_currentList = Collections.unmodifiableList(list); 00172 } catch (PCSCException e) { 00173 if(e.getMessage().contains("SCARD_E_NO_READERS_AVAILABLE")) 00174 { 00175 this.startPnPThread(); 00176 this.m_currentList = Collections.emptyList(); 00177 } 00178 else 00179 throw new CardException("list() failed", e); 00180 } 00181 00182 return (List<CardTerminal>) this.m_currentList; 00183 } 00184 00185 @Override 00186 public boolean isValidContext() { 00187 try 00188 { 00189 SCardIsValidContext(contextId); 00190 return true; 00191 } 00192 catch (PCSCException ex) 00193 { 00194 return false; 00195 } 00196 } 00197 00198 @Override 00199 public void closeContext() throws CardException { 00200 00201 if(m_cardTerminalsThread != null) 00202 { 00203 m_cardTerminalsThread.stop(); 00204 m_cardTerminalsThread = null; 00205 } 00206 00207 try 00208 { 00209 SCardReleaseContext(contextId); 00210 contextId = 0; 00211 } 00212 catch(PCSCException ex) 00213 { 00214 throw new CardException(ex.toString()); 00215 } 00216 } 00217 00218 private native boolean SCardIsPlugAndPlaySupported(long lContextId) 00219 throws PCSCException; 00220 00221 public boolean isPlugAndPlaySupported() throws CardException 00222 { 00223 try 00224 { 00225 if(contextId != 0) 00226 return SCardIsPlugAndPlaySupported(contextId); 00227 } 00228 catch(PCSCException ex) 00229 { 00230 throw new CardException(ex.toString()); 00231 } 00232 00233 return false; 00234 } 00235 00236 private void startPnPThread() 00237 { 00238 if(m_cardTerminalsThread == null) 00239 { 00240 m_cardTerminalsThread = PCSCPnPThread.getInstance(this); 00241 m_thread = m_cardTerminalsThread.start(); 00242 m_cardTerminalsThread.addObserver(this); 00243 } 00244 else 00245 { 00246 //m_cardTerminalsThread.addObserver(this); 00247 } 00248 } 00249 00250 public void updateCardTerminalsListByEvent() throws CardException 00251 { 00252 if (this.m_state == null) 00253 this.m_state = ALL; 00254 00255 try { 00256 // Be sure that the context is well established 00257 initContext(); 00258 } catch (PCSCException ex) {return;} 00259 00260 try { 00261 String[] readerNames = SCardListReaders(contextId); 00262 00263 List<CardTerminal> _tmpList = 00264 new ArrayList<CardTerminal>(readerNames.length); 00265 00266 for (String readerName : readerNames) { 00267 CardTerminal terminal = implGetTerminal(readerName); 00268 ReaderState readerState; 00269 switch (this.m_state) { 00270 case ALL: 00271 _tmpList.add(terminal); 00272 break; 00273 case CARD_PRESENT: 00274 if (terminal.isCardPresent()) { 00275 _tmpList.add(terminal); 00276 } 00277 break; 00278 case CARD_ABSENT: 00279 if (terminal.isCardPresent() == false) { 00280 _tmpList.add(terminal); 00281 } 00282 break; 00283 case CARD_INSERTION: 00284 readerState = stateMap.get(readerName); 00285 if ((readerState != null) && readerState.isInsertion()) { 00286 _tmpList.add(terminal); 00287 } 00288 break; 00289 case CARD_REMOVAL: 00290 readerState = stateMap.get(readerName); 00291 if ((readerState != null) && readerState.isRemoval()) { 00292 _tmpList.add(terminal); 00293 } 00294 break; 00295 default: 00296 throw new CardException("Unknown state: " + this.m_state); 00297 } 00298 } 00299 00300 this.m_currentList = _tmpList; 00301 00302 } catch (PCSCException e) { 00303 if(e.getMessage().contains("SCARD_E_NO_READERS_AVAILABLE")) 00304 { 00305 this.m_currentList = Collections.emptyList(); 00306 } 00307 else 00308 throw new CardException("update() failed", e); 00309 } 00310 00311 List _tmp = TerminalFactory.getPnPCallbacks(); 00312 00313 if(_tmp != null) 00314 { 00315 for(int _i = 0; _i < _tmp.size(); _i++) 00316 ((CardTerminalsEvent)_tmp.get(_i)). 00317 updateCardTerminalsListByEvent((List<CardTerminal>) 00318 this.m_currentList); 00319 } 00320 } 00321 00322 private static class ReaderState { 00323 private int current, previous; 00324 ReaderState() { 00325 current = SCARD_STATE_UNAWARE; 00326 previous = SCARD_STATE_UNAWARE; 00327 } 00328 int get() { 00329 return current; 00330 } 00331 void update(int newState) { 00332 previous = current; 00333 current = newState; 00334 } 00335 boolean isInsertion() { 00336 return !present(previous) && present(current); 00337 } 00338 boolean isRemoval() { 00339 return present(previous) && !present(current); 00340 } 00341 static boolean present(int state) { 00342 return (state & SCARD_STATE_PRESENT) != 0; 00343 } 00344 } 00345 00346 public synchronized boolean waitForChange(long timeout) throws CardException { 00347 if (timeout < 0) { 00348 throw new IllegalArgumentException 00349 ("Timeout must not be negative: " + timeout); 00350 } 00351 if (stateMap == null) { 00352 // We need to initialize the state database. 00353 // Do that with a recursive call, which will return immediately 00354 // because we pass SCARD_STATE_UNAWARE. 00355 // After that, proceed with the real call. 00356 stateMap = new HashMap<String,ReaderState>(); 00357 waitForChange(0); 00358 } 00359 if (timeout == 0) { 00360 timeout = TIMEOUT_INFINITE; 00361 } 00362 try { 00363 String[] readerNames = SCardListReaders(contextId); 00364 int n = readerNames.length; 00365 if (n == 0) { 00366 throw new IllegalStateException("No terminals available"); 00367 } 00368 int[] status = new int[n]; 00369 ReaderState[] readerStates = new ReaderState[n]; 00370 for (int i = 0; i < readerNames.length; i++) { 00371 String name = readerNames[i]; 00372 ReaderState state = stateMap.get(name); 00373 if (state == null) { 00374 state = new ReaderState(); 00375 } 00376 readerStates[i] = state; 00377 status[i] = state.get(); 00378 } 00379 status = SCardGetStatusChange(contextId, timeout, status, 00380 readerNames); 00381 stateMap.clear(); // remove any readers that are no longer available 00382 for (int i = 0; i < n; i++) { 00383 ReaderState state = readerStates[i]; 00384 if(status != null) 00385 state.update(status[i]); 00386 stateMap.put(readerNames[i], state); 00387 } 00388 return true; 00389 } catch (PCSCException e) { 00390 if (e.code == SCARD_E_TIMEOUT) { 00391 return false; 00392 } else { 00393 throw new CardException("waitForChange() failed", e); 00394 } 00395 } 00396 } 00397 00398 static List<CardTerminal> waitForCards(List<? extends CardTerminal> terminals, 00399 long timeout, boolean wantPresent) throws CardException { 00400 // the argument sanity checks are performed in 00401 // javax.smartcardio.TerminalFactory or TerminalImpl 00402 00403 long thisTimeout; 00404 if (timeout == 0) { 00405 timeout = TIMEOUT_INFINITE; 00406 thisTimeout = TIMEOUT_INFINITE; 00407 } else { 00408 // if timeout is not infinite, do the initial call that retrieves 00409 // the status with a 0 timeout. Otherwise, we might get incorrect 00410 // timeout exceptions (seen on Solaris with PC/SC shim) 00411 thisTimeout = 0; 00412 } 00413 00414 String[] names = new String[terminals.size()]; 00415 int i = 0; 00416 for (CardTerminal terminal : terminals) { 00417 if (terminal instanceof TerminalImpl == false) { 00418 throw new IllegalArgumentException 00419 ("Invalid terminal type: " + terminal.getClass().getName()); 00420 } 00421 TerminalImpl impl = (TerminalImpl)terminal; 00422 names[i++] = impl.name; 00423 } 00424 00425 int[] status = new int[names.length]; 00426 Arrays.fill(status, SCARD_STATE_UNAWARE); 00427 00428 try { 00429 while (true) { 00430 // note that we pass "timeout" on each native PC/SC call 00431 // that means that if we end up making multiple (more than 2) 00432 // calls, we might wait too long. 00433 // for now assume that is unlikely and not a problem. 00434 status = SCardGetStatusChange(contextId, thisTimeout, 00435 status, names); 00436 thisTimeout = timeout; 00437 00438 List<CardTerminal> results = null; 00439 for (i = 0; i < names.length; i++) { 00440 boolean nowPresent = (status[i] & SCARD_STATE_PRESENT) != 0; 00441 if (nowPresent == wantPresent) { 00442 if (results == null) { 00443 results = new ArrayList<CardTerminal>(); 00444 } 00445 results.add(implGetTerminal(names[i])); 00446 } 00447 } 00448 00449 if (results != null) { 00450 return Collections.unmodifiableList(results); 00451 } 00452 } 00453 } catch (PCSCException e) { 00454 if (e.code == SCARD_E_TIMEOUT) { 00455 return Collections.emptyList(); 00456 } else { 00457 throw new CardException("waitForCard() failed", e); 00458 } 00459 } 00460 } 00461 00462 }