ServerSession.java revision 3998bf009acaf8cde4d7f837f8b8e41ae0a65141
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 android.util.Log;
36
37import java.io.InputStream;
38import java.io.IOException;
39import java.io.OutputStream;
40
41/**
42 * This class in an implementation of the OBEX ServerSession.
43 *
44 * @hide
45 */
46public final class ServerSession extends ObexSession implements Runnable {
47
48    private static final String TAG = "Obex ServerSession";
49
50    private ObexTransport mTransport;
51
52    private InputStream mInput;
53
54    private OutputStream mOutput;
55
56    private ServerRequestHandler mListener;
57
58    private Thread mProcessThread;
59
60    private int mMaxPacketLength;
61
62    private boolean mClosed;
63
64    /**
65     * Creates new ServerSession.
66     *
67     * @param trans
68     *            the connection to the client
69     *
70     * @param handler
71     *            the event listener that will process requests
72     *
73     * @param auth
74     *            the authenticator to use with this connection
75     *
76     * @throws IOException
77     *                if an error occurred while opening the input and output
78     *                streams
79     */
80    public ServerSession(ObexTransport trans, ServerRequestHandler handler, Authenticator auth)
81            throws IOException {
82        mAuthenticator = auth;
83        mTransport = trans;
84        mInput = mTransport.openInputStream();
85        mOutput = mTransport.openOutputStream();
86        mListener = handler;
87        mMaxPacketLength = 256;
88
89        mClosed = false;
90        mProcessThread = new Thread(this);
91        mProcessThread.start();
92    }
93
94    /**
95     * Processes requests made to the server and forwards them to the
96     * appropriate event listener.
97     */
98    public void run() {
99        try {
100
101            boolean done = false;
102            while (!done && !mClosed) {
103                int requestType = mInput.read();
104                switch (requestType) {
105                    case ObexHelper.OBEX_OPCODE_CONNECT:
106                        handleConnectRequest();
107                        break;
108
109                    case ObexHelper.OBEX_OPCODE_DISCONNECT:
110                        handleDisconnectRequest();
111                        done = true;
112                        break;
113
114                    case ObexHelper.OBEX_OPCODE_GET:
115                    case ObexHelper.OBEX_OPCODE_GET_FINAL:
116                        handleGetRequest(requestType);
117                        break;
118
119                    case ObexHelper.OBEX_OPCODE_PUT:
120                    case ObexHelper.OBEX_OPCODE_PUT_FINAL:
121                        handlePutRequest(requestType);
122                        break;
123
124                    case ObexHelper.OBEX_OPCODE_SETPATH:
125                        handleSetPathRequest();
126                        break;
127
128                    case -1:
129                        done = true;
130                        break;
131
132                    default:
133
134                        /*
135                         * Received a request type that is not recognized so I am
136                         * just going to read the packet and send a not implemented
137                         * to the client
138                         */
139                        int length = mInput.read();
140                        length = (length << 8) + mInput.read();
141                        for (int i = 3; i < length; i++) {
142                            mInput.read();
143                        }
144                        sendResponse(ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED, null);
145                }
146            }
147
148        } catch (NullPointerException e) {
149            Log.d(TAG, e.toString());
150        } catch (Exception e) {
151            Log.d(TAG, e.toString());
152        }
153        close();
154    }
155
156    /**
157     * Handles a PUT request from a client. This method will provide a
158     * <code>ServerOperation</code> object to the request handler. The
159     * <code>ServerOperation</code> object will handle the rest of the request.
160     * It will also send replies and receive requests until the final reply
161     * should be sent. When the final reply should be sent, this method will get
162     * the response code to use and send the reply. The
163     * <code>ServerOperation</code> object will always reply with a
164     * OBEX_HTTP_CONTINUE reply. It will only reply if further information is
165     * needed.
166     *
167     * @param type
168     *            the type of request received; either 0x02 or 0x82
169     *
170     * @throws IOException
171     *                if an error occurred at the transport layer
172     */
173    private void handlePutRequest(int type) throws IOException {
174        ServerOperation op = new ServerOperation(this, mInput, type, mMaxPacketLength, mListener);
175        try {
176            int response = -1;
177
178            if ((op.finalBitSet) && !op.isValidBody()) {
179                response = validateResponseCode(mListener
180                        .onDelete(op.requestHeader, op.replyHeader));
181            } else {
182                response = validateResponseCode(mListener.onPut(op));
183            }
184            if (response != ResponseCodes.OBEX_HTTP_OK) {
185                op.sendReply(response);
186            } else if (!op.isAborted) {
187                // wait for the final bit
188                while (!op.finalBitSet) {
189                    op.sendReply(ResponseCodes.OBEX_HTTP_CONTINUE);
190                }
191                op.sendReply(response);
192            }
193        } catch (Exception e) {
194            sendResponse(ResponseCodes.OBEX_HTTP_INTERNAL_ERROR, null);
195        }
196    }
197
198    /**
199     * Handles a GET request from a client. This method will provide a
200     * <code>ServerOperation</code> object to the request handler. The
201     * <code>ServerOperation</code> object will handle the rest of the request.
202     * It will also send replies and receive requests until the final reply
203     * should be sent. When the final reply should be sent, this method will get
204     * the response code to use and send the reply. The
205     * <code>ServerOperation</code> object will always reply with a
206     * OBEX_HTTP_CONTINUE reply. It will only reply if further information is
207     * needed.
208     *
209     * @param type
210     *            the type of request received; either 0x03 or 0x83
211     *
212     * @throws IOException
213     *                if an error occurred at the transport layer
214     */
215    private void handleGetRequest(int type) throws IOException {
216        ServerOperation op = new ServerOperation(this, mInput, type, mMaxPacketLength, mListener);
217        try {
218            int response = validateResponseCode(mListener.onGet(op));
219
220            if (!op.isAborted) {
221                op.sendReply(response);
222            }
223        } catch (Exception e) {
224            sendResponse(ResponseCodes.OBEX_HTTP_INTERNAL_ERROR, null);
225        }
226    }
227
228    /**
229     * Send standard response.
230     *
231     * @param code
232     *            the response code to send
233     *
234     * @param header
235     *            the headers to include in the response
236     *
237     * @throws IOException
238     *                if an IO error occurs
239     */
240    public void sendResponse(int code, byte[] header) throws IOException {
241        int totalLength = 3;
242        byte[] data = null;
243
244        if (header != null) {
245            totalLength += header.length;
246            data = new byte[totalLength];
247            data[0] = (byte)code;
248            data[1] = (byte)(totalLength >> 8);
249            data[2] = (byte)totalLength;
250            System.arraycopy(header, 0, data, 3, header.length);
251        } else {
252            data = new byte[totalLength];
253            data[0] = (byte)code;
254            data[1] = (byte)0x00;
255            data[2] = (byte)totalLength;
256        }
257        mOutput.write(data);
258        mOutput.flush();
259    }
260
261    /**
262     * Handles a SETPATH request from a client. This method will read the rest
263     * of the request from the client. Assuming the request is valid, it will
264     * create a <code>HeaderSet</code> object to pass to the
265     * <code>ServerRequestHandler</code> object. After the handler processes the
266     * request, this method will create a reply message to send to the server
267     * with the response code provided.
268     *
269     * @throws IOException
270     *                if an error occurred at the transport layer
271     */
272    private void handleSetPathRequest() throws IOException {
273        int length;
274        int flags;
275        @SuppressWarnings("unused")
276        int constants;
277        int totalLength = 3;
278        byte[] head = null;
279        int code = -1;
280        int bytesReceived;
281        HeaderSet request = new HeaderSet();
282        HeaderSet reply = new HeaderSet();
283
284        length = mInput.read();
285        length = (length << 8) + mInput.read();
286        flags = mInput.read();
287        constants = mInput.read();
288
289        if (length > ObexHelper.MAX_PACKET_SIZE_INT) {
290            code = ResponseCodes.OBEX_HTTP_REQ_TOO_LARGE;
291            totalLength = 3;
292        } else {
293            if (length > 5) {
294                byte[] headers = new byte[length - 5];
295                bytesReceived = mInput.read(headers);
296
297                while (bytesReceived != headers.length) {
298                    bytesReceived += mInput.read(headers, bytesReceived, headers.length
299                            - bytesReceived);
300                }
301
302                ObexHelper.updateHeaderSet(request, headers);
303
304                if (request.mConnectionID != null) {
305                    mListener.setConnectionId(ObexHelper.convertToLong(request.mConnectionID));
306                } else {
307                    mListener.setConnectionId(-1);
308                }
309                // the Auth chan is initiated by the server, client sent back the authResp .
310                if (request.mAuthResp != null) {
311                    if (!handleAuthResp(request.mAuthResp)) {
312                        code = ResponseCodes.OBEX_HTTP_UNAUTHORIZED;
313                        mListener.onAuthenticationFailure(ObexHelper.getTagValue((byte)0x01,
314                                request.mAuthResp));
315                    }
316                    request.mAuthResp = null;
317                }
318            }
319
320            if (code != ResponseCodes.OBEX_HTTP_UNAUTHORIZED) {
321                // the Auth challenge is initiated by the client
322                // the server will send back the authResp to the client
323                if (request.mAuthChall != null) {
324                    handleAuthChall(request);
325                    reply.mAuthResp = new byte[request.mAuthResp.length];
326                    System.arraycopy(request.mAuthResp, 0, reply.mAuthResp, 0,
327                            reply.mAuthResp.length);
328                    request.mAuthChall = null;
329                    request.mAuthResp = null;
330                }
331                boolean backup = false;
332                boolean create = true;
333                if (!((flags & 1) == 0)) {
334                    backup = true;
335                }
336                if ((flags & 2) == 0) {
337                    create = false;
338                }
339
340                try {
341                    code = mListener.onSetPath(request, reply, backup, create);
342                } catch (Exception e) {
343                    sendResponse(ResponseCodes.OBEX_HTTP_INTERNAL_ERROR, null);
344                    return;
345                }
346
347                code = validateResponseCode(code);
348
349                if (reply.nonce != null) {
350                    mChallengeDigest = new byte[16];
351                    System.arraycopy(reply.nonce, 0, mChallengeDigest, 0, 16);
352                } else {
353                    mChallengeDigest = null;
354                }
355
356                long id = mListener.getConnectionId();
357                if (id == -1) {
358                    reply.mConnectionID = null;
359                } else {
360                    reply.mConnectionID = ObexHelper.convertToByteArray(id);
361                }
362
363                head = ObexHelper.createHeader(reply, false);
364                totalLength += head.length;
365
366                if (totalLength > mMaxPacketLength) {
367                    totalLength = 3;
368                    head = null;
369                    code = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
370                }
371            }
372        }
373
374        // Compute Length of OBEX SETPATH packet
375        byte[] replyData = new byte[totalLength];
376        replyData[0] = (byte)code;
377        replyData[1] = (byte)(totalLength >> 8);
378        replyData[2] = (byte)totalLength;
379        if (head != null) {
380            System.arraycopy(head, 0, replyData, 3, head.length);
381        }
382        /*
383         * Write the OBEX SETPATH packet to the server. Byte 0: response code
384         * Byte 1&2: Connect Packet Length Byte 3 to n: headers
385         */
386        mOutput.write(replyData);
387        mOutput.flush();
388    }
389
390    /**
391     * Handles a disconnect request from a client. This method will read the
392     * rest of the request from the client. Assuming the request is valid, it
393     * will create a <code>HeaderSet</code> object to pass to the
394     * <code>ServerRequestHandler</code> object. After the handler processes the
395     * request, this method will create a reply message to send to the server.
396     *
397     * @throws IOException
398     *                if an error occurred at the transport layer
399     */
400    private void handleDisconnectRequest() throws IOException {
401        int length;
402        int code = ResponseCodes.OBEX_HTTP_OK;
403        int totalLength = 3;
404        byte[] head = null;
405        int bytesReceived;
406        HeaderSet request = new HeaderSet();
407        HeaderSet reply = new HeaderSet();
408
409        length = mInput.read();
410        length = (length << 8) + mInput.read();
411
412        if (length > ObexHelper.MAX_PACKET_SIZE_INT) {
413            code = ResponseCodes.OBEX_HTTP_REQ_TOO_LARGE;
414            totalLength = 3;
415        } else {
416            if (length > 3) {
417                byte[] headers = new byte[length - 3];
418                bytesReceived = mInput.read(headers);
419
420                while (bytesReceived != headers.length) {
421                    bytesReceived += mInput.read(headers, bytesReceived, headers.length
422                            - bytesReceived);
423                }
424
425                ObexHelper.updateHeaderSet(request, headers);
426            }
427
428            if (request.mConnectionID != null) {
429                mListener.setConnectionId(ObexHelper.convertToLong(request.mConnectionID));
430            } else {
431                mListener.setConnectionId(1);
432            }
433
434            if (request.mAuthResp != null) {
435                if (!handleAuthResp(request.mAuthResp)) {
436                    code = ResponseCodes.OBEX_HTTP_UNAUTHORIZED;
437                    mListener.onAuthenticationFailure(ObexHelper.getTagValue((byte)0x01,
438                            request.mAuthResp));
439                }
440                request.mAuthResp = null;
441            }
442
443            if (code != ResponseCodes.OBEX_HTTP_UNAUTHORIZED) {
444
445                if (request.mAuthChall != null) {
446                    handleAuthChall(request);
447                    request.mAuthChall = null;
448                }
449
450                try {
451                    mListener.onDisconnect(request, reply);
452                } catch (Exception e) {
453                    sendResponse(ResponseCodes.OBEX_HTTP_INTERNAL_ERROR, null);
454                    return;
455                }
456
457                long id = mListener.getConnectionId();
458                if (id == -1) {
459                    reply.mConnectionID = null;
460                } else {
461                    reply.mConnectionID = ObexHelper.convertToByteArray(id);
462                }
463
464                head = ObexHelper.createHeader(reply, false);
465                totalLength += head.length;
466
467                if (totalLength > mMaxPacketLength) {
468                    totalLength = 3;
469                    head = null;
470                    code = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
471                }
472            }
473        }
474
475        // Compute Length of OBEX CONNECT packet
476        byte[] replyData;
477        if (head != null) {
478            replyData = new byte[3 + head.length];
479        } else {
480            replyData = new byte[3];
481        }
482        replyData[0] = (byte)code;
483        replyData[1] = (byte)(totalLength >> 8);
484        replyData[2] = (byte)totalLength;
485        if (head != null) {
486            System.arraycopy(head, 0, replyData, 3, head.length);
487        }
488        /*
489         * Write the OBEX DISCONNECT packet to the server. Byte 0: response code
490         * Byte 1&2: Connect Packet Length Byte 3 to n: headers
491         */
492        mOutput.write(replyData);
493        mOutput.flush();
494    }
495
496    /**
497     * Handles a connect request from a client. This method will read the rest
498     * of the request from the client. Assuming the request is valid, it will
499     * create a <code>HeaderSet</code> object to pass to the
500     * <code>ServerRequestHandler</code> object. After the handler processes the
501     * request, this method will create a reply message to send to the server
502     * with the response code provided.
503     *
504     * @throws IOException
505     *                if an error occurred at the transport layer
506     */
507    private void handleConnectRequest() throws IOException {
508        int packetLength;
509        @SuppressWarnings("unused")
510        int version;
511        @SuppressWarnings("unused")
512        int flags;
513        int totalLength = 7;
514        byte[] head = null;
515        int code = -1;
516        HeaderSet request = new HeaderSet();
517        HeaderSet reply = new HeaderSet();
518        int bytesReceived;
519
520        /*
521         * Read in the length of the OBEX packet, OBEX version, flags, and max
522         * packet length
523         */
524        packetLength = mInput.read();
525        packetLength = (packetLength << 8) + mInput.read();
526        version = mInput.read();
527        flags = mInput.read();
528        mMaxPacketLength = mInput.read();
529        mMaxPacketLength = (mMaxPacketLength << 8) + mInput.read();
530
531        // should we check it?
532        if (mMaxPacketLength > ObexHelper.MAX_PACKET_SIZE_INT) {
533            mMaxPacketLength = ObexHelper.MAX_PACKET_SIZE_INT;
534        }
535
536        if (packetLength > ObexHelper.MAX_PACKET_SIZE_INT) {
537            code = ResponseCodes.OBEX_HTTP_REQ_TOO_LARGE;
538            totalLength = 7;
539        } else {
540            if (packetLength > 7) {
541                byte[] headers = new byte[packetLength - 7];
542                bytesReceived = mInput.read(headers);
543
544                while (bytesReceived != headers.length) {
545                    bytesReceived += mInput.read(headers, bytesReceived, headers.length
546                            - bytesReceived);
547                }
548
549                ObexHelper.updateHeaderSet(request, headers);
550            }
551
552            if (request.mConnectionID != null) {
553                mListener.setConnectionId(ObexHelper.convertToLong(request.mConnectionID));
554            } else {
555                mListener.setConnectionId(1);
556            }
557
558            if (request.mAuthResp != null) {
559                if (!handleAuthResp(request.mAuthResp)) {
560                    code = ResponseCodes.OBEX_HTTP_UNAUTHORIZED;
561                    mListener.onAuthenticationFailure(ObexHelper.getTagValue((byte)0x01,
562                            request.mAuthResp));
563                }
564                request.mAuthResp = null;
565            }
566
567            if (code != ResponseCodes.OBEX_HTTP_UNAUTHORIZED) {
568                if (request.mAuthChall != null) {
569                    handleAuthChall(request);
570                    reply.mAuthResp = new byte[request.mAuthResp.length];
571                    System.arraycopy(request.mAuthResp, 0, reply.mAuthResp, 0,
572                            reply.mAuthResp.length);
573                    request.mAuthChall = null;
574                    request.mAuthResp = null;
575                }
576
577                try {
578                    code = mListener.onConnect(request, reply);
579                    code = validateResponseCode(code);
580
581                    if (reply.nonce != null) {
582                        mChallengeDigest = new byte[16];
583                        System.arraycopy(reply.nonce, 0, mChallengeDigest, 0, 16);
584                    } else {
585                        mChallengeDigest = null;
586                    }
587                    long id = mListener.getConnectionId();
588                    if (id == -1) {
589                        reply.mConnectionID = null;
590                    } else {
591                        reply.mConnectionID = ObexHelper.convertToByteArray(id);
592                    }
593
594                    head = ObexHelper.createHeader(reply, false);
595                    totalLength += head.length;
596
597                    if (totalLength > mMaxPacketLength) {
598                        totalLength = 7;
599                        head = null;
600                        code = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
601                    }
602                } catch (Exception e) {
603                    e.printStackTrace();
604                    totalLength = 7;
605                    head = null;
606                    code = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
607                }
608
609            }
610        }
611
612        // Compute Length of OBEX CONNECT packet
613        byte[] length = ObexHelper.convertToByteArray(totalLength);
614
615        /*
616         * Write the OBEX CONNECT packet to the server. Byte 0: response code
617         * Byte 1&2: Connect Packet Length Byte 3: OBEX Version Number
618         * (Presently, 0x10) Byte 4: Flags (For TCP 0x00) Byte 5&6: Max OBEX
619         * Packet Length (Defined in MAX_PACKET_SIZE) Byte 7 to n: headers
620         */
621        byte[] sendData = new byte[totalLength];
622        sendData[0] = (byte)code;
623        sendData[1] = length[2];
624        sendData[2] = length[3];
625        sendData[3] = (byte)0x10;
626        sendData[4] = (byte)0x00;
627        sendData[5] = (byte)(ObexHelper.MAX_PACKET_SIZE_INT >> 8);
628        sendData[6] = (byte)(ObexHelper.MAX_PACKET_SIZE_INT & 0xFF);
629
630        if (head != null) {
631            System.arraycopy(head, 0, sendData, 7, head.length);
632        }
633
634        mOutput.write(sendData);
635        mOutput.flush();
636    }
637
638    /**
639     * Closes the server session - in detail close I/O streams and the
640     * underlying transport layer. Internal flag is also set so that later
641     * attempt to read/write will throw an exception.
642     */
643    public synchronized void close() {
644        if (mListener != null) {
645            mListener.onClose();
646        }
647        try {
648            mInput.close();
649            mOutput.close();
650            mTransport.close();
651            mClosed = true;
652        } catch (Exception e) {
653        }
654        mTransport = null;
655        mInput = null;
656        mOutput = null;
657        mListener = null;
658    }
659
660    /**
661     * Verifies that the response code is valid. If it is not valid, it will
662     * return the <code>OBEX_HTTP_INTERNAL_ERROR</code> response code.
663     *
664     * @param code
665     *            the response code to check
666     *
667     * @return the valid response code or <code>OBEX_HTTP_INTERNAL_ERROR</code>
668     *         if <code>code</code> is not valid
669     */
670    private int validateResponseCode(int code) {
671
672        if ((code >= ResponseCodes.OBEX_HTTP_OK) && (code <= ResponseCodes.OBEX_HTTP_PARTIAL)) {
673            return code;
674        }
675        if ((code >= ResponseCodes.OBEX_HTTP_MULT_CHOICE)
676                && (code <= ResponseCodes.OBEX_HTTP_USE_PROXY)) {
677            return code;
678        }
679        if ((code >= ResponseCodes.OBEX_HTTP_BAD_REQUEST)
680                && (code <= ResponseCodes.OBEX_HTTP_UNSUPPORTED_TYPE)) {
681            return code;
682        }
683        if ((code >= ResponseCodes.OBEX_HTTP_INTERNAL_ERROR)
684                && (code <= ResponseCodes.OBEX_HTTP_VERSION)) {
685            return code;
686        }
687        if ((code >= ResponseCodes.OBEX_DATABASE_FULL)
688                && (code <= ResponseCodes.OBEX_DATABASE_LOCKED)) {
689            return code;
690        }
691        return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
692    }
693
694}
695