ServerOperation.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 java.io.IOException;
36import java.io.InputStream;
37import java.io.DataInputStream;
38import java.io.OutputStream;
39import java.io.DataOutputStream;
40import java.io.ByteArrayOutputStream;
41
42/**
43 * This class implements the Operation interface for server side connections.
44 * <P>
45 * <STRONG>Request Codes</STRONG>
46 * There are four different request codes that are in this class.  0x02 is a
47 * PUT request that signals that the request is not complete and requires an
48 * additional OBEX packet.  0x82 is a PUT request that says that request is
49 * complete.  In this case, the server can begin sending the response.  The
50 * 0x03 is a GET request that signals that the request is not finished.  When
51 * the server receives a 0x83, the client is signaling the server that it is
52 * done with its request.
53 *
54 * TODO: Extend the ClientOperation and reuse the methods defined
55 * TODO: in that class.
56 *
57 * @hide
58 */
59public final class ServerOperation implements Operation, BaseStream {
60
61    public boolean isAborted;
62
63    public HeaderSet requestHeader;
64
65    public HeaderSet replyHeader;
66
67    public boolean finalBitSet;
68
69    private InputStream mInput;
70
71    private ServerSession mParent;
72
73    private int mMaxPacketLength;
74
75    private int mResponseSize;
76
77    private boolean mClosed;
78
79    private boolean mGetOperation;
80
81    private PrivateInputStream mPrivateInput;
82
83    private PrivateOutputStream mPrivateOutput;
84
85    private boolean mPrivateOutputOpen;
86
87    private String mExceptionString;
88
89    private ServerRequestHandler mListener;
90
91    private boolean mRequestFinished;
92
93    private boolean mHasBody;
94
95    /**
96     * Creates new ServerOperation
97     *
98     * @param p the parent that created this object
99     *
100     * @param in the input stream to read from
101     *
102     * @param out the output stream to write to
103     *
104     * @param request the initial request that was received from the client
105     *
106     * @param maxSize the max packet size that the client will accept
107     *
108     * @param listen the listener that is responding to the request
109     *
110     * @throws IOException if an IO error occurs
111     */
112    public ServerOperation(ServerSession p, InputStream in, int request, int maxSize,
113            ServerRequestHandler listen) throws IOException {
114
115        isAborted = false;
116        mParent = p;
117        mInput = in;
118        mMaxPacketLength = maxSize;
119        mClosed = false;
120        requestHeader = new HeaderSet();
121        replyHeader = new HeaderSet();
122        mPrivateInput = new PrivateInputStream(this);
123        mResponseSize = 3;
124        mListener = listen;
125        mRequestFinished = false;
126        mPrivateOutputOpen = false;
127        mHasBody = false;
128        int bytesReceived;
129
130        /*
131         * Determine if this is a PUT request
132         */
133        if ((request == 0x02) || (request == 0x82)) {
134            /*
135             * It is a PUT request.
136             */
137            mGetOperation = false;
138        } else {
139            /*
140             * It is a GET request.
141             */
142            mGetOperation = true;
143        }
144
145        /*
146         * Determine if the final bit is set
147         */
148        if ((request & 0x80) == 0) {
149            finalBitSet = false;
150        } else {
151            finalBitSet = true;
152            mRequestFinished = true;
153        }
154
155        int length = in.read();
156        length = (length << 8) + in.read();
157
158        /*
159         * Determine if the packet length is larger than this device can receive
160         */
161        if (length > ObexHelper.MAX_PACKET_SIZE_INT) {
162            mParent.sendResponse(ResponseCodes.OBEX_HTTP_REQ_TOO_LARGE, null);
163            throw new IOException("Packet received was too large");
164        }
165
166        /*
167         * Determine if any headers were sent in the initial request
168         */
169        if (length > 3) {
170            byte[] data = new byte[length - 3];
171            bytesReceived = in.read(data);
172
173            while (bytesReceived != data.length) {
174                bytesReceived += in.read(data, bytesReceived, data.length - bytesReceived);
175            }
176
177            byte[] body = ObexHelper.updateHeaderSet(requestHeader, data);
178
179            if (body != null) {
180                mHasBody = true;
181            }
182
183            if (requestHeader.mConnectionID != null) {
184                mListener.setConnectionId(ObexHelper.convertToLong(requestHeader.mConnectionID));
185            } else {
186                mListener.setConnectionId(0);
187            }
188
189            if (requestHeader.mAuthResp != null) {
190                if (!mParent.handleAuthResp(requestHeader.mAuthResp)) {
191                    mExceptionString = "Authentication Failed";
192                    mParent.sendResponse(ResponseCodes.OBEX_HTTP_UNAUTHORIZED, null);
193                    mClosed = true;
194                    requestHeader.mAuthResp = null;
195                    return;
196                }
197            }
198
199            if (requestHeader.mAuthChall != null) {
200                mParent.handleAuthChall(requestHeader);
201                // send the  authResp to the client
202                replyHeader.mAuthResp = new byte[requestHeader.mAuthResp.length];
203                System.arraycopy(requestHeader.mAuthResp, 0, replyHeader.mAuthResp, 0,
204                        replyHeader.mAuthResp.length);
205                requestHeader.mAuthResp = null;
206                requestHeader.mAuthChall = null;
207
208            }
209
210            if (body != null) {
211                mPrivateInput.writeBytes(body, 1);
212            } else {
213                while ((!mGetOperation) && (!finalBitSet)) {
214                    sendReply(ResponseCodes.OBEX_HTTP_CONTINUE);
215                    if (mPrivateInput.available() > 0) {
216                        break;
217                    }
218                }
219            }
220        }
221
222        while ((!mGetOperation) && (!finalBitSet) && (mPrivateInput.available() == 0)) {
223            sendReply(ResponseCodes.OBEX_HTTP_CONTINUE);
224            if (mPrivateInput.available() > 0) {
225                break;
226            }
227        }
228
229        // wait for get request finished !!!!
230        while (mGetOperation && !finalBitSet) {
231            sendReply(ResponseCodes.OBEX_HTTP_CONTINUE);
232        }
233        if (finalBitSet && mGetOperation) {
234            mRequestFinished = true;
235        }
236    }
237
238    public boolean isValidBody() {
239        return mHasBody;
240    }
241
242    /**
243     * Determines if the operation should continue or should wait.  If it
244     * should continue, this method will continue the operation.
245     *
246     * @param sendEmpty if <code>true</code> then this will continue the
247     * operation even if no headers will be sent; if <code>false</code> then
248     * this method will only continue the operation if there are headers to
249     * send
250     * @param isStream  if<code>true</code> the stream is input stream, otherwise
251     * output stream
252     * @return <code>true</code> if the operation was completed;
253     * <code>false</code> if no operation took place
254     */
255    public synchronized boolean continueOperation(boolean sendEmpty, boolean inStream)
256            throws IOException {
257        if (!mGetOperation) {
258            if (!finalBitSet) {
259                if (sendEmpty) {
260                    sendReply(ResponseCodes.OBEX_HTTP_CONTINUE);
261                    return true;
262                } else {
263                    if ((mResponseSize > 3) || (mPrivateOutput.size() > 0)) {
264                        sendReply(ResponseCodes.OBEX_HTTP_CONTINUE);
265                        return true;
266                    } else {
267                        return false;
268                    }
269                }
270            } else {
271                return false;
272            }
273        } else {
274            sendReply(ResponseCodes.OBEX_HTTP_CONTINUE);
275            return true;
276        }
277    }
278
279    /**
280     * Sends a reply to the client.  If the reply is a OBEX_HTTP_CONTINUE, it
281     * will wait for a response from the client before ending.
282     *
283     * @param type the response code to send back to the client
284     *
285     * @return <code>true</code> if the final bit was not set on the reply;
286     * <code>false</code> if no reply was received because the operation ended,
287     * an abort was received, or the final bit was set in the reply
288     *
289     * @throws IOException if an IO error occurs
290     */
291    public synchronized boolean sendReply(int type) throws IOException {
292        ByteArrayOutputStream out = new ByteArrayOutputStream();
293        int bytesReceived;
294
295        long id = mListener.getConnectionId();
296        if (id == -1) {
297            replyHeader.mConnectionID = null;
298        } else {
299            replyHeader.mConnectionID = ObexHelper.convertToByteArray(id);
300        }
301
302        byte[] headerArray = ObexHelper.createHeader(replyHeader, true);
303        int bodyLength = -1;
304        int orginalBodyLength = -1;
305
306        if (mPrivateOutput != null) {
307            bodyLength = mPrivateOutput.size();
308            orginalBodyLength = bodyLength;
309        }
310
311        if ((ObexHelper.BASE_PACKET_LENGTH + headerArray.length) > mMaxPacketLength) {
312
313            int end = 0;
314            int start = 0;
315
316            while (end != headerArray.length) {
317                end = ObexHelper.findHeaderEnd(headerArray, start, mMaxPacketLength
318                        - ObexHelper.BASE_PACKET_LENGTH);
319                if (end == -1) {
320
321                    mClosed = true;
322
323                    if (mPrivateInput != null) {
324                        mPrivateInput.close();
325                    }
326
327                    if (mPrivateOutput != null) {
328                        mPrivateOutput.close();
329                    }
330                    mParent.sendResponse(ResponseCodes.OBEX_HTTP_INTERNAL_ERROR, null);
331                    throw new IOException("OBEX Packet exceeds max packet size");
332                }
333                byte[] sendHeader = new byte[end - start];
334                System.arraycopy(headerArray, start, sendHeader, 0, sendHeader.length);
335
336                mParent.sendResponse(type, sendHeader);
337                start = end;
338            }
339
340            if (bodyLength > 0) {
341                return true;
342            } else {
343                return false;
344            }
345
346        } else {
347            out.write(headerArray);
348        }
349
350        if ((finalBitSet) || (headerArray.length < (mMaxPacketLength - 20))) {
351            if (bodyLength > 0) {
352                /*
353                 * Determine if I can send the whole body or just part of
354                 * the body.  Remember that there is the 3 bytes for the
355                 * response message and 3 bytes for the header ID and length
356                 */
357                if (bodyLength > (mMaxPacketLength - headerArray.length - 6)) {
358                    bodyLength = mMaxPacketLength - headerArray.length - 6;
359                }
360
361                byte[] body = mPrivateOutput.readBytes(bodyLength);
362
363                /*
364                 * Since this is a put request if the final bit is set or
365                 * the output stream is closed we need to send the 0x49
366                 * (End of Body) otherwise, we need to send 0x48 (Body)
367                 */
368                if ((finalBitSet) || (mPrivateOutput.isClosed())) {
369                    out.write(0x49);
370                } else {
371                    out.write(0x48);
372                }
373
374                bodyLength += 3;
375                out.write((byte)(bodyLength >> 8));
376                out.write((byte)bodyLength);
377                out.write(body);
378            }
379        }
380
381        if ((finalBitSet) && (type == ResponseCodes.OBEX_HTTP_OK) && (orginalBodyLength <= 0)) {
382            out.write(0x49);
383            orginalBodyLength = 3;
384            out.write((byte)(orginalBodyLength >> 8));
385            out.write((byte)orginalBodyLength);
386
387        }
388
389        mResponseSize = 3;
390        mParent.sendResponse(type, out.toByteArray());
391
392        if (type == ResponseCodes.OBEX_HTTP_CONTINUE) {
393            int headerID = mInput.read();
394            int length = mInput.read();
395            length = (length << 8) + mInput.read();
396            if ((headerID != ObexHelper.OBEX_OPCODE_PUT)
397                    && (headerID != ObexHelper.OBEX_OPCODE_PUT_FINAL)
398                    && (headerID != ObexHelper.OBEX_OPCODE_GET)
399                    && (headerID != ObexHelper.OBEX_OPCODE_GET_FINAL)) {
400
401                if (length > 3) {
402                    byte[] temp = new byte[length];
403                    bytesReceived = mInput.read(temp);
404
405                    while (bytesReceived != length) {
406                        bytesReceived += mInput.read(temp, bytesReceived, length - bytesReceived);
407                    }
408                }
409
410                /*
411                 * Determine if an ABORT was sent as the reply
412                 */
413                if (headerID == ObexHelper.OBEX_OPCODE_ABORT) {
414                    mParent.sendResponse(ResponseCodes.OBEX_HTTP_OK, null);
415                    mClosed = true;
416                    isAborted = true;
417                    mExceptionString = "Abort Received";
418                    throw new IOException("Abort Received");
419                } else {
420                    mParent.sendResponse(ResponseCodes.OBEX_HTTP_BAD_REQUEST, null);
421                    mClosed = true;
422                    mExceptionString = "Bad Request Received";
423                    throw new IOException("Bad Request Received");
424                }
425            } else {
426
427                if ((headerID == ObexHelper.OBEX_OPCODE_PUT_FINAL)
428                        || (headerID == ObexHelper.OBEX_OPCODE_GET_FINAL)) {
429                    finalBitSet = true;
430                }
431
432                /*
433                 * Determine if the packet length is larger then this device can receive
434                 */
435                if (length > ObexHelper.MAX_PACKET_SIZE_INT) {
436                    mParent.sendResponse(ResponseCodes.OBEX_HTTP_REQ_TOO_LARGE, null);
437                    throw new IOException("Packet received was too large");
438                }
439
440                /*
441                 * Determine if any headers were sent in the initial request
442                 */
443                if (length > 3) {
444                    byte[] data = new byte[length - 3];
445                    bytesReceived = mInput.read(data);
446
447                    while (bytesReceived != data.length) {
448                        bytesReceived += mInput.read(data, bytesReceived, data.length
449                                - bytesReceived);
450                    }
451                    byte[] body = ObexHelper.updateHeaderSet(requestHeader, data);
452                    if (body != null) {
453                        mHasBody = true;
454                    }
455                    if (requestHeader.mConnectionID != null) {
456                        mListener.setConnectionId(ObexHelper
457                                .convertToLong(requestHeader.mConnectionID));
458                    } else {
459                        mListener.setConnectionId(1);
460                    }
461
462                    if (requestHeader.mAuthResp != null) {
463                        if (!mParent.handleAuthResp(requestHeader.mAuthResp)) {
464                            mExceptionString = "Authentication Failed";
465                            mParent.sendResponse(ResponseCodes.OBEX_HTTP_UNAUTHORIZED, null);
466                            mClosed = true;
467                            requestHeader.mAuthResp = null;
468                            return false;
469                        }
470                        requestHeader.mAuthResp = null;
471                    }
472
473                    if (requestHeader.mAuthChall != null) {
474                        mParent.handleAuthChall(requestHeader);
475                        // send the auhtResp to the client
476                        replyHeader.mAuthResp = new byte[requestHeader.mAuthResp.length];
477                        System.arraycopy(requestHeader.mAuthResp, 0, replyHeader.mAuthResp, 0,
478                                replyHeader.mAuthResp.length);
479                        requestHeader.mAuthResp = null;
480                        requestHeader.mAuthChall = null;
481                    }
482
483                    if (body != null) {
484                        mPrivateInput.writeBytes(body, 1);
485                    }
486                }
487            }
488            return true;
489        } else {
490            return false;
491        }
492    }
493
494    /**
495     * Sends an ABORT message to the server.  By calling this method, the
496     * corresponding input and output streams will be closed along with this
497     * object.
498     *
499     * @throws IOException if the transaction has already ended or if an
500     * OBEX server called this method
501     */
502    public void abort() throws IOException {
503        throw new IOException("Called from a server");
504    }
505
506    /**
507     * Returns the headers that have been received during the operation.
508     * Modifying the object returned has no effect on the headers that are
509     * sent or retrieved.
510     *
511     * @return the headers received during this <code>Operation</code>
512     *
513     * @throws IOException if this <code>Operation</code> has been closed
514     */
515    public HeaderSet getReceivedHeader() throws IOException {
516        ensureOpen();
517        return requestHeader;
518    }
519
520    /**
521     * Specifies the headers that should be sent in the next OBEX message that
522     * is sent.
523     *
524     * @param headers the headers to send in the next message
525     *
526     * @throws IOException  if this <code>Operation</code> has been closed
527     * or the transaction has ended and no further messages will be exchanged
528     *
529     * @throws IllegalArgumentException if <code>headers</code> was not created
530     * by a call to <code>ServerRequestHandler.createHeaderSet()</code>
531     */
532    public void sendHeaders(HeaderSet headers) throws IOException {
533        ensureOpen();
534
535        if (headers == null) {
536            throw new IOException("Headers may not be null");
537        }
538
539        int[] headerList = headers.getHeaderList();
540        if (headerList != null) {
541            for (int i = 0; i < headerList.length; i++) {
542                replyHeader.setHeader(headerList[i], headers.getHeader(headerList[i]));
543            }
544
545        }
546    }
547
548    /**
549     * Retrieves the response code retrieved from the server.  Response codes
550     * are defined in the <code>ResponseCodes</code> interface.
551     *
552     * @return the response code retrieved from the server
553     *
554     * @throws IOException if an error occurred in the transport layer during
555     * the transaction; if this method is called on a <code>HeaderSet</code>
556     * object created by calling <code>createHeaderSet</code> in a
557     * <code>ClientSession</code> object; if this is called from a server
558     */
559    public int getResponseCode() throws IOException {
560        throw new IOException("Called from a server");
561    }
562
563    /**
564     * Always returns <code>null</code>
565     *
566     * @return <code>null</code>
567     */
568    public String getEncoding() {
569        return null;
570    }
571
572    /**
573     * Returns the type of content that the resource connected to is providing.
574     * E.g. if the connection is via HTTP, then the value of the content-type
575     * header field is returned.
576     *
577     * @return the content type of the resource that the URL references, or
578     * <code>null</code> if not known
579     */
580    public String getType() {
581        try {
582            return (String)requestHeader.getHeader(HeaderSet.TYPE);
583        } catch (IOException e) {
584            return null;
585        }
586    }
587
588    /**
589     * Returns the length of the content which is being provided. E.g. if the
590     * connection is via HTTP, then the value of the content-length header
591     * field is returned.
592     *
593     * @return the content length of the resource that this connection's URL
594     * references, or -1 if the content length is not known
595     */
596    public long getLength() {
597        try {
598            Long temp = (Long)requestHeader.getHeader(HeaderSet.LENGTH);
599
600            if (temp == null) {
601                return -1;
602            } else {
603                return temp.longValue();
604            }
605        } catch (IOException e) {
606            return -1;
607        }
608    }
609
610    public int getMaxPacketSize() {
611        return mMaxPacketLength - 6;
612    }
613
614    /**
615     * Open and return an input stream for a connection.
616     *
617     * @return an input stream
618     *
619     * @throws IOException if an I/O error occurs
620     */
621    public InputStream openInputStream() throws IOException {
622        ensureOpen();
623        return mPrivateInput;
624    }
625
626    /**
627     * Open and return a data input stream for a connection.
628     *
629     * @return an input stream
630     *
631     * @throws IOException if an I/O error occurs
632     */
633    public DataInputStream openDataInputStream() throws IOException {
634        return new DataInputStream(openInputStream());
635    }
636
637    /**
638     * Open and return an output stream for a connection.
639     *
640     * @return an output stream
641     *
642     * @throws IOException if an I/O error occurs
643     */
644    public OutputStream openOutputStream() throws IOException {
645        ensureOpen();
646
647        if (mPrivateOutputOpen) {
648            throw new IOException("no more input streams available, stream already opened");
649        }
650
651        if (!mRequestFinished) {
652            throw new IOException("no  output streams available ,request not finished");
653        }
654
655        if (mPrivateOutput == null) {
656            mPrivateOutput = new PrivateOutputStream(this, mMaxPacketLength - 6);
657        }
658        mPrivateOutputOpen = true;
659        return mPrivateOutput;
660    }
661
662    /**
663     * Open and return a data output stream for a connection.
664     *
665     * @return an output stream
666     *
667     * @throws IOException if an I/O error occurs
668     */
669    public DataOutputStream openDataOutputStream() throws IOException {
670        return new DataOutputStream(openOutputStream());
671    }
672
673    /**
674     * Closes the connection and ends the transaction
675     *
676     * @throws IOException if the operation has already ended or is closed
677     */
678    public void close() throws IOException {
679        ensureOpen();
680        mClosed = true;
681    }
682
683    /**
684     * Verifies that the connection is open and no exceptions should be thrown.
685     *
686     * @throws IOException if an exception needs to be thrown
687     */
688    public void ensureOpen() throws IOException {
689        if (mExceptionString != null) {
690            throw new IOException(mExceptionString);
691        }
692        if (mClosed) {
693            throw new IOException("Operation has already ended");
694        }
695    }
696
697    /**
698     * Verifies that additional information may be sent.  In other words, the
699     * operation is not done.
700     * <P>
701     * Included to implement the BaseStream interface only.  It does not do
702     * anything on the server side since the operation of the Operation object
703     * is not done until after the handler returns from its method.
704     *
705     * @throws IOException if the operation is completed
706     */
707    public void ensureNotDone() throws IOException {
708    }
709
710    /**
711     * Called when the output or input stream is closed.  It does not do
712     * anything on the server side since the operation of the Operation object
713     * is not done until after the handler returns from its method.
714     *
715     * @param inStream <code>true</code> if the input stream is closed;
716     * <code>false</code> if the output stream is closed
717     *
718     * @throws IOException if an IO error occurs
719     */
720    public void streamClosed(boolean inStream) throws IOException {
721
722    }
723}
724