001: /*
002: *
003: *
004: * Copyright 1990-2007 Sun Microsystems, Inc. All Rights Reserved.
005: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License version
009: * 2 only, as published by the Free Software Foundation.
010: *
011: * This program is distributed in the hope that it will be useful, but
012: * WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * General Public License version 2 for more details (a copy is
015: * included at /legal/license.txt).
016: *
017: * You should have received a copy of the GNU General Public License
018: * version 2 along with this work; if not, write to the Free Software
019: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA
021: *
022: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
023: * Clara, CA 95054 or visit www.sun.com if you need additional
024: * information or have any questions.
025: */
026: package com.sun.kvem.jsr082.bluetooth;
027:
028: import javax.bluetooth.DataElement;
029: import javax.bluetooth.L2CAPConnection;
030: import javax.bluetooth.L2CAPConnectionNotifier;
031: import javax.bluetooth.ServiceRecord;
032: import javax.bluetooth.UUID;
033: import java.util.Enumeration;
034: import java.util.Vector;
035: import java.io.IOException;
036:
037: /**
038: * Represents Servive Discovery Protocol Server.
039: */
040: public class SDPServer {
041:
042: /** Set to false in RR version - then the javac skip the code. */
043: private static final boolean DEBUG = false;
044:
045: /**
046: * notifier - a connection used by the server
047: * to wait for a client connection
048: */
049: private L2CAPConnectionNotifier conNotifier = null;
050:
051: /** Collects connections to the server. */
052: private Vector connections;
053:
054: /** Requests acceptor, it is Runnable and works in its own thread. */
055: private Acceptor acceptor;
056:
057: /** Shows if acceptor thread is running. */
058: private boolean acceptorStarted = false;
059:
060: /** SDP UUID. */
061: private static final int SDP_UUID = 0x0001;
062:
063: /** SDP_ErrorResponse PDU ID. */
064: private static final int SDP_ERROR_RESPONSE = 0x01;
065:
066: /** SDP_ServiceSearchRequest PDU ID. */
067: private static final int SDP_SERVICE_SEARCH_REQUEST = 0x02;
068:
069: /** SDP_ServiceSearchResponse PDU ID. */
070: private static final int SDP_SERVICE_SEARCH_RESPONSE = 0x03;
071:
072: /** SDP_ServiceAttributeRequest PDU ID. */
073: private static final int SDP_SERVICE_ATTRIBUTE_REQUEST = 0x04;
074:
075: /** SDP_ServiceAttributeResponse PDU ID. */
076: private static final int SDP_SERVICE_ATTRIBUTE_RESPONSE = 0x05;
077:
078: /** SDP_ServiceSearchAttributeResponse PDU ID. */
079: private static final int SDP_SERVICE_SEARCH_ATTRIBUTE_REQUEST = 0x06;
080:
081: /** SDP_ServiceSearchAttributeResponse PDU ID. */
082: private static final int SDP_SERVICE_SEARCH_ATTRIBUTE_RESPONSE = 0x07;
083:
084: /** Error code for SDP_ErrorResponse: Invalid/unsupported SDP version. */
085: private static final int SDP_INVALID_VERSION = 0x01;
086:
087: /** Error code for SDP_ErrorResponse: Invalid Service Record Handle. */
088: private static final int SDP_INVALID_SR_HANDLE = 0x02;
089:
090: /** Error code for SDP_ErrorResponse: Invalid request syntax. */
091: private static final int SDP_INVALID_SYNTAX = 0x03;
092:
093: /*
094: * Note: The following constants aren't used by emulator
095: * but can be used in real device
096: * private static final int SDP_INVALID_PDU_SIZE = 0x04;
097: * private static final int SDP_INVALID_CONTINUATION_STATE = 0x05;
098: * private static final int SDP_INSUFFICIENT_RESOURCES = 0x06;
099: */
100:
101: /**
102: * Constructs <code>SDPServer</code> instance.
103: */
104: public SDPServer() {
105: connections = new Vector();
106: acceptor = new Acceptor();
107: }
108:
109: /**
110: * Starts this SDP server if not started.
111: */
112: public synchronized void start() {
113: if (acceptorStarted) {
114: return;
115: }
116:
117: requestPSM();
118: UUID sdpUUID = new UUID(SDP_UUID);
119: try {
120: conNotifier = (L2CAPConnectionNotifier) SDP
121: .getL2CAPConnection("//localhost:" + SDP.UUID
122: + ";name=SDPServer");
123: } catch (IOException ioe) {
124: // ignore
125: }
126:
127: if (conNotifier != null) {
128: acceptorStarted = true;
129: (new Thread(acceptor)).start();
130: }
131: }
132:
133: /**
134: * Notifies native emulation code that next PSM requested
135: * is for SDP server.
136: */
137: private native void requestPSM();
138:
139: /**
140: * Stops this server closing all the connections to it.
141: */
142: synchronized void stop() {
143: try {
144: conNotifier.close();
145: } catch (IOException ioe) {
146: // ignore
147: }
148:
149: for (int i = connections.size(); i >= 0; i--) {
150: L2CAPConnection con = (L2CAPConnection) connections
151: .elementAt(i);
152:
153: try {
154: synchronized (con) {
155: con.close();
156: }
157: } catch (IOException ioe) {
158: // ignore
159: }
160: }
161: connections.removeAllElements();
162: }
163:
164: /**
165: * Retrieves next PDU from a connection and processes it.
166: *
167: * @param rw <code>DataL2CAPReaderWriter</code> instance that represents
168: * desired connection and provides RW utilities.
169: *
170: * @throws IOException if a processing error occured.
171: */
172: private void processRequest(DataL2CAPReaderWriter rw)
173: throws IOException {
174: byte requestType = rw.readByte();
175: short transactionID = rw.readShort();
176: short length = rw.readShort();
177:
178: if (requestType == SDP_SERVICE_SEARCH_REQUEST) {
179: processServiceSearch(rw, transactionID);
180: } else if (requestType == SDP_SERVICE_ATTRIBUTE_REQUEST) {
181: processServiceAttribute(rw, transactionID);
182: } else if (requestType == SDP_SERVICE_SEARCH_ATTRIBUTE_REQUEST) {
183: processServiceSearchAttribute(rw, transactionID);
184: } else {
185: writeErrorResponce(rw, transactionID, SDP_INVALID_SYNTAX,
186: "Invalid Type of Request");
187: System.err.println("WARNING: Unsupported SDP request");
188: }
189: }
190:
191: /**
192: * Retrieves SDP_ServiceSearchRequest parameters from given connection,
193: * processes the requests and sends a respond.
194: *
195: * @param rw <code>DataL2CAPReaderWriter</code> instance that represents
196: * desired connection and provides RW utilities.
197: * @param transactionID ID of transaction the request is recieved in.
198: * @throws IOException if a processing error occured.
199: */
200: private void processServiceSearch(DataL2CAPReaderWriter rw,
201: short transactionID) throws IOException {
202: DataElement uuidSet = rw.readDataElement();
203: short maximimSRCount = rw.readShort();
204:
205: // IMPL_NOTE: ContinuationState isn't used, but should on real device
206: int continuationState = rw.readByte();
207:
208: if (continuationState != 0) {
209: writeErrorResponce(rw, transactionID, SDP_INVALID_VERSION,
210: "Current implementation don't support continuation state");
211: return;
212: }
213:
214: Vector currentHandles = new Vector();
215: int[] handles = SDDB.getInstance().getHandles();
216: for (int i = 0; i < handles.length; i++) {
217: ServiceRecord sr = SDDB.getInstance().getServiceRecord(
218: handles[i]);
219:
220: if (findUUIDs((ServiceRecordImpl) sr, uuidSet)) {
221: currentHandles.addElement(new Integer(handles[i]));
222: }
223: }
224: rw.writeByte((byte) SDP_SERVICE_SEARCH_RESPONSE);
225: rw.writeShort(transactionID);
226: rw.writeShort((short) (currentHandles.size() + 5));
227:
228: // Total and current counts are the same for all the results are
229: // sent in one response PDU
230: rw.writeShort((short) currentHandles.size());
231: rw.writeShort((short) currentHandles.size());
232:
233: for (int i = 0; i < currentHandles.size(); i++) {
234: if (i > maximimSRCount) {
235: break;
236: }
237: int h = ((Integer) currentHandles.elementAt(i)).intValue();
238: rw.writeInteger(h);
239: }
240:
241: // IMPL_NOTE: ContinuationState isn't used, but should on real device
242: rw.writeByte((byte) 0x00);
243: rw.flush();
244: }
245:
246: /**
247: * Retrieves SDP_ServiceAttribute parameters from given connection,
248: * processes the requests and sends a respond.
249: *
250: * @param rw <code>DataL2CAPReaderWriter</code> instance that represents
251: * desired connection and provides RW utilities.
252: * @param transactionID ID of transaction the request is recieved in.
253: *
254: * @throws IOException if a processing error occured.
255: */
256: private void processServiceAttribute(DataL2CAPReaderWriter rw,
257: short transactionID) throws IOException {
258: int handle = rw.readInteger();
259:
260: // IMPL_NOTE: Add checking for real device
261: short maximimSize = rw.readShort();
262: DataElement attrSet = rw.readDataElement();
263:
264: // IMPL_NOTE: ContinuationState isn't used, but should on real device
265: int continuationState = rw.readByte();
266:
267: if (continuationState != 0) {
268: writeErrorResponce(rw, transactionID, SDP_INVALID_VERSION,
269: "Current implementation don't support continuation state");
270: return;
271: }
272:
273: ServiceRecord sr = SDDB.getInstance().getServiceRecord(handle);
274:
275: // if service record not found process it
276: if (sr == null) {
277: writeErrorResponce(rw, transactionID,
278: SDP_INVALID_SR_HANDLE,
279: "Servicce Record with specified ID not found");
280: return;
281: }
282:
283: DataElement attrIDValues = new DataElement(DataElement.DATSEQ);
284: Enumeration e = (Enumeration) attrSet.getValue();
285:
286: while (e.hasMoreElements()) {
287: DataElement attrID = (DataElement) e.nextElement();
288: int attr = (int) attrID.getLong();
289: DataElement attrValue = sr.getAttributeValue(attr);
290:
291: if (attrValue != null) {
292: attrIDValues.addElement(attrID);
293: attrIDValues.addElement(attrValue);
294: }
295: }
296: int length = (int) rw.getDataSize(attrIDValues);
297: rw.writeByte((byte) SDP_SERVICE_ATTRIBUTE_RESPONSE);
298: rw.writeShort(transactionID);
299: rw.writeShort((short) (length + 3));
300: rw.writeShort((short) length);
301: rw.writeDataElement(attrIDValues);
302:
303: // IMPL_NOTE: ContinuationState isn't used, but should on real device
304: rw.writeByte((byte) 0x00);
305: rw.flush();
306: }
307:
308: /**
309: * Retrieves SDP_ServiceSearchAttribute parameters from given connection,
310: * processes the requests and sends a respond.
311: *
312: * @param rw <code>DataL2CAPReaderWriter</code> instance that represents
313: * desired connection and provides RW utilities.
314: * @param transactionID ID of transaction the request is recieved in.
315: *
316: * @throws IOException if a processing error occured.
317: */
318: private void processServiceSearchAttribute(
319: DataL2CAPReaderWriter rw, short transactionID)
320: throws IOException {
321: DataElement uuidSet = rw.readDataElement();
322:
323: // IMPL_NOTE: Add checking for real device
324: short maximimSize = rw.readShort();
325: DataElement attrSet = rw.readDataElement();
326:
327: // IMPL_NOTE: ContinuationState isn't used, but should on real device
328: int continuationState = rw.readByte();
329:
330: if (continuationState != 0) {
331: writeErrorResponce(rw, transactionID, SDP_INVALID_VERSION,
332: "Current implementation don't support continuation state");
333: return;
334: }
335:
336: int[] handles = SDDB.getInstance().getHandles();
337: int handle = -1;
338:
339: for (int i = 0; i < handles.length; i++) {
340: ServiceRecord sr = SDDB.getInstance().getServiceRecord(
341: handles[i]);
342:
343: if (findUUIDs((ServiceRecordImpl) sr, uuidSet)) {
344: handle = handles[i];
345: }
346: }
347: ServiceRecord sr = SDDB.getInstance().getServiceRecord(handle);
348:
349: // if service record not found process it
350: if (sr == null) {
351: writeErrorResponce(rw, transactionID,
352: SDP_INVALID_SR_HANDLE,
353: "Servicce Record with specified ID not found");
354: return;
355: }
356:
357: DataElement attributeLists = new DataElement(DataElement.DATSEQ);
358: DataElement attrIDValues = new DataElement(DataElement.DATSEQ);
359: Enumeration e = (Enumeration) attrSet.getValue();
360:
361: while (e.hasMoreElements()) {
362: DataElement attrID = (DataElement) e.nextElement();
363: int attr = (int) attrID.getLong();
364: DataElement attrValue = sr.getAttributeValue(attr);
365:
366: if (attrValue != null) {
367: attrIDValues.addElement(attrID);
368: attrIDValues.addElement(attrValue);
369: }
370: }
371:
372: attributeLists.addElement(attrIDValues);
373: int length = (int) rw.getDataSize(attributeLists);
374:
375: rw.writeByte((byte) SDP_SERVICE_SEARCH_ATTRIBUTE_RESPONSE);
376: rw.writeShort(transactionID);
377: rw.writeShort((short) (length + 3));
378: rw.writeShort((short) length);
379: rw.writeDataElement(attributeLists);
380:
381: // IMPL_NOTE: ContinuationState isn't used, but should on real device
382: rw.writeByte((byte) 0x00);
383: rw.flush();
384: }
385:
386: /**
387: * Sends SDP_ErrorResponse PDU.
388: *
389: * @param rw <code>DataL2CAPReaderWriter</code> instance that represents
390: * connection to send to and provides RW utilities.
391: * @param transactionID ID of transaction to send response within.
392: * @param errorCode error code.
393: * @param info error details.
394: *
395: * @throws IOException if a processing error occured.
396: */
397: private void writeErrorResponce(DataL2CAPReaderWriter rw,
398: short transactionID, int errorCode, String info)
399: throws IOException {
400: byte[] infoBytes = info.getBytes();
401: int length = infoBytes.length + 2;
402: rw.writeByte((byte) SDP_ERROR_RESPONSE);
403: rw.writeShort(transactionID);
404: rw.writeShort((short) length);
405: rw.writeShort((short) errorCode);
406: rw.writeBytes(infoBytes);
407: rw.flush();
408: }
409:
410: /**
411: * Checks if the specified service record contains all of the
412: * UUID with values from specified 'uuids' list.
413: *
414: * Note, that according to spec clarification from spec lead
415: * such a search is done over all of the service attribues
416: * (not just ServiceClassIDList and ProtocolDescriptorList).
417: *
418: * @param sr service record to check.
419: * @param uuids list of UUIDs to check record for.
420: *
421: * @return true if the record given contains all the UUIDs, false
422: * otherwise.
423: */
424: private boolean findUUIDs(ServiceRecordImpl sr, DataElement uuids) {
425: int[] attrs = sr.getAttributeIDs();
426: Enumeration e = (Enumeration) uuids.getValue();
427:
428: NEXT_UUID: while (e.hasMoreElements()) {
429: UUID uuid = (UUID) ((DataElement) e.nextElement())
430: .getValue();
431: for (int i = 0; i < attrs.length; i++) {
432: DataElement attr = sr.getAttributeValue(attrs[i]);
433:
434: if (containsUUID(attr, uuid)) {
435: continue NEXT_UUID;
436: }
437: }
438: return false;
439: }
440: return true;
441: }
442:
443: /**
444: * Checks if specified 'attr' contains (or equals) to specified 'uuid.
445: *
446: * @param attr data element to check if it equals to desired UUID or
447: * contains it.
448: * @param uuid the UUID to compare with.
449: *
450: * @return true if data element given represents either specified UUID
451: * or a sequence that contains it, false otherwise.
452: */
453: private boolean containsUUID(DataElement attr, UUID uuid) {
454: if (attr.getDataType() == DataElement.UUID) {
455: return uuid.equals((UUID) attr.getValue());
456: }
457:
458: if (attr.getDataType() != DataElement.DATSEQ) {
459: return false;
460: }
461: Enumeration e = (Enumeration) attr.getValue();
462:
463: while (e.hasMoreElements()) {
464: DataElement de = (DataElement) e.nextElement();
465:
466: if (containsUUID(de, uuid)) {
467: return true;
468: }
469: }
470: return false;
471: }
472:
473: /**
474: * Requests acceptor, it is Runnable to be launched in a separate thread.
475: */
476: class Acceptor implements Runnable {
477: /**
478: * The <code>run()</code> method, see interface
479: * {@link java.lang.Runnable Runnable}.
480: */
481: public void run() {
482: while (true) {
483: try {
484: L2CAPConnection con = conNotifier.acceptAndOpen();
485: DataL2CAPReaderWriter rw = new DataL2CAPReaderWriter(
486: con);
487:
488: synchronized (SDPServer.this ) {
489: connections.addElement(con);
490: }
491: Sender sender = new Sender(con, rw);
492: new Thread(sender).start();
493: } catch (IOException ioe) {
494: if (DEBUG) {
495: ioe.printStackTrace();
496: }
497: // connection was closed
498: break;
499: }
500: }
501:
502: synchronized (SDPServer.this ) {
503: acceptorStarted = false;
504: try {
505: conNotifier.close();
506: } catch (IOException e) {
507: // no matter
508: }
509: conNotifier = null;
510: }
511: }
512:
513: /** Cleans possible blockings and extra references up. */
514: private void finalize() {
515: if (conNotifier != null) {
516: acceptorStarted = false;
517: try {
518: conNotifier.close();
519: } catch (IOException e) {
520: // no matter
521: }
522: conNotifier = null;
523: }
524: }
525: }
526:
527: /**
528: * Responses sender, it is Runnable to be launched in a separate thread.
529: */
530: class Sender implements Runnable {
531: /** Connection to send to. */
532: L2CAPConnection connection;
533:
534: /** Utility object to write to L2CAP connection. */
535: DataL2CAPReaderWriter readerWriter;
536:
537: /**
538: * Constructs sender to send that sends to given connection using
539: * specified writer.
540: *
541: * @param connection L2CAP connection to send to.
542: * @param readerWriter read/write utility object to use for sending.
543: */
544: Sender(L2CAPConnection connection,
545: DataL2CAPReaderWriter readerWriter) {
546: this .connection = connection;
547: this .readerWriter = readerWriter;
548: }
549:
550: /**
551: * The <code>run()</code> method, see interface
552: * {@link java.lang.Runnable Runnable}.
553: */
554: public void run() {
555: while (true) {
556: try {
557:
558: /*
559: * If this call returns sucessfully, the
560: * next request will be processed (in the
561: * next 'while' loop). IOException means
562: * the remote end-point is closed - no
563: * more requests to be processed.
564: */
565: processRequest(readerWriter);
566: } catch (IOException ioe) {
567:
568: // this means the process is done
569: if (DEBUG) {
570: ioe.printStackTrace();
571: }
572:
573: synchronized (SDPServer.this ) {
574: connections.removeElement(connection);
575: }
576:
577: try {
578: connection.close();
579: } catch (IOException ioe1) {
580: }
581: break;
582: }
583:
584: // process is done successfully - go to next one
585: }
586: }
587: }
588: }
|