1/*
2 * Copyright (c) 2014 The Android Open Source Project
3 * Copyright (c) 2008-2009, Motorola, Inc.
4 *
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions are met:
9 *
10 * - Redistributions of source code must retain the above copyright notice,
11 * this list of conditions and the following disclaimer.
12 *
13 * - Redistributions in binary form must reproduce the above copyright notice,
14 * this list of conditions and the following disclaimer in the documentation
15 * and/or other materials provided with the distribution.
16 *
17 * - Neither the name of the Motorola, Inc. nor the names of its contributors
18 * may be used to endorse or promote products derived from this software
19 * without specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
25 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
26 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
27 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
28 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
29 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
30 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31 * POSSIBILITY OF SUCH DAMAGE.
32 */
33
34package javax.obex;
35
36import java.io.IOException;
37import java.io.InputStream;
38import java.io.OutputStream;
39import java.io.DataInputStream;
40import java.io.DataOutputStream;
41import java.io.ByteArrayOutputStream;
42
43/**
44 * This class implements the <code>Operation</code> interface. It will read and
45 * write data via puts and gets.
46 * @hide
47 */
48public final class ClientOperation implements Operation, BaseStream {
49
50    private ClientSession mParent;
51
52    private boolean mInputOpen;
53
54    private PrivateInputStream mPrivateInput;
55
56    private boolean mPrivateInputOpen;
57
58    private PrivateOutputStream mPrivateOutput;
59
60    private boolean mPrivateOutputOpen;
61
62    private String mExceptionMessage;
63
64    private int mMaxPacketSize;
65
66    private boolean mOperationDone;
67
68    private boolean mGetOperation;
69
70    private boolean mGetFinalFlag;
71
72    private HeaderSet mRequestHeader;
73
74    private HeaderSet mReplyHeader;
75
76    private boolean mEndOfBodySent;
77
78    /**
79     * Creates new OperationImpl to read and write data to a server
80     * @param maxSize the maximum packet size
81     * @param p the parent to this object
82     * @param type <code>true</code> if this is a get request;
83     *        <code>false</code. if this is a put request
84     * @param header the header to set in the initial request
85     * @throws IOException if the an IO error occurred
86     */
87    public ClientOperation(int maxSize, ClientSession p, HeaderSet header, boolean type)
88            throws IOException {
89
90        mParent = p;
91        mEndOfBodySent = false;
92        mInputOpen = true;
93        mOperationDone = false;
94        mMaxPacketSize = maxSize;
95        mGetOperation = type;
96        mGetFinalFlag = false;
97
98        mPrivateInputOpen = false;
99        mPrivateOutputOpen = false;
100        mPrivateInput = null;
101        mPrivateOutput = null;
102
103        mReplyHeader = new HeaderSet();
104
105        mRequestHeader = new HeaderSet();
106
107        int[] headerList = header.getHeaderList();
108
109        if (headerList != null) {
110
111            for (int i = 0; i < headerList.length; i++) {
112                mRequestHeader.setHeader(headerList[i], header.getHeader(headerList[i]));
113            }
114        }
115
116        if ((header).mAuthChall != null) {
117            mRequestHeader.mAuthChall = new byte[(header).mAuthChall.length];
118            System.arraycopy((header).mAuthChall, 0, mRequestHeader.mAuthChall, 0,
119                    (header).mAuthChall.length);
120        }
121
122        if ((header).mAuthResp != null) {
123            mRequestHeader.mAuthResp = new byte[(header).mAuthResp.length];
124            System.arraycopy((header).mAuthResp, 0, mRequestHeader.mAuthResp, 0,
125                    (header).mAuthResp.length);
126
127        }
128
129        if ((header).mConnectionID != null) {
130            mRequestHeader.mConnectionID = new byte[4];
131            System.arraycopy((header).mConnectionID, 0, mRequestHeader.mConnectionID, 0,
132                    4);
133
134        }
135    }
136
137    /**
138     * Allows to set flag which will force GET to be always sent as single packet request with
139     * final flag set. This is to improve compatibility with some profiles, i.e. PBAP which
140     * require requests to be sent this way.
141     */
142    public void setGetFinalFlag(boolean flag) {
143        mGetFinalFlag = flag;
144    }
145
146    /**
147     * Sends an ABORT message to the server. By calling this method, the
148     * corresponding input and output streams will be closed along with this
149     * object.
150     * @throws IOException if the transaction has already ended or if an OBEX
151     *         server called this method
152     */
153    public synchronized void abort() throws IOException {
154        ensureOpen();
155        //no compatible with sun-ri
156        if ((mOperationDone) && (mReplyHeader.responseCode != ResponseCodes.OBEX_HTTP_CONTINUE)) {
157            throw new IOException("Operation has already ended");
158        }
159
160        mExceptionMessage = "Operation aborted";
161        if ((!mOperationDone) && (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE)) {
162            mOperationDone = true;
163            /*
164             * Since we are not sending any headers or returning any headers then
165             * we just need to write and read the same bytes
166             */
167            mParent.sendRequest(ObexHelper.OBEX_OPCODE_ABORT, null, mReplyHeader, null);
168
169            if (mReplyHeader.responseCode != ResponseCodes.OBEX_HTTP_OK) {
170                throw new IOException("Invalid response code from server");
171            }
172
173            mExceptionMessage = null;
174        }
175
176        close();
177    }
178
179    /**
180     * Retrieves the response code retrieved from the server. Response codes are
181     * defined in the <code>ResponseCodes</code> interface.
182     * @return the response code retrieved from the server
183     * @throws IOException if an error occurred in the transport layer during
184     *         the transaction; if this method is called on a
185     *         <code>HeaderSet</code> object created by calling
186     *         <code>createHeaderSet</code> in a <code>ClientSession</code>
187     *         object
188     */
189    public synchronized int getResponseCode() throws IOException {
190        //avoid dup validateConnection
191        if ((mReplyHeader.responseCode == -1)
192                || (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE)) {
193            validateConnection();
194        }
195
196        return mReplyHeader.responseCode;
197    }
198
199    /**
200     * This method will always return <code>null</code>
201     * @return <code>null</code>
202     */
203    public String getEncoding() {
204        return null;
205    }
206
207    /**
208     * Returns the type of content that the resource connected to is providing.
209     * E.g. if the connection is via HTTP, then the value of the content-type
210     * header field is returned.
211     * @return the content type of the resource that the URL references, or
212     *         <code>null</code> if not known
213     */
214    public String getType() {
215        try {
216            return (String)mReplyHeader.getHeader(HeaderSet.TYPE);
217        } catch (IOException e) {
218            return null;
219        }
220    }
221
222    /**
223     * Returns the length of the content which is being provided. E.g. if the
224     * connection is via HTTP, then the value of the content-length header field
225     * is returned.
226     * @return the content length of the resource that this connection's URL
227     *         references, or -1 if the content length is not known
228     */
229    public long getLength() {
230        try {
231            Long temp = (Long)mReplyHeader.getHeader(HeaderSet.LENGTH);
232
233            if (temp == null) {
234                return -1;
235            } else {
236                return temp.longValue();
237            }
238        } catch (IOException e) {
239            return -1;
240        }
241    }
242
243    /**
244     * Open and return an input stream for a connection.
245     * @return an input stream
246     * @throws IOException if an I/O error occurs
247     */
248    public InputStream openInputStream() throws IOException {
249
250        ensureOpen();
251
252        if (mPrivateInputOpen)
253            throw new IOException("no more input streams available");
254        if (mGetOperation) {
255            // send the GET request here
256            validateConnection();
257        } else {
258            if (mPrivateInput == null) {
259                mPrivateInput = new PrivateInputStream(this);
260            }
261        }
262
263        mPrivateInputOpen = true;
264
265        return mPrivateInput;
266    }
267
268    /**
269     * Open and return a data input stream for a connection.
270     * @return an input stream
271     * @throws IOException if an I/O error occurs
272     */
273    public DataInputStream openDataInputStream() throws IOException {
274        return new DataInputStream(openInputStream());
275    }
276
277    /**
278     * Open and return an output stream for a connection.
279     * @return an output stream
280     * @throws IOException if an I/O error occurs
281     */
282    public OutputStream openOutputStream() throws IOException {
283
284        ensureOpen();
285        ensureNotDone();
286
287        if (mPrivateOutputOpen)
288            throw new IOException("no more output streams available");
289
290        if (mPrivateOutput == null) {
291            // there are 3 bytes operation headers and 3 bytes body headers //
292            mPrivateOutput = new PrivateOutputStream(this, getMaxPacketSize());
293        }
294
295        mPrivateOutputOpen = true;
296
297        return mPrivateOutput;
298    }
299
300    public int getMaxPacketSize() {
301        return mMaxPacketSize - 6 - getHeaderLength();
302    }
303
304    public int getHeaderLength() {
305        // OPP may need it
306        byte[] headerArray = ObexHelper.createHeader(mRequestHeader, false);
307        return headerArray.length;
308    }
309
310    /**
311     * Open and return a data output stream for a connection.
312     * @return an output stream
313     * @throws IOException if an I/O error occurs
314     */
315    public DataOutputStream openDataOutputStream() throws IOException {
316        return new DataOutputStream(openOutputStream());
317    }
318
319    /**
320     * Closes the connection and ends the transaction
321     * @throws IOException if the operation has already ended or is closed
322     */
323    public void close() throws IOException {
324        mInputOpen = false;
325        mPrivateInputOpen = false;
326        mPrivateOutputOpen = false;
327        mParent.setRequestInactive();
328    }
329
330    /**
331     * Returns the headers that have been received during the operation.
332     * Modifying the object returned has no effect on the headers that are sent
333     * or retrieved.
334     * @return the headers received during this <code>Operation</code>
335     * @throws IOException if this <code>Operation</code> has been closed
336     */
337    public HeaderSet getReceivedHeader() throws IOException {
338        ensureOpen();
339
340        return mReplyHeader;
341    }
342
343    /**
344     * Specifies the headers that should be sent in the next OBEX message that
345     * is sent.
346     * @param headers the headers to send in the next message
347     * @throws IOException if this <code>Operation</code> has been closed or the
348     *         transaction has ended and no further messages will be exchanged
349     * @throws IllegalArgumentException if <code>headers</code> was not created
350     *         by a call to <code>ServerRequestHandler.createHeaderSet()</code>
351     * @throws NullPointerException if <code>headers</code> is <code>null</code>
352     */
353    public void sendHeaders(HeaderSet headers) throws IOException {
354        ensureOpen();
355        if (mOperationDone) {
356            throw new IOException("Operation has already exchanged all data");
357        }
358
359        if (headers == null) {
360            throw new IOException("Headers may not be null");
361        }
362
363        int[] headerList = headers.getHeaderList();
364        if (headerList != null) {
365            for (int i = 0; i < headerList.length; i++) {
366                mRequestHeader.setHeader(headerList[i], headers.getHeader(headerList[i]));
367            }
368        }
369    }
370
371    /**
372     * Verifies that additional information may be sent. In other words, the
373     * operation is not done.
374     * @throws IOException if the operation is completed
375     */
376    public void ensureNotDone() throws IOException {
377        if (mOperationDone) {
378            throw new IOException("Operation has completed");
379        }
380    }
381
382    /**
383     * Verifies that the connection is open and no exceptions should be thrown.
384     * @throws IOException if an exception needs to be thrown
385     */
386    public void ensureOpen() throws IOException {
387        mParent.ensureOpen();
388
389        if (mExceptionMessage != null) {
390            throw new IOException(mExceptionMessage);
391        }
392        if (!mInputOpen) {
393            throw new IOException("Operation has already ended");
394        }
395    }
396
397    /**
398     * Verifies that the connection is open and the proper data has been read.
399     * @throws IOException if an IO error occurs
400     */
401    private void validateConnection() throws IOException {
402        ensureOpen();
403
404        // to sure only one privateInput object exist.
405        if (mPrivateInput == null) {
406            startProcessing();
407        }
408    }
409
410    /**
411     * Sends a request to the client of the specified type
412     * @param opCode the request code to send to the client
413     * @return <code>true</code> if there is more data to send;
414     *         <code>false</code> if there is no more data to send
415     * @throws IOException if an IO error occurs
416     */
417    private boolean sendRequest(int opCode) throws IOException {
418        boolean returnValue = false;
419        ByteArrayOutputStream out = new ByteArrayOutputStream();
420        int bodyLength = -1;
421        byte[] headerArray = ObexHelper.createHeader(mRequestHeader, true);
422        if (mPrivateOutput != null) {
423            bodyLength = mPrivateOutput.size();
424        }
425
426        /*
427         * Determine if there is space to add a body request.  At present
428         * this method checks to see if there is room for at least a 17
429         * byte body header.  This number needs to be at least 6 so that
430         * there is room for the header ID and length and the reply ID and
431         * length, but it is a waste of resources if we can't send much of
432         * the body.
433         */
434        if ((ObexHelper.BASE_PACKET_LENGTH + headerArray.length) > mMaxPacketSize) {
435            int end = 0;
436            int start = 0;
437            // split & send the headerArray in multiple packets.
438
439            while (end != headerArray.length) {
440                //split the headerArray
441                end = ObexHelper.findHeaderEnd(headerArray, start, mMaxPacketSize
442                        - ObexHelper.BASE_PACKET_LENGTH);
443                // can not split
444                if (end == -1) {
445                    mOperationDone = true;
446                    abort();
447                    mExceptionMessage = "Header larger then can be sent in a packet";
448                    mInputOpen = false;
449
450                    if (mPrivateInput != null) {
451                        mPrivateInput.close();
452                    }
453
454                    if (mPrivateOutput != null) {
455                        mPrivateOutput.close();
456                    }
457                    throw new IOException("OBEX Packet exceeds max packet size");
458                }
459
460                byte[] sendHeader = new byte[end - start];
461                System.arraycopy(headerArray, start, sendHeader, 0, sendHeader.length);
462                if (!mParent.sendRequest(opCode, sendHeader, mReplyHeader, mPrivateInput)) {
463                    return false;
464                }
465
466                if (mReplyHeader.responseCode != ResponseCodes.OBEX_HTTP_CONTINUE) {
467                    return false;
468                }
469
470                start = end;
471            }
472
473            if (bodyLength > 0) {
474                return true;
475            } else {
476                return false;
477            }
478        } else {
479            out.write(headerArray);
480        }
481
482        if (bodyLength > 0) {
483            /*
484             * Determine if we can send the whole body or just part of
485             * the body.  Remember that there is the 3 bytes for the
486             * response message and 3 bytes for the header ID and length
487             */
488            if (bodyLength > (mMaxPacketSize - headerArray.length - 6)) {
489                returnValue = true;
490
491                bodyLength = mMaxPacketSize - headerArray.length - 6;
492            }
493
494            byte[] body = mPrivateOutput.readBytes(bodyLength);
495
496            /*
497             * Since this is a put request if the final bit is set or
498             * the output stream is closed we need to send the 0x49
499             * (End of Body) otherwise, we need to send 0x48 (Body)
500             */
501            if ((mPrivateOutput.isClosed()) && (!returnValue) && (!mEndOfBodySent)
502                    && ((opCode & 0x80) != 0)) {
503                out.write(0x49);
504                mEndOfBodySent = true;
505            } else {
506                out.write(0x48);
507            }
508
509            bodyLength += 3;
510            out.write((byte)(bodyLength >> 8));
511            out.write((byte)bodyLength);
512
513            if (body != null) {
514                out.write(body);
515            }
516        }
517
518        if (mPrivateOutputOpen && bodyLength <= 0 && !mEndOfBodySent) {
519            // only 0x82 or 0x83 can send 0x49
520            if ((opCode & 0x80) == 0) {
521                out.write(0x48);
522            } else {
523                out.write(0x49);
524                mEndOfBodySent = true;
525
526            }
527
528            bodyLength = 3;
529            out.write((byte)(bodyLength >> 8));
530            out.write((byte)bodyLength);
531        }
532
533        if (out.size() == 0) {
534            if (!mParent.sendRequest(opCode, null, mReplyHeader, mPrivateInput)) {
535                return false;
536            }
537            return returnValue;
538        }
539        if ((out.size() > 0)
540                && (!mParent.sendRequest(opCode, out.toByteArray(), mReplyHeader, mPrivateInput))) {
541            return false;
542        }
543
544        // send all of the output data in 0x48,
545        // send 0x49 with empty body
546        if ((mPrivateOutput != null) && (mPrivateOutput.size() > 0))
547            returnValue = true;
548
549        return returnValue;
550    }
551
552    /**
553     * This method starts the processing thread results. It will send the
554     * initial request. If the response takes more then one packet, a thread
555     * will be started to handle additional requests
556     * @throws IOException if an IO error occurs
557     */
558    private synchronized void startProcessing() throws IOException {
559
560        if (mPrivateInput == null) {
561            mPrivateInput = new PrivateInputStream(this);
562        }
563        boolean more = true;
564
565        if (mGetOperation) {
566            if (!mOperationDone) {
567                if (!mGetFinalFlag) {
568                    mReplyHeader.responseCode = ResponseCodes.OBEX_HTTP_CONTINUE;
569                    while ((more) && (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE)) {
570                        more = sendRequest(0x03);
571                    }
572
573                    if (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE) {
574                        mParent.sendRequest(0x83, null, mReplyHeader, mPrivateInput);
575                    }
576                    if (mReplyHeader.responseCode != ResponseCodes.OBEX_HTTP_CONTINUE) {
577                        mOperationDone = true;
578                    }
579                } else {
580                    more = sendRequest(0x83);
581
582                    if (more) {
583                        throw new IOException("FINAL_GET forced but data did not fit into single packet!");
584                    }
585
586                    mOperationDone = true;
587                }
588            }
589        } else {
590
591            if (!mOperationDone) {
592                mReplyHeader.responseCode = ResponseCodes.OBEX_HTTP_CONTINUE;
593                while ((more) && (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE)) {
594                    more = sendRequest(0x02);
595
596                }
597            }
598
599            if (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE) {
600                mParent.sendRequest(0x82, null, mReplyHeader, mPrivateInput);
601            }
602
603            if (mReplyHeader.responseCode != ResponseCodes.OBEX_HTTP_CONTINUE) {
604                mOperationDone = true;
605            }
606        }
607    }
608
609    /**
610     * Continues the operation since there is no data to read.
611     * @param sendEmpty <code>true</code> if the operation should send an empty
612     *        packet or not send anything if there is no data to send
613     * @param inStream <code>true</code> if the stream is input stream or is
614     *        output stream
615     * @throws IOException if an IO error occurs
616     */
617    public synchronized boolean continueOperation(boolean sendEmpty, boolean inStream)
618            throws IOException {
619
620        if (mGetOperation) {
621            if ((inStream) && (!mOperationDone)) {
622                // to deal with inputstream in get operation
623                mParent.sendRequest(0x83, null, mReplyHeader, mPrivateInput);
624                /*
625                  * Determine if that was not the last packet in the operation
626                  */
627                if (mReplyHeader.responseCode != ResponseCodes.OBEX_HTTP_CONTINUE) {
628                    mOperationDone = true;
629                }
630
631                return true;
632
633            } else if ((!inStream) && (!mOperationDone)) {
634                // to deal with outputstream in get operation
635
636                if (mPrivateInput == null) {
637                    mPrivateInput = new PrivateInputStream(this);
638                }
639
640                if (!mGetFinalFlag) {
641                    sendRequest(0x03);
642                } else {
643                    sendRequest(0x83);
644
645                    if (mReplyHeader.responseCode != ResponseCodes.OBEX_HTTP_CONTINUE) {
646                        mOperationDone = true;
647                    }
648                }
649                return true;
650
651            } else if (mOperationDone) {
652                return false;
653            }
654
655        } else {
656            if ((!inStream) && (!mOperationDone)) {
657                // to deal with outputstream in put operation
658                if (mReplyHeader.responseCode == -1) {
659                    mReplyHeader.responseCode = ResponseCodes.OBEX_HTTP_CONTINUE;
660                }
661                sendRequest(0x02);
662                return true;
663            } else if ((inStream) && (!mOperationDone)) {
664                // How to deal with inputstream  in put operation ?
665                return false;
666
667            } else if (mOperationDone) {
668                return false;
669            }
670
671        }
672        return false;
673    }
674
675    /**
676     * Called when the output or input stream is closed.
677     * @param inStream <code>true</code> if the input stream is closed;
678     *        <code>false</code> if the output stream is closed
679     * @throws IOException if an IO error occurs
680     */
681    public void streamClosed(boolean inStream) throws IOException {
682        if (!mGetOperation) {
683            if ((!inStream) && (!mOperationDone)) {
684                // to deal with outputstream in put operation
685
686                boolean more = true;
687
688                if ((mPrivateOutput != null) && (mPrivateOutput.size() <= 0)) {
689                    byte[] headerArray = ObexHelper.createHeader(mRequestHeader, false);
690                    if (headerArray.length <= 0)
691                        more = false;
692                }
693                // If have not sent any data so send  all now
694                if (mReplyHeader.responseCode == -1) {
695                    mReplyHeader.responseCode = ResponseCodes.OBEX_HTTP_CONTINUE;
696                }
697
698                while ((more) && (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE)) {
699                    more = sendRequest(0x02);
700                }
701
702                /*
703                 * According to the IrOBEX specification, after the final put, you
704                 * only have a single reply to send.  so we don't need the while
705                 * loop.
706                 */
707                while (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE) {
708
709                    sendRequest(0x82);
710                }
711                mOperationDone = true;
712            } else if ((inStream) && (mOperationDone)) {
713                // how to deal with input stream in put stream ?
714                mOperationDone = true;
715            }
716        } else {
717            if ((inStream) && (!mOperationDone)) {
718
719                // to deal with inputstream in get operation
720                // Have not sent any data so send it all now
721
722                if (mReplyHeader.responseCode == -1) {
723                    mReplyHeader.responseCode = ResponseCodes.OBEX_HTTP_CONTINUE;
724                }
725
726                while (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE) {
727                    if (!sendRequest(0x83)) {
728                        break;
729                    }
730                }
731                while (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE) {
732                    mParent.sendRequest(0x83, null, mReplyHeader, mPrivateInput);
733                }
734                mOperationDone = true;
735            } else if ((!inStream) && (!mOperationDone)) {
736                // to deal with outputstream in get operation
737                // part of the data may have been sent in continueOperation.
738
739                boolean more = true;
740
741                if ((mPrivateOutput != null) && (mPrivateOutput.size() <= 0)) {
742                    byte[] headerArray = ObexHelper.createHeader(mRequestHeader, false);
743                    if (headerArray.length <= 0)
744                        more = false;
745                }
746
747                if (mPrivateInput == null) {
748                    mPrivateInput = new PrivateInputStream(this);
749                }
750                if ((mPrivateOutput != null) && (mPrivateOutput.size() <= 0))
751                    more = false;
752
753                mReplyHeader.responseCode = ResponseCodes.OBEX_HTTP_CONTINUE;
754                while ((more) && (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE)) {
755                    more = sendRequest(0x03);
756                }
757                sendRequest(0x83);
758                //                parent.sendRequest(0x83, null, replyHeaders, privateInput);
759                if (mReplyHeader.responseCode != ResponseCodes.OBEX_HTTP_CONTINUE) {
760                    mOperationDone = true;
761                }
762            }
763        }
764    }
765
766    public void noBodyHeader(){
767    }
768}
769