diff options
Diffstat (limited to 'obex/javax/obex/ServerOperation.java')
-rw-r--r-- | obex/javax/obex/ServerOperation.java | 772 |
1 files changed, 772 insertions, 0 deletions
diff --git a/obex/javax/obex/ServerOperation.java b/obex/javax/obex/ServerOperation.java new file mode 100644 index 0000000..1b9c4e9 --- /dev/null +++ b/obex/javax/obex/ServerOperation.java @@ -0,0 +1,772 @@ +/* + * Copyright (c) 2008-2009, Motorola, Inc. + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * - Neither the name of the Motorola, Inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package javax.obex; + +import java.io.IOException; +import java.io.InputStream; +import java.io.DataInputStream; +import java.io.OutputStream; +import java.io.DataOutputStream; +import java.io.ByteArrayOutputStream; + +/** + * This class implements the Operation interface for server side connections. + * <P> + * <STRONG>Request Codes</STRONG> + * There are four different request codes that are in this class. 0x02 is a + * PUT request that signals that the request is not complete and requires an + * additional OBEX packet. 0x82 is a PUT request that says that request is + * complete. In this case, the server can begin sending the response. The + * 0x03 is a GET request that signals that the request is not finished. When + * the server receives a 0x83, the client is signalling the server that it is + * done with its request. + * + * OPTIMIZATION: Extend the ClientOperation and reuse the methods defined + * OPTIMIZATION: in that class. + * + * @version 0.3 November 28, 2008 + */ +public class ServerOperation implements Operation, BaseStream { + + private InputStream socketInput; + + private ServerSession parent; + + private int maxPacketLength; + + private int responseSize; + + private boolean isClosed; + + boolean finalBitSet; + + private boolean endOfBody; // This variable defines when the end of body + + // header has been received. When this header + // is received, no further body data will be + // received from the client + private boolean isGet; + + boolean isAborted; + + HeaderSet requestHeaders; + + HeaderSet replyHeaders; + + PrivateInputStream privateInput; + + private PrivateOutputStream privateOutput; + + private String exceptionString; + + private ServerRequestHandler listener; + + private boolean outputStreamOpened; + + private boolean requestFinished; + + private static int BASE_PACKET_LENGTH = 3; + + private static final String TAG = "ServerOperation"; + + private boolean isHasBody; + + /** + * Creates new PutServerOperation + * + * @param p the parent that created this object + * + * @param in the input stream to read from + * + * @param out the output stream to write to + * + * @param request the initial request that was received from the client + * + * @param maxSize the max packet size that the client will accept + * + * @param listen the listener that is responding to the request + * + * @exception IOException if an IO error occurs + */ + public ServerOperation(ServerSession p, InputStream in, int request, int maxSize, + ServerRequestHandler listen) throws IOException { + + isAborted = false; + parent = p; + socketInput = in; + maxPacketLength = maxSize; + isClosed = false; + requestHeaders = new HeaderSet(); + replyHeaders = new HeaderSet(); + privateInput = new PrivateInputStream(this); + endOfBody = false; + responseSize = 3; + listener = listen; + requestFinished = false; + outputStreamOpened = false; + isHasBody = false; + int bytesReceived; + + /* + * Determine if this is a PUT request + */ + if ((request == 0x02) || (request == 0x82)) { + /* + * It is a PUT request. + */ + isGet = false; + } else { + /* + * It is a GET request. + */ + isGet = true; + } + + /* + * Determine if the final bit is set + */ + if ((request & 0x80) == 0) { + finalBitSet = false; + } else { + finalBitSet = true; + requestFinished = true; + } + + int length = in.read(); + length = (length << 8) + in.read(); + + /* + * Determine if the packet length is larger than this device can receive + */ + if (length > OBEXConstants.MAX_PACKET_SIZE_INT) { + parent.sendResponse(ResponseCodes.OBEX_HTTP_REQ_TOO_LARGE, null); + throw new IOException("Packet received was too large"); + } + + /* + * Determine if any headers were sent in the initial request + */ + if (length > 3) { + byte[] data = new byte[length - 3]; + bytesReceived = in.read(data); + + while (bytesReceived != data.length) { + bytesReceived += in.read(data, bytesReceived, data.length - bytesReceived); + } + + byte[] body = OBEXHelper.updateHeaderSet(requestHeaders, data); + + if (body != null) { + isHasBody = true; + } + + if (requestHeaders.connectionID != null) { + listener.setConnectionID(OBEXHelper.convertToLong(requestHeaders.connectionID)); + } else { + listener.setConnectionID(0); + } + + if (requestHeaders.authResp != null) { + if (!parent.handleAuthResp(requestHeaders.authResp)) { + exceptionString = "Authentication Failed"; + parent.sendResponse(ResponseCodes.OBEX_HTTP_UNAUTHORIZED, null); + isClosed = true; + requestHeaders.authResp = null; + return; + } + } + + if (requestHeaders.authChall != null) { + parent.handleAuthChall(requestHeaders); + // send the authResp to the client + replyHeaders.authResp = new byte[requestHeaders.authResp.length]; + System.arraycopy(requestHeaders.authResp, 0, replyHeaders.authResp, 0, + replyHeaders.authResp.length); + requestHeaders.authResp = null; + requestHeaders.authChall = null; + + } + + if (body != null) { + /* + * 0x49 is the end of body header. This signifies that no more + * body data will be sent from the client + */ + if (body[0] == 0x49) { + endOfBody = true; + } + //privateInput.writeBytes(body, body.length); + //byte [] body_tmp = new byte[body.length-1]; + //System.arraycopy(body,1,body_tmp,0,body.length-1); + //privateInput.writeBytes(body_tmp, body.length-1); + privateInput.writeBytes(body, 1); + } else { + while ((!isGet) && (!finalBitSet)) { + sendReply(OBEXConstants.OBEX_HTTP_CONTINUE); + if (privateInput.available() > 0) { + break; + } + } + }// if (body != null) + + }// if (length > 3) + + while ((!isGet) && (!finalBitSet) && (privateInput.available() == 0)) { + sendReply(OBEXConstants.OBEX_HTTP_CONTINUE); + if (privateInput.available() > 0) { + break; + } + } + + // wait for get request finished !!!! + while (isGet && !finalBitSet) { + sendReply(OBEXConstants.OBEX_HTTP_CONTINUE); + } + if (finalBitSet && isGet) { + requestFinished = true; + } + } + + public synchronized boolean isValidBody() { + return isHasBody; + } + + /** + * Determines if the operation should continue or should wait. If it + * should continue, this method will continue the operation. + * + * @param sendEmpty if <code>true</code> then this will continue the + * operation even if no headers will be sent; if <code>false</code> then + * this method will only continue the operation if there are headers to + * send + * @param isStream if<code>true</code> the stream is input stream or + * is outputstream + * @return <code>true</code> if the operation was completed; + * <code>false</code> if no operation took place + */ + public synchronized boolean continueOperation(boolean sendEmpty, boolean inStream) + throws IOException { + if (!isGet) { + if (!finalBitSet) { + if (sendEmpty) { + sendReply(OBEXConstants.OBEX_HTTP_CONTINUE); + return true; + } else { + if ((responseSize > 3) || (privateOutput.size() > 0)) { + sendReply(OBEXConstants.OBEX_HTTP_CONTINUE); + return true; + } else { + return false; + } + } + } else { + return false; + } + } else { + sendReply(OBEXConstants.OBEX_HTTP_CONTINUE); + return true; + } + } + + /** + * Sends a reply to the client. If the reply is a OBEX_HTTP_CONTINUE, it + * will wait for a response from the client before ending. + * + * @param type the response code to send back to the client + * + * @return <code>true</code> if the final bit was not set on the reply; + * <code>false</code> if no reply was received because the operation ended, + * an abort was received, or the final bit was set in the reply + * + * @exception IOException if an IO error occurs + */ + protected synchronized boolean sendReply(int type) throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + int bytesReceived; + + long id = listener.getConnectionID(); + if (id == -1) { + replyHeaders.connectionID = null; + } else { + replyHeaders.connectionID = OBEXHelper.convertToByteArray(id); + } + + byte[] headerArray = OBEXHelper.createHeader(replyHeaders, true); + int bodyLength = -1; + int orginalBodyLength = -1; + + if (privateOutput != null) { + bodyLength = privateOutput.size(); + orginalBodyLength = bodyLength; + } + + if ((BASE_PACKET_LENGTH + headerArray.length) > maxPacketLength) { + + int end = 0; + int start = 0; + + while (end != headerArray.length) { + end = OBEXHelper.findHeaderEnd(headerArray, start, maxPacketLength + - BASE_PACKET_LENGTH); + if (end == -1) { + + isClosed = true; + + if (privateInput != null) { + privateInput.close(); + } + + if (privateOutput != null) { + privateOutput.close(); + } + parent.sendResponse(ResponseCodes.OBEX_HTTP_INTERNAL_ERROR, null); + throw new IOException("OBEX Packet exceeds max packet size"); + } + byte[] sendHeader = new byte[end - start]; + System.arraycopy(headerArray, start, sendHeader, 0, sendHeader.length); + + parent.sendResponse(type, sendHeader); + start = end; + } + + if (bodyLength > 0) { + return true; + } else { + return false; + } + + } else { + out.write(headerArray); + } + + /* + * Determine if there is space to add a body reply. First, we need to + * verify that the client is finished sending the request. Next, there + * needs to be enough space to send the headers already defined along + * with the reply header (3 bytes) and the body header identifier + * (3 bytes). + */ + + /* if ((finalBitSet) && + ((bodyLength + 6 + headerArray.length) > maxPacketLength)) { + + exceptionString = "Header larger then can be sent in a packet"; + isClosed = true; + privateInput.close(); + if (privateOutput != null) { + privateOutput.close(); + } + parent.sendResponse(ResponseCodes.OBEX_HTTP_INTERNAL_ERROR, + null); + throw new IOException("OBEX Packet exceeds max packet size"); + } + */ + + if ((finalBitSet) || (headerArray.length < (maxPacketLength - 20))) { + if (bodyLength > 0) { + /* + * Determine if I can send the whole body or just part of + * the body. Remember that there is the 3 bytes for the + * response message and 3 bytes for the header ID and length + */ + if (bodyLength > (maxPacketLength - headerArray.length - 6)) { + bodyLength = maxPacketLength - headerArray.length - 6; + } + + byte[] body = privateOutput.readBytes(bodyLength); + + /* + * Since this is a put request if the final bit is set or + * the output stream is closed we need to send the 0x49 + * (End of Body) otherwise, we need to send 0x48 (Body) + */ + if ((finalBitSet) || (privateOutput.isClosed())) { + out.write(0x49); + } else { + out.write(0x48); + } + + bodyLength += 3; + out.write((byte)(bodyLength >> 8)); + out.write((byte)bodyLength); + out.write(body); + } + } + + if ((finalBitSet) && (type == ResponseCodes.OBEX_HTTP_OK) && (orginalBodyLength <= 0)) { + out.write(0x49); + orginalBodyLength = 3; + out.write((byte)(orginalBodyLength >> 8)); + out.write((byte)orginalBodyLength); + + } + + responseSize = 3; + parent.sendResponse(type, out.toByteArray()); + + if (type == OBEXConstants.OBEX_HTTP_CONTINUE) { + int headerID = socketInput.read(); + int length = socketInput.read(); + length = (length << 8) + socketInput.read(); + if ((headerID != 0x02) && (headerID != 0x82) && (headerID != 0x03) + && (headerID != 0x83)) { + + if (length > 3) { + byte[] temp = new byte[length]; + bytesReceived = socketInput.read(temp); + + while (bytesReceived != length) { + bytesReceived += socketInput.read(temp, bytesReceived, length + - bytesReceived); + } + } + + /* + * Determine if an ABORT was sent as the reply + */ + if (headerID == 0xFF) { + parent.sendResponse(ResponseCodes.OBEX_HTTP_OK, null); + isClosed = true; + isAborted = true; + exceptionString = "Abort Received"; + throw new IOException("Abort Received"); + } else { + parent.sendResponse(ResponseCodes.OBEX_HTTP_BAD_REQUEST, null); + isClosed = true; + exceptionString = "Bad Request Received"; + throw new IOException("Bad Request Received"); + } + } else { + + if ((headerID == 0x82) || (headerID == 0x83)) { + finalBitSet = true; + } + + /* + * Determine if the packet length is larger then this device can receive + */ + if (length > OBEXConstants.MAX_PACKET_SIZE_INT) { + parent.sendResponse(ResponseCodes.OBEX_HTTP_REQ_TOO_LARGE, null); + throw new IOException("Packet received was too large"); + } + + /* + * Determine if any headers were sent in the initial request + */ + if (length > 3) { + byte[] data = new byte[length - 3]; + bytesReceived = socketInput.read(data); + + while (bytesReceived != data.length) { + bytesReceived += socketInput.read(data, bytesReceived, data.length + - bytesReceived); + } + byte[] body = OBEXHelper.updateHeaderSet(requestHeaders, data); + if (body != null) { + isHasBody = true; + } + if (requestHeaders.connectionID != null) { + listener.setConnectionID(OBEXHelper + .convertToLong(requestHeaders.connectionID)); + } else { + listener.setConnectionID(1); + } + + if (requestHeaders.authResp != null) { + if (!parent.handleAuthResp(requestHeaders.authResp)) { + exceptionString = "Authentication Failed"; + parent.sendResponse(ResponseCodes.OBEX_HTTP_UNAUTHORIZED, null); + isClosed = true; + requestHeaders.authResp = null; + return false; + } + requestHeaders.authResp = null; + } + + if (requestHeaders.authChall != null) { + parent.handleAuthChall(requestHeaders); + // send the auhtResp to the client + replyHeaders.authResp = new byte[requestHeaders.authResp.length]; + System.arraycopy(requestHeaders.authResp, 0, replyHeaders.authResp, 0, + replyHeaders.authResp.length); + requestHeaders.authResp = null; + requestHeaders.authChall = null; + } + + if (body != null) { + if (body[0] == 0x49) { + endOfBody = true; + } + + /*byte [] body_tmp = new byte[body.length-1]; + System.arraycopy(body,1,body_tmp,0,body.length-1); + privateInput.writeBytes(body_tmp, body.length-1);*/ + privateInput.writeBytes(body, 1); + + } + } + } + return true; + } else { + return false; + } + } + + /** + * Sends an ABORT message to the server. By calling this method, the + * corresponding input and output streams will be closed along with this + * object. + * + * @exception IOException if the transaction has already ended or if an + * OBEX server called this method + */ + public void abort() throws IOException { + throw new IOException("Called from a server"); + } + + /** + * Returns the headers that have been received during the operation. + * Modifying the object returned has no effect on the headers that are + * sent or retrieved. + * + * @return the headers received during this <code>Operation</code> + * + * @exception IOException if this <code>Operation</code> has been closed + */ + public HeaderSet getReceivedHeaders() throws IOException { + ensureOpen(); + return requestHeaders; + } + + /** + * Specifies the headers that should be sent in the next OBEX message that + * is sent. + * + * @param headers the headers to send in the next message + * + * @exception IOException if this <code>Operation</code> has been closed + * or the transaction has ended and no further messages will be exchanged + * + * @exception IllegalArgumentException if <code>headers</code> was not created + * by a call to <code>ServerRequestHandler.createHeaderSet()</code> + */ + public void sendHeaders(HeaderSet headers) throws IOException { + ensureOpen(); + + if (headers == null) { + throw new NullPointerException("Headers may not be null"); + } + + int[] headerList = headers.getHeaderList(); + if (headerList != null) { + for (int i = 0; i < headerList.length; i++) { + replyHeaders.setHeader(headerList[i], headers.getHeader(headerList[i])); + } + + } + } + + /** + * Retrieves the response code retrieved from the server. Response codes + * are defined in the <code>ResponseCodes</code> interface. + * + * @return the response code retrieved from the server + * + * @exception IOException if an error occurred in the transport layer during + * the transaction; if this method is called on a <code>HeaderSet</code> + * object created by calling <code>createHeaderSet</code> in a + * <code>ClientSession</code> object; if this is called from a server + */ + public int getResponseCode() throws IOException { + throw new IOException("Called from a server"); + } + + /** + * Always returns <code>null</code> + * + * @return <code>null</code> + */ + public String getEncoding() { + return null; + } + + /** + * Returns the type of content that the resource connected to is providing. + * E.g. if the connection is via HTTP, then the value of the content-type + * header field is returned. + * + * @return the content type of the resource that the URL references, or + * <code>null</code> if not known + */ + public String getType() { + try { + return (String)requestHeaders.getHeader(HeaderSet.TYPE); + } catch (IOException e) { + return null; + } + } + + /** + * Returns the length of the content which is being provided. E.g. if the + * connection is via HTTP, then the value of the content-length header + * field is returned. + * + * @return the content length of the resource that this connection's URL + * references, or -1 if the content length is not known + */ + public long getLength() { + try { + Long temp = (Long)requestHeaders.getHeader(HeaderSet.LENGTH); + + if (temp == null) { + return -1; + } else { + return temp.longValue(); + } + } catch (IOException e) { + return -1; + } + } + + public int getMaxPacketSize() { + return maxPacketLength - 6; + } + + /** + * Open and return an input stream for a connection. + * + * @return an input stream + * + * @exception IOException if an I/O error occurs + */ + public InputStream openInputStream() throws IOException { + ensureOpen(); + return privateInput; + } + + /** + * Open and return a data input stream for a connection. + * + * @return an input stream + * + * @exception IOException if an I/O error occurs + */ + public DataInputStream openDataInputStream() throws IOException { + return new DataInputStream(openInputStream()); + } + + /** + * Open and return an output stream for a connection. + * + * @return an output stream + * + * @exception IOException if an I/O error occurs + */ + public OutputStream openOutputStream() throws IOException { + ensureOpen(); + + if (outputStreamOpened) + throw new IOException("no more input streams available, stream already opened"); + + if (!requestFinished) + throw new IOException("no output streams available ,request not finished"); + + if (privateOutput == null) { + privateOutput = new PrivateOutputStream(this, maxPacketLength - 6); + } + outputStreamOpened = true; + return privateOutput; + } + + /** + * Open and return a data output stream for a connection. + * + * @return an output stream + * + * @exception IOException if an I/O error occurs + */ + public DataOutputStream openDataOutputStream() throws IOException { + return new DataOutputStream(openOutputStream()); + } + + /** + * Closes the connection and ends the transaction + * + * @exception IOException if the operation has already ended or is closed + */ + public void close() throws IOException { + ensureOpen(); + isClosed = true; + } + + /** + * Verifies that the connection is open and no exceptions should be thrown. + * + * @exception IOException if an exception needs to be thrown + */ + public void ensureOpen() throws IOException { + if (exceptionString != null) { + throw new IOException(exceptionString); + } + if (isClosed) { + throw new IOException("Operation has already ended"); + } + } + + /** + * Verifies that additional information may be sent. In other words, the + * operation is not done. + * <P> + * Included to implement the BaseStream interface only. It does not do + * anything on the server side since the operation of the Operation object + * is not done until after the handler returns from its method. + * + * @exception IOException if the operation is completed + */ + public void ensureNotDone() throws IOException { + } + + /** + * Called when the output or input stream is closed. It does not do + * anything on the server side since the operation of the Operation object + * is not done until after the handler returns from its method. + * + * @param inStream <code>true</code> if the input stream is closed; + * <code>false</code> if the output stream is closed + * + * @exception IOException if an IO error occurs + */ + public void streamClosed(boolean inStream) throws IOException { + + } +} |