2 * Copyright (c) 2008-2009, Motorola, Inc.
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions are met:
9 * - Redistributions of source code must retain the above copyright notice,
10 * this list of conditions and the following disclaimer.
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.
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.
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.
35 import java.io.IOException;
36 import java.io.InputStream;
37 import java.io.DataInputStream;
38 import java.io.OutputStream;
39 import java.io.DataOutputStream;
40 import java.io.ByteArrayOutputStream;
43 * This class implements the Operation interface for server side connections.
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.
55 public final class ServerOperation implements Operation, BaseStream {
57 public boolean isAborted;
59 public HeaderSet requestHeader;
61 public HeaderSet replyHeader;
63 public boolean finalBitSet;
65 private InputStream mInput;
67 private ServerSession mParent;
69 private int mMaxPacketLength;
71 private int mResponseSize;
73 private boolean mClosed;
75 private boolean mGetOperation;
77 private PrivateInputStream mPrivateInput;
79 private PrivateOutputStream mPrivateOutput;
81 private boolean mPrivateOutputOpen;
83 private String mExceptionString;
85 private ServerRequestHandler mListener;
87 private boolean mRequestFinished;
89 private boolean mHasBody;
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
101 public ServerOperation(ServerSession p, InputStream in, int request, int maxSize,
102 ServerRequestHandler listen) throws IOException {
107 mMaxPacketLength = maxSize;
109 requestHeader = new HeaderSet();
110 replyHeader = new HeaderSet();
111 mPrivateInput = new PrivateInputStream(this);
114 mRequestFinished = false;
115 mPrivateOutputOpen = false;
120 * Determine if this is a PUT request
122 if ((request == 0x02) || (request == 0x82)) {
124 * It is a PUT request.
126 mGetOperation = false;
129 * Determine if the final bit is set
131 if ((request & 0x80) == 0) {
135 mRequestFinished = true;
137 } else if ((request == 0x03) || (request == 0x83)) {
139 * It is a GET request.
141 mGetOperation = true;
143 // For Get request, final bit set is decided by server side logic
146 if (request == 0x83) {
147 mRequestFinished = true;
150 throw new IOException("ServerOperation can not handle such request");
153 int length = in.read();
154 length = (length << 8) + in.read();
157 * Determine if the packet length is larger than this device can receive
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");
165 * Determine if any headers were sent in the initial request
168 byte[] data = new byte[length - 3];
169 bytesReceived = in.read(data);
171 while (bytesReceived != data.length) {
172 bytesReceived += in.read(data, bytesReceived, data.length - bytesReceived);
175 byte[] body = ObexHelper.updateHeaderSet(requestHeader, data);
181 if (mListener.getConnectionId() != -1 && requestHeader.mConnectionID != null) {
182 mListener.setConnectionId(ObexHelper.convertToLong(requestHeader.mConnectionID));
184 mListener.setConnectionId(1);
187 if (requestHeader.mAuthResp != null) {
188 if (!mParent.handleAuthResp(requestHeader.mAuthResp)) {
189 mExceptionString = "Authentication Failed";
190 mParent.sendResponse(ResponseCodes.OBEX_HTTP_UNAUTHORIZED, null);
192 requestHeader.mAuthResp = null;
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;
209 mPrivateInput.writeBytes(body, 1);
211 while ((!mGetOperation) && (!finalBitSet)) {
212 sendReply(ResponseCodes.OBEX_HTTP_CONTINUE);
213 if (mPrivateInput.available() > 0) {
220 while ((!mGetOperation) && (!finalBitSet) && (mPrivateInput.available() == 0)) {
221 sendReply(ResponseCodes.OBEX_HTTP_CONTINUE);
222 if (mPrivateInput.available() > 0) {
227 // wait for get request finished !!!!
228 while (mGetOperation && !mRequestFinished) {
229 sendReply(ResponseCodes.OBEX_HTTP_CONTINUE);
233 public boolean isValidBody() {
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
244 * @param inStream if<code>true</code> the stream is input stream, otherwise
246 * @return <code>true</code> if the operation was completed;
247 * <code>false</code> if no operation took place
249 public synchronized boolean continueOperation(boolean sendEmpty, boolean inStream)
251 if (!mGetOperation) {
254 sendReply(ResponseCodes.OBEX_HTTP_CONTINUE);
257 if ((mResponseSize > 3) || (mPrivateOutput.size() > 0)) {
258 sendReply(ResponseCodes.OBEX_HTTP_CONTINUE);
268 sendReply(ResponseCodes.OBEX_HTTP_CONTINUE);
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
281 * @throws IOException if an IO error occurs
283 public synchronized boolean sendReply(int type) throws IOException {
284 ByteArrayOutputStream out = new ByteArrayOutputStream();
287 long id = mListener.getConnectionId();
289 replyHeader.mConnectionID = null;
291 replyHeader.mConnectionID = ObexHelper.convertToByteArray(id);
294 byte[] headerArray = ObexHelper.createHeader(replyHeader, true);
296 int orginalBodyLength = -1;
298 if (mPrivateOutput != null) {
299 bodyLength = mPrivateOutput.size();
300 orginalBodyLength = bodyLength;
303 if ((ObexHelper.BASE_PACKET_LENGTH + headerArray.length) > mMaxPacketLength) {
308 while (end != headerArray.length) {
309 end = ObexHelper.findHeaderEnd(headerArray, start, mMaxPacketLength
310 - ObexHelper.BASE_PACKET_LENGTH);
315 if (mPrivateInput != null) {
316 mPrivateInput.close();
319 if (mPrivateOutput != null) {
320 mPrivateOutput.close();
322 mParent.sendResponse(ResponseCodes.OBEX_HTTP_INTERNAL_ERROR, null);
323 throw new IOException("OBEX Packet exceeds max packet size");
325 byte[] sendHeader = new byte[end - start];
326 System.arraycopy(headerArray, start, sendHeader, 0, sendHeader.length);
328 mParent.sendResponse(type, sendHeader);
332 if (bodyLength > 0) {
339 out.write(headerArray);
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) {
348 if ((finalBitSet) || (headerArray.length < (mMaxPacketLength - 20))) {
349 if (bodyLength > 0) {
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
355 if (bodyLength > (mMaxPacketLength - headerArray.length - 6)) {
356 bodyLength = mMaxPacketLength - headerArray.length - 6;
359 byte[] body = mPrivateOutput.readBytes(bodyLength);
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)
366 if ((finalBitSet) || (mPrivateOutput.isClosed())) {
373 out.write((byte)(bodyLength >> 8));
374 out.write((byte)bodyLength);
379 if ((finalBitSet) && (type == ResponseCodes.OBEX_HTTP_OK) && (orginalBodyLength <= 0)) {
381 orginalBodyLength = 3;
382 out.write((byte)(orginalBodyLength >> 8));
383 out.write((byte)orginalBodyLength);
388 mParent.sendResponse(type, out.toByteArray());
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)) {
400 byte[] temp = new byte[length];
401 bytesReceived = mInput.read(temp);
403 while (bytesReceived != length) {
404 bytesReceived += mInput.read(temp, bytesReceived, length - bytesReceived);
409 * Determine if an ABORT was sent as the reply
411 if (headerID == ObexHelper.OBEX_OPCODE_ABORT) {
412 mParent.sendResponse(ResponseCodes.OBEX_HTTP_OK, null);
415 mExceptionString = "Abort Received";
416 throw new IOException("Abort Received");
418 mParent.sendResponse(ResponseCodes.OBEX_HTTP_BAD_REQUEST, null);
420 mExceptionString = "Bad Request Received";
421 throw new IOException("Bad Request Received");
425 if ((headerID == ObexHelper.OBEX_OPCODE_PUT_FINAL)) {
427 } else if (headerID == ObexHelper.OBEX_OPCODE_GET_FINAL) {
428 mRequestFinished = true;
432 * Determine if the packet length is larger then this device can receive
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");
440 * Determine if any headers were sent in the initial request
443 byte[] data = new byte[length - 3];
444 bytesReceived = mInput.read(data);
446 while (bytesReceived != data.length) {
447 bytesReceived += mInput.read(data, bytesReceived, data.length
450 byte[] body = ObexHelper.updateHeaderSet(requestHeader, data);
454 if (mListener.getConnectionId() != -1 && requestHeader.mConnectionID != null) {
455 mListener.setConnectionId(ObexHelper
456 .convertToLong(requestHeader.mConnectionID));
458 mListener.setConnectionId(1);
461 if (requestHeader.mAuthResp != null) {
462 if (!mParent.handleAuthResp(requestHeader.mAuthResp)) {
463 mExceptionString = "Authentication Failed";
464 mParent.sendResponse(ResponseCodes.OBEX_HTTP_UNAUTHORIZED, null);
466 requestHeader.mAuthResp = null;
469 requestHeader.mAuthResp = null;
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;
483 mPrivateInput.writeBytes(body, 1);
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
497 * @throws IOException if the transaction has already ended or if an OBEX
498 * server called this method
500 public void abort() throws IOException {
501 throw new IOException("Called from a server");
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
508 * @return the headers received during this <code>Operation</code>
509 * @throws IOException if this <code>Operation</code> has been closed
511 public HeaderSet getReceivedHeader() throws IOException {
513 return requestHeader;
517 * Specifies the headers that should be sent in the next OBEX message that
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>
525 public void sendHeaders(HeaderSet headers) throws IOException {
528 if (headers == null) {
529 throw new IOException("Headers may not be null");
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]));
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
551 public int getResponseCode() throws IOException {
552 throw new IOException("Called from a server");
556 * Always returns <code>null</code>
557 * @return <code>null</code>
559 public String getEncoding() {
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
570 public String getType() {
572 return (String)requestHeader.getHeader(HeaderSet.TYPE);
573 } catch (IOException e) {
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
582 * @return the content length of the resource that this connection's URL
583 * references, or -1 if the content length is not known
585 public long getLength() {
587 Long temp = (Long)requestHeader.getHeader(HeaderSet.LENGTH);
592 return temp.longValue();
594 } catch (IOException e) {
599 public int getMaxPacketSize() {
600 return mMaxPacketLength - 6 - getHeaderLength();
603 public int getHeaderLength() {
604 long id = mListener.getConnectionId();
606 replyHeader.mConnectionID = null;
608 replyHeader.mConnectionID = ObexHelper.convertToByteArray(id);
611 byte[] headerArray = ObexHelper.createHeader(replyHeader, false);
613 return headerArray.length;
617 * Open and return an input stream for a connection.
618 * @return an input stream
619 * @throws IOException if an I/O error occurs
621 public InputStream openInputStream() throws IOException {
623 return mPrivateInput;
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
631 public DataInputStream openDataInputStream() throws IOException {
632 return new DataInputStream(openInputStream());
636 * Open and return an output stream for a connection.
637 * @return an output stream
638 * @throws IOException if an I/O error occurs
640 public OutputStream openOutputStream() throws IOException {
643 if (mPrivateOutputOpen) {
644 throw new IOException("no more input streams available, stream already opened");
647 if (!mRequestFinished) {
648 throw new IOException("no output streams available ,request not finished");
651 if (mPrivateOutput == null) {
652 mPrivateOutput = new PrivateOutputStream(this, getMaxPacketSize());
654 mPrivateOutputOpen = true;
655 return mPrivateOutput;
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
663 public DataOutputStream openDataOutputStream() throws IOException {
664 return new DataOutputStream(openOutputStream());
668 * Closes the connection and ends the transaction
669 * @throws IOException if the operation has already ended or is closed
671 public void close() throws IOException {
677 * Verifies that the connection is open and no exceptions should be thrown.
678 * @throws IOException if an exception needs to be thrown
680 public void ensureOpen() throws IOException {
681 if (mExceptionString != null) {
682 throw new IOException(mExceptionString);
685 throw new IOException("Operation has already ended");
690 * Verifies that additional information may be sent. In other words, the
691 * operation is not done.
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
698 public void ensureNotDone() throws IOException {
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
709 public void streamClosed(boolean inStream) throws IOException {