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