ServerSession.java revision 9439a7fe517b858bc5e5c654b459315e4722feb2
1/*
2 * Copyright (c) 2008-2009, Motorola, Inc.
3 *
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions are met:
8 *
9 * - Redistributions of source code must retain the above copyright notice,
10 * this list of conditions and the following disclaimer.
11 *
12 * - Redistributions in binary form must reproduce the above copyright notice,
13 * this list of conditions and the following disclaimer in the documentation
14 * and/or other materials provided with the distribution.
15 *
16 * - Neither the name of the Motorola, Inc. nor the names of its contributors
17 * may be used to endorse or promote products derived from this software
18 * without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
24 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30 * POSSIBILITY OF SUCH DAMAGE.
31 */
32
33package javax.obex;
34
35import java.io.*;
36
37/**
38 * This class in an implementation of the ServerSession interface.
39 *
40 * @version 0.3 November 28, 2008
41 */
42public class ServerSession implements Runnable, ObexSession {
43
44    private ObexTransport client;
45
46    // private Socket client ;
47    private InputStream input;
48
49    private OutputStream output;
50
51    private ServerRequestHandler listener;
52
53    private Thread processThread;
54
55    private int maxPacketLength;
56
57    private Authenticator authenticator;
58
59    byte[] challengeDigest;
60
61    public boolean isClosed;
62
63    private static final String TAG = "ServerSession";
64
65    /**
66     * Creates new ServerSession.
67     *
68     * @param conn
69     *            the connection to the client
70     *
71     * @param handler
72     *            the event listener that will process requests
73     *
74     * @param auth
75     *            the authenticator to use with this connection
76     *
77     * @exception IOException
78     *                if an error occurred while opening the input and output
79     *                streams
80     */
81    public ServerSession(ObexTransport conn, ServerRequestHandler handler, Authenticator auth)
82            throws IOException {
83        authenticator = auth;
84        client = conn;
85        input = client.openInputStream();
86        output = client.openOutputStream();
87        listener = handler;
88        maxPacketLength = 256;
89
90        isClosed = false;
91        processThread = new Thread(this);
92        processThread.start();
93    }
94
95    /* removed as they're provided to the API user. Not used internally. */
96    /*
97     public boolean isCreatedServer() {
98        if (client instanceof BTConnection)
99            return ((BTConnection)client).isServerCreated();
100        else
101            return false;
102    }
103
104    public boolean isClosed() {
105        if (client instanceof BTConnection)
106            return ((BTConnection)client).isClosed();
107        else
108            return false;
109    }
110
111    public int getConnectionHandle() {
112        if (client instanceof BTConnection)
113            return ((BTConnection)client).getConnectionHandle();
114        else
115            return -1;
116    }
117
118    public RemoteDevice getRemoteDevice() {
119        if (client instanceof BTConnection)
120            return ((BTConnection)client).getRemoteDevice();
121        else
122            return null;
123    }*/
124
125    /**
126     * Processes requests made to the server and forwards them to the
127     * appropriate event listener.
128     */
129    public void run() {
130        try {
131
132            boolean done = false;
133            while (!done && !isClosed) {
134                int requestType = input.read();
135                switch (requestType) {
136                    case 0x80:
137                        handleConnectRequest();
138                        break;
139
140                    case 0x81:
141                        handleDisconnectRequest();
142                        done = true;
143                        break;
144
145                    case 0x03:
146                    case 0x83:
147                        handleGetRequest(requestType);
148                        break;
149
150                    case 0x02:
151                    case 0x82:
152                        handlePutRequest(requestType);
153                        break;
154
155                    case 0x85:
156                        handleSetPathRequest();
157                        break;
158
159                    case -1:
160                        done = true;
161                        break;
162
163                    default:
164
165                        /*
166                         * Received a request type that is not recognized so I am
167                         * just going to read the packet and send a not implemented
168                         * to the client
169                         */
170                        int length = input.read();
171                        length = (length << 8) + input.read();
172                        for (int i = 3; i < length; i++) {
173                            input.read();
174                        }
175                        sendResponse(ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED, null);
176
177                        // done = true;
178                }
179            }
180
181        } catch (NullPointerException e) {
182        } catch (Exception e) {
183        }
184        close();
185    }
186
187    /**
188     * Handles a PUT request from a client. This method will provide a
189     * <code>ServerOperation</code> object to the request handler. The
190     * <code>ServerOperation</code> object will handle the rest of the request.
191     * It will also send replies and receive requests until the final reply
192     * should be sent. When the final reply should be sent, this method will get
193     * the response code to use and send the reply. The
194     * <code>ServerOperation</code> object will always reply with a
195     * OBEX_HTTP_CONTINUE reply. It will only reply if further information is
196     * needed.
197     *
198     * @param type
199     *            the type of request received; either 0x02 or 0x82
200     *
201     * @exception IOException
202     *                if an error occurred at the transport layer
203     */
204    private void handlePutRequest(int type) throws IOException {
205        ServerOperation client = new ServerOperation(this, input, type, maxPacketLength, listener);
206        try {
207            int response = -1;
208
209            if ((client.finalBitSet) && !client.isValidBody()) {
210                response = validateResponseCode(listener.onDelete(client.requestHeaders,
211                        client.replyHeaders));
212            } else {
213                response = validateResponseCode(listener.onPut(client));
214            }
215            if (response != ResponseCodes.OBEX_HTTP_OK) {
216                client.sendReply(response);
217            } else if (!client.isAborted) {
218                // wait for the final bit
219                while (!client.finalBitSet) {
220                    client.sendReply(OBEXConstants.OBEX_HTTP_CONTINUE);
221                }
222                client.sendReply(response);
223            }
224        } catch (Exception e) {
225            sendResponse(ResponseCodes.OBEX_HTTP_INTERNAL_ERROR, null);
226        }
227    }
228
229    /**
230     * Handles a GET request from a client. This method will provide a
231     * <code>ServerOperation</code> object to the request handler. The
232     * <code>ServerOperation</code> object will handle the rest of the request.
233     * It will also send replies and receive requests until the final reply
234     * should be sent. When the final reply should be sent, this method will get
235     * the response code to use and send the reply. The
236     * <code>ServerOperation</code> object will always reply with a
237     * OBEX_HTTP_CONTINUE reply. It will only reply if further information is
238     * needed.
239     *
240     * @param type
241     *            the type of request received; either 0x03 or 0x83
242     *
243     * @exception IOException
244     *                if an error occurred at the transport layer
245     */
246    private void handleGetRequest(int type) throws IOException {
247        ServerOperation client = new ServerOperation(this, input, type, maxPacketLength, listener);
248        try {
249            int response = validateResponseCode(listener.onGet(client));
250
251            if (!client.isAborted) {
252                client.sendReply(response);
253            }
254        } catch (Exception e) {
255            sendResponse(ResponseCodes.OBEX_HTTP_INTERNAL_ERROR, null);
256        }
257    }
258
259    /**
260     * Send standard response.
261     *
262     * @param code
263     *            the response code to send
264     *
265     * @param header
266     *            the headers to include in the response
267     *
268     * @exception IOException
269     *                if an IO error occurs
270     */
271    protected void sendResponse(int code, byte[] header) throws IOException {
272        int totalLength = 3;
273        byte[] data = null;
274
275        if (header != null) {
276            totalLength += header.length;
277            data = new byte[totalLength];
278            data[0] = (byte)code;
279            data[1] = (byte)(totalLength >> 8);
280            data[2] = (byte)totalLength;
281            System.arraycopy(header, 0, data, 3, header.length);
282        } else {
283            data = new byte[totalLength];
284            data[0] = (byte)code;
285            data[1] = (byte)0x00;
286            data[2] = (byte)totalLength;
287        }
288        output.write(data);
289        output.flush();
290    }
291
292    /**
293     * Handles a SETPATH request from a client. This method will read the rest
294     * of the request from the client. Assuming the request is valid, it will
295     * create a <code>HeaderSet</code> object to pass to the
296     * <code>ServerRequestHandler</code> object. After the handler processes the
297     * request, this method will create a reply message to send to the server
298     * with the response code provided.
299     *
300     * @exception IOException
301     *                if an error occurred at the transport layer
302     */
303    private void handleSetPathRequest() throws IOException {
304        int length;
305        int flags;
306        int constants;
307        int totalLength = 3;
308        byte[] head = null;
309        int code = -1;
310        int bytesReceived;
311        HeaderSet request = new HeaderSet();
312        HeaderSet reply = new HeaderSet();
313
314        length = input.read();
315        length = (length << 8) + input.read();
316        flags = input.read();
317        constants = input.read();
318
319        if (length > OBEXConstants.MAX_PACKET_SIZE_INT) {
320            code = ResponseCodes.OBEX_HTTP_REQ_TOO_LARGE;
321            totalLength = 3;
322        } else {
323            if (length > 5) {
324                byte[] headers = new byte[length - 5];
325                bytesReceived = input.read(headers);
326
327                while (bytesReceived != headers.length) {
328                    bytesReceived += input.read(headers, bytesReceived, headers.length
329                            - bytesReceived);
330                }
331
332                OBEXHelper.updateHeaderSet(request, headers);
333
334                if (request.connectionID != null) {
335                    listener.setConnectionID(OBEXHelper.convertToLong(request.connectionID));
336                } else {
337                    listener.setConnectionID(-1);
338                }
339                // the Auth chan is initiated by the server.
340                // client sent back the authResp .
341                if (request.authResp != null) {
342                    if (!handleAuthResp(request.authResp)) {
343                        code = ResponseCodes.OBEX_HTTP_UNAUTHORIZED;
344                        listener.onAuthenticationFailure(OBEXHelper.getTagValue((byte)0x01,
345                                request.authResp));
346                    }
347                    request.authResp = null;
348                }
349            }
350
351            if (code != ResponseCodes.OBEX_HTTP_UNAUTHORIZED) {
352                // the Auth chan is initiated by the client
353                // the server will send back the authResp to the client
354                if (request.authChall != null) {
355                    handleAuthChall(request);
356                    reply.authResp = new byte[request.authResp.length];
357                    System.arraycopy(request.authResp, 0, reply.authResp, 0, reply.authResp.length);
358                    request.authChall = null;
359                    request.authResp = null;
360                }
361                boolean backup = false;
362                boolean create = true;
363                if (!((flags & 1) == 0)) {
364                    backup = true;
365                }
366                if ((flags & 2) == 0) {
367                    create = false;
368                }
369
370                try {
371                    code = listener.onSetPath(request, reply, backup, create);
372                } catch (Exception e) {
373                    sendResponse(ResponseCodes.OBEX_HTTP_INTERNAL_ERROR, null);
374                    return;
375                }
376
377                code = validateResponseCode(code);
378
379                if (reply.nonce != null) {
380                    challengeDigest = new byte[16];
381                    System.arraycopy(reply.nonce, 0, challengeDigest, 0, 16);
382                } else {
383                    challengeDigest = null;
384                }
385
386                long id = listener.getConnectionID();
387                if (id == -1) {
388                    reply.connectionID = null;
389                } else {
390                    reply.connectionID = OBEXHelper.convertToByteArray(id);
391                }
392
393                head = OBEXHelper.createHeader(reply, false);
394                totalLength += head.length;
395
396                if (totalLength > maxPacketLength) {
397                    totalLength = 3;
398                    head = null;
399                    code = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
400                }
401            }
402        }
403
404        // Compute Length of OBEX SETPATH packet
405        byte[] replyData = new byte[totalLength];
406        replyData[0] = (byte)code;
407        replyData[1] = (byte)(totalLength >> 8);
408        replyData[2] = (byte)totalLength;
409        if (head != null) {
410            System.arraycopy(head, 0, replyData, 3, head.length);
411        }
412        /*
413         * Write the OBEX SETPATH packet to the server. Byte 0: response code
414         * Byte 1&2: Connect Packet Length Byte 3 to n: headers
415         */
416        output.write(replyData);
417        output.flush();
418    }
419
420    /**
421     * Handles a disconnect request from a client. This method will read the
422     * rest of the request from the client. Assuming the request is valid, it
423     * will create a <code>HeaderSet</code> object to pass to the
424     * <code>ServerRequestHandler</code> object. After the handler processes the
425     * request, this method will create a reply message to send to the server.
426     *
427     * @exception IOException
428     *                if an error occurred at the transport layer
429     */
430    private void handleDisconnectRequest() throws IOException {
431        int length;
432        int code = ResponseCodes.OBEX_HTTP_OK;
433        int totalLength = 3;
434        byte[] head = null;
435        int bytesReceived;
436        HeaderSet request = new HeaderSet();
437        HeaderSet reply = new HeaderSet();
438
439        length = input.read();
440        length = (length << 8) + input.read();
441
442        if (length > OBEXConstants.MAX_PACKET_SIZE_INT) {
443            code = ResponseCodes.OBEX_HTTP_REQ_TOO_LARGE;
444            totalLength = 3;
445        } else {
446            if (length > 3) {
447                byte[] headers = new byte[length - 3];
448                bytesReceived = input.read(headers);
449
450                while (bytesReceived != headers.length) {
451                    bytesReceived += input.read(headers, bytesReceived, headers.length
452                            - bytesReceived);
453                }
454
455                OBEXHelper.updateHeaderSet(request, headers);
456            }
457
458            if (request.connectionID != null) {
459                listener.setConnectionID(OBEXHelper.convertToLong(request.connectionID));
460            } else {
461                listener.setConnectionID(1);
462            }
463
464            if (request.authResp != null) {
465                if (!handleAuthResp(request.authResp)) {
466                    code = ResponseCodes.OBEX_HTTP_UNAUTHORIZED;
467                    listener.onAuthenticationFailure(OBEXHelper.getTagValue((byte)0x01,
468                            request.authResp));
469                }
470                request.authResp = null;
471            }
472
473            if (code != ResponseCodes.OBEX_HTTP_UNAUTHORIZED) {
474
475                if (request.authChall != null) {
476                    handleAuthChall(request);
477                    request.authChall = null;
478                }
479
480                try {
481                    listener.onDisconnect(request, reply);
482                } catch (Exception e) {
483                    sendResponse(ResponseCodes.OBEX_HTTP_INTERNAL_ERROR, null);
484                    return;
485                }
486
487                /*
488                 * Since a client will never response to an authentication
489                 * challenge on a DISCONNECT, there is no reason to keep track
490                 * of the challenge.
491                 *
492                 * if (reply.nonce != null) { challengeDigest = new byte[16];
493                 * System.arraycopy(reply.nonce, 0, challengeDigest, 0, 16); }
494                 * else { challengeDigest = null; }
495                 */
496
497                long id = listener.getConnectionID();
498                if (id == -1) {
499                    reply.connectionID = null;
500                } else {
501                    reply.connectionID = OBEXHelper.convertToByteArray(id);
502                }
503
504                head = OBEXHelper.createHeader(reply, false);
505                totalLength += head.length;
506
507                if (totalLength > maxPacketLength) {
508                    totalLength = 3;
509                    head = null;
510                    code = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
511                }
512            }
513        }
514
515        // Compute Length of OBEX CONNECT packet
516        byte[] replyData;
517        if (head != null) {
518            replyData = new byte[3 + head.length];
519        } else {
520            replyData = new byte[3];
521        }
522        replyData[0] = (byte)code;
523        replyData[1] = (byte)(totalLength >> 8);
524        replyData[2] = (byte)totalLength;
525        if (head != null) {
526            System.arraycopy(head, 0, replyData, 3, head.length);
527        }
528        /*
529         * Write the OBEX DISCONNECT packet to the server. Byte 0: response code
530         * Byte 1&2: Connect Packet Length Byte 3 to n: headers
531         */
532        output.write(replyData);
533        output.flush();
534    }
535
536    /**
537     * Handles a connect request from a client. This method will read the rest
538     * of the request from the client. Assuming the request is valid, it will
539     * create a <code>HeaderSet</code> object to pass to the
540     * <code>ServerRequestHandler</code> object. After the handler processes the
541     * request, this method will create a reply message to send to the server
542     * with the response code provided.
543     *
544     * @exception IOException
545     *                if an error occurred at the transport layer
546     */
547    private void handleConnectRequest() throws IOException {
548        int packetLength;
549        int version;
550        int flags;
551        int totalLength = 7;
552        byte[] head = null;
553        int code = -1;
554        HeaderSet request = new HeaderSet();
555        HeaderSet reply = new HeaderSet();
556        int bytesReceived;
557
558        /*
559         * Read in the length of the OBEX packet, OBEX version, flags, and max
560         * packet length
561         */
562        packetLength = input.read();
563        packetLength = (packetLength << 8) + input.read();
564        version = input.read();
565        flags = input.read();
566        maxPacketLength = input.read();
567        maxPacketLength = (maxPacketLength << 8) + input.read();
568
569        // should we check it?
570        if (maxPacketLength > OBEXConstants.MAX_PACKET_SIZE_INT) {
571            maxPacketLength = OBEXConstants.MAX_PACKET_SIZE_INT;
572        }
573
574        if (packetLength > OBEXConstants.MAX_PACKET_SIZE_INT) {
575            code = ResponseCodes.OBEX_HTTP_REQ_TOO_LARGE;
576            totalLength = 7;
577        } else {
578            if (packetLength > 7) {
579                byte[] headers = new byte[packetLength - 7];
580                bytesReceived = input.read(headers);
581
582                while (bytesReceived != headers.length) {
583                    bytesReceived += input.read(headers, bytesReceived, headers.length
584                            - bytesReceived);
585                }
586
587                OBEXHelper.updateHeaderSet(request, headers);
588            }
589
590            if (request.connectionID != null) {
591                listener.setConnectionID(OBEXHelper.convertToLong(request.connectionID));
592            } else {
593                listener.setConnectionID(1);
594            }
595
596            if (request.authResp != null) {
597                if (!handleAuthResp(request.authResp)) {
598                    code = ResponseCodes.OBEX_HTTP_UNAUTHORIZED;
599                    listener.onAuthenticationFailure(OBEXHelper.getTagValue((byte)0x01,
600                            request.authResp));
601                }
602                request.authResp = null;
603            }
604
605            if (code != ResponseCodes.OBEX_HTTP_UNAUTHORIZED) {
606                if (request.authChall != null) {
607                    handleAuthChall(request);
608                    reply.authResp = new byte[request.authResp.length];
609                    System.arraycopy(request.authResp, 0, reply.authResp, 0, reply.authResp.length);
610                    request.authChall = null;
611                    request.authResp = null;
612                }
613
614                try {
615                    code = listener.onConnect(request, reply);
616                    code = validateResponseCode(code);
617
618                    if (reply.nonce != null) {
619                        challengeDigest = new byte[16];
620                        System.arraycopy(reply.nonce, 0, challengeDigest, 0, 16);
621                    } else {
622                        challengeDigest = null;
623                    }
624                    long id = listener.getConnectionID();
625                    if (id == -1) {
626                        reply.connectionID = null;
627                    } else {
628                        reply.connectionID = OBEXHelper.convertToByteArray(id);
629                    }
630
631                    head = OBEXHelper.createHeader(reply, false);
632                    totalLength += head.length;
633
634                    if (totalLength > maxPacketLength) {
635                        totalLength = 7;
636                        head = null;
637                        code = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
638                    }
639                } catch (Exception e) {
640                    e.printStackTrace();
641                    totalLength = 7;
642                    head = null;
643                    code = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
644                }
645
646            }
647        }
648
649        // Compute Length of OBEX CONNECT packet
650        byte[] length = OBEXHelper.convertToByteArray(totalLength);
651
652        /*
653         * Write the OBEX CONNECT packet to the server. Byte 0: response code
654         * Byte 1&2: Connect Packet Length Byte 3: OBEX Version Number
655         * (Presently, 0x10) Byte 4: Flags (For TCP 0x00) Byte 5&6: Max OBEX
656         * Packet Length (Defined in MAX_PACKET_SIZE) Byte 7 to n: headers
657         */
658        byte[] sendData = new byte[totalLength];
659        sendData[0] = (byte)code;
660        sendData[1] = length[2];
661        sendData[2] = length[3];
662        sendData[3] = (byte)0x10;
663        sendData[4] = (byte)0x00;
664        sendData[5] = (byte)(OBEXConstants.MAX_PACKET_SIZE_INT >> 8);
665        sendData[6] = (byte)(OBEXConstants.MAX_PACKET_SIZE_INT & 0xFF);
666
667        if (head != null) {
668            System.arraycopy(head, 0, sendData, 7, head.length);
669        }
670
671        output.write(sendData);
672        output.flush();
673    }
674
675    /**
676     * Closes the server session - in detail close I/O streams and the
677     * underlying transport layer. Internal flag is also set so that later
678     * attempt to read/write will throw an exception.
679     */
680    public synchronized void close() {
681        if (listener != null) {
682            listener.onClose();
683        }
684        try {
685            input.close();
686            output.close();
687            client.close();
688            isClosed = true;
689        } catch (Exception e) {
690        }
691        client = null;
692        input = null;
693        output = null;
694        listener = null;
695    }
696
697    /**
698     * Verifies that the response code is valid. If it is not valid, it will
699     * return the <code>OBEX_HTTP_INTERNAL_ERROR</code> response code.
700     *
701     * @param code
702     *            the response code to check
703     *
704     * @return the valid response code or <code>OBEX_HTTP_INTERNAL_ERROR</code>
705     *         if <code>code</code> is not valid
706     */
707    private int validateResponseCode(int code) {
708
709        if ((code >= ResponseCodes.OBEX_HTTP_OK) && (code <= ResponseCodes.OBEX_HTTP_PARTIAL)) {
710            return code;
711        }
712        if ((code >= ResponseCodes.OBEX_HTTP_MULT_CHOICE)
713                && (code <= ResponseCodes.OBEX_HTTP_USE_PROXY)) {
714            return code;
715        }
716        if ((code >= ResponseCodes.OBEX_HTTP_BAD_REQUEST)
717                && (code <= ResponseCodes.OBEX_HTTP_UNSUPPORTED_TYPE)) {
718            return code;
719        }
720        if ((code >= ResponseCodes.OBEX_HTTP_INTERNAL_ERROR)
721                && (code <= ResponseCodes.OBEX_HTTP_VERSION)) {
722            return code;
723        }
724        if ((code >= ResponseCodes.OBEX_DATABASE_FULL)
725                && (code <= ResponseCodes.OBEX_DATABASE_LOCKED)) {
726            return code;
727        }
728        return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
729    }
730
731    /**
732     * Called when the server received an authentication challenge header. This
733     * will cause the authenticator to handle the authentication challenge.
734     *
735     * @param header
736     *            the header with the authentication challenge
737     *
738     * @return <code>true</code> if the last request should be resent;
739     *         <code>false</code> if the last request should not be resent
740     */
741    protected boolean handleAuthChall(HeaderSet header) {
742        if (authenticator == null) {
743            return false;
744        }
745
746        /*
747         * An authentication challenge is made up of one required and two
748         * optional tag length value triplets. The tag 0x00 is required to be in
749         * the authentication challenge and it represents the challenge digest
750         * that was received. The tag 0x01 is the options tag. This tag tracks
751         * if user ID is required and if full access will be granted. The tag
752         * 0x02 is the realm, which provides a description of which user name
753         * and password to use.
754         */
755        byte[] challenge = OBEXHelper.getTagValue((byte)0x00, header.authChall);
756        byte[] option = OBEXHelper.getTagValue((byte)0x01, header.authChall);
757        byte[] description = OBEXHelper.getTagValue((byte)0x02, header.authChall);
758
759        String realm = "";
760        if (description != null) {
761            byte[] realmString = new byte[description.length - 1];
762            System.arraycopy(description, 1, realmString, 0, realmString.length);
763
764            switch (description[0] & 0xFF) {
765
766                case 0x00:
767                    // ASCII encoding
768                    // Fall through
769                case 0x01:
770                    // ISO-8859-1 encoding
771                    try {
772                        realm = new String(realmString, "ISO8859_1");
773                    } catch (Exception e) {
774                        throw new RuntimeException("Unsupported Encoding Scheme");
775                    }
776                    break;
777
778                case 0xFF:
779                    // UNICODE Encoding
780                    realm = OBEXHelper.convertToUnicode(realmString, false);
781                    break;
782
783                case 0x02:
784                    // ISO-8859-2 encoding
785                    // Fall through
786                case 0x03:
787                    // ISO-8859-3 encoding
788                    // Fall through
789                case 0x04:
790                    // ISO-8859-4 encoding
791                    // Fall through
792                case 0x05:
793                    // ISO-8859-5 encoding
794                    // Fall through
795                case 0x06:
796                    // ISO-8859-6 encoding
797                    // Fall through
798                case 0x07:
799                    // ISO-8859-7 encoding
800                    // Fall through
801                case 0x08:
802                    // ISO-8859-8 encoding
803                    // Fall through
804                case 0x09:
805                    // ISO-8859-9 encoding
806                    // Fall through
807                default:
808                    throw new RuntimeException("Unsupported Encoding Scheme");
809            }
810        }
811
812        boolean isUserIDRequired = false;
813        boolean isFullAccess = true;
814        if (option != null) {
815            if ((option[0] & 0x01) != 0) {
816                isUserIDRequired = true;
817            }
818
819            if ((option[0] & 0x02) != 0) {
820                isFullAccess = false;
821            }
822        }
823
824        PasswordAuthentication result = null;
825        header.authChall = null;
826
827        try {
828            result = authenticator.onAuthenticationChallenge(realm, isUserIDRequired, isFullAccess);
829        } catch (Exception e) {
830            return false;
831        }
832
833        /*
834         * If no password is provided then we not resent the request
835         */
836        if (result == null) {
837            return false;
838        }
839
840        byte[] password = result.getPassword();
841        if (password == null) {
842            return false;
843        }
844
845        byte[] userName = result.getUserName();
846
847        /*
848         * Create the authentication response header. It includes 1 required and
849         * 2 option tag length value triples. The required triple has a tag of
850         * 0x00 and is the response digest. The first optional tag is 0x01 and
851         * represents the user ID. If no user ID is provided, then no user ID
852         * will be sent. The second optional tag is 0x02 and is the challenge
853         * that was received. This will always be sent
854         */
855        if (userName != null) {
856            header.authResp = new byte[38 + userName.length];
857            header.authResp[36] = (byte)0x01;
858            header.authResp[37] = (byte)userName.length;
859            System.arraycopy(userName, 0, header.authResp, 38, userName.length);
860        } else {
861            header.authResp = new byte[36];
862        }
863
864        // Create the secret String
865        byte[] digest = new byte[challenge.length + password.length + 1];
866        System.arraycopy(challenge, 0, digest, 0, challenge.length);
867        // Insert colon between challenge and password
868        digest[challenge.length] = (byte)0x3A;
869        System.arraycopy(password, 0, digest, challenge.length + 1, password.length);
870
871        // Add the Response Digest
872        header.authResp[0] = (byte)0x00;
873        header.authResp[1] = (byte)0x10;
874
875        System.arraycopy(OBEXHelper.computeMD5Hash(digest), 0, header.authResp, 2, 16);
876
877        // Add the challenge
878        header.authResp[18] = (byte)0x02;
879        header.authResp[19] = (byte)0x10;
880        System.arraycopy(challenge, 0, header.authResp, 20, 16);
881
882        return true;
883    }
884
885    /**
886     * Called when the server received an authentication response header. This
887     * will cause the authenticator to handle the authentication response.
888     *
889     * @param authResp
890     *            the authentication response
891     *
892     * @return <code>true</code> if the response passed; <code>false</code> if
893     *         the response failed
894     */
895    protected boolean handleAuthResp(byte[] authResp) {
896        if (authenticator == null) {
897            return false;
898        }
899        // get the correct password from the application
900        byte[] correctPassword = authenticator.onAuthenticationResponse(OBEXHelper.getTagValue(
901                (byte)0x01, authResp));
902        if (correctPassword == null) {
903            return false;
904        }
905
906        byte[] temp = new byte[correctPassword.length + 16];
907
908        System.arraycopy(challengeDigest, 0, temp, 0, 16);
909        System.arraycopy(correctPassword, 0, temp, 16, correctPassword.length);
910
911        byte[] correctResponse = OBEXHelper.computeMD5Hash(temp);
912        byte[] actualResponse = OBEXHelper.getTagValue((byte)0x00, authResp);
913
914        // compare the MD5 hash array .
915        for (int i = 0; i < 16; i++) {
916            if (correctResponse[i] != actualResponse[i]) {
917                return false;
918            }
919        }
920
921        return true;
922    }
923}
924