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