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