blob: bb3671bcc10dff98bd097cda7efbee4f38910025 [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 2005-2006 Sun Microsystems, Inc. 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. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25
26package sun.security.smartcardio;
27
28import java.util.*;
29import java.lang.ref.*;
30
31import javax.smartcardio.*;
32import static javax.smartcardio.CardTerminals.State.*;
33
34import static sun.security.smartcardio.PCSC.*;
35
36/**
37 * TerminalFactorySpi implementation class.
38 *
39 * @since 1.6
40 * @author Andreas Sterbenz
41 */
42final 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
50 PCSCTerminals() {
51 // empty
52 }
53
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
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
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;
132 ReaderState() {
133 current = SCARD_STATE_UNAWARE;
134 previous = SCARD_STATE_UNAWARE;
135 }
136 int get() {
137 return current;
138 }
139 void update(int newState) {
140 previous = current;
141 current = newState;
142 }
143 boolean isInsertion() {
144 return !present(previous) && present(current);
145 }
146 boolean isRemoval() {
147 return present(previous) && !present(current);
148 }
149 static boolean present(int state) {
150 return (state & SCARD_STATE_PRESENT) != 0;
151 }
152 }
153
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
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}