1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25 package com.intel.bluetooth;
26
27 import java.io.DataInputStream;
28 import java.io.DataOutputStream;
29 import java.io.IOException;
30 import java.io.InputStream;
31 import java.io.OutputStream;
32 import java.util.Enumeration;
33 import java.util.Hashtable;
34
35 import javax.bluetooth.BluetoothConnectionException;
36 import javax.bluetooth.L2CAPConnection;
37 import javax.bluetooth.UUID;
38 import javax.microedition.io.Connection;
39 import javax.microedition.io.ConnectionNotFoundException;
40 import javax.microedition.io.Connector;
41 import javax.microedition.io.InputConnection;
42 import javax.microedition.io.OutputConnection;
43
44 import com.intel.bluetooth.gcf.socket.ServerSocketConnection;
45 import com.intel.bluetooth.gcf.socket.SocketConnection;
46 import com.intel.bluetooth.obex.OBEXClientSessionImpl;
47 import com.intel.bluetooth.obex.OBEXConnectionParams;
48 import com.intel.bluetooth.obex.OBEXSessionNotifierImpl;
49
50
51
52
53
54
55
56
57
58
59 public abstract class MicroeditionConnector {
60
61
62
63
64 public static final int READ = Connector.READ;
65
66
67
68
69
70 public static final int WRITE = Connector.WRITE;
71
72
73
74
75
76 public static final int READ_WRITE = Connector.READ_WRITE;
77
78 private static Hashtable
79
80 private static Hashtable
81
82 private static Hashtable
83
84 private static Hashtable
85
86 private static Hashtable
87
88 private static final String AUTHENTICATE = "authenticate";
89
90 private static final String AUTHORIZE = "authorize";
91
92 private static final String ENCRYPT = "encrypt";
93
94 private static final String MASTER = "master";
95
96 private static final String NAME = "name";
97
98 private static final String RECEIVE_MTU = "receivemtu";
99
100 private static final String TRANSMIT_MTU = "transmitmtu";
101
102 private static final String EXT_BLUECOVE_L2CAP_PSM = "bluecovepsm";
103
104 static {
105
106 cliParams.put(AUTHENTICATE, AUTHENTICATE);
107 cliParams.put(ENCRYPT, ENCRYPT);
108 cliParams.put(MASTER, MASTER);
109
110
111 copyAll(srvParams, cliParams);
112 srvParams.put(AUTHORIZE, AUTHORIZE);
113 srvParams.put(NAME, NAME);
114
115 copyAll(cliParamsL2CAP, cliParams);
116
117 cliParamsL2CAP.put(RECEIVE_MTU, RECEIVE_MTU);
118 cliParamsL2CAP.put(TRANSMIT_MTU, TRANSMIT_MTU);
119
120 copyAll(srvParamsL2CAP, cliParamsL2CAP);
121 srvParamsL2CAP.put(AUTHORIZE, AUTHORIZE);
122 srvParamsL2CAP.put(NAME, NAME);
123 srvParamsL2CAP.put(EXT_BLUECOVE_L2CAP_PSM, EXT_BLUECOVE_L2CAP_PSM);
124
125
126
127
128 suportScheme.put(BluetoothConsts.PROTOCOL_SCHEME_RFCOMM, Boolean.TRUE);
129 suportScheme.put(BluetoothConsts.PROTOCOL_SCHEME_BT_OBEX, Boolean.TRUE);
130 suportScheme.put(BluetoothConsts.PROTOCOL_SCHEME_TCP_OBEX, Boolean.TRUE);
131 suportScheme.put(BluetoothConsts.PROTOCOL_SCHEME_L2CAP, Boolean.TRUE);
132 suportScheme.put("socket", Boolean.TRUE);
133 }
134
135 private MicroeditionConnector() {
136
137 }
138
139 static void copyAll(Hashtable dest, Hashtable src) {
140 for (Enumeration en = src.keys(); en.hasMoreElements();) {
141 Object key = en.nextElement();
142 dest.put(key, src.get(key));
143 }
144 }
145
146 static String validParamName(Hashtable map, String paramName) {
147 String validName = (String) map.get(paramName.toLowerCase());
148 if (validName != null) {
149 return validName;
150 }
151 return null;
152 }
153
154
155
156
157
158
159
160
161 public static Connection open(String name) throws IOException {
162 return openImpl(name, READ_WRITE, false, true);
163 }
164
165 private static Connection openImpl(String name, int mode, boolean timeouts, boolean allowServer) throws IOException {
166
167 DebugLog.debug("connecting", name);
168
169
170
171
172
173 String host = null;
174 String portORuuid = null;
175
176 Hashtable values = new Hashtable();
177
178
179 int schemeEnd = name.indexOf("://");
180 if (schemeEnd == -1) {
181 throw new ConnectionNotFoundException(name);
182 }
183 String scheme = name.substring(0, schemeEnd);
184 if (!suportScheme.containsKey(scheme)) {
185 throw new ConnectionNotFoundException(scheme);
186 }
187 boolean schemeBluetooth = (scheme.equals(BluetoothConsts.PROTOCOL_SCHEME_RFCOMM))
188 || (scheme.equals(BluetoothConsts.PROTOCOL_SCHEME_BT_OBEX) || (scheme
189 .equals(BluetoothConsts.PROTOCOL_SCHEME_L2CAP)));
190 boolean isL2CAP = scheme.equals(BluetoothConsts.PROTOCOL_SCHEME_L2CAP);
191 boolean isTCPOBEX = scheme.equals(BluetoothConsts.PROTOCOL_SCHEME_TCP_OBEX);
192
193 BluetoothStack bluetoothStack = null;
194
195 if (schemeBluetooth) {
196 bluetoothStack = BlueCoveImpl.instance().getBluetoothStack();
197 }
198
199 boolean isServer;
200
201 int hostEnd = name.indexOf(':', scheme.length() + 3);
202
203 if (hostEnd > -1) {
204 host = name.substring(scheme.length() + 3, hostEnd);
205 isServer = host.equals("localhost");
206
207 Hashtable params;
208 if (isTCPOBEX) {
209 params = new Hashtable();
210 isServer = (host.length() == 0);
211 } else if (isL2CAP) {
212 if (isServer) {
213 params = srvParamsL2CAP;
214 } else {
215 params = cliParamsL2CAP;
216 }
217 } else {
218 if (isServer) {
219 params = srvParams;
220 } else {
221 params = cliParams;
222 }
223 }
224
225 String paramsStr = name.substring(hostEnd + 1);
226 UtilsStringTokenizer tok = new UtilsStringTokenizer(paramsStr, ";");
227 if (tok.hasMoreTokens()) {
228 portORuuid = tok.nextToken();
229 } else {
230 portORuuid = paramsStr;
231 }
232 while (tok.hasMoreTokens()) {
233 String t = tok.nextToken();
234 int equals = t.indexOf('=');
235 if (equals > -1) {
236 String param = t.substring(0, equals);
237 String value = t.substring(equals + 1);
238 String validName = validParamName(params, param);
239 if (validName != null) {
240 String hasValue = (String) values.get(validName);
241 if ((hasValue != null) && (!hasValue.equals(value))) {
242 throw new IllegalArgumentException("duplicate param [" + param + "] value [" + value + "]");
243 }
244 values.put(validName, value);
245 } else {
246 throw new IllegalArgumentException("invalid param [" + param + "] value [" + value + "]");
247 }
248 } else {
249 throw new IllegalArgumentException("invalid param [" + t + "]");
250 }
251 }
252 } else if (isTCPOBEX) {
253 host = name.substring(scheme.length() + 3);
254 isServer = (host.length() == 0);
255 } else {
256 throw new IllegalArgumentException(name.substring(scheme.length() + 3));
257 }
258
259 if (isTCPOBEX) {
260 if ((portORuuid == null) || (portORuuid.length() == 0)) {
261 portORuuid = String.valueOf(BluetoothConsts.TCP_OBEX_DEFAULT_PORT);
262 }
263
264
265
266
267
268
269
270
271
272
273
274
275 }
276
277 if (host == null || portORuuid == null) {
278 throw new IllegalArgumentException();
279 }
280
281 BluetoothConnectionNotifierParams notifierParams = null;
282
283 BluetoothConnectionParams connectionParams = null;
284
285 int channel = 0;
286 if (isServer) {
287 if (!allowServer) {
288 throw new IllegalArgumentException("Can't use server connection URL");
289 }
290 if (values.get(NAME) == null) {
291 values.put(NAME, "BlueCove");
292 } else if (schemeBluetooth) {
293 validateBluetoothServiceName((String) values.get(NAME));
294 }
295 if (schemeBluetooth) {
296 notifierParams = new BluetoothConnectionNotifierParams(new UUID(portORuuid, false), paramBoolean(
297 values, AUTHENTICATE), paramBoolean(values, ENCRYPT), paramBoolean(values, AUTHORIZE),
298 (String) values.get(NAME), paramBoolean(values, MASTER));
299 notifierParams.timeouts = timeouts;
300 if (notifierParams.encrypt && (!notifierParams.authenticate)) {
301 if (values.get(AUTHENTICATE) == null) {
302 notifierParams.authenticate = true;
303 } else {
304 throw new BluetoothConnectionException(BluetoothConnectionException.UNACCEPTABLE_PARAMS,
305 "encryption requires authentication");
306 }
307 }
308 if (notifierParams.authorize && (!notifierParams.authenticate)) {
309 if (values.get(AUTHENTICATE) == null) {
310 notifierParams.authenticate = true;
311 } else {
312 throw new BluetoothConnectionException(BluetoothConnectionException.UNACCEPTABLE_PARAMS,
313 "authorization requires authentication");
314 }
315 }
316 if (isL2CAP) {
317 String bluecove_ext_psm = (String) values.get(EXT_BLUECOVE_L2CAP_PSM);
318 if (bluecove_ext_psm != null) {
319 int psm = Integer.parseInt(bluecove_ext_psm, 16);
320 validateL2CAPPSM(psm, bluecove_ext_psm);
321 notifierParams.bluecove_ext_psm = psm;
322 }
323 }
324 }
325 } else {
326 try {
327 channel = Integer.parseInt(portORuuid, isL2CAP ? 16 : 10);
328 } catch (NumberFormatException e) {
329 throw new IllegalArgumentException("channel " + portORuuid);
330 }
331 if (channel < 0) {
332 throw new IllegalArgumentException("channel " + portORuuid);
333 }
334 if (schemeBluetooth) {
335 if (isL2CAP) {
336 validateL2CAPPSM(channel, portORuuid);
337 } else {
338 if ((channel < BluetoothConsts.RFCOMM_CHANNEL_MIN)
339 || (channel > BluetoothConsts.RFCOMM_CHANNEL_MAX)) {
340 throw new IllegalArgumentException("RFCOMM channel " + portORuuid);
341 }
342 }
343
344 connectionParams = new BluetoothConnectionParams(RemoteDeviceHelper.getAddress(host), channel,
345 paramBoolean(values, AUTHENTICATE), paramBoolean(values, ENCRYPT));
346 connectionParams.timeouts = timeouts;
347 if (connectionParams.encrypt && (!connectionParams.authenticate)) {
348 if (values.get(AUTHENTICATE) == null) {
349 connectionParams.authenticate = true;
350 } else {
351 throw new BluetoothConnectionException(BluetoothConnectionException.UNACCEPTABLE_PARAMS,
352 "encryption requires authentication");
353 }
354 }
355 connectionParams.timeout = BlueCoveImpl.getConfigProperty(
356 BlueCoveConfigProperties.PROPERTY_CONNECT_TIMEOUT,
357 BluetoothConnectionParams.DEFAULT_CONNECT_TIMEOUT);
358 }
359 }
360 OBEXConnectionParams obexConnectionParams = null;
361
362 if (scheme.equals(BluetoothConsts.PROTOCOL_SCHEME_TCP_OBEX)
363 || scheme.equals(BluetoothConsts.PROTOCOL_SCHEME_BT_OBEX)) {
364 obexConnectionParams = new OBEXConnectionParams();
365 obexConnectionParams.timeouts = timeouts;
366 obexConnectionParams.timeout = BlueCoveImpl.getConfigProperty(
367 BlueCoveConfigProperties.PROPERTY_OBEX_TIMEOUT, OBEXConnectionParams.DEFAULT_TIMEOUT);
368 obexConnectionParams.mtu = BlueCoveImpl.getConfigProperty(BlueCoveConfigProperties.PROPERTY_OBEX_MTU,
369 OBEXConnectionParams.OBEX_DEFAULT_MTU);
370 }
371
372
373
374
375 if (scheme.equals(BluetoothConsts.PROTOCOL_SCHEME_RFCOMM)) {
376 if (isServer) {
377 return new BluetoothRFCommConnectionNotifier(bluetoothStack, notifierParams);
378 } else {
379 return new BluetoothRFCommClientConnection(bluetoothStack, connectionParams);
380 }
381 } else if (scheme.equals(BluetoothConsts.PROTOCOL_SCHEME_BT_OBEX)) {
382 if (isServer) {
383 notifierParams.obex = true;
384 return new OBEXSessionNotifierImpl(
385 new BluetoothRFCommConnectionNotifier(bluetoothStack, notifierParams), obexConnectionParams);
386 } else {
387 return new OBEXClientSessionImpl(new BluetoothRFCommClientConnection(bluetoothStack, connectionParams),
388 obexConnectionParams);
389 }
390 } else if (scheme.equals(BluetoothConsts.PROTOCOL_SCHEME_L2CAP)) {
391 if (isServer) {
392 return new BluetoothL2CAPConnectionNotifier(bluetoothStack, notifierParams, paramL2CAPMTU(values,
393 RECEIVE_MTU), paramL2CAPMTU(values, TRANSMIT_MTU));
394 } else {
395 return new BluetoothL2CAPClientConnection(bluetoothStack, connectionParams, paramL2CAPMTU(values,
396 RECEIVE_MTU), paramL2CAPMTU(values, TRANSMIT_MTU));
397 }
398 } else if (scheme.equals(BluetoothConsts.PROTOCOL_SCHEME_TCP_OBEX)) {
399 if (isServer) {
400 try {
401 channel = Integer.parseInt(portORuuid);
402 } catch (NumberFormatException e) {
403 throw new IllegalArgumentException("port " + portORuuid);
404 }
405 return new OBEXSessionNotifierImpl(new ServerSocketConnection(channel), obexConnectionParams);
406 } else {
407 return new OBEXClientSessionImpl(new SocketConnection(host, channel), obexConnectionParams);
408 }
409 } else if (scheme.equals("socket")) {
410 if (isServer) {
411 try {
412 channel = Integer.parseInt(portORuuid);
413 } catch (NumberFormatException e) {
414 throw new IllegalArgumentException("port " + portORuuid);
415 }
416 return new ServerSocketConnection(channel);
417 } else {
418 return new SocketConnection(host, channel);
419 }
420 } else {
421 throw new ConnectionNotFoundException("scheme [" + scheme + "]");
422 }
423 }
424
425 private static void validateL2CAPPSM(int channel, String channelAsString) throws IllegalArgumentException {
426
427
428 if ((channel < BluetoothConsts.L2CAP_PSM_MIN) || (channel > BluetoothConsts.L2CAP_PSM_MAX)) {
429
430 throw new IllegalArgumentException("PCM " + channelAsString);
431 }
432 if ((channel < BluetoothConsts.L2CAP_PSM_MIN_JSR_82)
433 && (!BlueCoveImpl.getConfigProperty(BlueCoveConfigProperties.PROPERTY_JSR_82_PSM_MINIMUM_OFF, false))) {
434 throw new IllegalArgumentException("PCM values restricted by JAR82 to minimum "
435 + BluetoothConsts.L2CAP_PSM_MIN_JSR_82);
436 }
437
438
439 if ((channel & 0x100) != 0) {
440 throw new IllegalArgumentException("9th bit set in PCM " + channelAsString);
441 }
442
443 byte lsByte = (byte) (0xFF & channel);
444 if ((lsByte % 2) == 0) {
445 throw new IllegalArgumentException("PSM value " + channelAsString + " least significant byte must be odd");
446 }
447 byte msByte = (byte) ((0xFF00 & channel) >> 8);
448 if ((msByte % 2) == 1) {
449 throw new IllegalArgumentException("PSM value " + channelAsString + " most significant byte must be even");
450 }
451 }
452
453 private static void validateBluetoothServiceName(String serviceName) {
454 if (serviceName.length() == 0) {
455 throw new IllegalArgumentException("zero length service name");
456 }
457 final String allowNameCharactes = " -_";
458 for (int i = 0; i < serviceName.length(); i++) {
459 char c = serviceName.charAt(i);
460 if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9')
461 || allowNameCharactes.indexOf(c) != -1) {
462 continue;
463 }
464 throw new IllegalArgumentException("Illegal character '" + c + "' in service name");
465 }
466 }
467
468 private static boolean paramBoolean(Hashtable values, String name) {
469 String v = (String) values.get(name);
470 if (v == null) {
471 return false;
472 } else if ("true".equals(v)) {
473 return true;
474 } else if ("false".equals(v)) {
475 return false;
476 } else {
477 throw new IllegalArgumentException("invalid param value " + name + "=" + v);
478 }
479 }
480
481 private static int paramL2CAPMTU(Hashtable values, String name) {
482 String v = (String) values.get(name);
483 if (v == null) {
484 if (name.equals(TRANSMIT_MTU)) {
485
486 return -1;
487 } else {
488 return L2CAPConnection.DEFAULT_MTU;
489 }
490 }
491 try {
492 int mtu = Integer.parseInt(v);
493 if (mtu >= L2CAPConnection.MINIMUM_MTU) {
494 return mtu;
495 }
496 if ((mtu > 0) && (mtu < L2CAPConnection.MINIMUM_MTU) && (name.equals(TRANSMIT_MTU))) {
497 return L2CAPConnection.MINIMUM_MTU;
498 }
499 } catch (NumberFormatException e) {
500 throw new IllegalArgumentException("invalid MTU value " + v);
501 }
502 throw new IllegalArgumentException("invalid MTU param value " + name + "=" + v);
503 }
504
505
506
507
508
509
510
511
512 public static Connection open(String name, int mode) throws IOException {
513 return openImpl(name, mode, false, true);
514 }
515
516
517
518
519
520
521
522
523
524 public static Connection open(String name, int mode, boolean timeouts) throws IOException {
525 return openImpl(name, mode, timeouts, true);
526 }
527
528
529
530
531
532
533
534
535 public static DataInputStream openDataInputStream(String name) throws IOException {
536 return new DataInputStream(openInputStream(name));
537 }
538
539
540
541
542
543
544
545
546 public static DataOutputStream openDataOutputStream(String name) throws IOException {
547 return new DataOutputStream(openOutputStream(name));
548 }
549
550
551
552
553
554
555
556
557 public static InputStream openInputStream(String name) throws IOException {
558 InputConnection con = ((InputConnection) openImpl(name, READ, false, false));
559 try {
560 return con.openInputStream();
561 } finally {
562 con.close();
563 }
564 }
565
566
567
568
569
570
571
572
573 public static OutputStream openOutputStream(String name) throws IOException {
574 OutputConnection con = ((OutputConnection) openImpl(name, WRITE, false, false));
575 try {
576 return con.openOutputStream();
577 } finally {
578 con.close();
579 }
580 }
581
582 }