diff options
Diffstat (limited to 'obex')
-rw-r--r-- | obex/Android.mk | 9 | ||||
-rw-r--r-- | obex/javax/obex/ApplicationParameter.java | 134 | ||||
-rw-r--r-- | obex/javax/obex/Authenticator.java | 124 | ||||
-rw-r--r-- | obex/javax/obex/BaseStream.java | 83 | ||||
-rw-r--r-- | obex/javax/obex/ClientOperation.java | 811 | ||||
-rw-r--r-- | obex/javax/obex/ClientSession.java | 536 | ||||
-rw-r--r-- | obex/javax/obex/HeaderSet.java | 650 | ||||
-rw-r--r-- | obex/javax/obex/ObexHelper.java | 1019 | ||||
-rw-r--r-- | obex/javax/obex/ObexSession.java | 226 | ||||
-rw-r--r-- | obex/javax/obex/ObexTransport.java | 77 | ||||
-rw-r--r-- | obex/javax/obex/Operation.java | 200 | ||||
-rw-r--r-- | obex/javax/obex/PasswordAuthentication.java | 85 | ||||
-rw-r--r-- | obex/javax/obex/PrivateInputStream.java | 191 | ||||
-rw-r--r-- | obex/javax/obex/PrivateOutputStream.java | 184 | ||||
-rw-r--r-- | obex/javax/obex/ResponseCodes.java | 327 | ||||
-rw-r--r-- | obex/javax/obex/ServerOperation.java | 723 | ||||
-rw-r--r-- | obex/javax/obex/ServerRequestHandler.java | 291 | ||||
-rw-r--r-- | obex/javax/obex/ServerSession.java | 694 | ||||
-rw-r--r-- | obex/javax/obex/SessionNotifier.java | 167 |
19 files changed, 6531 insertions, 0 deletions
diff --git a/obex/Android.mk b/obex/Android.mk new file mode 100644 index 0000000..fbfe9be --- /dev/null +++ b/obex/Android.mk @@ -0,0 +1,9 @@ +LOCAL_PATH:= $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := $(call all-subdir-java-files) + +LOCAL_MODULE:= javax.obex + +include $(BUILD_JAVA_LIBRARY) diff --git a/obex/javax/obex/ApplicationParameter.java b/obex/javax/obex/ApplicationParameter.java new file mode 100644 index 0000000..a62210f --- /dev/null +++ b/obex/javax/obex/ApplicationParameter.java @@ -0,0 +1,134 @@ +/* + * 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; + +/** + * @hide + */ +public final class ApplicationParameter { + + private byte[] mArray; + + private int mLength; + + private int mMaxLength = 1000; + + public static class TRIPLET_TAGID { + public static final byte ORDER_TAGID = 0x01; + + public static final byte SEARCH_VALUE_TAGID = 0x02; + + public static final byte SEARCH_ATTRIBUTE_TAGID = 0x03; + + // if equals to "0", PSE only reply number of contacts + public static final byte MAXLISTCOUNT_TAGID = 0x04; + + public static final byte LISTSTARTOFFSET_TAGID = 0x05; + + public static final byte FILTER_TAGID = 0x06; + + public static final byte FORMAT_TAGID = 0x07; + + // only used if max list count = 0 + public static final byte PHONEBOOKSIZE_TAGID = 0x08; + + // only used in "mch" in response + public static final byte NEWMISSEDCALLS_TAGID = 0x09; + } + + public static class TRIPLET_VALUE { + public static class ORDER { + public static final byte ORDER_BY_INDEX = 0x00; + + public static final byte ORDER_BY_ALPHANUMERIC = 0x01; + + public static final byte ORDER_BY_PHONETIC = 0x02; + } + + public static class SEARCHATTRIBUTE { + public static final byte SEARCH_BY_NAME = 0x00; + + public static final byte SEARCH_BY_NUMBER = 0x01; + + public static final byte SEARCH_BY_SOUND = 0x02; + } + + public static class FORMAT { + public static final byte VCARD_VERSION_21 = 0x00; + + public static final byte VCARD_VERSION_30 = 0x01; + } + } + + public static class TRIPLET_LENGTH { + public static final byte ORDER_LENGTH = 1; + + public static final byte SEARCH_ATTRIBUTE_LENGTH = 1; + + public static final byte MAXLISTCOUNT_LENGTH = 2; + + public static final byte LISTSTARTOFFSET_LENGTH = 2; + + public static final byte FILTER_LENGTH = 8; + + public static final byte FORMAT_LENGTH = 1; + + public static final byte PHONEBOOKSIZE_LENGTH = 2; + + public static final byte NEWMISSEDCALLS_LENGTH = 1; + } + + public ApplicationParameter() { + mArray = new byte[mMaxLength]; + mLength = 0; + } + + public void addAPPHeader(byte tag, byte len, byte[] value) { + if ((mLength + len + 2) > mMaxLength) { + byte[] array_tmp = new byte[mLength + 4 * len]; + System.arraycopy(mArray, 0, array_tmp, 0, mLength); + mArray = array_tmp; + mMaxLength = mLength + 4 * len; + } + mArray[mLength++] = tag; + mArray[mLength++] = len; + System.arraycopy(value, 0, mArray, mLength, len); + mLength += len; + } + + public byte[] getAPPparam() { + byte[] para = new byte[mLength]; + System.arraycopy(mArray, 0, para, 0, mLength); + return para; + } +} diff --git a/obex/javax/obex/Authenticator.java b/obex/javax/obex/Authenticator.java new file mode 100644 index 0000000..7246e91 --- /dev/null +++ b/obex/javax/obex/Authenticator.java @@ -0,0 +1,124 @@ +/* + * 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; + +/** + * This interface provides a way to respond to authentication challenge and + * authentication response headers. When a client or server receives an + * authentication challenge or authentication response header, the + * <code>onAuthenticationChallenge()</code> or + * <code>onAuthenticationResponse()</code> will be called, respectively, by + * the implementation. + * <P> + * For more information on how the authentication procedure works in OBEX, + * please review the IrOBEX specification at + * <A HREF="http://www.irda.org">http://www.irda.org</A>. + * <P> + * <STRONG>Authentication Challenges</STRONG> + * <P> + * When a client or server receives an authentication challenge header, the + * <code>onAuthenticationChallenge()</code> method will be invoked by the + * OBEX API implementation. The application will then return the user name + * (if needed) and password via a <code>PasswordAuthentication</code> object. + * The password in this object is not sent in the authentication response. + * Instead, the 16-byte challenge received in the authentication challenge is + * combined with the password returned from the + * <code>onAuthenticationChallenge()</code> method and passed through the MD5 + * hash algorithm. The resulting value is sent in the authentication response + * along with the user name if it was provided. + * <P> + * <STRONG>Authentication Responses</STRONG> + * <P> + * When a client or server receives an authentication response header, the + * <code>onAuthenticationResponse()</code> method is invoked by the API + * implementation with the user name received in the authentication response + * header. (The user name will be <code>null</code> if no user name was + * provided in the authentication response header.) The application must + * determine the correct password. This value should be returned from the + * <code>onAuthenticationResponse()</code> method. If the authentication + * request should fail without the implementation checking the password, + * <code>null</code> should + * be returned by the application. (This is needed for reasons like not + * recognizing the user name, etc.) If the returned value is not + * <code>null</code>, the OBEX API implementation will combine the password + * returned from the <code>onAuthenticationResponse()</code> method and + * challenge sent via the authentication challenge, apply the MD5 hash + * algorithm, and compare the result to the response hash received in the + * authentication response header. If the values are not equal, an + * <code>IOException</code> will be thrown if the client requested authentication. + * If the server requested authentication, the + * <code>onAuthenticationFailure()</code> method will be called on the + * <code>ServerRequestHandler</code> that failed authentication. The + * connection is <B>not</B> closed if authentication failed. + * + * @hide + */ +public interface Authenticator { + + /** + * Called when a client or a server receives an authentication challenge + * header. It should respond to the challenge with a + * <code>PasswordAuthentication</code> that contains the correct user name + * and password for the challenge. + * + * @param description the description of which user name and password + * should be used; if no description is provided in the authentication + * challenge or the description is encoded in an encoding scheme that is + * not supported, an empty string will be provided + * + * @param isUserIdRequired <code>true</code> if the user ID is required; + * <code>false</code> if the user ID is not required + * + * @param isFullAccess <code>true</code> if full access to the server + * will be granted; <code>false</code> if read only access will be + * granted + * + * @return a <code>PasswordAuthentication</code> object containing the + * user name and password used for authentication + */ + PasswordAuthentication onAuthenticationChallenge(String description, boolean isUserIdRequired, + boolean isFullAccess); + + /** + * Called when a client or server receives an authentication response + * header. This method will provide the user name and expect the correct + * password to be returned. + * + * @param userName the user name provided in the authentication response; + * may be <code>null</code> + * + * @return the correct password for the user name provided; if + * <code>null</code> is returned then the authentication request failed + */ + byte[] onAuthenticationResponse(byte[] userName); +} diff --git a/obex/javax/obex/BaseStream.java b/obex/javax/obex/BaseStream.java new file mode 100644 index 0000000..c32717f --- /dev/null +++ b/obex/javax/obex/BaseStream.java @@ -0,0 +1,83 @@ +/* + * 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; + +/** + * This interface defines the methods needed by a parent that uses the + * PrivateInputStream and PrivateOutputStream objects defined in this package. + * + * @hide + */ +public interface BaseStream { + + /** + * Verifies that this object is still open. + * + * @throws IOException if the object is closed + */ + void ensureOpen() throws IOException; + + /** + * Verifies that additional information may be sent. In other words, the + * operation is not done. + * + * @throws IOException if the operation is completed + */ + void ensureNotDone() throws IOException; + + /** + * Continues the operation since there is no data to read. + * + * @param sendEmpty <code>true</code> if the operation should send an + * empty packet or not send anything if there is no data to send + * @param inStream <code>true</code> if the stream is input stream or + * is output stream + * @return <code>true</code> if the operation was completed; + * <code>false</code> if no operation took place + * + * @throws IOException if an IO error occurs + */ + boolean continueOperation(boolean sendEmpty, boolean inStream) throws IOException; + + /** + * Called when the output or input stream is closed. + * + * @param inStream <code>true</code> if the input stream is closed; + * <code>false</code> if the output stream is closed + * + * @throws IOException if an IO error occurs + */ + void streamClosed(boolean inStream) throws IOException; +} diff --git a/obex/javax/obex/ClientOperation.java b/obex/javax/obex/ClientOperation.java new file mode 100644 index 0000000..b3807af --- /dev/null +++ b/obex/javax/obex/ClientOperation.java @@ -0,0 +1,811 @@ +/* + * 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.OutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.ByteArrayOutputStream; + +/** + * This class implements the <code>Operation</code> interface. It will read + * and write data via puts and gets. + * + * @hide + */ +public final class ClientOperation implements Operation, BaseStream { + + private ClientSession mParent; + + private boolean mInputOpen; + + private PrivateInputStream mPrivateInput; + + private boolean mPrivateInputOpen; + + private PrivateOutputStream mPrivateOutput; + + private boolean mPrivateOutputOpen; + + private String mExceptionMessage; + + private int mMaxPacketSize; + + private boolean mOperationDone; + + private boolean mGetOperation; + + private HeaderSet mRequestHeader; + + private HeaderSet mReplyHeader; + + private boolean mEndOfBodySent; + + /** + * Creates new OperationImpl to read and write data to a server + * @param maxSize the maximum packet size + * @param p the parent to this object + * @param type <code>true</code> if this is a get request; + * <code>false</code. if this is a put request + * @param headers the headers to set in the initial request + * + * @throws IOExcpetion if the an IO error occured + */ + public ClientOperation(int maxSize, ClientSession p, HeaderSet header, boolean type) + throws IOException { + + mParent = p; + mEndOfBodySent = false; + mInputOpen = true; + mOperationDone = false; + mMaxPacketSize = maxSize; + mGetOperation = type; + + mPrivateInputOpen = false; + mPrivateOutputOpen = false; + mPrivateInput = null; + mPrivateOutput = null; + + mReplyHeader = new HeaderSet(); + + mRequestHeader = new HeaderSet(); + + int[] headerList = header.getHeaderList(); + + if (headerList != null) { + + for (int i = 0; i < headerList.length; i++) { + mRequestHeader.setHeader(headerList[i], header.getHeader(headerList[i])); + } + } + + if ((header).mAuthChall != null) { + mRequestHeader.mAuthChall = new byte[(header).mAuthChall.length]; + System.arraycopy((header).mAuthChall, 0, mRequestHeader.mAuthChall, 0, + (header).mAuthChall.length); + } + + if ((header).mAuthResp != null) { + mRequestHeader.mAuthResp = new byte[(header).mAuthResp.length]; + System.arraycopy((header).mAuthResp, 0, mRequestHeader.mAuthResp, 0, + (header).mAuthResp.length); + + } + } + + /** + * Sends an ABORT message to the server. By calling this method, the + * corresponding input and output streams will be closed along with this + * object. + * + * @throws IOException if the transaction has already ended or if an + * OBEX server called this method + */ + public synchronized void abort() throws IOException { + ensureOpen(); + // need check again . + // if(isDone) { + // throw new IOException("Operation has already ended"); + // } + + //no compatible with sun-ri + if ((mOperationDone) && (mReplyHeader.responseCode != ResponseCodes.OBEX_HTTP_CONTINUE)) { + throw new IOException("Operation has already ended"); + } + + mExceptionMessage = "Operation aborted"; + if ((!mOperationDone) && (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE)) { + mOperationDone = true; + /* + * Since we are not sending any headers or returning any headers then + * we just need to write and read the same bytes + */ + mParent.sendRequest(ObexHelper.OBEX_OPCODE_ABORT, null, mReplyHeader, null); + + if (mReplyHeader.responseCode != ResponseCodes.OBEX_HTTP_OK) { + throw new IOException("Invalid response code from server"); + } + + mExceptionMessage = null; + } + + close(); + } + + /** + * 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 + * + * @throws 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 + */ + public synchronized int getResponseCode() throws IOException { + //avoid dup validateConnection + if ((mReplyHeader.responseCode == -1) + || (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE)) { + validateConnection(); + } + + return mReplyHeader.responseCode; + } + + /** + * This method will always return <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)mReplyHeader.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)mReplyHeader.getHeader(HeaderSet.LENGTH); + + if (temp == null) { + return -1; + } else { + return temp.longValue(); + } + } catch (IOException e) { + return -1; + } + } + + /** + * Open and return an input stream for a connection. + * + * @return an input stream + * + * @throws IOException if an I/O error occurs + */ + public InputStream openInputStream() throws IOException { + + ensureOpen(); + + if (mPrivateInputOpen) + throw new IOException("no more input streams available"); + if (mGetOperation) { + // send the GET request here + validateConnection(); + } else { + if (mPrivateInput == null) { + mPrivateInput = new PrivateInputStream(this); + } + } + + mPrivateInputOpen = true; + + return mPrivateInput; + } + + /**8 + * Open and return a data input stream for a connection. + * + * @return an input stream + * + * @throws 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 + * + * @throws IOException if an I/O error occurs + */ + public OutputStream openOutputStream() throws IOException { + + ensureOpen(); + ensureNotDone(); + + if (mPrivateOutputOpen) + throw new IOException("no more output streams available"); + + if (mPrivateOutput == null) { + // there are 3 bytes operation headers and 3 bytes body headers // + mPrivateOutput = new PrivateOutputStream(this, mMaxPacketSize - 6); + } + + mPrivateOutputOpen = true; + + return mPrivateOutput; + } + + public int getMaxPacketSize() { + return mMaxPacketSize - 6; + } + + /** + * Open and return a data output stream for a connection. + * + * @return an output stream + * + * @throws IOException if an I/O error occurs + */ + public DataOutputStream openDataOutputStream() throws IOException { + return new DataOutputStream(openOutputStream()); + } + + /** + * Closes the connection and ends the transaction + * + * @throws IOException if the operation has already ended or is closed + */ + public void close() throws IOException { + mInputOpen = false; + mPrivateInputOpen = false; + mPrivateOutputOpen = false; + mParent.setRequestInactive(); + } + + /** + * 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> + * + * @throws IOException if this <code>Operation</code> has been closed + */ + public HeaderSet getReceivedHeader() throws IOException { + ensureOpen(); + + return mReplyHeader; + } + + /** + * 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 + * + * @throws IOException if this <code>Operation</code> has been closed + * or the transaction has ended and no further messages will be exchanged + * + * @throws IllegalArgumentException if <code>headers</code> was not created + * by a call to <code>ServerRequestHandler.createHeaderSet()</code> + * + * @throws NullPointerException if <code>headers</code> is <code>null</code> + */ + public void sendHeaders(HeaderSet headers) throws IOException { + ensureOpen(); + if (mOperationDone) { + throw new IOException("Operation has already exchanged all data"); + } + + if (headers == null) { + throw new IOException("Headers may not be null"); + } + + int[] headerList = headers.getHeaderList(); + if (headerList != null) { + for (int i = 0; i < headerList.length; i++) { + mRequestHeader.setHeader(headerList[i], headers.getHeader(headerList[i])); + } + } + } + + /** + * Reads a response from the server. It will populate the appropriate body + * and headers. + * + * @return <code>true</code> if the transaction should end; + * <code>false</code> if the transaction should not end + * + * @throws IOException if an IO error occurred + */ + /* + private boolean readResponse() throws IOException { + mReplyHeader.responseCode = mInput.read(); + int packetLength = mInput.read(); + packetLength = (packetLength << 8) + mInput.read(); + + if (packetLength > ObexHelper.MAX_PACKET_SIZE_INT) { + if (mExceptionMessage != null) { + abort(); + } + throw new IOException("Received a packet that was too big"); + } + + if (packetLength > ObexHelper.BASE_PACKET_LENGTH) { + int dataLength = packetLength - ObexHelper.BASE_PACKET_LENGTH; + byte[] data = new byte[dataLength]; + int readLength = mInput.read(data); + if (readLength != dataLength) { + throw new IOException("Received a packet without data as decalred length"); + } + byte[] body = ObexHelper.updateHeaderSet(mReplyHeader, data); + + if (body != null) { + mPrivateInput.writeBytes(body, 1); + + /* + * Determine if a body (0x48) header or an end of body (0x49) + * was received. If we received an end of body and + * a response code of OBEX_HTTP_OK, then the operation should + * end. + * + if ((body[0] == 0x49) && (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_OK)) { + return false; + } + } + } + + if (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE) { + return true; + } else { + return false; + } + } + */ + /** + * Verifies that additional information may be sent. In other words, the + * operation is not done. + * + * @throws IOException if the operation is completed + */ + public void ensureNotDone() throws IOException { + if (mOperationDone) { + throw new IOException("Operation has completed"); + } + } + + /** + * Verifies that the connection is open and no exceptions should be thrown. + * + * @throws IOException if an exception needs to be thrown + */ + public void ensureOpen() throws IOException { + mParent.ensureOpen(); + + if (mExceptionMessage != null) { + throw new IOException(mExceptionMessage); + } + if (!mInputOpen) { + throw new IOException("Operation has already ended"); + } + } + + /** + * Verifies that the connection is open and the proper data has been read. + * + * @throws IOException if an IO error occurs + */ + private void validateConnection() throws IOException { + ensureOpen(); + + // to sure only one privateInput object exist. + if (mPrivateInput == null) { + startProcessing(); + } + } + + /** + * Sends a request to the client of the specified type + * + * @param response the response code to send back to the client + * + * @return <code>true</code> if there is more data to send; + * <code>false</code> if there is no more data to send + * + * @throws IOException if an IO error occurs + */ + private boolean sendRequest(int type) throws IOException { + boolean returnValue = false; + ByteArrayOutputStream out = new ByteArrayOutputStream(); + int bodyLength = -1; + byte[] headerArray = ObexHelper.createHeader(mRequestHeader, true); + if (mPrivateOutput != null) { + bodyLength = mPrivateOutput.size(); + } + + /* + * Determine if there is space to add a body request. At present + * this method checks to see if there is room for at least a 17 + * byte body header. This number needs to be at least 6 so that + * there is room for the header ID and length and the reply ID and + * length, but it is a waste of resources if we can't send much of + * the body. + */ + if ((ObexHelper.BASE_PACKET_LENGTH + headerArray.length) > mMaxPacketSize) { + int end = 0; + int start = 0; + // split & send the headerArray in multiple packets. + + while (end != headerArray.length) { + //split the headerArray + end = ObexHelper.findHeaderEnd(headerArray, start, mMaxPacketSize + - ObexHelper.BASE_PACKET_LENGTH); + // can not split + if (end == -1) { + mOperationDone = true; + abort(); + mExceptionMessage = "Header larger then can be sent in a packet"; + mInputOpen = false; + + if (mPrivateInput != null) { + mPrivateInput.close(); + } + + if (mPrivateOutput != null) { + mPrivateOutput.close(); + } + throw new IOException("OBEX Packet exceeds max packet size"); + } + + byte[] sendHeader = new byte[end - start]; + System.arraycopy(headerArray, start, sendHeader, 0, sendHeader.length); + if (!mParent.sendRequest(type, sendHeader, mReplyHeader, mPrivateInput)) { + return false; + } + + if (mReplyHeader.responseCode != ResponseCodes.OBEX_HTTP_CONTINUE) { + return false; + } + + start = end; + } + + if (bodyLength > 0) { + return true; + } else { + return false; + } + } else { + out.write(headerArray); + } + + if (bodyLength > 0) { + /* + * Determine if we 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 > (mMaxPacketSize - headerArray.length - 6)) { + returnValue = true; + + bodyLength = mMaxPacketSize - headerArray.length - 6; + } + + byte[] body = mPrivateOutput.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 ((mPrivateOutput.isClosed()) && (!returnValue) && (!mEndOfBodySent) + && ((type & 0x80) != 0)) { + out.write(0x49); + mEndOfBodySent = true; + } else { + out.write(0x48); + } + + bodyLength += 3; + out.write((byte)(bodyLength >> 8)); + out.write((byte)bodyLength); + + if (body != null) { + out.write(body); + } + } + + if (mPrivateOutputOpen && bodyLength <= 0 && !mEndOfBodySent) { + // only 0x82 or 0x83 can send 0x49 + if ((type & 0x80) == 0) { + out.write(0x48); + } else { + out.write(0x49); + mEndOfBodySent = true; + + } + + bodyLength = 3; + out.write((byte)(bodyLength >> 8)); + out.write((byte)bodyLength); + } + + if (out.size() == 0) { + if (!mParent.sendRequest(type, null, mReplyHeader, mPrivateInput)) { + return false; + } + return returnValue; + } + if ((out.size() > 0) + && (!mParent.sendRequest(type, out.toByteArray(), mReplyHeader, mPrivateInput))) { + return false; + } + + // send all of the output data in 0x48, + // send 0x49 with empty body + if ((mPrivateOutput != null) && (mPrivateOutput.size() > 0)) + returnValue = true; + + return returnValue; + } + + /** + * This method starts the processing thread results. It will send the + * initial request. If the response takes more then one packet, a thread + * will be started to handle additional requests + * + * @throws IOException if an IO error occurs + */ + private synchronized void startProcessing() throws IOException { + + if (mPrivateInput == null) { + mPrivateInput = new PrivateInputStream(this); + } + boolean more = true; + + if (mGetOperation) { + if (!mOperationDone) { + mReplyHeader.responseCode = ResponseCodes.OBEX_HTTP_CONTINUE; + while ((more) && (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE)) { + more = sendRequest(0x03); + } + + if (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE) { + mParent.sendRequest(0x83, null, mReplyHeader, mPrivateInput); + } + if (mReplyHeader.responseCode != ResponseCodes.OBEX_HTTP_CONTINUE) { + mOperationDone = true; + } + } + } else { + + if (!mOperationDone) { + mReplyHeader.responseCode = ResponseCodes.OBEX_HTTP_CONTINUE; + while ((more) && (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE)) { + more = sendRequest(0x02); + + } + } + + if (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE) { + mParent.sendRequest(0x82, null, mReplyHeader, mPrivateInput); + } + + if (mReplyHeader.responseCode != ResponseCodes.OBEX_HTTP_CONTINUE) { + mOperationDone = true; + } + } + } + + /** + * Continues the operation since there is no data to read. + * + * @param sendEmpty <code>true</code> if the operation should send an + * empty packet or not send anything if there is no data to send + * @param inStream <code>true</code> if the stream is input stream or + * is output stream + * @throws IOException if an IO error occurs + */ + public synchronized boolean continueOperation(boolean sendEmpty, boolean inStream) + throws IOException { + + if (mGetOperation) { + if ((inStream) && (!mOperationDone)) { + // to deal with inputstream in get operation + mParent.sendRequest(0x83, null, mReplyHeader, mPrivateInput); + /* + * Determine if that was not the last packet in the operation + */ + if (mReplyHeader.responseCode != ResponseCodes.OBEX_HTTP_CONTINUE) { + mOperationDone = true; + } + + return true; + + } else if ((!inStream) && (!mOperationDone)) { + // to deal with outputstream in get operation + + if (mPrivateInput == null) { + mPrivateInput = new PrivateInputStream(this); + } + sendRequest(0x03); + return true; + + } else if (mOperationDone) { + return false; + } + + } else { + if ((!inStream) && (!mOperationDone)) { + // to deal with outputstream in put operation + if (mReplyHeader.responseCode == -1) { + mReplyHeader.responseCode = ResponseCodes.OBEX_HTTP_CONTINUE; + } + sendRequest(0x02); + return true; + } else if ((inStream) && (!mOperationDone)) { + // How to deal with inputstream in put operation ? + return false; + + } else if (mOperationDone) { + return false; + } + + } + return false; + } + + /** + * Called when the output or input stream is closed. + * + * @param inStream <code>true</code> if the input stream is closed; + * <code>false</code> if the output stream is closed + * + * @throws IOException if an IO error occurs + */ + public void streamClosed(boolean inStream) throws IOException { + if (!mGetOperation) { + if ((!inStream) && (!mOperationDone)) { + // to deal with outputstream in put operation + + boolean more = true; + + if ((mPrivateOutput != null) && (mPrivateOutput.size() <= 0)) { + byte[] headerArray = ObexHelper.createHeader(mRequestHeader, false); + if (headerArray.length <= 0) + more = false; + } + // If have not sent any data so send all now + if (mReplyHeader.responseCode == -1) { + mReplyHeader.responseCode = ResponseCodes.OBEX_HTTP_CONTINUE; + } + + while ((more) && (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE)) { + more = sendRequest(0x02); + } + + /* + * According to the IrOBEX specification, after the final put, you + * only have a single reply to send. so we don't need the while + * loop. + */ + while (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE) { + + sendRequest(0x82); + } + mOperationDone = true; + } else if ((inStream) && (mOperationDone)) { + // how to deal with input stream in put stream ? + mOperationDone = true; + } + } else { + if ((inStream) && (!mOperationDone)) { + + // to deal with inputstream in get operation + // Have not sent any data so send it all now + + if (mReplyHeader.responseCode == -1) { + mReplyHeader.responseCode = ResponseCodes.OBEX_HTTP_CONTINUE; + } + + while (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE) { + if (!sendRequest(0x83)) { + break; + } + } + while (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE) { + mParent.sendRequest(0x83, null, mReplyHeader, mPrivateInput); + } + mOperationDone = true; + } else if ((!inStream) && (!mOperationDone)) { + // to deal with outputstream in get operation + // part of the data may have been sent in continueOperation. + + boolean more = true; + + if ((mPrivateOutput != null) && (mPrivateOutput.size() <= 0)) { + byte[] headerArray = ObexHelper.createHeader(mRequestHeader, false); + if (headerArray.length <= 0) + more = false; + } + + if (mPrivateInput == null) { + mPrivateInput = new PrivateInputStream(this); + } + if ((mPrivateOutput != null) && (mPrivateOutput.size() <= 0)) + more = false; + + mReplyHeader.responseCode = ResponseCodes.OBEX_HTTP_CONTINUE; + while ((more) && (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE)) { + more = sendRequest(0x03); + } + sendRequest(0x83); + // parent.sendRequest(0x83, null, replyHeaders, privateInput); + if (mReplyHeader.responseCode != ResponseCodes.OBEX_HTTP_CONTINUE) { + mOperationDone = true; + } + + } + } + } +} diff --git a/obex/javax/obex/ClientSession.java b/obex/javax/obex/ClientSession.java new file mode 100644 index 0000000..d554922 --- /dev/null +++ b/obex/javax/obex/ClientSession.java @@ -0,0 +1,536 @@ +/* + * 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.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * This class in an implementation of the OBEX ClientSession. + * + * @hide + */ +public final class ClientSession extends ObexSession { + + private boolean mOpen; + + // Determines if an OBEX layer connection has been established + private boolean mObexConnected; + + private byte[] mConnectionId = null; + + /* + * The max Packet size must be at least 256 according to the OBEX + * specification. + */ + private int maxPacketSize = 256; + + private boolean mRequestActive; + + private final InputStream mInput; + + private final OutputStream mOutput; + + public ClientSession(final ObexTransport trans) throws IOException { + mInput = trans.openInputStream(); + mOutput = trans.openOutputStream(); + mOpen = true; + mRequestActive = false; + } + + public HeaderSet connect(final HeaderSet header) throws IOException { + ensureOpen(); + if (mObexConnected) { + throw new IOException("Already connected to server"); + } + setRequestActive(); + + int totalLength = 4; + byte[] head = null; + + // Determine the header byte array + if (header != null) { + if (header.nonce != null) { + mChallengeDigest = new byte[16]; + System.arraycopy(header.nonce, 0, mChallengeDigest, 0, 16); + } + head = ObexHelper.createHeader(header, false); + totalLength += head.length; + } + /* + * Write the OBEX CONNECT packet to the server. + * Byte 0: 0x80 + * Byte 1&2: Connect Packet Length + * Byte 3: OBEX Version Number (Presently, 0x10) + * Byte 4: Flags (For TCP 0x00) + * Byte 5&6: Max OBEX Packet Length (Defined in MAX_PACKET_SIZE) + * Byte 7 to n: headers + */ + byte[] requestPacket = new byte[totalLength]; + // We just need to start at byte 3 since the sendRequest() method will + // handle the length and 0x80. + requestPacket[0] = (byte)0x10; + requestPacket[1] = (byte)0x00; + requestPacket[2] = (byte)(ObexHelper.MAX_PACKET_SIZE_INT >> 8); + requestPacket[3] = (byte)(ObexHelper.MAX_PACKET_SIZE_INT & 0xFF); + if (head != null) { + System.arraycopy(head, 0, requestPacket, 4, head.length); + } + + // check with local max packet size + if ((requestPacket.length + 3) > ObexHelper.MAX_PACKET_SIZE_INT) { + throw new IOException("Packet size exceeds max packet size"); + } + + HeaderSet returnHeaderSet = new HeaderSet(); + sendRequest(ObexHelper.OBEX_OPCODE_CONNECT, requestPacket, returnHeaderSet, null); + + /* + * Read the response from the OBEX server. + * Byte 0: Response Code (If successful then OBEX_HTTP_OK) + * Byte 1&2: Packet Length + * Byte 3: OBEX Version Number + * Byte 4: Flags3 + * Byte 5&6: Max OBEX packet Length + * Byte 7 to n: Optional HeaderSet + */ + if (returnHeaderSet.responseCode == ResponseCodes.OBEX_HTTP_OK) { + mObexConnected = true; + } + setRequestInactive(); + + return returnHeaderSet; + } + + public Operation get(HeaderSet header) throws IOException { + + if (!mObexConnected) { + throw new IOException("Not connected to the server"); + } + setRequestActive(); + + ensureOpen(); + + HeaderSet head; + if (header == null) { + head = new HeaderSet(); + } else { + head = header; + if (head.nonce != null) { + mChallengeDigest = new byte[16]; + System.arraycopy(head.nonce, 0, mChallengeDigest, 0, 16); + } + } + // Add the connection ID if one exists + if (mConnectionId != null) { + head.mConnectionID = new byte[4]; + System.arraycopy(mConnectionId, 0, head.mConnectionID, 0, 4); + } + + return new ClientOperation(maxPacketSize, this, head, true); + } + + /** + * 0xCB Connection Id an identifier used for OBEX connection multiplexing + */ + public void setConnectionID(long id) { + if ((id < 0) || (id > 0xFFFFFFFFL)) { + throw new IllegalArgumentException("Connection ID is not in a valid range"); + } + mConnectionId = ObexHelper.convertToByteArray(id); + } + + public HeaderSet delete(HeaderSet header) throws IOException { + + Operation op = put(header); + op.getResponseCode(); + HeaderSet returnValue = op.getReceivedHeader(); + op.close(); + + return returnValue; + } + + public HeaderSet disconnect(HeaderSet header) throws IOException { + if (!mObexConnected) { + throw new IOException("Not connected to the server"); + } + setRequestActive(); + + ensureOpen(); + // Determine the header byte array + byte[] head = null; + if (header != null) { + if (header.nonce != null) { + mChallengeDigest = new byte[16]; + System.arraycopy(header.nonce, 0, mChallengeDigest, 0, 16); + } + // Add the connection ID if one exists + if (mConnectionId != null) { + header.mConnectionID = new byte[4]; + System.arraycopy(mConnectionId, 0, header.mConnectionID, 0, 4); + } + head = ObexHelper.createHeader(header, false); + + if ((head.length + 3) > maxPacketSize) { + throw new IOException("Packet size exceeds max packet size"); + } + } else { + // Add the connection ID if one exists + if (mConnectionId != null) { + head = new byte[5]; + head[0] = (byte)HeaderSet.CONNECTION_ID; + System.arraycopy(mConnectionId, 0, head, 1, 4); + } + } + + HeaderSet returnHeaderSet = new HeaderSet(); + sendRequest(ObexHelper.OBEX_OPCODE_DISCONNECT, head, returnHeaderSet, null); + + /* + * An OBEX DISCONNECT reply from the server: + * Byte 1: Response code + * Bytes 2 & 3: packet size + * Bytes 4 & up: headers + */ + + /* response code , and header are ignored + * */ + + synchronized (this) { + mObexConnected = false; + setRequestInactive(); + } + + return returnHeaderSet; + } + + public long getConnectionID() { + + if (mConnectionId == null) { + return -1; + } + return ObexHelper.convertToLong(mConnectionId); + } + + public Operation put(HeaderSet header) throws IOException { + if (!mObexConnected) { + throw new IOException("Not connected to the server"); + } + setRequestActive(); + + ensureOpen(); + HeaderSet head; + if (header == null) { + head = new HeaderSet(); + } else { + head = header; + // when auth is initiated by client ,save the digest + if (head.nonce != null) { + mChallengeDigest = new byte[16]; + System.arraycopy(head.nonce, 0, mChallengeDigest, 0, 16); + } + } + + // Add the connection ID if one exists + if (mConnectionId != null) { + + head.mConnectionID = new byte[4]; + System.arraycopy(mConnectionId, 0, head.mConnectionID, 0, 4); + } + + return new ClientOperation(maxPacketSize, this, head, false); + } + + public void setAuthenticator(Authenticator auth) throws IOException { + if (auth == null) { + throw new IOException("Authenticator may not be null"); + } + mAuthenticator = auth; + } + + public HeaderSet setPath(HeaderSet header, boolean backup, boolean create) throws IOException { + if (!mObexConnected) { + throw new IOException("Not connected to the server"); + } + setRequestActive(); + ensureOpen(); + + int totalLength = 2; + byte[] head = null; + HeaderSet headset; + if (header == null) { + headset = new HeaderSet(); + } else { + headset = header; + if (headset.nonce != null) { + mChallengeDigest = new byte[16]; + System.arraycopy(headset.nonce, 0, mChallengeDigest, 0, 16); + } + } + + // when auth is initiated by client ,save the digest + if (headset.nonce != null) { + mChallengeDigest = new byte[16]; + System.arraycopy(headset.nonce, 0, mChallengeDigest, 0, 16); + } + + // Add the connection ID if one exists + if (mConnectionId != null) { + headset.mConnectionID = new byte[4]; + System.arraycopy(mConnectionId, 0, headset.mConnectionID, 0, 4); + } + + head = ObexHelper.createHeader(headset, false); + totalLength += head.length; + + if (totalLength > maxPacketSize) { + throw new IOException("Packet size exceeds max packet size"); + } + + int flags = 0; + /* + * The backup flag bit is bit 0 so if we add 1, this will set that bit + */ + if (backup) { + flags++; + } + /* + * The create bit is bit 1 so if we or with 2 the bit will be set. + */ + if (!create) { + flags |= 2; + } + + /* + * An OBEX SETPATH packet to the server: + * Byte 1: 0x85 + * Byte 2 & 3: packet size + * Byte 4: flags + * Byte 5: constants + * Byte 6 & up: headers + */ + byte[] packet = new byte[totalLength]; + packet[0] = (byte)flags; + packet[1] = (byte)0x00; + if (headset != null) { + System.arraycopy(head, 0, packet, 2, head.length); + } + + HeaderSet returnHeaderSet = new HeaderSet(); + sendRequest(ObexHelper.OBEX_OPCODE_SETPATH, packet, returnHeaderSet, null); + + /* + * An OBEX SETPATH reply from the server: + * Byte 1: Response code + * Bytes 2 & 3: packet size + * Bytes 4 & up: headers + */ + + setRequestInactive(); + + return returnHeaderSet; + } + + /** + * Verifies that the connection is open. + * + * @throws IOException if the connection is closed + */ + public synchronized void ensureOpen() throws IOException { + if (!mOpen) { + throw new IOException("Connection closed"); + } + } + + /** + * Set request inactive. + * Allows Put and get operation objects to tell this object when they are + * done. + */ + /*package*/synchronized void setRequestInactive() { + mRequestActive = false; + } + + /** + * Set request to active. + * @throws IOException if already active + */ + private synchronized void setRequestActive() throws IOException { + if (mRequestActive) { + throw new IOException("OBEX request is already being performed"); + } + mRequestActive = true; + } + + /** + * Sends a standard request to the client. It will then wait for the reply + * and update the header set object provided. If any authentication + * headers (i.e. authentication challenge or authentication response) are + * received, they will be processed. + * + * @param opCode the type of request to send to the client + * + * @param head the headers to send to the server + * + * @param challenge the nonce that was sent in the authentication + * challenge header located in <code>head</code>; <code>null</code> + * if no authentication header is included in <code>head</code> + * + * @param header the header object to update with the response + * + * @param input the input stream used by the Operation object; null if this + * is called on a CONNECT, SETPATH or DISCONNECT + * + * return <code>true</code> if the operation completed successfully; + * <code>false</code> if an authentication response failed to pass + * + * @throws IOException if an IO error occurs + */ + public boolean sendRequest(int opCode, byte[] head, HeaderSet header, + PrivateInputStream privateInput) throws IOException { + //check header length with local max size + if (head != null) { + if ((head.length + 3) > ObexHelper.MAX_PACKET_SIZE_INT) { + throw new IOException("header too large "); + } + } + + int bytesReceived; + ByteArrayOutputStream out = new ByteArrayOutputStream(); + out.write((byte)opCode); + + // Determine if there are any headers to send + if (head == null) { + out.write(0x00); + out.write(0x03); + } else { + out.write((byte)((head.length + 3) >> 8)); + out.write((byte)(head.length + 3)); + out.write(head); + } + + // Write the request to the output stream and flush the stream + mOutput.write(out.toByteArray()); + mOutput.flush(); + + header.responseCode = mInput.read(); + + int length = ((mInput.read() << 8) | (mInput.read())); + + if (length > ObexHelper.MAX_PACKET_SIZE_INT) { + throw new IOException("Packet received exceeds packet size limit"); + } + if (length > ObexHelper.BASE_PACKET_LENGTH) { + byte[] data = null; + if (opCode == ObexHelper.OBEX_OPCODE_CONNECT) { + @SuppressWarnings("unused") + int version = mInput.read(); + @SuppressWarnings("unused") + int flags = mInput.read(); + maxPacketSize = (mInput.read() << 8) + mInput.read(); + + //check with local max size + if (maxPacketSize > ObexHelper.MAX_PACKET_SIZE_INT) { + maxPacketSize = ObexHelper.MAX_PACKET_SIZE_INT; + } + + if (length > 7) { + data = new byte[length - 7]; + + bytesReceived = mInput.read(data); + while (bytesReceived != (length - 7)) { + bytesReceived += mInput.read(data, bytesReceived, data.length + - bytesReceived); + } + } else { + return true; + } + } else { + data = new byte[length - 3]; + bytesReceived = mInput.read(data); + + while (bytesReceived != (length - 3)) { + bytesReceived += mInput.read(data, bytesReceived, data.length - bytesReceived); + } + if (opCode == ObexHelper.OBEX_OPCODE_ABORT) { + return true; + } + } + + byte[] body = ObexHelper.updateHeaderSet(header, data); + if ((privateInput != null) && (body != null)) { + privateInput.writeBytes(body, 1); + } + + if (header.mConnectionID != null) { + mConnectionId = new byte[4]; + System.arraycopy(header.mConnectionID, 0, mConnectionId, 0, 4); + } + + if (header.mAuthResp != null) { + if (!handleAuthResp(header.mAuthResp)) { + setRequestInactive(); + throw new IOException("Authentication Failed"); + } + } + + if ((header.responseCode == ResponseCodes.OBEX_HTTP_UNAUTHORIZED) + && (header.mAuthChall != null)) { + + if (handleAuthChall(header)) { + out.write((byte)HeaderSet.AUTH_RESPONSE); + out.write((byte)((header.mAuthResp.length + 3) >> 8)); + out.write((byte)(header.mAuthResp.length + 3)); + out.write(header.mAuthResp); + header.mAuthChall = null; + header.mAuthResp = null; + + byte[] sendHeaders = new byte[out.size() - 3]; + System.arraycopy(out.toByteArray(), 3, sendHeaders, 0, sendHeaders.length); + + return sendRequest(opCode, sendHeaders, header, privateInput); + } + } + } + + return true; + } + + public void close() throws IOException { + mOpen = false; + mInput.close(); + mOutput.close(); + } +} diff --git a/obex/javax/obex/HeaderSet.java b/obex/javax/obex/HeaderSet.java new file mode 100644 index 0000000..f777da6 --- /dev/null +++ b/obex/javax/obex/HeaderSet.java @@ -0,0 +1,650 @@ +/* + * 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.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Calendar; +import java.util.Random; + +/** + * This class implements the javax.obex.HeaderSet interface for OBEX over + * RFCOMM. + * + * @hide + */ +public final class HeaderSet { + + /** + * Represents the OBEX Count header. This allows the connection statement + * to tell the server how many objects it plans to send or retrieve. + * <P> + * The value of <code>COUNT</code> is 0xC0 (192). + */ + public static final int COUNT = 0xC0; + + /** + * Represents the OBEX Name header. This specifies the name of the object. + * <P> + * The value of <code>NAME</code> is 0x01 (1). + */ + public static final int NAME = 0x01; + + /** + * Represents the OBEX Type header. This allows a request to specify the + * type of the object (e.g. text, html, binary, etc.). + * <P> + * The value of <code>TYPE</code> is 0x42 (66). + */ + public static final int TYPE = 0x42; + + /** + * Represents the OBEX Length header. This is the length of the object in + * bytes. + * <P> + * The value of <code>LENGTH</code> is 0xC3 (195). + */ + public static final int LENGTH = 0xC3; + + /** + * Represents the OBEX Time header using the ISO 8601 standards. This is + * the preferred time header. + * <P> + * The value of <code>TIME_ISO_8601</code> is 0x44 (68). + */ + public static final int TIME_ISO_8601 = 0x44; + + /** + * Represents the OBEX Time header using the 4 byte representation. This + * is only included for backwards compatibility. It represents the number + * of seconds since January 1, 1970. + * <P> + * The value of <code>TIME_4_BYTE</code> is 0xC4 (196). + */ + public static final int TIME_4_BYTE = 0xC4; + + /** + * Represents the OBEX Description header. This is a text description of + * the object. + * <P> + * The value of <code>DESCRIPTION</code> is 0x05 (5). + */ + public static final int DESCRIPTION = 0x05; + + /** + * Represents the OBEX Target header. This is the name of the service an + * operation is targeted to. + * <P> + * The value of <code>TARGET</code> is 0x46 (70). + */ + public static final int TARGET = 0x46; + + /** + * Represents the OBEX HTTP header. This allows an HTTP 1.X header to be + * included in a request or reply. + * <P> + * The value of <code>HTTP</code> is 0x47 (71). + */ + public static final int HTTP = 0x47; + + /** + * Represents the OBEX BODY header. + * <P> + * The value of <code>BODY</code> is 0x48 (72). + */ + public static final int BODY = 0x48; + + /** + * Represents the OBEX End of BODY header. + * <P> + * The value of <code>BODY</code> is 0x49 (73). + */ + public static final int END_OF_BODY = 0x49; + + /** + * Represents the OBEX Who header. Identifies the OBEX application to + * determine if the two peers are talking to each other. + * <P> + * The value of <code>WHO</code> is 0x4A (74). + */ + public static final int WHO = 0x4A; + + /** + * Represents the OBEX Connection ID header. Identifies used for OBEX + * connection multiplexing. + * <P> + * The value of <code>CONNECTION_ID</code> is 0xCB (203). + */ + + public static final int CONNECTION_ID = 0xCB; + + /** + * Represents the OBEX Application Parameter header. This header specifies + * additional application request and response information. + * <P> + * The value of <code>APPLICATION_PARAMETER</code> is 0x4C (76). + */ + public static final int APPLICATION_PARAMETER = 0x4C; + + /** + * Represents the OBEX authentication digest-challenge. + * <P> + * The value of <code>AUTH_CHALLENGE</code> is 0x4D (77). + */ + public static final int AUTH_CHALLENGE = 0x4D; + + /** + * Represents the OBEX authentication digest-response. + * <P> + * The value of <code>AUTH_RESPONSE</code> is 0x4E (78). + */ + public static final int AUTH_RESPONSE = 0x4E; + + /** + * Represents the OBEX Object Class header. This header specifies the + * OBEX object class of the object. + * <P> + * The value of <code>OBJECT_CLASS</code> is 0x4F (79). + */ + public static final int OBJECT_CLASS = 0x4F; + + private Long mCount; // 4 byte unsigned integer + + private String mName; // null terminated Unicode text string + + private String mType; // null terminated ASCII text string + + private Long mLength; // 4 byte unsigend integer + + private Calendar mIsoTime; // String of the form YYYYMMDDTHHMMSSZ + + private Calendar mByteTime; // 4 byte unsigned integer + + private String mDescription; // null terminated Unicode text String + + private byte[] mTarget; // byte sequence + + private byte[] mHttpHeader; // byte sequence + + private byte[] mWho; // length prefixed byte sequence + + private byte[] mAppParam; // byte sequence of the form tag length value + + public byte[] mAuthChall; // The authentication challenge header + + public byte[] mAuthResp; // The authentication response header + + public byte[] mConnectionID; // THe connection ID + + private byte[] mObjectClass; // byte sequence + + private String[] mUnicodeUserDefined; //null terminated unicode string + + private byte[][] mSequenceUserDefined; // byte sequence user defined + + private Byte[] mByteUserDefined; // 1 byte + + private Long[] mIntegerUserDefined; // 4 byte unsigned integer + + /*package*/int responseCode; + + /*package*/byte[] nonce; + + private final Random mRandom; + + /** + * Creates new <code>HeaderSet</code> object. + * + * @param size the max packet size for this connection + */ + public HeaderSet() { + mUnicodeUserDefined = new String[16]; + mSequenceUserDefined = new byte[16][]; + mByteUserDefined = new Byte[16]; + mIntegerUserDefined = new Long[16]; + responseCode = -1; + mRandom = new Random(); + } + + /** + * Sets the value of the header identifier to the value provided. The type + * of object must correspond to the Java type defined in the description of + * this interface. If <code>null</code> is passed as the + * <code>headerValue</code> then the header will be removed from the set of + * headers to include in the next request. + * + * @param headerID the identifier to include in the message + * + * @param headerValue the value of the header identifier + * + * @throws IllegalArgumentException if the header identifier provided is + * not one defined in this interface or a user-defined header; if the type of + * <code>headerValue</code> is not the correct Java type as defined in the + * description of this interface\ + */ + public void setHeader(int headerID, Object headerValue) { + long temp = -1; + + switch (headerID) { + case COUNT: + if (!(headerValue instanceof Long)) { + if (headerValue == null) { + mCount = null; + break; + } + throw new IllegalArgumentException("Count must be a Long"); + } + temp = ((Long)headerValue).longValue(); + if ((temp < 0L) || (temp > 0xFFFFFFFFL)) { + throw new IllegalArgumentException("Count must be between 0 and 0xFFFFFFFF"); + } + mCount = (Long)headerValue; + break; + case NAME: + if ((headerValue != null) && (!(headerValue instanceof String))) { + throw new IllegalArgumentException("Name must be a String"); + } + mName = (String)headerValue; + break; + case TYPE: + if ((headerValue != null) && (!(headerValue instanceof String))) { + throw new IllegalArgumentException("Type must be a String"); + } + mType = (String)headerValue; + break; + case LENGTH: + if (!(headerValue instanceof Long)) { + if (headerValue == null) { + mLength = null; + break; + } + throw new IllegalArgumentException("Length must be a Long"); + } + temp = ((Long)headerValue).longValue(); + if ((temp < 0L) || (temp > 0xFFFFFFFFL)) { + throw new IllegalArgumentException("Length must be between 0 and 0xFFFFFFFF"); + } + mLength = (Long)headerValue; + break; + case TIME_ISO_8601: + if ((headerValue != null) && (!(headerValue instanceof Calendar))) { + throw new IllegalArgumentException("Time ISO 8601 must be a Calendar"); + } + mIsoTime = (Calendar)headerValue; + break; + case TIME_4_BYTE: + if ((headerValue != null) && (!(headerValue instanceof Calendar))) { + throw new IllegalArgumentException("Time 4 Byte must be a Calendar"); + } + mByteTime = (Calendar)headerValue; + break; + case DESCRIPTION: + if ((headerValue != null) && (!(headerValue instanceof String))) { + throw new IllegalArgumentException("Description must be a String"); + } + mDescription = (String)headerValue; + break; + case TARGET: + if (headerValue == null) { + mTarget = null; + } else { + if (!(headerValue instanceof byte[])) { + throw new IllegalArgumentException("Target must be a byte array"); + } else { + mTarget = new byte[((byte[])headerValue).length]; + System.arraycopy(headerValue, 0, mTarget, 0, mTarget.length); + } + } + break; + case HTTP: + if (headerValue == null) { + mHttpHeader = null; + } else { + if (!(headerValue instanceof byte[])) { + throw new IllegalArgumentException("HTTP must be a byte array"); + } else { + mHttpHeader = new byte[((byte[])headerValue).length]; + System.arraycopy(headerValue, 0, mHttpHeader, 0, mHttpHeader.length); + } + } + break; + case WHO: + if (headerValue == null) { + mWho = null; + } else { + if (!(headerValue instanceof byte[])) { + throw new IllegalArgumentException("WHO must be a byte array"); + } else { + mWho = new byte[((byte[])headerValue).length]; + System.arraycopy(headerValue, 0, mWho, 0, mWho.length); + } + } + break; + case OBJECT_CLASS: + if (headerValue == null) { + mObjectClass = null; + } else { + if (!(headerValue instanceof byte[])) { + throw new IllegalArgumentException("Object Class must be a byte array"); + } else { + mObjectClass = new byte[((byte[])headerValue).length]; + System.arraycopy(headerValue, 0, mObjectClass, 0, mObjectClass.length); + } + } + break; + case APPLICATION_PARAMETER: + if (headerValue == null) { + mAppParam = null; + } else { + if (!(headerValue instanceof byte[])) { + throw new IllegalArgumentException( + "Application Parameter must be a byte array"); + } else { + mAppParam = new byte[((byte[])headerValue).length]; + System.arraycopy(headerValue, 0, mAppParam, 0, mAppParam.length); + } + } + break; + default: + // Verify that it was not a Unicode String user Defined + if ((headerID >= 0x30) && (headerID <= 0x3F)) { + if ((headerValue != null) && (!(headerValue instanceof String))) { + throw new IllegalArgumentException( + "Unicode String User Defined must be a String"); + } + mUnicodeUserDefined[headerID - 0x30] = (String)headerValue; + + break; + } + // Verify that it was not a byte sequence user defined value + if ((headerID >= 0x70) && (headerID <= 0x7F)) { + + if (headerValue == null) { + mSequenceUserDefined[headerID - 0x70] = null; + } else { + if (!(headerValue instanceof byte[])) { + throw new IllegalArgumentException( + "Byte Sequence User Defined must be a byte array"); + } else { + mSequenceUserDefined[headerID - 0x70] = new byte[((byte[])headerValue).length]; + System.arraycopy(headerValue, 0, mSequenceUserDefined[headerID - 0x70], + 0, mSequenceUserDefined[headerID - 0x70].length); + } + } + break; + } + // Verify that it was not a Byte user Defined + if ((headerID >= 0xB0) && (headerID <= 0xBF)) { + if ((headerValue != null) && (!(headerValue instanceof Byte))) { + throw new IllegalArgumentException("ByteUser Defined must be a Byte"); + } + mByteUserDefined[headerID - 0xB0] = (Byte)headerValue; + + break; + } + // Verify that is was not the 4 byte unsigned integer user + // defined header + if ((headerID >= 0xF0) && (headerID <= 0xFF)) { + if (!(headerValue instanceof Long)) { + if (headerValue == null) { + mIntegerUserDefined[headerID - 0xF0] = null; + break; + } + throw new IllegalArgumentException("Integer User Defined must be a Long"); + } + temp = ((Long)headerValue).longValue(); + if ((temp < 0L) || (temp > 0xFFFFFFFFL)) { + throw new IllegalArgumentException( + "Integer User Defined must be between 0 and 0xFFFFFFFF"); + } + mIntegerUserDefined[headerID - 0xF0] = (Long)headerValue; + break; + } + throw new IllegalArgumentException("Invalid Header Identifier"); + } + } + + /** + * Retrieves the value of the header identifier provided. The type of the + * Object returned is defined in the description of this interface. + * + * @param headerID the header identifier whose value is to be returned + * + * @return the value of the header provided or <code>null</code> if the + * header identifier specified is not part of this <code>HeaderSet</code> + * object + * + * @throws IllegalArgumentException if the <code>headerID</code> is not + * one defined in this interface or any of the user-defined headers + * + * @throws IOException if an error occurred in the transport layer during + * the operation or if the connection has been closed + */ + public Object getHeader(int headerID) throws IOException { + + switch (headerID) { + case COUNT: + return mCount; + case NAME: + return mName; + case TYPE: + return mType; + case LENGTH: + return mLength; + case TIME_ISO_8601: + return mIsoTime; + case TIME_4_BYTE: + return mByteTime; + case DESCRIPTION: + return mDescription; + case TARGET: + return mTarget; + case HTTP: + return mHttpHeader; + case WHO: + return mWho; + case OBJECT_CLASS: + return mObjectClass; + case APPLICATION_PARAMETER: + return mAppParam; + default: + // Verify that it was not a Unicode String user Defined + if ((headerID >= 0x30) && (headerID <= 0x3F)) { + return mUnicodeUserDefined[headerID - 0x30]; + } + // Verify that it was not a byte sequence user defined header + if ((headerID >= 0x70) && (headerID <= 0x7F)) { + return mSequenceUserDefined[headerID - 0x70]; + } + // Verify that it was not a byte user defined header + if ((headerID >= 0xB0) && (headerID <= 0xBF)) { + return mByteUserDefined[headerID - 0xB0]; + } + // Verify that it was not a integer user defined header + if ((headerID >= 0xF0) && (headerID <= 0xFF)) { + return mIntegerUserDefined[headerID - 0xF0]; + } + throw new IllegalArgumentException("Invalid Header Identifier"); + } + } + + /** + * Retrieves the list of headers that may be retrieved via the + * <code>getHeader</code> method that will not return <code>null</code>. + * In other words, this method returns all the headers that are available + * in this object. + * + * @see #getHeader + * + * @return the array of headers that are set in this object or + * <code>null</code> if no headers are available + * + * @throws IOException if an error occurred in the transport layer during + * the operation or the connection has been closed + */ + public int[] getHeaderList() throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + + if (mCount != null) { + out.write(COUNT); + } + if (mName != null) { + out.write(NAME); + } + if (mType != null) { + out.write(TYPE); + } + if (mLength != null) { + out.write(LENGTH); + } + if (mIsoTime != null) { + out.write(TIME_ISO_8601); + } + if (mByteTime != null) { + out.write(TIME_4_BYTE); + } + if (mDescription != null) { + out.write(DESCRIPTION); + } + if (mTarget != null) { + out.write(TARGET); + } + if (mHttpHeader != null) { + out.write(HTTP); + } + if (mWho != null) { + out.write(WHO); + } + if (mAppParam != null) { + out.write(APPLICATION_PARAMETER); + } + if (mObjectClass != null) { + out.write(OBJECT_CLASS); + } + + for (int i = 0x30; i < 0x40; i++) { + if (mUnicodeUserDefined[i - 0x30] != null) { + out.write(i); + } + } + + for (int i = 0x70; i < 0x80; i++) { + if (mSequenceUserDefined[i - 0x70] != null) { + out.write(i); + } + } + + for (int i = 0xB0; i < 0xC0; i++) { + if (mByteUserDefined[i - 0xB0] != null) { + out.write(i); + } + } + + for (int i = 0xF0; i < 0x100; i++) { + if (mIntegerUserDefined[i - 0xF0] != null) { + out.write(i); + } + } + + byte[] headers = out.toByteArray(); + out.close(); + + if ((headers == null) || (headers.length == 0)) { + return null; + } + + int[] result = new int[headers.length]; + for (int i = 0; i < headers.length; i++) { + // Convert the byte to a positive integer. That is, an integer + // between 0 and 256. + result[i] = headers[i] & 0xFF; + } + + return result; + } + + /** + * Sets the authentication challenge header. The <code>realm</code> will + * be encoded based upon the default encoding scheme used by the + * implementation to encode strings. Therefore, the encoding scheme used + * to encode the <code>realm</code> is application dependent. + * + * @param realm a short description that describes what password to use; if + * <code>null</code> no realm will be sent in the authentication challenge + * header + * + * @param userID if <code>true</code>, a user ID is required in the reply; + * if <code>false</code>, no user ID is required + * + * @param access if <code>true</code> then full access will be granted if + * successful; if <code>false</code> then read-only access will be granted + * if successful + * @throws IOException + */ + public void createAuthenticationChallenge(String realm, boolean userID, boolean access) + throws IOException { + + try { + nonce = new byte[16]; + for (int i = 0; i < 16; i++) { + nonce[i] = (byte)mRandom.nextInt(); + } + + mAuthChall = ObexHelper.computeAuthenticationChallenge(nonce, realm, access, userID); + } catch (IOException e) { + throw e; + } + } + + /** + * Returns the response code received from the server. Response codes + * are defined in the <code>ResponseCodes</code> class. + * + * @see ResponseCodes + * + * @return the response code retrieved from the server + * + * @throws 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 object was created by an OBEX + * server + */ + public int getResponseCode() throws IOException { + if (responseCode == -1) { + throw new IOException("May not be called on a server"); + } else { + return responseCode; + } + } +} diff --git a/obex/javax/obex/ObexHelper.java b/obex/javax/obex/ObexHelper.java new file mode 100644 index 0000000..511b7c6 --- /dev/null +++ b/obex/javax/obex/ObexHelper.java @@ -0,0 +1,1019 @@ +/* + * 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 android.security.Md5MessageDigest; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.util.Calendar; +import java.util.Date; +import java.util.TimeZone; + +/** + * This class defines a set of helper methods for the implementation of Obex. + * + * @hide + */ +public final class ObexHelper { + + /** + * Defines the basic packet length used by OBEX. Every OBEX packet has the + * same basic format:<BR> + * Byte 0: Request or Response Code + * Byte 1&2: Length of the packet. + */ + public static final int BASE_PACKET_LENGTH = 3; + + /** Prevent object construction of helper class */ + private ObexHelper() { + } + + /** + * The maximum packet size for OBEX packets that this client can handle. + * At present, this must be changed for each port. + * + * TODO: The max packet size should be the Max incoming MTU minus + * TODO: L2CAP package headers and RFCOMM package headers. + * + * TODO: Retrieve the max incoming MTU from + * TODO: LocalDevice.getProperty(). + */ + /** android note + * set as 0xFFFE to match remote MPS + */ + public static final int MAX_PACKET_SIZE_INT = 0xFFFE; + + public static final int OBEX_OPCODE_CONNECT = 0x80; + + public static final int OBEX_OPCODE_DISCONNECT = 0x81; + + public static final int OBEX_OPCODE_PUT = 0x02; + + public static final int OBEX_OPCODE_PUT_FINAL = 0x82; + + public static final int OBEX_OPCODE_GET = 0x03; + + public static final int OBEX_OPCODE_GET_FINAL = 0x83; + + public static final int OBEX_OPCODE_RESERVED = 0x04; + + public static final int OBEX_OPCODE_RESERVED_FINAL = 0x84; + + public static final int OBEX_OPCODE_SETPATH = 0x85; + + public static final int OBEX_OPCODE_ABORT = 0xFF; + + public static final int OBEX_AUTH_REALM_CHARSET_ASCII = 0x00; + + public static final int OBEX_AUTH_REALM_CHARSET_ISO_8859_1 = 0x01; + + public static final int OBEX_AUTH_REALM_CHARSET_ISO_8859_2 = 0x02; + + public static final int OBEX_AUTH_REALM_CHARSET_ISO_8859_3 = 0x03; + + public static final int OBEX_AUTH_REALM_CHARSET_ISO_8859_4 = 0x04; + + public static final int OBEX_AUTH_REALM_CHARSET_ISO_8859_5 = 0x05; + + public static final int OBEX_AUTH_REALM_CHARSET_ISO_8859_6 = 0x06; + + public static final int OBEX_AUTH_REALM_CHARSET_ISO_8859_7 = 0x07; + + public static final int OBEX_AUTH_REALM_CHARSET_ISO_8859_8 = 0x08; + + public static final int OBEX_AUTH_REALM_CHARSET_ISO_8859_9 = 0x09; + + public static final int OBEX_AUTH_REALM_CHARSET_UNICODE = 0xFF; + + /** + * Updates the HeaderSet with the headers received in the byte array + * provided. Invalid headers are ignored. + * <P> + * The first two bits of an OBEX Header specifies the type of object that + * is being sent. The table below specifies the meaning of the high + * bits. + * <TABLE> + * <TR><TH>Bits 8 and 7</TH><TH>Value</TH><TH>Description</TH></TR> + * <TR><TD>00</TD><TD>0x00</TD><TD>Null Terminated Unicode text, prefixed + * with 2 byte unsigned integer</TD></TR> + * <TR><TD>01</TD><TD>0x40</TD><TD>Byte Sequence, length prefixed with + * 2 byte unsigned integer</TD></TR> + * <TR><TD>10</TD><TD>0x80</TD><TD>1 byte quantity</TD></TR> + * <TR><TD>11</TD><TD>0xC0</TD><TD>4 byte quantity - transmitted in + * network byte order (high byte first</TD></TR> + * </TABLE> + * This method uses the information in this table to determine the type of + * Java object to create and passes that object with the full header + * to setHeader() to update the HeaderSet object. Invalid headers will + * cause an exception to be thrown. When it is thrown, it is ignored. + * + * @param header the HeaderSet to update + * + * @param headerArray the byte array containing headers + * + * @return the result of the last start body or end body header provided; + * the first byte in the result will specify if a body or end of body is + * received + * + * @throws IOException if an invalid header was found + */ + public static byte[] updateHeaderSet(HeaderSet header, byte[] headerArray) throws IOException { + int index = 0; + int length = 0; + int headerID; + byte[] value = null; + byte[] body = null; + HeaderSet headerImpl = header; + try { + while (index < headerArray.length) { + headerID = 0xFF & headerArray[index]; + switch (headerID & (0xC0)) { + + /* + * 0x00 is a unicode null terminate string with the first + * two bytes after the header identifier being the length + */ + case 0x00: + // Fall through + /* + * 0x40 is a byte sequence with the first + * two bytes after the header identifier being the length + */ + case 0x40: + boolean trimTail = true; + index++; + length = 0xFF & headerArray[index]; + length = length << 8; + index++; + length += 0xFF & headerArray[index]; + length -= 3; + index++; + value = new byte[length]; + System.arraycopy(headerArray, index, value, 0, length); + if (length == 0 || (length > 0 && (value[length - 1] != 0))) { + trimTail = false; + } + switch (headerID) { + case HeaderSet.TYPE: + try { + // Remove trailing null + if (trimTail == false) { + headerImpl.setHeader(headerID, new String(value, 0, + value.length, "ISO8859_1")); + } else { + headerImpl.setHeader(headerID, new String(value, 0, + value.length - 1, "ISO8859_1")); + } + } catch (UnsupportedEncodingException e) { + throw e; + } + break; + + case HeaderSet.AUTH_CHALLENGE: + headerImpl.mAuthChall = new byte[length]; + System.arraycopy(headerArray, index, headerImpl.mAuthChall, 0, + length); + break; + + case HeaderSet.AUTH_RESPONSE: + headerImpl.mAuthResp = new byte[length]; + System.arraycopy(headerArray, index, headerImpl.mAuthResp, 0, + length); + break; + + case HeaderSet.BODY: + /* Fall Through */ + case HeaderSet.END_OF_BODY: + body = new byte[length + 1]; + body[0] = (byte)headerID; + System.arraycopy(headerArray, index, body, 1, length); + break; + + case HeaderSet.TIME_ISO_8601: + try { + String dateString = new String(value, "ISO8859_1"); + Calendar temp = Calendar.getInstance(); + if ((dateString.length() == 16) + && (dateString.charAt(15) == 'Z')) { + temp.setTimeZone(TimeZone.getTimeZone("UTC")); + } + temp.set(Calendar.YEAR, Integer.parseInt(dateString.substring( + 0, 4))); + temp.set(Calendar.MONTH, Integer.parseInt(dateString.substring( + 4, 6))); + temp.set(Calendar.DAY_OF_MONTH, Integer.parseInt(dateString + .substring(6, 8))); + temp.set(Calendar.HOUR_OF_DAY, Integer.parseInt(dateString + .substring(9, 11))); + temp.set(Calendar.MINUTE, Integer.parseInt(dateString + .substring(11, 13))); + temp.set(Calendar.SECOND, Integer.parseInt(dateString + .substring(13, 15))); + headerImpl.setHeader(HeaderSet.TIME_ISO_8601, temp); + } catch (UnsupportedEncodingException e) { + throw e; + } + break; + + default: + if ((headerID & 0xC0) == 0x00) { + headerImpl.setHeader(headerID, ObexHelper.convertToUnicode( + value, true)); + } else { + headerImpl.setHeader(headerID, value); + } + } + + index += length; + break; + + /* + * 0x80 is a byte header. The only valid byte headers are + * the 16 user defined byte headers. + */ + case 0x80: + index++; + try { + headerImpl.setHeader(headerID, Byte.valueOf(headerArray[index])); + } catch (Exception e) { + // Not a valid header so ignore + } + index++; + break; + + /* + * 0xC0 is a 4 byte unsigned integer header and with the + * exception of TIME_4_BYTE will be converted to a Long + * and added. + */ + case 0xC0: + index++; + value = new byte[4]; + System.arraycopy(headerArray, index, value, 0, 4); + try { + if (headerID != HeaderSet.TIME_4_BYTE) { + // Determine if it is a connection ID. These + // need to be handled differently + if (headerID == HeaderSet.CONNECTION_ID) { + headerImpl.mConnectionID = new byte[4]; + System.arraycopy(value, 0, headerImpl.mConnectionID, 0, 4); + } else { + headerImpl.setHeader(headerID, Long + .valueOf(convertToLong(value))); + } + } else { + Calendar temp = Calendar.getInstance(); + temp.setTime(new Date(convertToLong(value) * 1000L)); + headerImpl.setHeader(HeaderSet.TIME_4_BYTE, temp); + } + } catch (Exception e) { + // Not a valid header so ignore + throw new IOException("Header was not formatted properly"); + } + index += 4; + break; + } + + } + } catch (IOException e) { + throw new IOException("Header was not formatted properly"); + } + + return body; + } + + /** + * Creates the header part of OBEX packet based on the header provided. + * + * TODO: Could use getHeaderList() to get the array of headers to + * TODO: include and then use the high two bits to determine the + * TODO: the type of the object and construct the byte array from + * TODO: that. This will make the size smaller. + * + * @param head the header used to construct the byte array + * + * @param nullOut <code>true</code> if the header should be set to + * <code>null</code> once it is added to the array or <code>false</code> + * if it should not be nulled out + * + * @return the header of an OBEX packet + */ + public static byte[] createHeader(HeaderSet head, boolean nullOut) { + Long intHeader = null; + String stringHeader = null; + Calendar dateHeader = null; + Byte byteHeader = null; + StringBuffer buffer = null; + byte[] value = null; + byte[] result = null; + byte[] lengthArray = new byte[2]; + int length; + HeaderSet headImpl = null; + ByteArrayOutputStream out = new ByteArrayOutputStream(); + if (!(head instanceof HeaderSet)) { + throw new IllegalArgumentException("Header not created by createHeaderSet"); + } + headImpl = head; + + try { + /* + * Determine if there is a connection ID to send. If there is, + * then it should be the first header in the packet. + */ + if ((headImpl.mConnectionID != null) && (headImpl.getHeader(HeaderSet.TARGET) == null)) { + + out.write((byte)HeaderSet.CONNECTION_ID); + out.write(headImpl.mConnectionID); + } + + // Count Header + intHeader = (Long)headImpl.getHeader(HeaderSet.COUNT); + if (intHeader != null) { + out.write((byte)HeaderSet.COUNT); + value = ObexHelper.convertToByteArray(intHeader.longValue()); + out.write(value); + if (nullOut) { + headImpl.setHeader(HeaderSet.COUNT, null); + } + } + + // Name Header + stringHeader = (String)headImpl.getHeader(HeaderSet.NAME); + if (stringHeader != null) { + out.write((byte)HeaderSet.NAME); + value = ObexHelper.convertToUnicodeByteArray(stringHeader); + length = value.length + 3; + lengthArray[0] = (byte)(0xFF & (length >> 8)); + lengthArray[1] = (byte)(0xFF & length); + out.write(lengthArray); + out.write(value); + if (nullOut) { + headImpl.setHeader(HeaderSet.NAME, null); + } + } + + // Type Header + stringHeader = (String)headImpl.getHeader(HeaderSet.TYPE); + if (stringHeader != null) { + out.write((byte)HeaderSet.TYPE); + try { + value = stringHeader.getBytes("ISO8859_1"); + } catch (UnsupportedEncodingException e) { + throw e; + } + + length = value.length + 4; + lengthArray[0] = (byte)(255 & (length >> 8)); + lengthArray[1] = (byte)(255 & length); + out.write(lengthArray); + out.write(value); + out.write(0x00); + if (nullOut) { + headImpl.setHeader(HeaderSet.TYPE, null); + } + } + + // Length Header + intHeader = (Long)headImpl.getHeader(HeaderSet.LENGTH); + if (intHeader != null) { + out.write((byte)HeaderSet.LENGTH); + value = ObexHelper.convertToByteArray(intHeader.longValue()); + out.write(value); + if (nullOut) { + headImpl.setHeader(HeaderSet.LENGTH, null); + } + } + + // Time ISO Header + dateHeader = (Calendar)headImpl.getHeader(HeaderSet.TIME_ISO_8601); + if (dateHeader != null) { + + /* + * The ISO Header should take the form YYYYMMDDTHHMMSSZ. The + * 'Z' will only be included if it is a UTC time. + */ + buffer = new StringBuffer(); + int temp = dateHeader.get(Calendar.YEAR); + for (int i = temp; i < 1000; i = i * 10) { + buffer.append("0"); + } + buffer.append(temp); + temp = dateHeader.get(Calendar.MONTH); + if (temp < 10) { + buffer.append("0"); + } + buffer.append(temp); + temp = dateHeader.get(Calendar.DAY_OF_MONTH); + if (temp < 10) { + buffer.append("0"); + } + buffer.append(temp); + buffer.append("T"); + temp = dateHeader.get(Calendar.HOUR_OF_DAY); + if (temp < 10) { + buffer.append("0"); + } + buffer.append(temp); + temp = dateHeader.get(Calendar.MINUTE); + if (temp < 10) { + buffer.append("0"); + } + buffer.append(temp); + temp = dateHeader.get(Calendar.SECOND); + if (temp < 10) { + buffer.append("0"); + } + buffer.append(temp); + + if (dateHeader.getTimeZone().getID().equals("UTC")) { + buffer.append("Z"); + } + + try { + value = buffer.toString().getBytes("ISO8859_1"); + } catch (UnsupportedEncodingException e) { + throw e; + } + + length = value.length + 3; + lengthArray[0] = (byte)(255 & (length >> 8)); + lengthArray[1] = (byte)(255 & length); + out.write(HeaderSet.TIME_ISO_8601); + out.write(lengthArray); + out.write(value); + if (nullOut) { + headImpl.setHeader(HeaderSet.TIME_ISO_8601, null); + } + } + + // Time 4 Byte Header + dateHeader = (Calendar)headImpl.getHeader(HeaderSet.TIME_4_BYTE); + if (dateHeader != null) { + out.write(HeaderSet.TIME_4_BYTE); + + /* + * Need to call getTime() twice. The first call will return + * a java.util.Date object. The second call returns the number + * of milliseconds since January 1, 1970. We need to convert + * it to seconds since the TIME_4_BYTE expects the number of + * seconds since January 1, 1970. + */ + value = ObexHelper.convertToByteArray(dateHeader.getTime().getTime() / 1000L); + out.write(value); + if (nullOut) { + headImpl.setHeader(HeaderSet.TIME_4_BYTE, null); + } + } + + // Description Header + stringHeader = (String)headImpl.getHeader(HeaderSet.DESCRIPTION); + if (stringHeader != null) { + out.write((byte)HeaderSet.DESCRIPTION); + value = ObexHelper.convertToUnicodeByteArray(stringHeader); + length = value.length + 3; + lengthArray[0] = (byte)(255 & (length >> 8)); + lengthArray[1] = (byte)(255 & length); + out.write(lengthArray); + out.write(value); + if (nullOut) { + headImpl.setHeader(HeaderSet.DESCRIPTION, null); + } + } + + // Target Header + value = (byte[])headImpl.getHeader(HeaderSet.TARGET); + if (value != null) { + out.write((byte)HeaderSet.TARGET); + length = value.length + 3; + lengthArray[0] = (byte)(255 & (length >> 8)); + lengthArray[1] = (byte)(255 & length); + out.write(lengthArray); + out.write(value); + if (nullOut) { + headImpl.setHeader(HeaderSet.TARGET, null); + } + } + + // HTTP Header + value = (byte[])headImpl.getHeader(HeaderSet.HTTP); + if (value != null) { + out.write((byte)HeaderSet.HTTP); + length = value.length + 3; + lengthArray[0] = (byte)(255 & (length >> 8)); + lengthArray[1] = (byte)(255 & length); + out.write(lengthArray); + out.write(value); + if (nullOut) { + headImpl.setHeader(HeaderSet.HTTP, null); + } + } + + // Who Header + value = (byte[])headImpl.getHeader(HeaderSet.WHO); + if (value != null) { + out.write((byte)HeaderSet.WHO); + length = value.length + 3; + lengthArray[0] = (byte)(255 & (length >> 8)); + lengthArray[1] = (byte)(255 & length); + out.write(lengthArray); + out.write(value); + if (nullOut) { + headImpl.setHeader(HeaderSet.WHO, null); + } + } + + // Connection ID Header + value = (byte[])headImpl.getHeader(HeaderSet.APPLICATION_PARAMETER); + if (value != null) { + out.write((byte)HeaderSet.APPLICATION_PARAMETER); + length = value.length + 3; + lengthArray[0] = (byte)(255 & (length >> 8)); + lengthArray[1] = (byte)(255 & length); + out.write(lengthArray); + out.write(value); + if (nullOut) { + headImpl.setHeader(HeaderSet.APPLICATION_PARAMETER, null); + } + } + + // Object Class Header + value = (byte[])headImpl.getHeader(HeaderSet.OBJECT_CLASS); + if (value != null) { + out.write((byte)HeaderSet.OBJECT_CLASS); + length = value.length + 3; + lengthArray[0] = (byte)(255 & (length >> 8)); + lengthArray[1] = (byte)(255 & length); + out.write(lengthArray); + out.write(value); + if (nullOut) { + headImpl.setHeader(HeaderSet.OBJECT_CLASS, null); + } + } + + // Check User Defined Headers + for (int i = 0; i < 16; i++) { + + //Unicode String Header + stringHeader = (String)headImpl.getHeader(i + 0x30); + if (stringHeader != null) { + out.write((byte)i + 0x30); + value = ObexHelper.convertToUnicodeByteArray(stringHeader); + length = value.length + 3; + lengthArray[0] = (byte)(255 & (length >> 8)); + lengthArray[1] = (byte)(255 & length); + out.write(lengthArray); + out.write(value); + if (nullOut) { + headImpl.setHeader(i + 0x30, null); + } + } + + // Byte Sequence Header + value = (byte[])headImpl.getHeader(i + 0x70); + if (value != null) { + out.write((byte)i + 0x70); + length = value.length + 3; + lengthArray[0] = (byte)(255 & (length >> 8)); + lengthArray[1] = (byte)(255 & length); + out.write(lengthArray); + out.write(value); + if (nullOut) { + headImpl.setHeader(i + 0x70, null); + } + } + + // Byte Header + byteHeader = (Byte)headImpl.getHeader(i + 0xB0); + if (byteHeader != null) { + out.write((byte)i + 0xB0); + out.write(byteHeader.byteValue()); + if (nullOut) { + headImpl.setHeader(i + 0xB0, null); + } + } + + // Integer header + intHeader = (Long)headImpl.getHeader(i + 0xF0); + if (intHeader != null) { + out.write((byte)i + 0xF0); + out.write(ObexHelper.convertToByteArray(intHeader.longValue())); + if (nullOut) { + headImpl.setHeader(i + 0xF0, null); + } + } + } + + // Add the authentication challenge header + if (headImpl.mAuthChall != null) { + out.write((byte)HeaderSet.AUTH_CHALLENGE); + length = headImpl.mAuthChall.length + 3; + lengthArray[0] = (byte)(255 & (length >> 8)); + lengthArray[1] = (byte)(255 & length); + out.write(lengthArray); + out.write(headImpl.mAuthChall); + if (nullOut) { + headImpl.mAuthChall = null; + } + } + + // Add the authentication response header + if (headImpl.mAuthResp != null) { + out.write((byte)HeaderSet.AUTH_RESPONSE); + length = headImpl.mAuthResp.length + 3; + lengthArray[0] = (byte)(255 & (length >> 8)); + lengthArray[1] = (byte)(255 & length); + out.write(lengthArray); + out.write(headImpl.mAuthResp); + if (nullOut) { + headImpl.mAuthResp = null; + } + } + + } catch (IOException e) { + } finally { + result = out.toByteArray(); + try { + out.close(); + } catch (Exception ex) { + } + } + + return result; + + } + + /** + * Determines where the maximum divide is between headers. This method is + * used by put and get operations to separate headers to a size that meets + * the max packet size allowed. + * + * @param headerArray the headers to separate + * + * @param start the starting index to search + * + * @param maxSize the maximum size of a packet + * + * @return the index of the end of the header block to send or -1 if the + * header could not be divided because the header is too large + */ + public static int findHeaderEnd(byte[] headerArray, int start, int maxSize) { + + int fullLength = 0; + int lastLength = -1; + int index = start; + int length = 0; + + while ((fullLength < maxSize) && (index < headerArray.length)) { + int headerID = (headerArray[index] < 0 ? headerArray[index] + 256 : headerArray[index]); + lastLength = fullLength; + + switch (headerID & (0xC0)) { + + case 0x00: + // Fall through + case 0x40: + + index++; + length = (headerArray[index] < 0 ? headerArray[index] + 256 + : headerArray[index]); + length = length << 8; + index++; + length += (headerArray[index] < 0 ? headerArray[index] + 256 + : headerArray[index]); + length -= 3; + index++; + index += length; + fullLength += length + 3; + break; + + case 0x80: + + index++; + index++; + fullLength += 2; + break; + + case 0xC0: + + index += 5; + fullLength += 5; + break; + + } + + } + + /* + * Determine if this is the last header or not + */ + if (lastLength == 0) { + /* + * Since this is the last header, check to see if the size of this + * header is less then maxSize. If it is, return the length of the + * header, otherwise return -1. The length of the header is + * returned since it would be the start of the next header + */ + if (fullLength < maxSize) { + return headerArray.length; + } else { + return -1; + } + } else { + return lastLength + start; + } + } + + /** + * Converts the byte array to a long. + * + * @param b the byte array to convert to a long + * + * @return the byte array as a long + */ + public static long convertToLong(byte[] b) { + long result = 0; + long value = 0; + long power = 0; + + for (int i = (b.length - 1); i >= 0; i--) { + value = b[i]; + if (value < 0) { + value += 256; + } + + result = result | (value << power); + power += 8; + } + + return result; + } + + /** + * Converts the long to a 4 byte array. The long must be non negative. + * + * @param l the long to convert + * + * @return a byte array that is the same as the long + */ + public static byte[] convertToByteArray(long l) { + byte[] b = new byte[4]; + + b[0] = (byte)(255 & (l >> 24)); + b[1] = (byte)(255 & (l >> 16)); + b[2] = (byte)(255 & (l >> 8)); + b[3] = (byte)(255 & l); + + return b; + } + + /** + * Converts the String to a UNICODE byte array. It will also add the ending + * null characters to the end of the string. + * + * @param s the string to convert + * + * @return the unicode byte array of the string + */ + public static byte[] convertToUnicodeByteArray(String s) { + if (s == null) { + return null; + } + + char c[] = s.toCharArray(); + byte[] result = new byte[(c.length * 2) + 2]; + for (int i = 0; i < c.length; i++) { + result[(i * 2)] = (byte)(c[i] >> 8); + result[((i * 2) + 1)] = (byte)c[i]; + } + + // Add the UNICODE null character + result[result.length - 2] = 0; + result[result.length - 1] = 0; + + return result; + } + + /** + * Retrieves the value from the byte array for the tag value specified. The + * array should be of the form Tag - Length - Value triplet. + * + * @param tag the tag to retrieve from the byte array + * + * @param triplet the byte sequence containing the tag length value form + * + * @return the value of the specified tag + */ + public static byte[] getTagValue(byte tag, byte[] triplet) { + + int index = findTag(tag, triplet); + if (index == -1) { + return null; + } + + index++; + int length = triplet[index] & 0xFF; + + byte[] result = new byte[length]; + index++; + System.arraycopy(triplet, index, result, 0, length); + + return result; + } + + /** + * Finds the index that starts the tag value pair in the byte array provide. + * + * @param tag the tag to look for + * + * @param value the byte array to search + * + * @return the starting index of the tag or -1 if the tag could not be found + */ + public static int findTag(byte tag, byte[] value) { + int length = 0; + + if (value == null) { + return -1; + } + + int index = 0; + + while ((index < value.length) && (value[index] != tag)) { + length = value[index + 1] & 0xFF; + index += length + 2; + } + + if (index >= value.length) { + return -1; + } + + return index; + } + + /** + * Converts the byte array provided to a unicode string. + * + * @param b the byte array to convert to a string + * + * @param includesNull determine if the byte string provided contains the + * UNICODE null character at the end or not; if it does, it will be + * removed + * + * @return a Unicode string + * + * @param IllegalArgumentException if the byte array has an odd length + */ + public static String convertToUnicode(byte[] b, boolean includesNull) { + if (b == null) { + return null; + } + int arrayLength = b.length; + if (!((arrayLength % 2) == 0)) { + throw new IllegalArgumentException("Byte array not of a valid form"); + } + arrayLength = (arrayLength >> 1); + if (includesNull) { + arrayLength -= 1; + } + + char[] c = new char[arrayLength]; + for (int i = 0; i < arrayLength; i++) { + int upper = b[2 * i]; + int lower = b[(2 * i) + 1]; + if (upper < 0) { + upper += 256; + } + if (lower < 0) { + lower += 256; + } + + c[i] = (char)((upper << 8) | lower); + } + + return new String(c); + } + + /** + * Compute the MD5 hash of the byte array provided. + * Does not accumulate input. + * + * @param in the byte array to hash + * @return the MD5 hash of the byte array + */ + public static byte[] computeMd5Hash(byte[] in) { + Md5MessageDigest md5 = new Md5MessageDigest(); + return md5.digest(in); + } + + /** + * Computes an authentication challenge header. + * + * + * @param nonce the challenge that will be provided to the peer; the + * challenge must be 16 bytes long + * + * @param realm a short description that describes what password to use + * + * @param access if <code>true</code> then full access will be granted if + * successful; if <code>false</code> then read only access will be granted + * if successful + * + * @param userID if <code>true</code>, a user ID is required in the reply; + * if <code>false</code>, no user ID is required + * + * @throws IllegalArgumentException if the challenge is not 16 bytes + * long; if the realm can not be encoded in less then 255 bytes + * + * @throws IOException if the encoding scheme ISO 8859-1 is not supported + */ + public static byte[] computeAuthenticationChallenge(byte[] nonce, String realm, boolean access, + boolean userID) throws IOException { + byte[] authChall = null; + + if (nonce.length != 16) { + throw new IllegalArgumentException("Nonce must be 16 bytes long"); + } + + /* + * The authentication challenge is a byte sequence of the following form + * byte 0: 0x00 - the tag for the challenge + * byte 1: 0x10 - the length of the challenge; must be 16 + * byte 2-17: the authentication challenge + * byte 18: 0x01 - the options tag; this is optional in the spec, but + * we are going to include it in every message + * byte 19: 0x01 - length of the options; must be 1 + * byte 20: the value of the options; bit 0 is set if user ID is + * required; bit 1 is set if access mode is read only + * byte 21: 0x02 - the tag for authentication realm; only included if + * an authentication realm is specified + * byte 22: the length of the authentication realm; only included if + * the authentication realm is specified + * byte 23: the encoding scheme of the authentication realm; we will use + * the ISO 8859-1 encoding scheme since it is part of the KVM + * byte 24 & up: the realm if one is specified. + */ + if (realm == null) { + authChall = new byte[21]; + } else { + if (realm.length() >= 255) { + throw new IllegalArgumentException("Realm must be less then 255 bytes"); + } + authChall = new byte[24 + realm.length()]; + authChall[21] = 0x02; + authChall[22] = (byte)(realm.length() + 1); + authChall[23] = 0x01; // ISO 8859-1 Encoding + System.arraycopy(realm.getBytes("ISO8859_1"), 0, authChall, 24, realm.length()); + } + + // Include the nonce field in the header + authChall[0] = 0x00; + authChall[1] = 0x10; + System.arraycopy(nonce, 0, authChall, 2, 16); + + // Include the options header + authChall[18] = 0x01; + authChall[19] = 0x01; + authChall[20] = 0x00; + + if (!access) { + authChall[20] = (byte)(authChall[20] | 0x02); + } + if (userID) { + authChall[20] = (byte)(authChall[20] | 0x01); + } + + return authChall; + } +} diff --git a/obex/javax/obex/ObexSession.java b/obex/javax/obex/ObexSession.java new file mode 100644 index 0000000..97d65e0 --- /dev/null +++ b/obex/javax/obex/ObexSession.java @@ -0,0 +1,226 @@ +/* + * 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; + +/** + * The <code>ObexSession</code> interface characterizes the term + * "OBEX Connection" as defined in the IrDA Object Exchange Protocol v1.2, which + * could be the server-side view of an OBEX connection, or the client-side view + * of the same connection, which is established by server's accepting of a + * client issued "CONNECT". + * <P> + * + * This interface serves as the common super class for + * <CODE>ClientSession</CODE> and <CODE>ServerSession</CODE>. + * + * @hide + */ +public class ObexSession { + + protected Authenticator mAuthenticator; + + protected byte[] mChallengeDigest; + + /** + * Called when the server received an authentication challenge header. This + * will cause the authenticator to handle the authentication challenge. + * + * @param header + * the header with the authentication challenge + * + * @return <code>true</code> if the last request should be resent; + * <code>false</code> if the last request should not be resent + * @throws IOException + */ + public boolean handleAuthChall(HeaderSet header) throws IOException { + if (mAuthenticator == null) { + return false; + } + + /* + * An authentication challenge is made up of one required and two + * optional tag length value triplets. The tag 0x00 is required to be in + * the authentication challenge and it represents the challenge digest + * that was received. The tag 0x01 is the options tag. This tag tracks + * if user ID is required and if full access will be granted. The tag + * 0x02 is the realm, which provides a description of which user name + * and password to use. + */ + byte[] challenge = ObexHelper.getTagValue((byte)0x00, header.mAuthChall); + byte[] option = ObexHelper.getTagValue((byte)0x01, header.mAuthChall); + byte[] description = ObexHelper.getTagValue((byte)0x02, header.mAuthChall); + + String realm = null; + if (description != null) { + byte[] realmString = new byte[description.length - 1]; + System.arraycopy(description, 1, realmString, 0, realmString.length); + + switch (description[0] & 0xFF) { + + case ObexHelper.OBEX_AUTH_REALM_CHARSET_ASCII: + // ASCII encoding + // Fall through + case ObexHelper.OBEX_AUTH_REALM_CHARSET_ISO_8859_1: + // ISO-8859-1 encoding + try { + realm = new String(realmString, "ISO8859_1"); + } catch (Exception e) { + throw new IOException("Unsupported Encoding Scheme"); + } + break; + + case ObexHelper.OBEX_AUTH_REALM_CHARSET_UNICODE: + // UNICODE Encoding + realm = ObexHelper.convertToUnicode(realmString, false); + break; + + default: + throw new IOException("Unsupported Encoding Scheme"); + } + } + + boolean isUserIDRequired = false; + boolean isFullAccess = true; + if (option != null) { + if ((option[0] & 0x01) != 0) { + isUserIDRequired = true; + } + + if ((option[0] & 0x02) != 0) { + isFullAccess = false; + } + } + + PasswordAuthentication result = null; + header.mAuthChall = null; + + try { + result = mAuthenticator + .onAuthenticationChallenge(realm, isUserIDRequired, isFullAccess); + } catch (Exception e) { + return false; + } + + /* + * If no password is provided then we not resent the request + */ + if (result == null) { + return false; + } + + byte[] password = result.getPassword(); + if (password == null) { + return false; + } + + byte[] userName = result.getUserName(); + + /* + * Create the authentication response header. It includes 1 required and + * 2 option tag length value triples. The required triple has a tag of + * 0x00 and is the response digest. The first optional tag is 0x01 and + * represents the user ID. If no user ID is provided, then no user ID + * will be sent. The second optional tag is 0x02 and is the challenge + * that was received. This will always be sent + */ + if (userName != null) { + header.mAuthResp = new byte[38 + userName.length]; + header.mAuthResp[36] = (byte)0x01; + header.mAuthResp[37] = (byte)userName.length; + System.arraycopy(userName, 0, header.mAuthResp, 38, userName.length); + } else { + header.mAuthResp = new byte[36]; + } + + // Create the secret String + byte[] digest = new byte[challenge.length + password.length + 1]; + System.arraycopy(challenge, 0, digest, 0, challenge.length); + // Insert colon between challenge and password + digest[challenge.length] = (byte)0x3A; + System.arraycopy(password, 0, digest, challenge.length + 1, password.length); + + // Add the Response Digest + header.mAuthResp[0] = (byte)0x00; + header.mAuthResp[1] = (byte)0x10; + + System.arraycopy(ObexHelper.computeMd5Hash(digest), 0, header.mAuthResp, 2, 16); + + // Add the challenge + header.mAuthResp[18] = (byte)0x02; + header.mAuthResp[19] = (byte)0x10; + System.arraycopy(challenge, 0, header.mAuthResp, 20, 16); + + return true; + } + + /** + * Called when the server received an authentication response header. This + * will cause the authenticator to handle the authentication response. + * + * @param authResp + * the authentication response + * + * @return <code>true</code> if the response passed; <code>false</code> if + * the response failed + */ + public boolean handleAuthResp(byte[] authResp) { + if (mAuthenticator == null) { + return false; + } + // get the correct password from the application + byte[] correctPassword = mAuthenticator.onAuthenticationResponse(ObexHelper.getTagValue( + (byte)0x01, authResp)); + if (correctPassword == null) { + return false; + } + + byte[] temp = new byte[correctPassword.length + 16]; + + System.arraycopy(mChallengeDigest, 0, temp, 0, 16); + System.arraycopy(correctPassword, 0, temp, 16, correctPassword.length); + + byte[] correctResponse = ObexHelper.computeMd5Hash(temp); + byte[] actualResponse = ObexHelper.getTagValue((byte)0x00, authResp); + + // compare the MD5 hash array . + for (int i = 0; i < 16; i++) { + if (correctResponse[i] != actualResponse[i]) { + return false; + } + } + + return true; + } +} diff --git a/obex/javax/obex/ObexTransport.java b/obex/javax/obex/ObexTransport.java new file mode 100644 index 0000000..d0ba0c9 --- /dev/null +++ b/obex/javax/obex/ObexTransport.java @@ -0,0 +1,77 @@ +/* + * 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.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * The <code>ObexTransport</code> interface defines the underlying transport + * connection which carries the OBEX protocol( such as TCP, RFCOMM device file + * exposed by Bluetooth or USB in kernel, RFCOMM socket emulated in Android + * platform, Irda). This interface provides an abstract layer to be used by the + * <code>ObexConnection</code>. Each kind of medium shall have its own + * implementation to wrap and follow the same interface. + * <P> + * See section 1.2.2 of IrDA Object Exchange Protocol specification. + * <P> + * Different kind of medium may have different construction - for example, the + * RFCOMM device file medium may be constructed from a file descriptor or simply + * a string while the TCP medium usually from a socket. + * + * @hide + */ +public interface ObexTransport { + + void create() throws IOException; + + void listen() throws IOException; + + void close() throws IOException; + + void connect() throws IOException; + + void disconnect() throws IOException; + + InputStream openInputStream() throws IOException; + + OutputStream openOutputStream() throws IOException; + + DataInputStream openDataInputStream() throws IOException; + + DataOutputStream openDataOutputStream() throws IOException; + +} diff --git a/obex/javax/obex/Operation.java b/obex/javax/obex/Operation.java new file mode 100644 index 0000000..f265f53 --- /dev/null +++ b/obex/javax/obex/Operation.java @@ -0,0 +1,200 @@ +/* + * 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.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * The <code>Operation</code> interface provides ways to manipulate a single + * OBEX PUT or GET operation. The implementation of this interface sends + * OBEX packets as they are built. If during the operation the peer in the + * operation ends the operation, an <code>IOException</code> is thrown on + * the next read from the input stream, write to the output stream, or call to + * <code>sendHeaders()</code>. + * <P> + * <STRONG>Definition of methods inherited from <code>ContentConnection</code></STRONG> + * <P> + * <code>getEncoding()</code> will always return <code>null</code>. + * <BR><code>getLength()</code> will return the length specified by the OBEX Length + * header or -1 if the OBEX Length header was not included. + * <BR><code>getType()</code> will return the value specified in the OBEX Type + * header or <code>null</code> if the OBEX Type header was not included.<BR> + * <P> + * <STRONG>How Headers are Handled</STRONG> + * <P> + * As headers are received, they may be retrieved through the + * <code>getReceivedHeaders()</code> method. If new headers are set during the + * operation, the new headers will be sent during the next packet exchange. + * <P> + * <STRONG>PUT example</STRONG> + * <P> + * <PRE> + * void putObjectViaOBEX(ClientSession conn, HeaderSet head, byte[] obj) + * throws IOException { + * + * // Include the length header + * head.setHeader(head.LENGTH, new Long(obj.length)); + * + * // Initiate the PUT request + * Operation op = conn.put(head); + * + * // Open the output stream to put the object to it + * DataOutputStream out = op.openDataOutputStream(); + * + * // Send the object to the server + * out.write(obj); + * + * // End the transaction + * out.close(); + * op.close(); + * } + * </PRE> + * <P> + * <STRONG>GET example</STRONG> + * <P> + * <PRE> + * byte[] getObjectViaOBEX(ClientSession conn, HeaderSet head) throws IOException { + * + * // Send the initial GET request to the server + * Operation op = conn.get(head); + * + * // Retrieve the length of the object being sent back + * int length = op.getLength(); + * + * // Create space for the object + * byte[] obj = new byte[length]; + * + * // Get the object from the input stream + * DataInputStream in = trans.openDataInputStream(); + * in.read(obj); + * + * // End the transaction + * in.close(); + * op.close(); + * + * return obj; + * } + * </PRE> + * <H3>Client PUT Operation Flow</H3> + * For PUT operations, a call to <code>close()</code> the <code>OutputStream</code> + * returned from <code>openOutputStream()</code> or <code>openDataOutputStream()</code> + * will signal that the request is done. (In OBEX terms, the End-Of-Body header should + * be sent and the final bit in the request will be set.) At this point, the + * reply from the server may begin to be processed. A call to + * <code>getResponseCode()</code> will do an implicit close on the + * <code>OutputStream</code> and therefore signal that the request is done. + * <H3>Client GET Operation Flow</H3> + * For GET operation, a call to <code>openInputStream()</code> or + * <code>openDataInputStream()</code> will signal that the request is done. (In OBEX + * terms, the final bit in the request will be set.) A call to + * <code>getResponseCode()</code> will cause an implicit close on the + * <code>InputStream</code>. No further data may be read at this point. + * + * @hide + */ +public interface Operation { + + /** + * Sends an ABORT message to the server. By calling this method, the + * corresponding input and output streams will be closed along with this + * object. No headers are sent in the abort request. This will end the + * operation since <code>close()</code> will be called by this method. + * + * @throws IOException if the transaction has already ended or if an + * OBEX server calls this method + */ + void abort() throws IOException; + + /** + * 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> + * + * @throws IOException if this <code>Operation</code> has been closed + */ + HeaderSet getReceivedHeader() throws IOException; + + /** + * 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 + * + * @throws IOException if this <code>Operation</code> has been closed + * or the transaction has ended and no further messages will be exchanged + * + * @throws IllegalArgumentException if <code>headers</code> was not created + * by a call to <code>ServerRequestHandler.createHeaderSet()</code> or + * <code>ClientSession.createHeaderSet()</code> + * + * @throws NullPointerException if <code>headers</code> if <code>null</code> + */ + void sendHeaders(HeaderSet headers) throws IOException; + + /** + * Returns the response code received from the server. Response codes + * are defined in the <code>ResponseCodes</code> class. + * + * @see ResponseCodes + * + * @return the response code retrieved from the server + * + * @throws IOException if an error occurred in the transport layer during + * the transaction; if this object was created by an OBEX server + */ + int getResponseCode() throws IOException; + + String getEncoding(); + + long getLength(); + + String getType(); + + InputStream openInputStream() throws IOException; + + DataInputStream openDataInputStream() throws IOException; + + OutputStream openOutputStream() throws IOException; + + DataOutputStream openDataOutputStream() throws IOException; + + void close() throws IOException; + + int getMaxPacketSize(); +} diff --git a/obex/javax/obex/PasswordAuthentication.java b/obex/javax/obex/PasswordAuthentication.java new file mode 100644 index 0000000..e81a861 --- /dev/null +++ b/obex/javax/obex/PasswordAuthentication.java @@ -0,0 +1,85 @@ +/* + * 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; + +/** + * This class holds user name and password combinations. + * + * @hide + */ +public final class PasswordAuthentication { + + private byte[] mUserName; + + private final byte[] mPassword; + + /** + * Creates a new <code>PasswordAuthentication</code> with the user name + * and password provided. + * + * @param userName the user name to include; this may be <code>null</code> + * + * @param password the password to include in the response + * + * @throws NullPointerException if <code>password</code> is + * <code>null</code> + */ + public PasswordAuthentication(final byte[] userName, final byte[] password) { + if (userName != null) { + mUserName = new byte[userName.length]; + System.arraycopy(userName, 0, mUserName, 0, userName.length); + } + + mPassword = new byte[password.length]; + System.arraycopy(password, 0, mPassword, 0, password.length); + } + + /** + * Retrieves the user name that was specified in the constructor. + * The user name may be <code>null</code>. + * + * @return the user name + */ + public byte[] getUserName() { + return mUserName; + } + + /** + * Retrieves the password. + * + * @return the password + */ + public byte[] getPassword() { + return mPassword; + } +} diff --git a/obex/javax/obex/PrivateInputStream.java b/obex/javax/obex/PrivateInputStream.java new file mode 100644 index 0000000..2dc02da --- /dev/null +++ b/obex/javax/obex/PrivateInputStream.java @@ -0,0 +1,191 @@ +/* + * 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.InputStream; +import java.io.IOException; + +/** + * This object provides an input stream to the Operation objects used in this + * package. + * + * @hide + */ +public final class PrivateInputStream extends InputStream { + + private BaseStream mParent; + + private byte[] mData; + + private int mIndex; + + private boolean mOpen; + + /** + * Creates an input stream for the <code>Operation</code> to read from + * + * @param p the connection this input stream is for + */ + public PrivateInputStream(BaseStream p) { + mParent = p; + mData = new byte[0]; + mIndex = 0; + mOpen = true; + } + + /** + * Returns the number of bytes that can be read (or skipped over) from this + * input stream without blocking by the next caller of a method for this + * input stream. The next caller might be the same thread or or another + * thread. + * + * @return the number of bytes that can be read from this input stream + * without blocking + * + * @throws IOException if an I/O error occurs + */ + @Override + public synchronized int available() throws IOException { + ensureOpen(); + return mData.length - mIndex; + } + + /** + * Reads the next byte of data from the input stream. The value byte is + * returned as an int in the range 0 to 255. If no byte is available + * because the end of the stream has been reached, the value -1 is + * returned. This method blocks until input data is available, the end of + * the stream is detected, or an exception is thrown. + * + * @return the byte read from the input stream or -1 if it reaches the end + * of stream + * + * @throws IOException if an I/O error occurs + */ + @Override + public synchronized int read() throws IOException { + ensureOpen(); + while (mData.length == mIndex) { + if (!mParent.continueOperation(true, true)) { + return -1; + } + } + return (mData[mIndex++] & 0xFF); + } + + @Override + public int read(byte[] b) throws IOException { + return read(b, 0, b.length); + } + + @Override + public synchronized int read(byte[] b, int offset, int length) throws IOException { + + if (b == null) { + throw new IOException("buffer is null"); + } + if ((offset | length) < 0 || length > b.length - offset) { + throw new ArrayIndexOutOfBoundsException("index outof bound"); + } + ensureOpen(); + + int currentDataLength = mData.length - mIndex; + int remainReadLength = length; + int offset1 = offset; + int result = 0; + + while (currentDataLength <= remainReadLength) { + System.arraycopy(mData, mIndex, b, offset1, currentDataLength); + mIndex += currentDataLength; + offset1 += currentDataLength; + result += currentDataLength; + remainReadLength -= currentDataLength; + + if (!mParent.continueOperation(true, true)) { + return result == 0 ? -1 : result; + } + currentDataLength = mData.length - mIndex; + } + if (remainReadLength > 0) { + System.arraycopy(mData, mIndex, b, offset1, remainReadLength); + mIndex += remainReadLength; + result += remainReadLength; + } + return result; + } + + /** + * Allows the <code>OperationImpl</code> thread to add body data to the + * input stream. + * + * @param body the data to add to the stream + * + * @param start the start of the body to array to copy + */ + public synchronized void writeBytes(byte[] body, int start) { + + int length = (body.length - start) + (mData.length - mIndex); + byte[] temp = new byte[length]; + + System.arraycopy(mData, mIndex, temp, 0, mData.length - mIndex); + System.arraycopy(body, start, temp, mData.length - mIndex, body.length - start); + + mData = temp; + mIndex = 0; + notifyAll(); + } + + /** + * Verifies that this stream is open + * + * @throws IOException if the stream is not open + */ + private void ensureOpen() throws IOException { + mParent.ensureOpen(); + if (!mOpen) { + throw new IOException("Input stream is closed"); + } + } + + /** + * Closes the input stream. If the input stream is already closed, do + * nothing. + * + * @throws IOException this will never happen + */ + @Override + public void close() throws IOException { + mOpen = false; + mParent.streamClosed(true); + } +} diff --git a/obex/javax/obex/PrivateOutputStream.java b/obex/javax/obex/PrivateOutputStream.java new file mode 100644 index 0000000..d972f78 --- /dev/null +++ b/obex/javax/obex/PrivateOutputStream.java @@ -0,0 +1,184 @@ +/* + * 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.OutputStream; +import java.io.ByteArrayOutputStream; + +/** + * This object provides an output stream to the Operation objects used in this + * package. + * + * @hide + */ +public final class PrivateOutputStream extends OutputStream { + + private BaseStream mParent; + + private ByteArrayOutputStream mArray; + + private boolean mOpen; + + private int mMaxPacketSize; + + /** + * Creates an empty <code>PrivateOutputStream</code> to write to. + * + * @param p the connection that this stream runs over + */ + public PrivateOutputStream(BaseStream p, int maxSize) { + mParent = p; + mArray = new ByteArrayOutputStream(); + mMaxPacketSize = maxSize; + } + + /** + * Determines how many bytes have been written to the output stream. + * + * @return the number of bytes written to the output stream + */ + public int size() { + return mArray.size(); + } + + /** + * Writes the specified byte to this output stream. The general contract + * for write is that one byte is written to the output stream. The byte to + * be written is the eight low-order bits of the argument b. The 24 + * high-order bits of b are ignored. + * + * @param b the byte to write + * + * @throws IOException if an I/O error occurs + */ + @Override + public synchronized void write(int b) throws IOException { + ensureOpen(); + mParent.ensureNotDone(); + mArray.write(b); + if (mArray.size() == mMaxPacketSize) { + mParent.continueOperation(true, false); + } + } + + @Override + public void write(byte[] buffer) throws IOException { + write(buffer, 0, buffer.length); + } + + @Override + public synchronized void write(byte[] buffer, int offset, int count) throws IOException { + int offset1 = offset; + int remainLength = count; + + if (buffer == null) { + throw new IOException("buffer is null"); + } + if ((offset | count) < 0 || count > buffer.length - offset) { + throw new IndexOutOfBoundsException("index outof bound"); + } + + ensureOpen(); + mParent.ensureNotDone(); + if (count < mMaxPacketSize) { + mArray.write(buffer, offset, count); + } else { + while (remainLength >= mMaxPacketSize) { + mArray.write(buffer, offset1, mMaxPacketSize); + offset1 += mMaxPacketSize; + remainLength = count - offset1; + mParent.continueOperation(true, false); + } + if (remainLength > 0) { + mArray.write(buffer, offset1, remainLength); + } + } + } + + /** + * Reads the bytes that have been written to this stream. + * + * @param size the size of the array to return + * + * @return the byte array that is written + */ + public synchronized byte[] readBytes(int size) { + if (mArray.size() > 0) { + byte[] temp = mArray.toByteArray(); + mArray.reset(); + byte[] result = new byte[size]; + System.arraycopy(temp, 0, result, 0, size); + if (temp.length != size) { + mArray.write(temp, size, temp.length - size); + } + return result; + } else { + return null; + } + } + + /** + * Verifies that this stream is open + * + * @throws IOException if the stream is not open + */ + private void ensureOpen() throws IOException { + mParent.ensureOpen(); + if (!mOpen) { + throw new IOException("Output stream is closed"); + } + } + + /** + * Closes the output stream. If the input stream is already closed, do + * nothing. + * + * @throws IOException this will never happen + */ + @Override + public void close() throws IOException { + mOpen = false; + mParent.streamClosed(false); + } + + /** + * Determines if the connection is closed + * + * @return <code>true</code> if the connection is closed; + * <code>false</code> if the connection is open + */ + public boolean isClosed() { + return !mOpen; + } +} diff --git a/obex/javax/obex/ResponseCodes.java b/obex/javax/obex/ResponseCodes.java new file mode 100644 index 0000000..f6cc9c1 --- /dev/null +++ b/obex/javax/obex/ResponseCodes.java @@ -0,0 +1,327 @@ +/* + * 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; + +/** + * The <code>ResponseCodes</code> class contains the list of valid + * response codes a server may send to a client. + * <P> + * <STRONG>IMPORTANT NOTE</STRONG> + * <P> + * The values in this interface represent the values defined in the IrOBEX + * specification, which is different with the HTTP specification. + * <P> + * <code>OBEX_DATABASE_FULL</code> and <code>OBEX_DATABASE_LOCKED</code> require + * further description since they are not defined in HTTP. The server will send + * an <code>OBEX_DATABASE_FULL</code> message when the client requests that + * something be placed into a database but the database is full (cannot take + * more data). <code>OBEX_DATABASE_LOCKED</code> will be returned when the + * client wishes to access a database, database table, or database record that + * has been locked. + * + * @hide + */ +public final class ResponseCodes { + + /** + * Defines the OBEX CONTINUE response code. + * <P> + * The value of <code>OBEX_HTTP_CONTINUE</code> is 0x90 (144). + */ + public static final int OBEX_HTTP_CONTINUE = 0x90; + + /** + * Defines the OBEX SUCCESS response code. + * <P> + * The value of <code>OBEX_HTTP_OK</code> is 0xA0 (160). + */ + public static final int OBEX_HTTP_OK = 0xA0; + + /** + * Defines the OBEX CREATED response code. + * <P> + * The value of <code>OBEX_HTTP_CREATED</code> is 0xA1 (161). + */ + public static final int OBEX_HTTP_CREATED = 0xA1; + + /** + * Defines the OBEX ACCEPTED response code. + * <P> + * The value of <code>OBEX_HTTP_ACCEPTED</code> is 0xA2 (162). + */ + public static final int OBEX_HTTP_ACCEPTED = 0xA2; + + /** + * Defines the OBEX NON-AUTHORITATIVE INFORMATION response code. + * <P> + * The value of <code>OBEX_HTTP_NOT_AUTHORITATIVE</code> is 0xA3 (163). + */ + public static final int OBEX_HTTP_NOT_AUTHORITATIVE = 0xA3; + + /** + * Defines the OBEX NO CONTENT response code. + * <P> + * The value of <code>OBEX_HTTP_NO_CONTENT</code> is 0xA4 (164). + */ + public static final int OBEX_HTTP_NO_CONTENT = 0xA4; + + /** + * Defines the OBEX RESET CONTENT response code. + * <P> + * The value of <code>OBEX_HTTP_RESET</code> is 0xA5 (165). + */ + public static final int OBEX_HTTP_RESET = 0xA5; + + /** + * Defines the OBEX PARTIAL CONTENT response code. + * <P> + * The value of <code>OBEX_HTTP_PARTIAL</code> is 0xA6 (166). + */ + public static final int OBEX_HTTP_PARTIAL = 0xA6; + + /** + * Defines the OBEX MULTIPLE_CHOICES response code. + * <P> + * The value of <code>OBEX_HTTP_MULT_CHOICE</code> is 0xB0 (176). + */ + public static final int OBEX_HTTP_MULT_CHOICE = 0xB0; + + /** + * Defines the OBEX MOVED PERMANENTLY response code. + * <P> + * The value of <code>OBEX_HTTP_MOVED_PERM</code> is 0xB1 (177). + */ + public static final int OBEX_HTTP_MOVED_PERM = 0xB1; + + /** + * Defines the OBEX MOVED TEMPORARILY response code. + * <P> + * The value of <code>OBEX_HTTP_MOVED_TEMP</code> is 0xB2 (178). + */ + public static final int OBEX_HTTP_MOVED_TEMP = 0xB2; + + /** + * Defines the OBEX SEE OTHER response code. + * <P> + * The value of <code>OBEX_HTTP_SEE_OTHER</code> is 0xB3 (179). + */ + public static final int OBEX_HTTP_SEE_OTHER = 0xB3; + + /** + * Defines the OBEX NOT MODIFIED response code. + * <P> + * The value of <code>OBEX_HTTP_NOT_MODIFIED</code> is 0xB4 (180). + */ + public static final int OBEX_HTTP_NOT_MODIFIED = 0xB4; + + /** + * Defines the OBEX USE PROXY response code. + * <P> + * The value of <code>OBEX_HTTP_USE_PROXY</code> is 0xB5 (181). + */ + public static final int OBEX_HTTP_USE_PROXY = 0xB5; + + /** + * Defines the OBEX BAD REQUEST response code. + * <P> + * The value of <code>OBEX_HTTP_BAD_REQUEST</code> is 0xC0 (192). + */ + public static final int OBEX_HTTP_BAD_REQUEST = 0xC0; + + /** + * Defines the OBEX UNAUTHORIZED response code. + * <P> + * The value of <code>OBEX_HTTP_UNAUTHORIZED</code> is 0xC1 (193). + */ + public static final int OBEX_HTTP_UNAUTHORIZED = 0xC1; + + /** + * Defines the OBEX PAYMENT REQUIRED response code. + * <P> + * The value of <code>OBEX_HTTP_PAYMENT_REQUIRED</code> is 0xC2 (194). + */ + public static final int OBEX_HTTP_PAYMENT_REQUIRED = 0xC2; + + /** + * Defines the OBEX FORBIDDEN response code. + * <P> + * The value of <code>OBEX_HTTP_FORBIDDEN</code> is 0xC3 (195). + */ + public static final int OBEX_HTTP_FORBIDDEN = 0xC3; + + /** + * Defines the OBEX NOT FOUND response code. + * <P> + * The value of <code>OBEX_HTTP_NOT_FOUND</code> is 0xC4 (196). + */ + public static final int OBEX_HTTP_NOT_FOUND = 0xC4; + + /** + * Defines the OBEX METHOD NOT ALLOWED response code. + * <P> + * The value of <code>OBEX_HTTP_BAD_METHOD</code> is 0xC5 (197). + */ + public static final int OBEX_HTTP_BAD_METHOD = 0xC5; + + /** + * Defines the OBEX NOT ACCEPTABLE response code. + * <P> + * The value of <code>OBEX_HTTP_NOT_ACCEPTABLE</code> is 0xC6 (198). + */ + public static final int OBEX_HTTP_NOT_ACCEPTABLE = 0xC6; + + /** + * Defines the OBEX PROXY AUTHENTICATION REQUIRED response code. + * <P> + * The value of <code>OBEX_HTTP_PROXY_AUTH</code> is 0xC7 (199). + */ + public static final int OBEX_HTTP_PROXY_AUTH = 0xC7; + + /** + * Defines the OBEX REQUEST TIME OUT response code. + * <P> + * The value of <code>OBEX_HTTP_TIMEOUT</code> is 0xC8 (200). + */ + public static final int OBEX_HTTP_TIMEOUT = 0xC8; + + /** + * Defines the OBEX METHOD CONFLICT response code. + * <P> + * The value of <code>OBEX_HTTP_CONFLICT</code> is 0xC9 (201). + */ + public static final int OBEX_HTTP_CONFLICT = 0xC9; + + /** + * Defines the OBEX METHOD GONE response code. + * <P> + * The value of <code>OBEX_HTTP_GONE</code> is 0xCA (202). + */ + public static final int OBEX_HTTP_GONE = 0xCA; + + /** + * Defines the OBEX METHOD LENGTH REQUIRED response code. + * <P> + * The value of <code>OBEX_HTTP_LENGTH_REQUIRED</code> is 0xCB (203). + */ + public static final int OBEX_HTTP_LENGTH_REQUIRED = 0xCB; + + /** + * Defines the OBEX PRECONDITION FAILED response code. + * <P> + * The value of <code>OBEX_HTTP_PRECON_FAILED</code> is 0xCC (204). + */ + public static final int OBEX_HTTP_PRECON_FAILED = 0xCC; + + /** + * Defines the OBEX REQUESTED ENTITY TOO LARGE response code. + * <P> + * The value of <code>OBEX_HTTP_ENTITY_TOO_LARGE</code> is 0xCD (205). + */ + public static final int OBEX_HTTP_ENTITY_TOO_LARGE = 0xCD; + + /** + * Defines the OBEX REQUESTED URL TOO LARGE response code. + * <P> + * The value of <code>OBEX_HTTP_REQ_TOO_LARGE</code> is 0xCE (206). + */ + public static final int OBEX_HTTP_REQ_TOO_LARGE = 0xCE; + + /** + * Defines the OBEX UNSUPPORTED MEDIA TYPE response code. + * <P> + * The value of <code>OBEX_HTTP_UNSUPPORTED_TYPE</code> is 0xCF (207). + */ + public static final int OBEX_HTTP_UNSUPPORTED_TYPE = 0xCF; + + /** + * Defines the OBEX INTERNAL SERVER ERROR response code. + * <P> + * The value of <code>OBEX_HTTP_INTERNAL_ERROR</code> is 0xD0 (208). + */ + public static final int OBEX_HTTP_INTERNAL_ERROR = 0xD0; + + /** + * Defines the OBEX NOT IMPLEMENTED response code. + * <P> + * The value of <code>OBEX_HTTP_NOT_IMPLEMENTED</code> is 0xD1 (209). + */ + public static final int OBEX_HTTP_NOT_IMPLEMENTED = 0xD1; + + /** + * Defines the OBEX BAD GATEWAY response code. + * <P> + * The value of <code>OBEX_HTTP_BAD_GATEWAY</code> is 0xD2 (210). + */ + public static final int OBEX_HTTP_BAD_GATEWAY = 0xD2; + + /** + * Defines the OBEX SERVICE UNAVAILABLE response code. + * <P> + * The value of <code>OBEX_HTTP_UNAVAILABLE</code> is 0xD3 (211). + */ + public static final int OBEX_HTTP_UNAVAILABLE = 0xD3; + + /** + * Defines the OBEX GATEWAY TIMEOUT response code. + * <P> + * The value of <code>OBEX_HTTP_GATEWAY_TIMEOUT</code> is 0xD4 (212). + */ + public static final int OBEX_HTTP_GATEWAY_TIMEOUT = 0xD4; + + /** + * Defines the OBEX HTTP VERSION NOT SUPPORTED response code. + * <P> + * The value of <code>OBEX_HTTP_VERSION</code> is 0xD5 (213). + */ + public static final int OBEX_HTTP_VERSION = 0xD5; + + /** + * Defines the OBEX DATABASE FULL response code. + * <P> + * The value of <code>OBEX_DATABASE_FULL</code> is 0xE0 (224). + */ + public static final int OBEX_DATABASE_FULL = 0xE0; + + /** + * Defines the OBEX DATABASE LOCKED response code. + * <P> + * The value of <code>OBEX_DATABASE_LOCKED</code> is 0xE1 (225). + */ + public static final int OBEX_DATABASE_LOCKED = 0xE1; + + /** + * Constructor does nothing. + */ + private ResponseCodes() { + } +} diff --git a/obex/javax/obex/ServerOperation.java b/obex/javax/obex/ServerOperation.java new file mode 100644 index 0000000..6c3d9ba --- /dev/null +++ b/obex/javax/obex/ServerOperation.java @@ -0,0 +1,723 @@ +/* + * 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 signaling the server that it is + * done with its request. + * + * TODO: Extend the ClientOperation and reuse the methods defined + * TODO: in that class. + * + * @hide + */ +public final class ServerOperation implements Operation, BaseStream { + + public boolean isAborted; + + public HeaderSet requestHeader; + + public HeaderSet replyHeader; + + public boolean finalBitSet; + + private InputStream mInput; + + private ServerSession mParent; + + private int mMaxPacketLength; + + private int mResponseSize; + + private boolean mClosed; + + private boolean mGetOperation; + + private PrivateInputStream mPrivateInput; + + private PrivateOutputStream mPrivateOutput; + + private boolean mPrivateOutputOpen; + + private String mExceptionString; + + private ServerRequestHandler mListener; + + private boolean mRequestFinished; + + private boolean mHasBody; + + /** + * Creates new ServerOperation + * + * @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 + * + * @throws IOException if an IO error occurs + */ + public ServerOperation(ServerSession p, InputStream in, int request, int maxSize, + ServerRequestHandler listen) throws IOException { + + isAborted = false; + mParent = p; + mInput = in; + mMaxPacketLength = maxSize; + mClosed = false; + requestHeader = new HeaderSet(); + replyHeader = new HeaderSet(); + mPrivateInput = new PrivateInputStream(this); + mResponseSize = 3; + mListener = listen; + mRequestFinished = false; + mPrivateOutputOpen = false; + mHasBody = false; + int bytesReceived; + + /* + * Determine if this is a PUT request + */ + if ((request == 0x02) || (request == 0x82)) { + /* + * It is a PUT request. + */ + mGetOperation = false; + } else { + /* + * It is a GET request. + */ + mGetOperation = true; + } + + /* + * Determine if the final bit is set + */ + if ((request & 0x80) == 0) { + finalBitSet = false; + } else { + finalBitSet = true; + mRequestFinished = true; + } + + int length = in.read(); + length = (length << 8) + in.read(); + + /* + * Determine if the packet length is larger than this device can receive + */ + if (length > ObexHelper.MAX_PACKET_SIZE_INT) { + mParent.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(requestHeader, data); + + if (body != null) { + mHasBody = true; + } + + if (requestHeader.mConnectionID != null) { + mListener.setConnectionId(ObexHelper.convertToLong(requestHeader.mConnectionID)); + } else { + mListener.setConnectionId(0); + } + + if (requestHeader.mAuthResp != null) { + if (!mParent.handleAuthResp(requestHeader.mAuthResp)) { + mExceptionString = "Authentication Failed"; + mParent.sendResponse(ResponseCodes.OBEX_HTTP_UNAUTHORIZED, null); + mClosed = true; + requestHeader.mAuthResp = null; + return; + } + } + + if (requestHeader.mAuthChall != null) { + mParent.handleAuthChall(requestHeader); + // send the authResp to the client + replyHeader.mAuthResp = new byte[requestHeader.mAuthResp.length]; + System.arraycopy(requestHeader.mAuthResp, 0, replyHeader.mAuthResp, 0, + replyHeader.mAuthResp.length); + requestHeader.mAuthResp = null; + requestHeader.mAuthChall = null; + + } + + if (body != null) { + mPrivateInput.writeBytes(body, 1); + } else { + while ((!mGetOperation) && (!finalBitSet)) { + sendReply(ResponseCodes.OBEX_HTTP_CONTINUE); + if (mPrivateInput.available() > 0) { + break; + } + } + } + } + + while ((!mGetOperation) && (!finalBitSet) && (mPrivateInput.available() == 0)) { + sendReply(ResponseCodes.OBEX_HTTP_CONTINUE); + if (mPrivateInput.available() > 0) { + break; + } + } + + // wait for get request finished !!!! + while (mGetOperation && !finalBitSet) { + sendReply(ResponseCodes.OBEX_HTTP_CONTINUE); + } + if (finalBitSet && mGetOperation) { + mRequestFinished = true; + } + } + + public boolean isValidBody() { + return mHasBody; + } + + /** + * 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, otherwise + * output stream + * @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 (!mGetOperation) { + if (!finalBitSet) { + if (sendEmpty) { + sendReply(ResponseCodes.OBEX_HTTP_CONTINUE); + return true; + } else { + if ((mResponseSize > 3) || (mPrivateOutput.size() > 0)) { + sendReply(ResponseCodes.OBEX_HTTP_CONTINUE); + return true; + } else { + return false; + } + } + } else { + return false; + } + } else { + sendReply(ResponseCodes.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 + * + * @throws IOException if an IO error occurs + */ + public synchronized boolean sendReply(int type) throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + int bytesReceived; + + long id = mListener.getConnectionId(); + if (id == -1) { + replyHeader.mConnectionID = null; + } else { + replyHeader.mConnectionID = ObexHelper.convertToByteArray(id); + } + + byte[] headerArray = ObexHelper.createHeader(replyHeader, true); + int bodyLength = -1; + int orginalBodyLength = -1; + + if (mPrivateOutput != null) { + bodyLength = mPrivateOutput.size(); + orginalBodyLength = bodyLength; + } + + if ((ObexHelper.BASE_PACKET_LENGTH + headerArray.length) > mMaxPacketLength) { + + int end = 0; + int start = 0; + + while (end != headerArray.length) { + end = ObexHelper.findHeaderEnd(headerArray, start, mMaxPacketLength + - ObexHelper.BASE_PACKET_LENGTH); + if (end == -1) { + + mClosed = true; + + if (mPrivateInput != null) { + mPrivateInput.close(); + } + + if (mPrivateOutput != null) { + mPrivateOutput.close(); + } + mParent.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); + + mParent.sendResponse(type, sendHeader); + start = end; + } + + if (bodyLength > 0) { + return true; + } else { + return false; + } + + } else { + out.write(headerArray); + } + + if ((finalBitSet) || (headerArray.length < (mMaxPacketLength - 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 > (mMaxPacketLength - headerArray.length - 6)) { + bodyLength = mMaxPacketLength - headerArray.length - 6; + } + + byte[] body = mPrivateOutput.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) || (mPrivateOutput.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); + + } + + mResponseSize = 3; + mParent.sendResponse(type, out.toByteArray()); + + if (type == ResponseCodes.OBEX_HTTP_CONTINUE) { + int headerID = mInput.read(); + int length = mInput.read(); + length = (length << 8) + mInput.read(); + if ((headerID != ObexHelper.OBEX_OPCODE_PUT) + && (headerID != ObexHelper.OBEX_OPCODE_PUT_FINAL) + && (headerID != ObexHelper.OBEX_OPCODE_GET) + && (headerID != ObexHelper.OBEX_OPCODE_GET_FINAL)) { + + if (length > 3) { + byte[] temp = new byte[length]; + bytesReceived = mInput.read(temp); + + while (bytesReceived != length) { + bytesReceived += mInput.read(temp, bytesReceived, length - bytesReceived); + } + } + + /* + * Determine if an ABORT was sent as the reply + */ + if (headerID == ObexHelper.OBEX_OPCODE_ABORT) { + mParent.sendResponse(ResponseCodes.OBEX_HTTP_OK, null); + mClosed = true; + isAborted = true; + mExceptionString = "Abort Received"; + throw new IOException("Abort Received"); + } else { + mParent.sendResponse(ResponseCodes.OBEX_HTTP_BAD_REQUEST, null); + mClosed = true; + mExceptionString = "Bad Request Received"; + throw new IOException("Bad Request Received"); + } + } else { + + if ((headerID == ObexHelper.OBEX_OPCODE_PUT_FINAL) + || (headerID == ObexHelper.OBEX_OPCODE_GET_FINAL)) { + finalBitSet = true; + } + + /* + * Determine if the packet length is larger then this device can receive + */ + if (length > ObexHelper.MAX_PACKET_SIZE_INT) { + mParent.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 = mInput.read(data); + + while (bytesReceived != data.length) { + bytesReceived += mInput.read(data, bytesReceived, data.length + - bytesReceived); + } + byte[] body = ObexHelper.updateHeaderSet(requestHeader, data); + if (body != null) { + mHasBody = true; + } + if (requestHeader.mConnectionID != null) { + mListener.setConnectionId(ObexHelper + .convertToLong(requestHeader.mConnectionID)); + } else { + mListener.setConnectionId(1); + } + + if (requestHeader.mAuthResp != null) { + if (!mParent.handleAuthResp(requestHeader.mAuthResp)) { + mExceptionString = "Authentication Failed"; + mParent.sendResponse(ResponseCodes.OBEX_HTTP_UNAUTHORIZED, null); + mClosed = true; + requestHeader.mAuthResp = null; + return false; + } + requestHeader.mAuthResp = null; + } + + if (requestHeader.mAuthChall != null) { + mParent.handleAuthChall(requestHeader); + // send the auhtResp to the client + replyHeader.mAuthResp = new byte[requestHeader.mAuthResp.length]; + System.arraycopy(requestHeader.mAuthResp, 0, replyHeader.mAuthResp, 0, + replyHeader.mAuthResp.length); + requestHeader.mAuthResp = null; + requestHeader.mAuthChall = null; + } + + if (body != null) { + mPrivateInput.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. + * + * @throws 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> + * + * @throws IOException if this <code>Operation</code> has been closed + */ + public HeaderSet getReceivedHeader() throws IOException { + ensureOpen(); + return requestHeader; + } + + /** + * 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 + * + * @throws IOException if this <code>Operation</code> has been closed + * or the transaction has ended and no further messages will be exchanged + * + * @throws 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 IOException("Headers may not be null"); + } + + int[] headerList = headers.getHeaderList(); + if (headerList != null) { + for (int i = 0; i < headerList.length; i++) { + replyHeader.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 + * + * @throws 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)requestHeader.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)requestHeader.getHeader(HeaderSet.LENGTH); + + if (temp == null) { + return -1; + } else { + return temp.longValue(); + } + } catch (IOException e) { + return -1; + } + } + + public int getMaxPacketSize() { + return mMaxPacketLength - 6; + } + + /** + * Open and return an input stream for a connection. + * + * @return an input stream + * + * @throws IOException if an I/O error occurs + */ + public InputStream openInputStream() throws IOException { + ensureOpen(); + return mPrivateInput; + } + + /** + * Open and return a data input stream for a connection. + * + * @return an input stream + * + * @throws 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 + * + * @throws IOException if an I/O error occurs + */ + public OutputStream openOutputStream() throws IOException { + ensureOpen(); + + if (mPrivateOutputOpen) { + throw new IOException("no more input streams available, stream already opened"); + } + + if (!mRequestFinished) { + throw new IOException("no output streams available ,request not finished"); + } + + if (mPrivateOutput == null) { + mPrivateOutput = new PrivateOutputStream(this, mMaxPacketLength - 6); + } + mPrivateOutputOpen = true; + return mPrivateOutput; + } + + /** + * Open and return a data output stream for a connection. + * + * @return an output stream + * + * @throws IOException if an I/O error occurs + */ + public DataOutputStream openDataOutputStream() throws IOException { + return new DataOutputStream(openOutputStream()); + } + + /** + * Closes the connection and ends the transaction + * + * @throws IOException if the operation has already ended or is closed + */ + public void close() throws IOException { + ensureOpen(); + mClosed = true; + } + + /** + * Verifies that the connection is open and no exceptions should be thrown. + * + * @throws IOException if an exception needs to be thrown + */ + public void ensureOpen() throws IOException { + if (mExceptionString != null) { + throw new IOException(mExceptionString); + } + if (mClosed) { + 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. + * + * @throws 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 + * + * @throws IOException if an IO error occurs + */ + public void streamClosed(boolean inStream) throws IOException { + + } +} diff --git a/obex/javax/obex/ServerRequestHandler.java b/obex/javax/obex/ServerRequestHandler.java new file mode 100644 index 0000000..e468b83 --- /dev/null +++ b/obex/javax/obex/ServerRequestHandler.java @@ -0,0 +1,291 @@ +/* + * 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; + +/** + * The <code>ServerRequestHandler</code> class defines an event + * listener that will respond to OBEX requests made to the server. + * <P> + * The <code>onConnect()</code>, <code>onSetPath()</code>, <code>onDelete()</code>, + * <code>onGet()</code>, + * and <code>onPut()</code> methods may return any response code defined + * in the <code>ResponseCodes</code> class except for + * <code>OBEX_HTTP_CONTINUE</code>. If <code>OBEX_HTTP_CONTINUE</code> or + * a value not defined in the <code>ResponseCodes</code> class is returned, + * the server implementation will send an <code>OBEX_HTTP_INTERNAL_ERROR</code> + * response to the client. + * <P> + * <STRONG>Connection ID and Target Headers</STRONG> + * <P> + * According to the IrOBEX specification, a packet may not contain a Connection + * ID and Target header. Since the Connection ID header is managed by the + * implementation, it will not send a Connection ID header, if a Connection ID + * was specified, in a packet that has a Target header. In other words, if an + * application adds a Target header to a <code>HeaderSet</code> object used + * in an OBEX operation and a Connection ID was specified, no Connection ID + * will be sent in the packet containing the Target header. + * <P> + * <STRONG>CREATE-EMPTY Requests</STRONG> + * <P> + * A CREATE-EMPTY request allows clients to create empty objects on the server. + * When a CREATE-EMPTY request is received, the <code>onPut()</code> method + * will be called by the implementation. To differentiate between a normal + * PUT request and a CREATE-EMPTY request, an application must open the + * <code>InputStream</code> from the <code>Operation</code> object passed + * to the <code>onPut()</code> method. For a PUT request, the application + * will be able to read Body data from this <code>InputStream</code>. For + * a CREATE-EMPTY request, there will be no Body data to read. Therefore, + * a call to <code>InputStream.read()</code> will return -1. + * + * @hide + */ +public class ServerRequestHandler { + + private long mConnectionId; + + /** + * Creates a <code>ServerRequestHandler</code>. + */ + protected ServerRequestHandler() { + /* + * A connection ID of -1 implies there is no conenction ID + */ + mConnectionId = -1; + } + + /** + * Sets the connection ID header to include in the reply packets. + * + * @param connectionId the connection ID to use; -1 if no connection ID should be + * sent + * + * @throws IllegalArgumentException if <code>id</code> is not in the + * range -1 to 2<sup>32</sup>-1 + */ + public void setConnectionId(final long connectionId) { + if ((connectionId < -1) || (connectionId > 0xFFFFFFFFL)) { + throw new IllegalArgumentException("Illegal Connection ID"); + } + mConnectionId = connectionId; + } + + /** + * Retrieves the connection ID that is being used in the present connection. + * This method will return -1 if no connection ID is being used. + * + * @return the connection id being used or -1 if no connection ID is being + * used + */ + public long getConnectionId() { + return mConnectionId; + } + + /** + * Called when a CONNECT request is received. + * <P> + * If this method is not implemented by the class that extends this + * class, <code>onConnect()</code> will always return an + * <code>OBEX_HTTP_OK</code> response code. + * <P> + * The headers received in the request can be retrieved from the + * <code>request</code> argument. The headers that should be sent + * in the reply must be specified in the <code>reply</code> argument. + * + * @param request contains the headers sent by the client; + * <code>request</code> will never be <code>null</code> + * + * @param reply the headers that should be sent in the reply; + * <code>reply</code> will never be <code>null</code> + * + * @return a response code defined in <code>ResponseCodes</code> that will be + * returned to the client; if an invalid response code is provided, the + * <code>OBEX_HTTP_INTERNAL_ERROR</code> response code will be used + */ + public int onConnect(HeaderSet request, HeaderSet reply) { + return ResponseCodes.OBEX_HTTP_OK; + } + + /** + * Called when a DISCONNECT request is received. + * <P> + * The headers received in the request can be retrieved from the + * <code>request</code> argument. The headers that should be sent + * in the reply must be specified in the <code>reply</code> argument. + * + * @param request contains the headers sent by the client; + * <code>request</code> will never be <code>null</code> + * + * @param reply the headers that should be sent in the reply; + * <code>reply</code> will never be <code>null</code> + */ + public void onDisconnect(HeaderSet request, HeaderSet reply) { + } + + /** + * Called when a SETPATH request is received. + * <P> + * If this method is not implemented by the class that extends this + * class, <code>onSetPath()</code> will always return an + * <code>OBEX_HTTP_NOT_IMPLEMENTED</code> response code. + * <P> + * The headers received in the request can be retrieved from the + * <code>request</code> argument. The headers that should be sent + * in the reply must be specified in the <code>reply</code> argument. + * + * @param request contains the headers sent by the client; + * <code>request</code> will never be <code>null</code> + * + * @param reply the headers that should be sent in the reply; + * <code>reply</code> will never be <code>null</code> + * + * @param backup <code>true</code> if the client requests that the server + * back up one directory before changing to the path described by + * <code>name</code>; <code>false</code> to apply the request to the present + * path + * + * @param create <code>true</code> if the path should be created if it does + * not already exist; <code>false</code> if the path should not be created + * if it does not exist and an error code should be returned + * + * @return a response code defined in <code>ResponseCodes</code> that will be + * returned to the client; if an invalid response code is provided, the + * <code>OBEX_HTTP_INTERNAL_ERROR</code> response code will be used + */ + public int onSetPath(HeaderSet request, HeaderSet reply, boolean backup, boolean create) { + + return ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED; + } + + /** + * Called when a DELETE request is received. + * <P> + * If this method is not implemented by the class that extends this + * class, <code>onDelete()</code> will always return an + * <code>OBEX_HTTP_NOT_IMPLEMENTED</code> response code. + * <P> + * The headers received in the request can be retrieved from the + * <code>request</code> argument. The headers that should be sent + * in the reply must be specified in the <code>reply</code> argument. + * + * @param request contains the headers sent by the client; + * <code>request</code> will never be <code>null</code> + * + * @param reply the headers that should be sent in the reply; + * <code>reply</code> will never be <code>null</code> + * + * @return a response code defined in <code>ResponseCodes</code> that will be + * returned to the client; if an invalid response code is provided, the + * <code>OBEX_HTTP_INTERNAL_ERROR</code> response code will be used + */ + public int onDelete(HeaderSet request, HeaderSet reply) { + return ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED; + } + + /** + * Called when a PUT request is received. + * <P> + * If this method is not implemented by the class that extends this + * class, <code>onPut()</code> will always return an + * <code>OBEX_HTTP_NOT_IMPLEMENTED</code> response code. + * <P> + * If an ABORT request is received during the processing of a PUT request, + * <code>op</code> will be closed by the implementation. + * + * @param operation contains the headers sent by the client and allows new + * headers to be sent in the reply; <code>op</code> will never be + * <code>null</code> + * + * @return a response code defined in <code>ResponseCodes</code> that will be + * returned to the client; if an invalid response code is provided, the + * <code>OBEX_HTTP_INTERNAL_ERROR</code> response code will be used + */ + public int onPut(Operation operation) { + return ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED; + } + + /** + * Called when a GET request is received. + * <P> + * If this method is not implemented by the class that extends this + * class, <code>onGet()</code> will always return an + * <code>OBEX_HTTP_NOT_IMPLEMENTED</code> response code. + * <P> + * If an ABORT request is received during the processing of a GET request, + * <code>op</code> will be closed by the implementation. + * + * @param operation contains the headers sent by the client and allows new + * headers to be sent in the reply; <code>op</code> will never be + * <code>null</code> + * + * @return a response code defined in <code>ResponseCodes</code> that will be + * returned to the client; if an invalid response code is provided, the + * <code>OBEX_HTTP_INTERNAL_ERROR</code> response code will be used + */ + public int onGet(Operation operation) { + return ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED; + } + + /** + * Called when this object attempts to authenticate a client and the + * authentication request fails because the response digest in the + * authentication response header was wrong. + * <P> + * If this method is not implemented by the class that extends this class, + * this method will do nothing. + * + * @param userName the user name returned in the authentication response; + * <code>null</code> if no user name was provided in the response + */ + public void onAuthenticationFailure(byte[] userName) { + } + + /** + * Called by ServerSession to update the status of current transaction + * <P> + * If this method is not implemented by the class that extends this class, + * this method will do nothing. + * + */ + public void updateStatus(String message) { + } + + /** + * Called when session is closed. + * <P> + * If this method is not implemented by the class that extends this class, + * this method will do nothing. + * + */ + public void onClose() { + } +} diff --git a/obex/javax/obex/ServerSession.java b/obex/javax/obex/ServerSession.java new file mode 100644 index 0000000..3a0e8150 --- /dev/null +++ b/obex/javax/obex/ServerSession.java @@ -0,0 +1,694 @@ +/* + * 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 android.util.Log; + +import java.io.InputStream; +import java.io.IOException; +import java.io.OutputStream; + +/** + * This class in an implementation of the OBEX ServerSession. + * + * @hide + */ +public final class ServerSession extends ObexSession implements Runnable { + + private static final String TAG = "Obex ServerSession"; + + private ObexTransport mTransport; + + private InputStream mInput; + + private OutputStream mOutput; + + private ServerRequestHandler mListener; + + private Thread mProcessThread; + + private int mMaxPacketLength; + + private boolean mClosed; + + /** + * Creates new ServerSession. + * + * @param trans + * the connection to the client + * + * @param handler + * the event listener that will process requests + * + * @param auth + * the authenticator to use with this connection + * + * @throws IOException + * if an error occurred while opening the input and output + * streams + */ + public ServerSession(ObexTransport trans, ServerRequestHandler handler, Authenticator auth) + throws IOException { + mAuthenticator = auth; + mTransport = trans; + mInput = mTransport.openInputStream(); + mOutput = mTransport.openOutputStream(); + mListener = handler; + mMaxPacketLength = 256; + + mClosed = false; + mProcessThread = new Thread(this); + mProcessThread.start(); + } + + /** + * Processes requests made to the server and forwards them to the + * appropriate event listener. + */ + public void run() { + try { + + boolean done = false; + while (!done && !mClosed) { + int requestType = mInput.read(); + switch (requestType) { + case ObexHelper.OBEX_OPCODE_CONNECT: + handleConnectRequest(); + break; + + case ObexHelper.OBEX_OPCODE_DISCONNECT: + handleDisconnectRequest(); + done = true; + break; + + case ObexHelper.OBEX_OPCODE_GET: + case ObexHelper.OBEX_OPCODE_GET_FINAL: + handleGetRequest(requestType); + break; + + case ObexHelper.OBEX_OPCODE_PUT: + case ObexHelper.OBEX_OPCODE_PUT_FINAL: + handlePutRequest(requestType); + break; + + case ObexHelper.OBEX_OPCODE_SETPATH: + handleSetPathRequest(); + break; + + case -1: + done = true; + break; + + default: + + /* + * Received a request type that is not recognized so I am + * just going to read the packet and send a not implemented + * to the client + */ + int length = mInput.read(); + length = (length << 8) + mInput.read(); + for (int i = 3; i < length; i++) { + mInput.read(); + } + sendResponse(ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED, null); + } + } + + } catch (NullPointerException e) { + Log.d(TAG, e.toString()); + } catch (Exception e) { + Log.d(TAG, e.toString()); + } + close(); + } + + /** + * Handles a PUT request from a client. This method will provide a + * <code>ServerOperation</code> object to the request handler. The + * <code>ServerOperation</code> object will handle the rest of the request. + * It will also send replies and receive requests until the final reply + * should be sent. When the final reply should be sent, this method will get + * the response code to use and send the reply. The + * <code>ServerOperation</code> object will always reply with a + * OBEX_HTTP_CONTINUE reply. It will only reply if further information is + * needed. + * + * @param type + * the type of request received; either 0x02 or 0x82 + * + * @throws IOException + * if an error occurred at the transport layer + */ + private void handlePutRequest(int type) throws IOException { + ServerOperation op = new ServerOperation(this, mInput, type, mMaxPacketLength, mListener); + try { + int response = -1; + + if ((op.finalBitSet) && !op.isValidBody()) { + response = validateResponseCode(mListener + .onDelete(op.requestHeader, op.replyHeader)); + } else { + response = validateResponseCode(mListener.onPut(op)); + } + if (response != ResponseCodes.OBEX_HTTP_OK) { + op.sendReply(response); + } else if (!op.isAborted) { + // wait for the final bit + while (!op.finalBitSet) { + op.sendReply(ResponseCodes.OBEX_HTTP_CONTINUE); + } + op.sendReply(response); + } + } catch (Exception e) { + sendResponse(ResponseCodes.OBEX_HTTP_INTERNAL_ERROR, null); + } + } + + /** + * Handles a GET request from a client. This method will provide a + * <code>ServerOperation</code> object to the request handler. The + * <code>ServerOperation</code> object will handle the rest of the request. + * It will also send replies and receive requests until the final reply + * should be sent. When the final reply should be sent, this method will get + * the response code to use and send the reply. The + * <code>ServerOperation</code> object will always reply with a + * OBEX_HTTP_CONTINUE reply. It will only reply if further information is + * needed. + * + * @param type + * the type of request received; either 0x03 or 0x83 + * + * @throws IOException + * if an error occurred at the transport layer + */ + private void handleGetRequest(int type) throws IOException { + ServerOperation op = new ServerOperation(this, mInput, type, mMaxPacketLength, mListener); + try { + int response = validateResponseCode(mListener.onGet(op)); + + if (!op.isAborted) { + op.sendReply(response); + } + } catch (Exception e) { + sendResponse(ResponseCodes.OBEX_HTTP_INTERNAL_ERROR, null); + } + } + + /** + * Send standard response. + * + * @param code + * the response code to send + * + * @param header + * the headers to include in the response + * + * @throws IOException + * if an IO error occurs + */ + public void sendResponse(int code, byte[] header) throws IOException { + int totalLength = 3; + byte[] data = null; + + if (header != null) { + totalLength += header.length; + data = new byte[totalLength]; + data[0] = (byte)code; + data[1] = (byte)(totalLength >> 8); + data[2] = (byte)totalLength; + System.arraycopy(header, 0, data, 3, header.length); + } else { + data = new byte[totalLength]; + data[0] = (byte)code; + data[1] = (byte)0x00; + data[2] = (byte)totalLength; + } + mOutput.write(data); + mOutput.flush(); + } + + /** + * Handles a SETPATH request from a client. This method will read the rest + * of the request from the client. Assuming the request is valid, it will + * create a <code>HeaderSet</code> object to pass to the + * <code>ServerRequestHandler</code> object. After the handler processes the + * request, this method will create a reply message to send to the server + * with the response code provided. + * + * @throws IOException + * if an error occurred at the transport layer + */ + private void handleSetPathRequest() throws IOException { + int length; + int flags; + @SuppressWarnings("unused") + int constants; + int totalLength = 3; + byte[] head = null; + int code = -1; + int bytesReceived; + HeaderSet request = new HeaderSet(); + HeaderSet reply = new HeaderSet(); + + length = mInput.read(); + length = (length << 8) + mInput.read(); + flags = mInput.read(); + constants = mInput.read(); + + if (length > ObexHelper.MAX_PACKET_SIZE_INT) { + code = ResponseCodes.OBEX_HTTP_REQ_TOO_LARGE; + totalLength = 3; + } else { + if (length > 5) { + byte[] headers = new byte[length - 5]; + bytesReceived = mInput.read(headers); + + while (bytesReceived != headers.length) { + bytesReceived += mInput.read(headers, bytesReceived, headers.length + - bytesReceived); + } + + ObexHelper.updateHeaderSet(request, headers); + + if (request.mConnectionID != null) { + mListener.setConnectionId(ObexHelper.convertToLong(request.mConnectionID)); + } else { + mListener.setConnectionId(-1); + } + // the Auth chan is initiated by the server, client sent back the authResp . + if (request.mAuthResp != null) { + if (!handleAuthResp(request.mAuthResp)) { + code = ResponseCodes.OBEX_HTTP_UNAUTHORIZED; + mListener.onAuthenticationFailure(ObexHelper.getTagValue((byte)0x01, + request.mAuthResp)); + } + request.mAuthResp = null; + } + } + + if (code != ResponseCodes.OBEX_HTTP_UNAUTHORIZED) { + // the Auth challenge is initiated by the client + // the server will send back the authResp to the client + if (request.mAuthChall != null) { + handleAuthChall(request); + reply.mAuthResp = new byte[request.mAuthResp.length]; + System.arraycopy(request.mAuthResp, 0, reply.mAuthResp, 0, + reply.mAuthResp.length); + request.mAuthChall = null; + request.mAuthResp = null; + } + boolean backup = false; + boolean create = true; + if (!((flags & 1) == 0)) { + backup = true; + } + if ((flags & 2) == 0) { + create = false; + } + + try { + code = mListener.onSetPath(request, reply, backup, create); + } catch (Exception e) { + sendResponse(ResponseCodes.OBEX_HTTP_INTERNAL_ERROR, null); + return; + } + + code = validateResponseCode(code); + + if (reply.nonce != null) { + mChallengeDigest = new byte[16]; + System.arraycopy(reply.nonce, 0, mChallengeDigest, 0, 16); + } else { + mChallengeDigest = null; + } + + long id = mListener.getConnectionId(); + if (id == -1) { + reply.mConnectionID = null; + } else { + reply.mConnectionID = ObexHelper.convertToByteArray(id); + } + + head = ObexHelper.createHeader(reply, false); + totalLength += head.length; + + if (totalLength > mMaxPacketLength) { + totalLength = 3; + head = null; + code = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; + } + } + } + + // Compute Length of OBEX SETPATH packet + byte[] replyData = new byte[totalLength]; + replyData[0] = (byte)code; + replyData[1] = (byte)(totalLength >> 8); + replyData[2] = (byte)totalLength; + if (head != null) { + System.arraycopy(head, 0, replyData, 3, head.length); + } + /* + * Write the OBEX SETPATH packet to the server. Byte 0: response code + * Byte 1&2: Connect Packet Length Byte 3 to n: headers + */ + mOutput.write(replyData); + mOutput.flush(); + } + + /** + * Handles a disconnect request from a client. This method will read the + * rest of the request from the client. Assuming the request is valid, it + * will create a <code>HeaderSet</code> object to pass to the + * <code>ServerRequestHandler</code> object. After the handler processes the + * request, this method will create a reply message to send to the server. + * + * @throws IOException + * if an error occurred at the transport layer + */ + private void handleDisconnectRequest() throws IOException { + int length; + int code = ResponseCodes.OBEX_HTTP_OK; + int totalLength = 3; + byte[] head = null; + int bytesReceived; + HeaderSet request = new HeaderSet(); + HeaderSet reply = new HeaderSet(); + + length = mInput.read(); + length = (length << 8) + mInput.read(); + + if (length > ObexHelper.MAX_PACKET_SIZE_INT) { + code = ResponseCodes.OBEX_HTTP_REQ_TOO_LARGE; + totalLength = 3; + } else { + if (length > 3) { + byte[] headers = new byte[length - 3]; + bytesReceived = mInput.read(headers); + + while (bytesReceived != headers.length) { + bytesReceived += mInput.read(headers, bytesReceived, headers.length + - bytesReceived); + } + + ObexHelper.updateHeaderSet(request, headers); + } + + if (request.mConnectionID != null) { + mListener.setConnectionId(ObexHelper.convertToLong(request.mConnectionID)); + } else { + mListener.setConnectionId(1); + } + + if (request.mAuthResp != null) { + if (!handleAuthResp(request.mAuthResp)) { + code = ResponseCodes.OBEX_HTTP_UNAUTHORIZED; + mListener.onAuthenticationFailure(ObexHelper.getTagValue((byte)0x01, + request.mAuthResp)); + } + request.mAuthResp = null; + } + + if (code != ResponseCodes.OBEX_HTTP_UNAUTHORIZED) { + + if (request.mAuthChall != null) { + handleAuthChall(request); + request.mAuthChall = null; + } + + try { + mListener.onDisconnect(request, reply); + } catch (Exception e) { + sendResponse(ResponseCodes.OBEX_HTTP_INTERNAL_ERROR, null); + return; + } + + long id = mListener.getConnectionId(); + if (id == -1) { + reply.mConnectionID = null; + } else { + reply.mConnectionID = ObexHelper.convertToByteArray(id); + } + + head = ObexHelper.createHeader(reply, false); + totalLength += head.length; + + if (totalLength > mMaxPacketLength) { + totalLength = 3; + head = null; + code = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; + } + } + } + + // Compute Length of OBEX CONNECT packet + byte[] replyData; + if (head != null) { + replyData = new byte[3 + head.length]; + } else { + replyData = new byte[3]; + } + replyData[0] = (byte)code; + replyData[1] = (byte)(totalLength >> 8); + replyData[2] = (byte)totalLength; + if (head != null) { + System.arraycopy(head, 0, replyData, 3, head.length); + } + /* + * Write the OBEX DISCONNECT packet to the server. Byte 0: response code + * Byte 1&2: Connect Packet Length Byte 3 to n: headers + */ + mOutput.write(replyData); + mOutput.flush(); + } + + /** + * Handles a connect request from a client. This method will read the rest + * of the request from the client. Assuming the request is valid, it will + * create a <code>HeaderSet</code> object to pass to the + * <code>ServerRequestHandler</code> object. After the handler processes the + * request, this method will create a reply message to send to the server + * with the response code provided. + * + * @throws IOException + * if an error occurred at the transport layer + */ + private void handleConnectRequest() throws IOException { + int packetLength; + @SuppressWarnings("unused") + int version; + @SuppressWarnings("unused") + int flags; + int totalLength = 7; + byte[] head = null; + int code = -1; + HeaderSet request = new HeaderSet(); + HeaderSet reply = new HeaderSet(); + int bytesReceived; + + /* + * Read in the length of the OBEX packet, OBEX version, flags, and max + * packet length + */ + packetLength = mInput.read(); + packetLength = (packetLength << 8) + mInput.read(); + version = mInput.read(); + flags = mInput.read(); + mMaxPacketLength = mInput.read(); + mMaxPacketLength = (mMaxPacketLength << 8) + mInput.read(); + + // should we check it? + if (mMaxPacketLength > ObexHelper.MAX_PACKET_SIZE_INT) { + mMaxPacketLength = ObexHelper.MAX_PACKET_SIZE_INT; + } + + if (packetLength > ObexHelper.MAX_PACKET_SIZE_INT) { + code = ResponseCodes.OBEX_HTTP_REQ_TOO_LARGE; + totalLength = 7; + } else { + if (packetLength > 7) { + byte[] headers = new byte[packetLength - 7]; + bytesReceived = mInput.read(headers); + + while (bytesReceived != headers.length) { + bytesReceived += mInput.read(headers, bytesReceived, headers.length + - bytesReceived); + } + + ObexHelper.updateHeaderSet(request, headers); + } + + if (request.mConnectionID != null) { + mListener.setConnectionId(ObexHelper.convertToLong(request.mConnectionID)); + } else { + mListener.setConnectionId(1); + } + + if (request.mAuthResp != null) { + if (!handleAuthResp(request.mAuthResp)) { + code = ResponseCodes.OBEX_HTTP_UNAUTHORIZED; + mListener.onAuthenticationFailure(ObexHelper.getTagValue((byte)0x01, + request.mAuthResp)); + } + request.mAuthResp = null; + } + + if (code != ResponseCodes.OBEX_HTTP_UNAUTHORIZED) { + if (request.mAuthChall != null) { + handleAuthChall(request); + reply.mAuthResp = new byte[request.mAuthResp.length]; + System.arraycopy(request.mAuthResp, 0, reply.mAuthResp, 0, + reply.mAuthResp.length); + request.mAuthChall = null; + request.mAuthResp = null; + } + + try { + code = mListener.onConnect(request, reply); + code = validateResponseCode(code); + + if (reply.nonce != null) { + mChallengeDigest = new byte[16]; + System.arraycopy(reply.nonce, 0, mChallengeDigest, 0, 16); + } else { + mChallengeDigest = null; + } + long id = mListener.getConnectionId(); + if (id == -1) { + reply.mConnectionID = null; + } else { + reply.mConnectionID = ObexHelper.convertToByteArray(id); + } + + head = ObexHelper.createHeader(reply, false); + totalLength += head.length; + + if (totalLength > mMaxPacketLength) { + totalLength = 7; + head = null; + code = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; + } + } catch (Exception e) { + e.printStackTrace(); + totalLength = 7; + head = null; + code = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; + } + + } + } + + // Compute Length of OBEX CONNECT packet + byte[] length = ObexHelper.convertToByteArray(totalLength); + + /* + * Write the OBEX CONNECT packet to the server. Byte 0: response code + * Byte 1&2: Connect Packet Length Byte 3: OBEX Version Number + * (Presently, 0x10) Byte 4: Flags (For TCP 0x00) Byte 5&6: Max OBEX + * Packet Length (Defined in MAX_PACKET_SIZE) Byte 7 to n: headers + */ + byte[] sendData = new byte[totalLength]; + sendData[0] = (byte)code; + sendData[1] = length[2]; + sendData[2] = length[3]; + sendData[3] = (byte)0x10; + sendData[4] = (byte)0x00; + sendData[5] = (byte)(ObexHelper.MAX_PACKET_SIZE_INT >> 8); + sendData[6] = (byte)(ObexHelper.MAX_PACKET_SIZE_INT & 0xFF); + + if (head != null) { + System.arraycopy(head, 0, sendData, 7, head.length); + } + + mOutput.write(sendData); + mOutput.flush(); + } + + /** + * Closes the server session - in detail close I/O streams and the + * underlying transport layer. Internal flag is also set so that later + * attempt to read/write will throw an exception. + */ + public synchronized void close() { + if (mListener != null) { + mListener.onClose(); + } + try { + mInput.close(); + mOutput.close(); + mTransport.close(); + mClosed = true; + } catch (Exception e) { + } + mTransport = null; + mInput = null; + mOutput = null; + mListener = null; + } + + /** + * Verifies that the response code is valid. If it is not valid, it will + * return the <code>OBEX_HTTP_INTERNAL_ERROR</code> response code. + * + * @param code + * the response code to check + * + * @return the valid response code or <code>OBEX_HTTP_INTERNAL_ERROR</code> + * if <code>code</code> is not valid + */ + private int validateResponseCode(int code) { + + if ((code >= ResponseCodes.OBEX_HTTP_OK) && (code <= ResponseCodes.OBEX_HTTP_PARTIAL)) { + return code; + } + if ((code >= ResponseCodes.OBEX_HTTP_MULT_CHOICE) + && (code <= ResponseCodes.OBEX_HTTP_USE_PROXY)) { + return code; + } + if ((code >= ResponseCodes.OBEX_HTTP_BAD_REQUEST) + && (code <= ResponseCodes.OBEX_HTTP_UNSUPPORTED_TYPE)) { + return code; + } + if ((code >= ResponseCodes.OBEX_HTTP_INTERNAL_ERROR) + && (code <= ResponseCodes.OBEX_HTTP_VERSION)) { + return code; + } + if ((code >= ResponseCodes.OBEX_DATABASE_FULL) + && (code <= ResponseCodes.OBEX_DATABASE_LOCKED)) { + return code; + } + return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; + } + +} diff --git a/obex/javax/obex/SessionNotifier.java b/obex/javax/obex/SessionNotifier.java new file mode 100644 index 0000000..36e0ebf --- /dev/null +++ b/obex/javax/obex/SessionNotifier.java @@ -0,0 +1,167 @@ +/* + * 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; + +/** + * The <code>SessionNotifier</code> interface defines a connection notifier for + * server-side OBEX connections. When a <code>SessionNotifier</code> is + * created and calls <code>acceptAndOpen()</code>, it will begin listening for + * clients to create a connection at the transport layer. When the transport + * layer connection is received, the <code>acceptAndOpen()</code> method will + * return a <code>javax.microedition.io.Connection</code> that is the + * connection to the client. The <code>acceptAndOpen()</code> method also takes a + * <code>ServerRequestHandler</code> argument that will process the requests + * from the client that connects to the server. + * + * @hide + */ +public interface SessionNotifier { + + /** + * Waits for a transport layer connection to be established and specifies + * the handler to handle the requests from the client. No authenticator + * is associated with this connection, therefore, it is implementation + * dependent as to how an authentication challenge and authentication + * response header will be received and processed. + * <P> + * <H4>Additional Note for OBEX over Bluetooth</H4> + * If this method is called on a <code>SessionNotifier</code> object that + * does not have a <code>ServiceRecord</code> in the SDDB, the + * <code>ServiceRecord</code> for this object will be added to the SDDB. + * This method requests the BCC to put the + * local device in connectible mode so that it will respond to + * connection attempts by clients. + * <P> + * The following checks are done to verify that the service record + * provided is valid. If any of these checks fail, then a + * <code>ServiceRegistrationException</code> is thrown. + * <UL> + * <LI>ServiceClassIDList and ProtocolDescriptorList, the mandatory + * service attributes for a <code>btgoep</code> service record, must be + * present in the <code>ServiceRecord</code> associated with this notifier. + * <LI>L2CAP, RFCOMM and OBEX must all be in the ProtocolDescriptorList + * <LI>The <code>ServiceRecord</code> associated with this notifier must + * not have changed the RFCOMM server channel number + * </UL> + * <P> + * This method will not ensure that <code>ServiceRecord</code> associated + * with this notifier is a completely + * valid service record. It is the responsibility of the application to + * ensure that the service record follows all of the applicable + * syntactic and semantic rules for service record correctness. + * + * @param handler the request handler that will respond to OBEX requests + * + * @return the connection to the client + * + * @throws IOException if an error occurs in the transport layer + * + * @throws NullPointerException if <code>handler</code> is + * <code>null</code> + * + * @throws ServiceRegistrationException if the structure of the + * associated service record is invalid or if the service record + * could not be added successfully to the local SDDB. The + * structure of service record is invalid if the service + * record is missing any mandatory service attributes, or has + * changed any of the values described above which are fixed and + * cannot be changed. Failures to add the record to the SDDB could + * be due to insufficient disk space, database locks, etc. + * + * @throws BluetoothStateException if the server device could + * not be placed in connectible mode because the device user has + * configured the device to be non-connectible + */ + ObexSession acceptAndOpen(ServerRequestHandler handler) throws IOException; + + /** + * Waits for a transport layer connection to be established and specifies + * the handler to handle the requests from the client and the + * <code>Authenticator</code> to use to respond to authentication challenge + * and authentication response headers. + * <P> + * <H4>Additional Note for OBEX over Bluetooth</H4> + * If this method is called on a <code>SessionNotifier</code> object that + * does not have a <code>ServiceRecord</code> in the SDDB, the + * <code>ServiceRecord</code> for this object will be added to the SDDB. + * This method requests the BCC to put the + * local device in connectible mode so that it will respond to + * connection attempts by clients. + * <P> + * The following checks are done to verify that the service record + * provided is valid. If any of these checks fail, then a + * <code>ServiceRegistrationException</code> is thrown. + * <UL> + * <LI>ServiceClassIDList and ProtocolDescriptorList, the mandatory + * service attributes for a <code>btgoep</code> service record, must be + * present in the <code>ServiceRecord</code> associated with this notifier. + * <LI>L2CAP, RFCOMM and OBEX must all be in the ProtocolDescriptorList + * <LI>The <code>ServiceRecord</code> associated with this notifier must + * not have changed the RFCOMM server channel number + * </UL> + * <P> + * This method will not ensure that <code>ServiceRecord</code> associated + * with this notifier is a completely + * valid service record. It is the responsibility of the application to + * ensure that the service record follows all of the applicable + * syntactic and semantic rules for service record correctness. + * + * @param handler the request handler that will respond to OBEX requests + * + * @param auth the <code>Authenticator</code> to use with this connection; + * if <code>null</code> then no <code>Authenticator</code> will be used + * + * @return the connection to the client + * + * @throws IOException if an error occurs in the transport layer + * + * @throws NullPointerException if <code>handler</code> is + * <code>null</code> + * + * @throws ServiceRegistrationException if the structure of the + * associated service record is invalid or if the service record + * could not be added successfully to the local SDDB. The + * structure of service record is invalid if the service + * record is missing any mandatory service attributes, or has + * changed any of the values described above which are fixed and + * cannot be changed. Failures to add the record to the SDDB could + * be due to insufficient disk space, database locks, etc. + * + * @throws BluetoothStateException if the server device could + * not be placed in connectible mode because the device user has + * configured the device to be non-connectible + */ + ObexSession acceptAndOpen(ServerRequestHandler handler, Authenticator auth) throws IOException; +} |