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