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