From 9439a7fe517b858bc5e5c654b459315e4722feb2 Mon Sep 17 00:00:00 2001 From: Nick Pelly Date: Tue, 30 Jun 2009 12:04:36 -0700 Subject: Add javax.obex library, contributed by Motorola. Based of a JSR-82 reference implementation. This check-in is 'as-is' from the source provided June 25. This code does not conform to Android style and quality guidelines. I will be working with Motorola to improve it. If it does not see substantial improvement in the Eclair timeframe it will be deleted. --- obex/javax/obex/ApplicationParameter.java | 135 +++ obex/javax/obex/Authenticator.java | 124 +++ obex/javax/obex/BaseStream.java | 83 ++ obex/javax/obex/ClientOperation.java | 840 +++++++++++++++++ obex/javax/obex/ClientSession.java | 783 ++++++++++++++++ obex/javax/obex/HeaderSet.java | 610 ++++++++++++ obex/javax/obex/OBEXConstants.java | 74 ++ obex/javax/obex/OBEXHelper.java | 1326 +++++++++++++++++++++++++++ obex/javax/obex/ObexSession.java | 63 ++ obex/javax/obex/ObexTransport.java | 78 ++ obex/javax/obex/Operation.java | 200 ++++ obex/javax/obex/PasswordAuthentication.java | 85 ++ obex/javax/obex/PrivateInputStream.java | 196 ++++ obex/javax/obex/PrivateOutputStream.java | 197 ++++ obex/javax/obex/ResponseCodes.java | 324 +++++++ obex/javax/obex/ServerOperation.java | 772 ++++++++++++++++ obex/javax/obex/ServerRequestHandler.java | 288 ++++++ obex/javax/obex/ServerSession.java | 923 +++++++++++++++++++ obex/javax/obex/SessionNotifier.java | 168 ++++ 19 files changed, 7269 insertions(+) create mode 100644 obex/javax/obex/ApplicationParameter.java create mode 100644 obex/javax/obex/Authenticator.java create mode 100644 obex/javax/obex/BaseStream.java create mode 100644 obex/javax/obex/ClientOperation.java create mode 100644 obex/javax/obex/ClientSession.java create mode 100644 obex/javax/obex/HeaderSet.java create mode 100644 obex/javax/obex/OBEXConstants.java create mode 100644 obex/javax/obex/OBEXHelper.java create mode 100644 obex/javax/obex/ObexSession.java create mode 100644 obex/javax/obex/ObexTransport.java create mode 100644 obex/javax/obex/Operation.java create mode 100644 obex/javax/obex/PasswordAuthentication.java create mode 100644 obex/javax/obex/PrivateInputStream.java create mode 100644 obex/javax/obex/PrivateOutputStream.java create mode 100644 obex/javax/obex/ResponseCodes.java create mode 100644 obex/javax/obex/ServerOperation.java create mode 100644 obex/javax/obex/ServerRequestHandler.java create mode 100644 obex/javax/obex/ServerSession.java create mode 100644 obex/javax/obex/SessionNotifier.java (limited to 'obex') diff --git a/obex/javax/obex/ApplicationParameter.java b/obex/javax/obex/ApplicationParameter.java new file mode 100644 index 0000000..8ed4ca6 --- /dev/null +++ b/obex/javax/obex/ApplicationParameter.java @@ -0,0 +1,135 @@ +/* + * 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; + +public class ApplicationParameter { + private int max_length_ini = 1000; + + private byte[] b_array; + + private int length; + + 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; + + public static final byte MAXLISTCOUNT_TAGID = 0x04;//if equals to "0", PSE only reply number of contacts + + public static final byte LISTSTARTOFFSET_TAGID = 0x05; + + public static final byte FILTER_TAGID = 0x06; + + public static final byte FORMAT_TAGID = 0x07; + + public static final byte PHONEBOOKSIZE_TAGID = 0x08;//only used if max list count = 0 + + public static final byte NEWMISSEDCALLS_TAGID = 0x09;//only used in "mch" in response + } + + 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 final byte SEARCH_VALUE_LENGTH = 0x02; + 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 class TRIPLET_STRUCTURE{ + TRIPLET_TAGID id; + TRIPLET_LENGTH length; + byte[] value; + } + */ + public ApplicationParameter() { + b_array = new byte[max_length_ini]; + length = 0; + } + + public void addAPPHeader(byte tag, byte len, byte[] value) { + if ((length + len + 2) > max_length_ini) { + byte[] array_tmp = new byte[length + 4 * len]; + System.arraycopy(b_array, 0, array_tmp, 0, length); + b_array = array_tmp; + max_length_ini = length + 4 * len; + } + b_array[length++] = tag; + b_array[length++] = len; + System.arraycopy(value, 0, b_array, length, len); + length += len; + } + + public byte[] getAPPparam() { + byte[] para = new byte[length]; + System.arraycopy(b_array, 0, para, 0, length); + return para; + } +} diff --git a/obex/javax/obex/Authenticator.java b/obex/javax/obex/Authenticator.java new file mode 100644 index 0000000..a1729f8 --- /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 + * onAuthenticationChallenge() or + * onAuthenticationResponse() will be called, respectively, by + * the implementation. + *

+ * For more information on how the authentication procedure works in OBEX, + * please review the IrOBEX specification at + * http://www.irda.org. + *

+ * Authentication Challenges + *

+ * When a client or server receives an authentication challenge header, the + * onAuthenticationChallenge() method will be invoked by the + * OBEX API implementation. The application will then return the user name + * (if needed) and password via a PasswordAuthentication 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 + * onAuthenticationChallenge() 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. + *

+ * Authentication Responses + *

+ * When a client or server receives an authentication response header, the + * onAuthenticationResponse() method is invoked by the API + * implementation with the user name received in the authentication response + * header. (The user name will be null 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 + * onAuthenticationResponse() method. If the authentication + * request should fail without the implementation checking the password, + * null should + * be returned by the application. (This is needed for reasons like not + * recognizing the user name, etc.) If the returned value is not + * null, the OBEX API implementation will combine the password + * returned from the onAuthenticationResponse() 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 + * IOException will be thrown if the client requested authentication. + * If the server requested authentication, the + * onAuthenticationFailure() method will be called on the + * ServerRequestHandler that failed authentication. The + * connection is not closed if authentication failed. + * + * @version 0.3 November 28, 2008 + */ +public interface Authenticator { + + /** + * Called when a client or a server receives an authentication challenge + * header. It should respond to the challenge with a + * PasswordAuthentication 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 true if the user ID is required; + * false if the user ID is not required + * + * @param isFullAccess true if full access to the server + * will be granted; false if read only access will be + * granted + * + * @return a PasswordAuthentication object containing the + * user name and password used for authentication + */ + public 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 null + * + * @return the correct password for the user name provided; if + * null is returned then the authentication request failed + */ + public byte[] onAuthenticationResponse(byte[] userName); +} diff --git a/obex/javax/obex/BaseStream.java b/obex/javax/obex/BaseStream.java new file mode 100644 index 0000000..ffd0b63 --- /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.*; + +/** + * This interface defines the methods needed by a parent that uses the + * PrivateInputStream and PrivateOutputStream objects defined in this package. + * + * @version 0.3 November 28, 2008 + */ +public interface BaseStream { + + /** + * Verifies that this object is still open. + * + * @exception IOException if the object is closed + */ + public void ensureOpen() throws IOException; + + /** + * Verifies that additional information may be sent. In other words, the + * operation is not done. + * + * @exception IOException if the operation is completed + */ + public void ensureNotDone() throws IOException; + + /** + * Continues the operation since there is no data to read. + * + * @param sendEmpty true if the operation should send an + * empty packet or not send anything if there is no data to send + * @param inStream true if the stream is input stream or + * is output stream + * @return true if the operation was completed; + * false if no operation took place + * + * @exception IOException if an IO error occurs + */ + public boolean continueOperation(boolean sendEmpty, boolean inStream) throws IOException; + + /** + * Called when the output or input stream is closed. + * + * @param inStream true if the input stream is closed; + * false if the output stream is closed + * + * @exception IOException if an IO error occurs + */ + public 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..dc4e944 --- /dev/null +++ b/obex/javax/obex/ClientOperation.java @@ -0,0 +1,840 @@ +/* + * 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 Operation interface. It will read + * and write data via puts and gets. + * + * @version 0.3 November 28, 2008 + */ +public class ClientOperation implements Operation, BaseStream { + + /** + * Defines the basic packet length used by OBEX. Event OBEX packet has the + * same basic format:
+ * Byte 0: Request or Response Code + * Byte 1&2: Length of the packet. + */ + private static final int BASE_PACKET_LENGTH = 3; + + private ClientSession parent; + + private InputStream socketInput; + + private PrivateInputStream privateInput; + + private PrivateOutputStream privateOutput; + + private boolean isClosed; + + private String exceptionMessage; + + private int maxPacketSize; + + private boolean isDone; + + private boolean isGet; + + private HeaderSet requestHeaders; + + private HeaderSet replyHeaders; + + private boolean isEndOfBodySent; + + private boolean inputStreamOpened; + + private boolean outputStreamOpened; + + private boolean isValidateConnected; + + /** + * Creates new OperationImpl to read and write data to a server + * + * @param in the input stream to read from + * + * @param maxSize the maximum packet size + * + * @param p the parent to this object + * + * @param headers the headers to set in the initial request + * + * @param type true if this is a get request; + * falseResponseCodes interface. + * + * @return the response code retrieved from the server + * + * @exception IOException if an error occurred in the transport layer during + * the transaction; if this method is called on a HeaderSet + * object created by calling createHeaderSet in a + * ClientSession object + */ + public synchronized int getResponseCode() throws IOException { + //avoid dup validateConnection + if ((replyHeaders.responseCode == -1) + || (replyHeaders.responseCode == OBEXConstants.OBEX_HTTP_CONTINUE)) { + validateConnection(); + } + + return replyHeaders.responseCode; + } + + /** + * This method will always return null + * + * @return null + */ + 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 + * null if not known + */ + public String getType() { + try { + return (String)replyHeaders.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)replyHeaders.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 + * + * @exception IOException if an I/O error occurs + */ + public InputStream openInputStream() throws IOException { + // TODO: this mode is not set yet. + // if ((parent.mode & Connector.READ) == 0) + // throw new IOException("write-only connection"); + + ensureOpen(); + + if (inputStreamOpened) + throw new IOException("no more input streams available"); + if (isGet) { + // send the GET request here + validateConnection(); + isValidateConnected = true; + } else { + if (privateInput == null) { + privateInput = new PrivateInputStream(this); + } + } + + inputStreamOpened = true; + + return privateInput; + } + + /**8 + * Open and return a data input stream for a connection. + * + * @return an input stream + * + * @exception IOException if an I/O error occurs + */ + public DataInputStream openDataInputStream() throws IOException { + return new DataInputStream(openInputStream()); + } + + /** + * Open and return an output stream for a connection. + * + * @return an output stream + * + * @exception IOException if an I/O error occurs + */ + public OutputStream openOutputStream() throws IOException { + // TODO: this mode is not set yet. + // if ((parent.mode & Connector.WRITE) == 0) + // throw new IOException("read-only connection"); + ensureOpen(); + ensureNotDone(); + + if (outputStreamOpened) + throw new IOException("no more output streams available"); + + if (privateOutput == null) { + // there are 3 bytes operation headers and 3 bytes body headers // + privateOutput = new PrivateOutputStream(this, maxPacketSize - 6); + } + + outputStreamOpened = true; + + return privateOutput; + } + + public int getMaxPacketSize() { + return maxPacketSize - 6; + } + + /** + * Open and return a data output stream for a connection. + * + * @return an output stream + * + * @exception IOException if an I/O error occurs + */ + public DataOutputStream openDataOutputStream() throws IOException { + return new DataOutputStream(openOutputStream()); + } + + /** + * Closes the connection and ends the transaction + * + * @exception IOException if the operation has already ended or is closed + */ + public void close() throws IOException { + isClosed = true; + inputStreamOpened = false; + outputStreamOpened = false; + parent.setInactive(); + } + + /** + * 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 Operation + * + * @exception IOException if this Operation has been closed + */ + public HeaderSet getReceivedHeaders() throws IOException { + ensureOpen(); + + return replyHeaders; + } + + /** + * Specifies the headers that should be sent in the next OBEX message that + * is sent. + * + * @param headers the headers to send in the next message + * + * @exception IOException if this Operation has been closed + * or the transaction has ended and no further messages will be exchanged + * + * @exception IllegalArgumentException if headers was not created + * by a call to ServerRequestHandler.createHeaderSet() + * + * @exception NullPointerException if headers is null + */ + public void sendHeaders(HeaderSet headers) throws IOException { + ensureOpen(); + if (isDone) { + throw new IOException("Operation has already exchanged all data"); + } + + if (headers == null) { + throw new NullPointerException("Headers may not be null"); + } + + int[] headerList = headers.getHeaderList(); + if (headerList != null) { + for (int i = 0; i < headerList.length; i++) { + requestHeaders.setHeader(headerList[i], headers.getHeader(headerList[i])); + } + } + } + + /** + * Reads a response from the server. It will populate the appropriate body + * and headers. + * + * @return true if the transaction should end; + * false if the transaction should not end + * + * @exception IOException if an IO error occurred + */ + private boolean readResponse() throws IOException { + replyHeaders.responseCode = socketInput.read(); + int packetLength = socketInput.read(); + packetLength = (packetLength << 8) + socketInput.read(); + + if (packetLength > OBEXConstants.MAX_PACKET_SIZE_INT) { + if (exceptionMessage != null) { + abort(); + } + throw new IOException("Received a packet that was too big"); + } + + if (packetLength > BASE_PACKET_LENGTH) { + int dataLength = packetLength - BASE_PACKET_LENGTH; + byte[] data = new byte[dataLength]; + int readLength = socketInput.read(data); + if (readLength != dataLength) { + throw new IOException("Received a packet without data as decalred length"); + } + byte[] body = OBEXHelper.updateHeaderSet(replyHeaders, data); + + if (body != null) { + privateInput.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) && (replyHeaders.responseCode == ResponseCodes.OBEX_HTTP_OK)) { + return false; + } + } + } + + if (replyHeaders.responseCode == OBEXConstants.OBEX_HTTP_CONTINUE) { + return true; + } else { + return false; + } + } + + /** + * Verifies that additional information may be sent. In other words, the + * operation is not done. + * + * @exception IOException if the operation is completed + */ + public void ensureNotDone() throws IOException { + if (isDone) { + throw new IOException("Operation has completed"); + } + } + + /** + * Verifies that the connection is open and no exceptions should be thrown. + * + * @exception IOException if an exception needs to be thrown + */ + public void ensureOpen() throws IOException { + parent.ensureOpen(); + + if (exceptionMessage != null) { + throw new IOException(exceptionMessage); + } + if (isClosed) { + throw new IOException("Operation has already ended"); + } + } + + /** + * Verifies that the connection is open and the proper data has been read. + * + * @exception IOException if an IO error occurs + */ + private void validateConnection() throws IOException { + ensureOpen(); + + // to sure only one privateInput object exist. + if (privateInput == null) { + startProcessing(); + } + } + + /** + * Sends a request to the client of the specified type + * + * @param response the response code to send back to the client + * + * @return true if there is more data to send; + * false if there is no more data to send + * + * @exception IOException if an IO error occurs + */ + protected boolean sendRequest(int type) throws IOException { + boolean returnValue = false; + ByteArrayOutputStream out = new ByteArrayOutputStream(); + int bodyLength = -1; + byte[] headerArray = OBEXHelper.createHeader(requestHeaders, true); + if (privateOutput != null) { + bodyLength = privateOutput.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 ((BASE_PACKET_LENGTH + headerArray.length) > maxPacketSize) { + 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, maxPacketSize + - BASE_PACKET_LENGTH); + // can not split + if (end == -1) { + isDone = true; + abort(); + // isDone = true; + exceptionMessage = "Header larger then can be sent in a packet"; + isClosed = true; + + if (privateInput != null) { + privateInput.close(); + } + + if (privateOutput != null) { + privateOutput.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 (!parent.sendRequest(type, sendHeader, replyHeaders, privateInput)) { + return false; + } + + if (replyHeaders.responseCode != OBEXConstants.OBEX_HTTP_CONTINUE) { + return false; + } + + start = end; + } + + if (bodyLength > 0) { + return true; + } else { + return false; + } + } else { + out.write(headerArray); + } + + 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 > (maxPacketSize - headerArray.length - 6)) { + returnValue = true; + + bodyLength = maxPacketSize - headerArray.length - 6; + } + + byte[] body = privateOutput.readBytes(bodyLength); + + /* + * Since this is a put request if the final bit is set or + * the output stream is closed we need to send the 0x49 + * (End of Body) otherwise, we need to send 0x48 (Body) + */ + if ((privateOutput.isClosed()) && (!returnValue) && (!isEndOfBodySent) + && ((type & 0x80) != 0)) { + out.write(0x49); + isEndOfBodySent = true; + } else { + out.write(0x48); + } + + bodyLength += 3; + out.write((byte)(bodyLength >> 8)); + out.write((byte)bodyLength); + + if (body != null) { + out.write(body); + } + } + + if (outputStreamOpened && bodyLength <= 0 && !isEndOfBodySent) { + // only 0x82 or 0x83 can send 0x49 + if ((type & 0x80) == 0) { + out.write(0x48); + } else { + out.write(0x49); + isEndOfBodySent = true; + + } + + bodyLength = 3; + out.write((byte)(bodyLength >> 8)); + out.write((byte)bodyLength); + } + + if (out.size() == 0) { + if (!parent.sendRequest(type, null, replyHeaders, privateInput)) { + return false; + } + return returnValue; + } + if ((out.size() > 0) + && (!parent.sendRequest(type, out.toByteArray(), replyHeaders, privateInput))) { + return false; + } + + // send all of the output data in 0x48, + // send 0x49 with empty body + if ((privateOutput != null) && (privateOutput.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 + * + * @exception IOException if an IO error occurs + */ + private synchronized void startProcessing() throws IOException { + + if (privateInput == null) { + privateInput = new PrivateInputStream(this); + } + boolean more = true; + + if (isGet) { + if (!isDone) { + replyHeaders.responseCode = OBEXConstants.OBEX_HTTP_CONTINUE; + while ((more) && (replyHeaders.responseCode == OBEXConstants.OBEX_HTTP_CONTINUE)) { + more = sendRequest(0x03); + } + + if (replyHeaders.responseCode == OBEXConstants.OBEX_HTTP_CONTINUE) { + parent.sendRequest(0x83, null, replyHeaders, privateInput); + } + if (replyHeaders.responseCode != OBEXConstants.OBEX_HTTP_CONTINUE) { + isDone = true; + } + } + } else { + + if (!isDone) { + replyHeaders.responseCode = OBEXConstants.OBEX_HTTP_CONTINUE; + while ((more) && (replyHeaders.responseCode == OBEXConstants.OBEX_HTTP_CONTINUE)) { + more = sendRequest(0x02); + + } + } + + if (replyHeaders.responseCode == OBEXConstants.OBEX_HTTP_CONTINUE) { + parent.sendRequest(0x82, null, replyHeaders, privateInput); + } + + if (replyHeaders.responseCode != OBEXConstants.OBEX_HTTP_CONTINUE) { + isDone = true; + } + } + } + + /** + * Continues the operation since there is no data to read. + * + * @param sendEmpty true if the operation should send an + * empty packet or not send anything if there is no data to send + * @param inStream true if the stream is input stream or + * is output stream + * @exception IOException if an IO error occurs + */ + public synchronized boolean continueOperation(boolean sendEmpty, boolean inStream) + throws IOException { + + if (isGet) { + if ((inStream) && (!isDone)) { + // to deal with inputstream in get operation + parent.sendRequest(0x83, null, replyHeaders, privateInput); + /* + * Determine if that was not the last packet in the operation + */ + if (replyHeaders.responseCode != OBEXConstants.OBEX_HTTP_CONTINUE) { + isDone = true; + } + + return true; + + } else if ((!inStream) && (!isDone)) { + // to deal with outputstream in get operation + + if (privateInput == null) { + privateInput = new PrivateInputStream(this); + } + sendRequest(0x03); + return true; + + } else if (isDone) { + return false; + } + + } else { + if ((!inStream) && (!isDone)) { + // to deal with outputstream in put operation + if (replyHeaders.responseCode == -1) { + replyHeaders.responseCode = OBEXConstants.OBEX_HTTP_CONTINUE; + } + sendRequest(0x02); + return true; + } else if ((inStream) && (!isDone)) { + // How to deal with inputstream in put operation ? + return false; + + } else if (isDone) { + return false; + } + + } + return false; + } + + /** + * Called when the output or input stream is closed. + * + * @param inStream true if the input stream is closed; + * false if the output stream is closed + * + * @exception IOException if an IO error occurs + */ + public void streamClosed(boolean inStream) throws IOException { + if (!isGet) { + if ((!inStream) && (!isDone)) { + // to deal with outputstream in put operation + + boolean more = true; + + if ((privateOutput != null) && (privateOutput.size() <= 0)) { + byte[] headerArray = OBEXHelper.createHeader(requestHeaders, false); + if (headerArray.length <= 0) + more = false; + } + // If have not sent any data so send all now + if (replyHeaders.responseCode == -1) { + replyHeaders.responseCode = OBEXConstants.OBEX_HTTP_CONTINUE; + } + + while ((more) && (replyHeaders.responseCode == OBEXConstants.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 (replyHeaders.responseCode == OBEXConstants.OBEX_HTTP_CONTINUE) { + + sendRequest(0x82); + } + isDone = true; + } else if ((inStream) && (isDone)) { + // how to deal with input stream in put stream ? + isDone = true; + } + } else { + isValidateConnected = false; + if ((inStream) && (!isDone)) { + + // to deal with inputstream in get operation + // Have not sent any data so send it all now + + if (replyHeaders.responseCode == -1) { + replyHeaders.responseCode = OBEXConstants.OBEX_HTTP_CONTINUE; + } + + while (replyHeaders.responseCode == OBEXConstants.OBEX_HTTP_CONTINUE) { + if (!sendRequest(0x83)) { + break; + } + } + while (replyHeaders.responseCode == OBEXConstants.OBEX_HTTP_CONTINUE) { + parent.sendRequest(0x83, null, replyHeaders, privateInput); + } + isDone = true; + } else if ((!inStream) && (!isDone)) { + // to deal with outputstream in get operation + // part of the data may have been sent in continueOperation. + + boolean more = true; + + if ((privateOutput != null) && (privateOutput.size() <= 0)) { + byte[] headerArray = OBEXHelper.createHeader(requestHeaders, false); + if (headerArray.length <= 0) + more = false; + } + + if (privateInput == null) { + privateInput = new PrivateInputStream(this); + } + if ((privateOutput != null) && (privateOutput.size() <= 0)) + more = false; + + replyHeaders.responseCode = OBEXConstants.OBEX_HTTP_CONTINUE; + while ((more) && (replyHeaders.responseCode == OBEXConstants.OBEX_HTTP_CONTINUE)) { + more = sendRequest(0x03); + } + sendRequest(0x83); + // parent.sendRequest(0x83, null, replyHeaders, privateInput); + if (replyHeaders.responseCode != OBEXConstants.OBEX_HTTP_CONTINUE) { + isDone = true; + } + + } + } + } +} diff --git a/obex/javax/obex/ClientSession.java b/obex/javax/obex/ClientSession.java new file mode 100644 index 0000000..c624c30 --- /dev/null +++ b/obex/javax/obex/ClientSession.java @@ -0,0 +1,783 @@ +/* + * 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 implements the Operation interface. It will read + * and write data via puts and gets. + * + * @version 0.3 November 28, 2008 + */ +public class ClientSession implements ObexSession { + //protected StreamConnection client; + protected Authenticator authenticator; + + protected boolean connectionOpen = false; + + protected boolean isConnected; // Determines if an OBEX layer connection + + // has been established + private byte[] connectionID = null; + + byte[] challengeDigest = null; + + protected InputStream input = null; + + protected OutputStream output = null; + + protected ObexTransport trans = null; + + public int mode; + + /* + * The max Packet size must be at least 256 according to the OBEX + * specification. + */ + private int maxPacketSize = 256; + + protected boolean isActive; + + /* public ClientSession() { + connectionOpen = false; + }*/ + + public ClientSession(ObexTransport trans) { + try { + this.trans = trans; + input = trans.openInputStream(); + output = trans.openOutputStream(); + connectionOpen = true; + } catch (IOException ioe) { + } + + } + + /* comment out as not used internally + public boolean isClosed() { + // if (client instanceof BTConnection) + // return ((BTConnection)client).isClosed(); + // else + return false; + } + + public int getConnectionHandle() { + //if (client instanceof BTConnection) + // return ((BTConnection)client).getConnectionHandle(); + //else + return -1; + } + + public RemoteDevice getRemoteDevice() { + if (client instanceof BTConnection) + return ((BTConnection)client).getRemoteDevice(); + else + return null; + } + */ + + public HeaderSet connect(HeaderSet header) throws java.io.IOException { + ensureOpen(); + if (isConnected) { + throw new IOException("Already connected to server"); + } + synchronized (this) { + if (isActive) { + throw new IOException("OBEX request is already being performed"); + } + isActive = true; + } + int totalLength = 4; + byte[] head = null; + + // Determine the header byte array + if (header != null) { + if ((header).nonce != null) { + challengeDigest = new byte[16]; + System.arraycopy((header).nonce, 0, challengeDigest, 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)(OBEXConstants.MAX_PACKET_SIZE_INT >> 8); + requestPacket[3] = (byte)(OBEXConstants.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) > OBEXConstants.MAX_PACKET_SIZE_INT) { + throw new IOException("Packet size exceeds max packet size"); + } + + HeaderSet returnHeaderSet = new HeaderSet(); + sendRequest(0x80, 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) { + isConnected = true; + } + synchronized (this) { + isActive = false; + } + + return returnHeaderSet; + } + + public Operation get(HeaderSet header) throws java.io.IOException { + + if (!isConnected) { + throw new IOException("Not connected to the server"); + } + synchronized (this) { + if (isActive) { + throw new IOException("OBEX request is already being performed"); + } + isActive = true; + } + ensureOpen(); + + if (header == null) { + header = new HeaderSet(); + } else { + if ((header).nonce != null) { + challengeDigest = new byte[16]; + System.arraycopy((header).nonce, 0, challengeDigest, 0, 16); + } + } + // Add the connection ID if one exists + if (connectionID != null) { + (header).connectionID = new byte[4]; + System.arraycopy(connectionID, 0, (header).connectionID, 0, 4); + } + + // this.mode = javax.microedition.io.Connector.READ ; + return new ClientOperation(input, maxPacketSize, this, header, 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"); + } + connectionID = OBEXHelper.convertToByteArray(id); + } + + public HeaderSet createHeaderSet() { + return new HeaderSet(); + } + + public HeaderSet delete(HeaderSet headers) throws java.io.IOException { + + Operation op = put(headers); + op.getResponseCode(); + HeaderSet returnValue = op.getReceivedHeaders(); + op.close(); + + return returnValue; + } + + public HeaderSet disconnect(HeaderSet header) throws java.io.IOException { + if (!isConnected) { + throw new IOException("Not connected to the server"); + } + synchronized (this) { + if (isActive) { + throw new IOException("OBEX request is already being performed"); + } + isActive = true; + } + ensureOpen(); + // Determine the header byte array + byte[] head = null; + if (header != null) { + if ((header).nonce != null) { + challengeDigest = new byte[16]; + System.arraycopy((header).nonce, 0, challengeDigest, 0, 16); + } + // Add the connection ID if one exists + if (connectionID != null) { + (header).connectionID = new byte[4]; + System.arraycopy(connectionID, 0, (header).connectionID, 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 (connectionID != null) { + head = new byte[5]; + head[0] = (byte)0xCB; + System.arraycopy(connectionID, 0, head, 1, 4); + } + } + + HeaderSet returnHeaderSet = new HeaderSet(); + sendRequest(0x81, 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) { + isConnected = false; + isActive = false; + } + + return returnHeaderSet; + } + + public long getConnectionID() { + + if (connectionID == null) { + return -1; + } + return OBEXHelper.convertToLong(connectionID); + } + + public Operation put(HeaderSet header) throws java.io.IOException { + if (!isConnected) { + throw new IOException("Not connected to the server"); + } + synchronized (this) { + if (isActive) { + throw new IOException("OBEX request is already being performed"); + } + isActive = true; + } + + ensureOpen(); + + if (header == null) { + header = new HeaderSet(); + } else { + // when auth is initated by client ,save the digest + if ((header).nonce != null) { + challengeDigest = new byte[16]; + System.arraycopy((header).nonce, 0, challengeDigest, 0, 16); + } + } + + // Add the connection ID if one exists + if (connectionID != null) { + + (header).connectionID = new byte[4]; + System.arraycopy(connectionID, 0, (header).connectionID, 0, 4); + } + + // this.mode = javax.microedition.io.Connector.WRITE ; + return new ClientOperation(input, maxPacketSize, this, header, false); + } + + public void setAuthenticator(Authenticator auth) { + if (auth == null) { + throw new NullPointerException("Authenticator may not be null"); + } + authenticator = auth; + } + + public HeaderSet setPath(HeaderSet header, boolean backup, boolean create) + throws java.io.IOException { + if (!isConnected) { + throw new IOException("Not connected to the server"); + } + synchronized (this) { + if (isActive) { + throw new IOException("OBEX request is already being performed"); + } + isActive = true; + } + + ensureOpen(); + + int totalLength = 2; + byte[] head = null; + + if (header == null) { + header = new HeaderSet(); + } else { + if ((header).nonce != null) { + challengeDigest = new byte[16]; + System.arraycopy((header).nonce, 0, challengeDigest, 0, 16); + } + } + + // when auth is initiated by client ,save the digest + if ((header).nonce != null) { + challengeDigest = new byte[16]; + System.arraycopy((header).nonce, 0, challengeDigest, 0, 16); + } + + // Add the connection ID if one exists + if (connectionID != null) { + (header).connectionID = new byte[4]; + System.arraycopy(connectionID, 0, (header).connectionID, 0, 4); + } + + head = OBEXHelper.createHeader(header, 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 (header != null) { + System.arraycopy(head, 0, packet, 2, head.length); + } + + HeaderSet returnHeaderSet = new HeaderSet(); + sendRequest(0x85, packet, returnHeaderSet, null); + + /* + * An OBEX SETPATH reply from the server: + * Byte 1: Response code + * Bytes 2 & 3: packet size + * Bytes 4 & up: headers + */ + + synchronized (this) { + isActive = false; + } + + return returnHeaderSet; + } + + /** + * Verifies that the connection is open. + * + * @exception IOException if the connection is closed + */ + public synchronized void ensureOpen() throws IOException { + if (!connectionOpen) { + throw new IOException("Connection closed"); + } + } + + /** + * Sets the active mode to off. This allows Put and get operation objects + * to tell this object when they are done. + */ + public void setInactive() { + synchronized (this) { + isActive = false; + } + } + + /** + * 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 code 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 head; null + * if no authentication header is included in head + * + * @param headers 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 true if the operation completed successfully; + * false if an authentication response failed to pass + * + * @exception IOException if an IO error occurs + */ + public boolean sendRequest(int code, byte[] head, HeaderSet headers, + PrivateInputStream privateInput) throws IOException { + //check header length with local max size + if (head != null) { + if ((head.length + 3) > OBEXConstants.MAX_PACKET_SIZE_INT) { + throw new IOException("header too large "); + } + } + //byte[] nonce; + int bytesReceived; + ByteArrayOutputStream out = new ByteArrayOutputStream(); + out.write((byte)code); + + // 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 + output.write(out.toByteArray()); + output.flush(); + + headers.responseCode = input.read(); + + int length = ((input.read() << 8) | (input.read())); + + if (length > OBEXConstants.MAX_PACKET_SIZE_INT) { + throw new IOException("Packet received exceeds packet size limit"); + } + if (length > 3) { + byte[] data = null; + if (code == 0x80) { + int version = input.read(); + int flags = input.read(); + maxPacketSize = (input.read() << 8) + input.read(); + + //check with local max size + if (maxPacketSize > OBEXConstants.MAX_PACKET_SIZE_INT) { + maxPacketSize = OBEXConstants.MAX_PACKET_SIZE_INT; + } + + if (length > 7) { + data = new byte[length - 7]; + + bytesReceived = input.read(data); + while (bytesReceived != (length - 7)) { + bytesReceived += input.read(data, bytesReceived, data.length + - bytesReceived); + } + } else { + return true; + } + } else { + data = new byte[length - 3]; + bytesReceived = input.read(data); + + while (bytesReceived != (length - 3)) { + bytesReceived += input.read(data, bytesReceived, data.length - bytesReceived); + } + if (code == 0xFF) { + return true; + } + } + + byte[] body = OBEXHelper.updateHeaderSet(headers, data); + if ((privateInput != null) && (body != null)) { + privateInput.writeBytes(body, 1); + } + + if (headers.connectionID != null) { + connectionID = new byte[4]; + System.arraycopy(headers.connectionID, 0, connectionID, 0, 4); + } + + if (headers.authResp != null) { + if (!handleAuthResp(headers.authResp)) { + setInactive(); + throw new IOException("Authentication Failed"); + } + } + + if ((headers.responseCode == ResponseCodes.OBEX_HTTP_UNAUTHORIZED) + && (headers.authChall != null)) { + + if (handleAuthChall(headers)) { + out.write((byte)0x4E); + out.write((byte)((headers.authResp.length + 3) >> 8)); + out.write((byte)(headers.authResp.length + 3)); + out.write(headers.authResp); + headers.authChall = null; + headers.authResp = null; + + byte[] sendHeaders = new byte[out.size() - 3]; + System.arraycopy(out.toByteArray(), 3, sendHeaders, 0, sendHeaders.length); + + return sendRequest(code, sendHeaders, headers, privateInput); + } + } + } + + return true; + } + + /** + * Called when the client received an authentication challenge header. This + * will cause the authenticator to handle the authentication challenge. + * + * @param header the header with the authentication challenge + * + * @return true if the last request should be resent; + * false if the last request should not be resent + */ + protected boolean handleAuthChall(HeaderSet header) { + + if (authenticator == 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.authChall); + byte[] option = OBEXHelper.getTagValue((byte)0x01, header.authChall); + byte[] description = OBEXHelper.getTagValue((byte)0x02, header.authChall); + + String realm = ""; + if (description != null) { + byte[] realmString = new byte[description.length - 1]; + System.arraycopy(description, 1, realmString, 0, realmString.length); + + switch (description[0] & 0xFF) { + + case 0x00: + // ASCII encoding + // Fall through + case 0x01: + // ISO-8859-1 encoding + try { + realm = new String(realmString, "ISO8859_1"); + } catch (Exception e) { + throw new RuntimeException("Unsupported Encoding Scheme"); + } + break; + + case 0xFF: + // UNICODE Encoding + realm = OBEXHelper.convertToUnicode(realmString, false); + break; + + case 0x02: + // ISO-8859-2 encoding + // Fall through + case 0x03: + // ISO-8859-3 encoding + // Fall through + case 0x04: + // ISO-8859-4 encoding + // Fall through + case 0x05: + // ISO-8859-5 encoding + // Fall through + case 0x06: + // ISO-8859-6 encoding + // Fall through + case 0x07: + // ISO-8859-7 encoding + // Fall through + case 0x08: + // ISO-8859-8 encoding + // Fall through + case 0x09: + // ISO-8859-9 encoding + // Fall through + default: + throw new RuntimeException("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.authChall = null; + + try { + result = authenticator.onAuthenticationChallenge(realm, isUserIDRequired, isFullAccess); + } catch (Exception e) { + return false; + } + + /* + * If no password was provided then do not resend 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.authResp = new byte[38 + userName.length]; + header.authResp[36] = (byte)0x01; + header.authResp[37] = (byte)userName.length; + System.arraycopy(userName, 0, header.authResp, 38, userName.length); + } else { + header.authResp = new byte[36]; + } + + // Create the secret String + byte[] digest = new byte[challenge.length + password.length]; + System.arraycopy(challenge, 0, digest, 0, challenge.length); + System.arraycopy(password, 0, digest, challenge.length, password.length); + + // Add the Response Digest + header.authResp[0] = (byte)0x00; + header.authResp[1] = (byte)0x10; + + byte[] responseDigest = OBEXHelper.computeMD5Hash(digest); + System.arraycopy(responseDigest, 0, header.authResp, 2, 16); + + // Add the challenge + header.authResp[18] = (byte)0x02; + header.authResp[19] = (byte)0x10; + System.arraycopy(challenge, 0, header.authResp, 20, 16); + + return true; + } + + /** + * Called when the client received an authentication response header. This + * will cause the authenticator to handle the authentication response. + * + * @param authResp the authentication response + * + * @return true if the response passed; false if + * the response failed + */ + protected boolean handleAuthResp(byte[] authResp) { + if (authenticator == null) { + return false; + } + + byte[] correctPassword = authenticator.onAuthenticationResponse(OBEXHelper.getTagValue( + (byte)0x01, authResp)); + if (correctPassword == null) { + return false; + } + + byte[] temp = new byte[correctPassword.length + 16]; + System.arraycopy(challengeDigest, 0, temp, 0, 16); + System.arraycopy(correctPassword, 0, temp, 16, correctPassword.length); + + byte[] correctResponse = OBEXHelper.computeMD5Hash(temp); + byte[] actualResponse = OBEXHelper.getTagValue((byte)0x00, authResp); + for (int i = 0; i < 16; i++) { + if (correctResponse[i] != actualResponse[i]) { + return false; + } + } + + return true; + } + + public void close() throws IOException { + connectionOpen = false; + input.close(); + output.close(); + //client.close(); + } +} diff --git a/obex/javax/obex/HeaderSet.java b/obex/javax/obex/HeaderSet.java new file mode 100644 index 0000000..07abe15 --- /dev/null +++ b/obex/javax/obex/HeaderSet.java @@ -0,0 +1,610 @@ +/* + * 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.*; +import java.util.*; + +/** + * This class implements the javax.obex.HeaderSet interface for OBEX over + * RFCOMM. + * + * @version 0.3 November 28, 2008 + */ +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. + *

+ * The value of COUNT is 0xC0 (192). + */ + public static final int COUNT = 0xC0; + + /** + * Represents the OBEX Name header. This specifies the name of the object. + *

+ * The value of NAME 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.). + *

+ * The value of TYPE is 0x42 (66). + */ + public static final int TYPE = 0x42; + + /** + * Represents the OBEX Length header. This is the length of the object in + * bytes. + *

+ * The value of LENGTH 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. + *

+ * The value of TIME_ISO_8601 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. + *

+ * The value of TIME_4_BYTE is 0xC4 (196). + */ + public static final int TIME_4_BYTE = 0xC4; + + /** + * Represents the OBEX Description header. This is a text description of + * the object. + *

+ * The value of DESCRIPTION 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. + *

+ * The value of TARGET 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. + *

+ * The value of HTTP is 0x47 (71). + */ + public static final int HTTP = 0x47; + + /** + * Represents the OBEX Who header. Identifies the OBEX application to + * determine if the two peers are talking to each other. + *

+ * The value of WHO is 0x4A (74). + */ + public static final int WHO = 0x4A; + + /** + * Represents the OBEX Object Class header. This header specifies the + * OBEX object class of the object. + *

+ * The value of OBJECT_CLASS is 0x4F (79). + */ + public static final int OBJECT_CLASS = 0x4F; + + /** + * Represents the OBEX Application Parameter header. This header specifies + * additional application request and response information. + *

+ * The value of APPLICATION_PARAMETER is 0x4C (76). + */ + public static final int APPLICATION_PARAMETER = 0x4C; + + private Long count; // 4 byte unsigned integer + + private String name; // null terminated Unicode text string + + private String type; // null terminated ASCII text string + + private Long length; // 4 byte unsigend integer + + private Calendar isoTime; // String of the form YYYYMMDDTHHMMSSZ + + private Calendar byteTime; // 4 byte unsigned integer + + private String description; // null terminated Unicode text String + + private byte[] target; // byte sequence + + private byte[] http; // byte sequence + + private byte[] who; // length prefixed byte sequence + + private byte[] appParam; // byte sequence of the form tag length + + //value + public byte[] authChall; // The authentication challenge header + + public byte[] authResp; // The authentication response header + + public byte[] connectionID; // THe connection ID + + private byte[] objectClass; // byte sequence + + private String[] unicodeUserDefined; //null terminated unicode string + + private byte[][] sequenceUserDefined; // byte sequence user defined + + private Byte[] byteUserDefined; // 1 byte + + private Long[] integerUserDefined; // 4 byte unsigned integer + + public int responseCode; + + public byte[] nonce; + + private Random random; + + /** + * Creates new HeaderSet object. + * + * @param size the max packet size for this connection + */ + public HeaderSet() { + unicodeUserDefined = new String[16]; + sequenceUserDefined = new byte[16][]; + byteUserDefined = new Byte[16]; + integerUserDefined = new Long[16]; + responseCode = -1; + random = 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 null is passed as the + * headerValue 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 + * + * @exception IllegalArgumentException if the header identifier provided is + * not one defined in this interface or a user-defined header; if the type of + * headerValue 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) { + count = 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"); + } + count = (Long)headerValue; + break; + case NAME: + if ((headerValue != null) && (!(headerValue instanceof String))) { + throw new IllegalArgumentException("Name must be a String"); + } + name = (String)headerValue; + break; + case TYPE: + if ((headerValue != null) && (!(headerValue instanceof String))) { + throw new IllegalArgumentException("Type must be a String"); + } + type = (String)headerValue; + break; + case LENGTH: + if (!(headerValue instanceof Long)) { + if (headerValue == null) { + length = 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"); + } + length = (Long)headerValue; + break; + case TIME_ISO_8601: + if ((headerValue != null) && (!(headerValue instanceof Calendar))) { + throw new IllegalArgumentException("Time ISO 8601 must be a Calendar"); + } + isoTime = (Calendar)headerValue; + break; + case TIME_4_BYTE: + if ((headerValue != null) && (!(headerValue instanceof Calendar))) { + throw new IllegalArgumentException("Time 4 Byte must be a Calendar"); + } + byteTime = (Calendar)headerValue; + break; + case DESCRIPTION: + if ((headerValue != null) && (!(headerValue instanceof String))) { + throw new IllegalArgumentException("Description must be a String"); + } + description = (String)headerValue; + break; + case TARGET: + if (headerValue == null) { + target = null; + } else { + if (!(headerValue instanceof byte[])) { + throw new IllegalArgumentException("Target must be a byte array"); + } else { + target = new byte[((byte[])headerValue).length]; + System.arraycopy(headerValue, 0, target, 0, target.length); + } + } + break; + case HTTP: + if (headerValue == null) { + http = null; + } else { + if (!(headerValue instanceof byte[])) { + throw new IllegalArgumentException("HTTP must be a byte array"); + } else { + http = new byte[((byte[])headerValue).length]; + System.arraycopy(headerValue, 0, http, 0, http.length); + } + } + break; + case WHO: + if (headerValue == null) { + who = null; + } else { + if (!(headerValue instanceof byte[])) { + throw new IllegalArgumentException("WHO must be a byte array"); + } else { + who = new byte[((byte[])headerValue).length]; + System.arraycopy(headerValue, 0, who, 0, who.length); + } + } + break; + case OBJECT_CLASS: + if (headerValue == null) { + objectClass = null; + } else { + if (!(headerValue instanceof byte[])) { + throw new IllegalArgumentException("Object Class must be a byte array"); + } else { + objectClass = new byte[((byte[])headerValue).length]; + System.arraycopy(headerValue, 0, objectClass, 0, objectClass.length); + } + } + break; + case APPLICATION_PARAMETER: + if (headerValue == null) { + appParam = null; + } else { + if (!(headerValue instanceof byte[])) { + throw new IllegalArgumentException( + "Application Parameter must be a byte array"); + } else { + appParam = new byte[((byte[])headerValue).length]; + System.arraycopy(headerValue, 0, appParam, 0, appParam.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"); + } + unicodeUserDefined[headerID - 0x30] = (String)headerValue; + + break; + } + // Verify that it was not a byte sequence user defined value + if ((headerID >= 0x70) && (headerID <= 0x7F)) { + + if (headerValue == null) { + sequenceUserDefined[headerID - 0x70] = null; + } else { + if (!(headerValue instanceof byte[])) { + throw new IllegalArgumentException( + "Byte Sequence User Defined must be a byte array"); + } else { + sequenceUserDefined[headerID - 0x70] = new byte[((byte[])headerValue).length]; + System.arraycopy(headerValue, 0, sequenceUserDefined[headerID - 0x70], + 0, sequenceUserDefined[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"); + } + byteUserDefined[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) { + integerUserDefined[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"); + } + integerUserDefined[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 null if the + * header identifier specified is not part of this HeaderSet + * object + * + * @exception IllegalArgumentException if the headerID is not + * one defined in this interface or any of the user-defined headers + * + * @exception 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 count; + case NAME: + return name; + case TYPE: + return type; + case LENGTH: + return length; + case TIME_ISO_8601: + return isoTime; + case TIME_4_BYTE: + return byteTime; + case DESCRIPTION: + return description; + case TARGET: + return target; + case HTTP: + return http; + case WHO: + return who; + case OBJECT_CLASS: + return objectClass; + case APPLICATION_PARAMETER: + return appParam; + default: + // Verify that it was not a Unicode String user Defined + if ((headerID >= 0x30) && (headerID <= 0x3F)) { + return unicodeUserDefined[headerID - 0x30]; + } + // Verify that it was not a byte sequence user defined header + if ((headerID >= 0x70) && (headerID <= 0x7F)) { + return sequenceUserDefined[headerID - 0x70]; + } + // Verify that it was not a byte user defined header + if ((headerID >= 0xB0) && (headerID <= 0xBF)) { + return byteUserDefined[headerID - 0xB0]; + } + // Verify that it was not a itneger user defined header + if ((headerID >= 0xF0) && (headerID <= 0xFF)) { + return integerUserDefined[headerID - 0xF0]; + } + throw new IllegalArgumentException("Invalid Header Identifier"); + } + } + + /** + * Retrieves the list of headers that may be retrieved via the + * getHeader method that will not return null. + * 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 + * null if no headers are available + * + * @exception 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 (count != null) { + out.write(COUNT); + } + if (name != null) { + out.write(NAME); + } + if (type != null) { + out.write(TYPE); + } + if (length != null) { + out.write(LENGTH); + } + if (isoTime != null) { + out.write(TIME_ISO_8601); + } + if (byteTime != null) { + out.write(TIME_4_BYTE); + } + if (description != null) { + out.write(DESCRIPTION); + } + if (target != null) { + out.write(TARGET); + } + if (http != null) { + out.write(HTTP); + } + if (who != null) { + out.write(WHO); + } + if (appParam != null) { + out.write(APPLICATION_PARAMETER); + } + if (objectClass != null) { + out.write(OBJECT_CLASS); + } + + for (int i = 0x30; i < 0x40; i++) { + if (unicodeUserDefined[i - 0x30] != null) { + out.write(i); + } + } + + for (int i = 0x70; i < 0x80; i++) { + if (sequenceUserDefined[i - 0x70] != null) { + out.write(i); + } + } + + for (int i = 0xB0; i < 0xC0; i++) { + if (byteUserDefined[i - 0xB0] != null) { + out.write(i); + } + } + + for (int i = 0xF0; i < 0x100; i++) { + if (integerUserDefined[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 realm will + * be encoded based upon the default encoding scheme used by the + * implementation to encode strings. Therefore, the encoding scheme used + * to encode the realm is application dependent. + * + * @param realm a short description that describes what password to use; if + * null no realm will be sent in the authentication challenge + * header + * + * @param userID if true, a user ID is required in the reply; + * if false, no user ID is required + * + * @param access if true then full access will be granted if + * successful; if false then read-only access will be granted + * if successful + */ + public void createAuthenticationChallenge(String realm, boolean userID, boolean access) { + + try { + nonce = new byte[16]; + for (int i = 0; i < 16; i++) { + nonce[i] = (byte)random.nextInt(); + } + + authChall = OBEXHelper.computeAuthenticationChallenge(nonce, realm, access, userID); + } catch (IOException e) { + throw new RuntimeException(e.getMessage()); + } + } + + /** + * Returns the response code received from the server. Response codes + * are defined in the ResponseCodes class. + * + * @see ResponseCodes + * + * @return the response code retrieved from the server + * + * @exception IOException if an error occurred in the transport layer during + * the transaction; if this method is called on a HeaderSet + * object created by calling createHeaderSet() in a + * ClientSession 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/OBEXConstants.java b/obex/javax/obex/OBEXConstants.java new file mode 100644 index 0000000..2da5f30 --- /dev/null +++ b/obex/javax/obex/OBEXConstants.java @@ -0,0 +1,74 @@ +/* + * 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 implements the Operation interface. It will read + * and write data via puts and gets. + * + * @version 0.3 November 28, 2008 + */ +public class OBEXConstants { + /** + * Defines the OBEX CONTINUE response code. + *

+ * The value of OBEX_HTTP_CONTINUE is 0x90 (144). + */ + public static final int OBEX_HTTP_CONTINUE = 0x90; + + /** + * The maximum packet size for OBEX packets that this client can handle. + * At present, this must be changed for each port. + * + * OPTIMIZATION: The max packet size should be the Max incoming MTU minus + * OPTIMIZATION: L2CAP package headers and RFCOMM package headers. + * + * OPTIMIZATION: Retrieve the max incoming MTU from + * OPTIMIZATION: LocalDevice.getProperty(). + */ + /** android note + * set as 0xFFFE to match remote MPS + */ + //public static final byte[] MAX_PACKET_SIZE = {0x01, 0x00}; // To be removed + //public static final int MAX_PACKET_SIZE_INT = 667*6;//0x0100; + public static final int MAX_PACKET_SIZE_INT = 0xFFFE; + + /** + * The number of server parser threads that may be active at one time. + * This should be changed for each port. + * + * OPTIMIZATION: Retrieve this value by a native call to the KOSI layer. + */ + public static final int MAX_PARSER_THREADS = 5; + +} diff --git a/obex/javax/obex/OBEXHelper.java b/obex/javax/obex/OBEXHelper.java new file mode 100644 index 0000000..5c413af --- /dev/null +++ b/obex/javax/obex/OBEXHelper.java @@ -0,0 +1,1326 @@ +/* + * 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.*; +import java.util.*; + +/** + * This class defines a set of helper methods for the implementation of OBEX. + * + * @version 0.3 November 28, 2008 + */ +public class OBEXHelper { + + /** + * Creates new OBEXHelper + */ + private OBEXHelper() { + } + + private static final String TAG = "OBEXHelper"; + + /** + * Updates the HeaderSet with the headers received in the byte array + * provided. Invalid headers are ignored. + *

+ * 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. + * + * + * + * + * + * + *
Bits 8 and 7ValueDescription
000x00Null Terminated Unicode text, prefixed + * with 2 byte unsigned integer
010x40Byte Sequence, length prefixed with + * 2 byte unsigned integer
100x801 byte quantity
110xC04 byte quantity - transmitted in + * network byte order (high byte first
+ * 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 + * + * @exception 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 new RuntimeException("ISO8859_1 is not supported" + + e.getMessage()); + } + break; + + // This is the constant for the authentication challenge header + // This header does not have a constant defined in the Java + // OBEX API + case 0x4D: + headerImpl.authChall = new byte[length]; + System.arraycopy(headerArray, index, headerImpl.authChall, 0, + length); + break; + + // This is the constant for the authentication response header + // This header does not have a constant defined in the Java + // OBEX API + case 0x4E: + headerImpl.authResp = new byte[length]; + System + .arraycopy(headerArray, index, headerImpl.authResp, 0, + length); + break; + + /* + * These two case statements are for the body (0x48) + * and end of body (0x49) headers. + */ + case 0x48: + /* Fall Through */ + case 0x49: + 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 new RuntimeException("ISO8859_1 is not supported" + + e.getMessage()); + } catch (Exception e) { + throw new IOException( + "Time Header does not follow ISO 8601 standard"); + } + break; + + default: + try { + if ((headerID & 0xC0) == 0x00) { + headerImpl.setHeader(headerID, OBEXHelper.convertToUnicode( + value, true)); + } else { + headerImpl.setHeader(headerID, value); + } + } catch (Exception e) { + // Not a valid header so ignore + } + } + + 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 == 0xCB) { + headerImpl.connectionID = new byte[4]; + System.arraycopy(value, 0, headerImpl.connectionID, 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. + * + * OPTIMIZATION: Could use getHeaderList() to get the array of headers to + * OPTIMIZATION: include and then use the high two bits to determine the + * OPTIMIZATION: the type of the object and construct the byte array from + * OPTIMIZATION: that. This will make the size smaller. + * + * @param head the header used to construct the byte array + * + * @param nullOut true if the header should be set to + * null once it is added to the array or false + * 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.connectionID != null) && (headImpl.getHeader(HeaderSet.TARGET) == null)) { + + out.write((byte)0xCB); + out.write(headImpl.connectionID); + } + + // 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)(255 & (length >> 8)); + lengthArray[1] = (byte)(255 & 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 new RuntimeException("Unsupported Encoding Scheme: " + e.getMessage()); + } + + 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 new RuntimeException("UnsupportedEncodingException: " + e.getMessage()); + } + + 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.authChall != null) { + out.write((byte)0x4D); + length = headImpl.authChall.length + 3; + lengthArray[0] = (byte)(255 & (length >> 8)); + lengthArray[1] = (byte)(255 & length); + out.write(lengthArray); + out.write(headImpl.authChall); + if (nullOut) { + headImpl.authChall = null; + } + } + + // Add the authentication response header + if (headImpl.authResp != null) { + out.write((byte)0x4E); + length = headImpl.authResp.length + 3; + lengthArray[0] = (byte)(255 & (length >> 8)); + lengthArray[1] = (byte)(255 & length); + out.write(lengthArray); + out.write(headImpl.authResp); + if (nullOut) { + headImpl.authResp = 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); + } + + /** + * Computes the MD5 hash algorithm on the byte array provided. This + * implementation of MD5 is optimized for OBEX in that it provides for a + * single entry and exist and thus does not build up the input before + * applying the hash. + * + * OPTIMIZATION: Embedd MD5 algorithm in this method. This will make the + * OPTIMIZATION: size smaller. + * + * @param in the byte array to hash + * + * @return the MD5 hash of the byte array + */ + public static byte[] computeMD5Hash(byte[] in) { + + MD5Hash hash = new MD5Hash(in); + return hash.computeDigest(); + } + + /** + * 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 true then full access will be granted if + * successful; if false then read only access will be granted + * if successful + * + * @param userID if true, a user ID is required in the reply; + * if false, no user ID is required + * + * @exception IllegalArgumentException if the challenge is not 16 bytes + * long; if the realm can not be encoded in less then 255 bytes + * + * @exception 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; + } +} + +/** + * This class will complete an MD5 hash of the buffer provided. + */ +class MD5Hash { + + // Constants for MD5Transform routine. + private static final int A1 = 7; + + private static final int A2 = 12; + + private static final int A3 = 17; + + private static final int A4 = 22; + + private static final int B1 = 5; + + private static final int B2 = 9; + + private static final int B3 = 14; + + private static final int B4 = 20; + + private static final int C1 = 4; + + private static final int C2 = 11; + + private static final int C3 = 16; + + private static final int C4 = 23; + + private static final int D1 = 6; + + private static final int D2 = 10; + + private static final int D3 = 15; + + private static final int D4 = 21; + + private int state[]; + + private int count[]; + + /** + * Keeps the present digest + */ + private byte buffer[]; + + /** + * Completes a hash on the data provided. + * + * @param data the byte array to hash + */ + public MD5Hash(byte data[]) { + buffer = new byte[64]; + state = new int[4]; + count = new int[2]; + + state[0] = 0x67452301; + state[1] = 0xefcdab89; + state[2] = 0x98badcfe; + state[3] = 0x10325476; + + count[0] = 0; + count[1] = 0; + + MD5Update(data, 0, data.length); + } + + /** + * Updates the MD5 hash buffer. + * + * @param input byte array of data + * + * @param offset offset into the array to start the digest calculation + * + * @param inputLen the length of the byte array + */ + private void MD5Update(byte input[], int offset, int inputLen) { + int i, index, partLen; + + // Compute number of bytes mod 64 + index = (count[0] >>> 3) & 0x3F; + + // Update number of bits + int slen = inputLen << 3; + if ((count[0] += slen) < slen) { + count[1]++; + } + + count[1] += (inputLen >>> 29); + + partLen = 64 - index; + // Transform as many times as possible. + if (inputLen >= partLen) { + copy(index, input, offset, partLen); + + MD5Transform(buffer, 0, 0); + + for (i = partLen; i + 63 < inputLen; i += 64) { + MD5Transform(input, offset, i); + } + + index = 0; + } else { + i = 0; + } + + copy(index, input, i + offset, inputLen - i); + } + + /** + * Computes the final MD5 digest + * + * @return the MD5 digest + */ + public byte[] computeDigest() { + byte bits[]; + byte digest[]; + int index; + int length; + + // Save number of bits + bits = MD5Encode(count, 8); + + // Pad out to 56 mod 64. + index = ((count[0] >>> 3) & 0x3f); + length = (index < 56) ? (56 - index) : (120 - index); + + // build padding buffer. + if (length > 0) { + byte padding[] = new byte[length]; + padding[0] = (byte)0x80; + for (int i = 1; i < length; i++) { + padding[i] = 0; + } + + MD5Update(padding, 0, length); + } + + // Append length + MD5Update(bits, 0, 8); + + // Store state in digest + digest = MD5Encode(state, 16); + + return digest; + } + + /** + * Copies the input array into the local objects buffer. It performs a + * check to verify that data is available to copy. + * + * @param startIndex the offset into the local buffer to copy + * + * @param input the array to copy into the local buffer + * + * @param index the index to start copying from + * + * @param length the amount of data to copy + */ + private void copy(int startIndex, byte input[], int index, int length) { + if (index == input.length) + return; + System.arraycopy(input, index, buffer, startIndex, length); + } + + /** + * Rotates the bytes in x n times to the left. + * The rotation wraps the bits around. + * + * @param x the integer to rotate + * + * @param n the number of bits to rotate + * + * @return x rotated to the left n times + */ + private int rotate(int x, int n) { + return (((x) << (n)) | ((x) >>> (32 - (n)))); + } + + /** + * Completes a single step in the MD5 hash algorithm. + */ + private int MD5Step(int a, int b, int c, int d, int x, int s, int ac, int round) { + switch (round) { + case 1: + a += (((b) & (c)) | ((~b) & (d))) + (x) + (ac); + break; + case 2: + a += (((b) & (d)) | ((c) & (~d))) + (x) + (ac); + break; + case 3: + a += ((b) ^ (c) ^ (d)) + (x) + (ac); + break; + case 4: + a += ((c) ^ ((b) | (~d))) + (x) + (ac); + break; + } + a = rotate(a, (s)); + a += (b); + return a; + } + + /** + * Performs the core MD5 algorithm. This method will add the data provided + * and recompute the hash. + * + * @param data the block of data to add + * + * @param index the starting index into the data to start processing + * + * @param length the length of the byte array to process + */ + private void MD5Transform(byte data[], int index, int length) { + int a = state[0]; + int b = state[1]; + int c = state[2]; + int d = state[3]; + int x[]; + + x = MD5Decode(data, index, length, 64); + + // Round 1 + a = MD5Step(a, b, c, d, x[0], A1, 0xd76aa478, 1); + d = MD5Step(d, a, b, c, x[1], A2, 0xe8c7b756, 1); + c = MD5Step(c, d, a, b, x[2], A3, 0x242070db, 1); + b = MD5Step(b, c, d, a, x[3], A4, 0xc1bdceee, 1); + a = MD5Step(a, b, c, d, x[4], A1, 0xf57c0faf, 1); + d = MD5Step(d, a, b, c, x[5], A2, 0x4787c62a, 1); + c = MD5Step(c, d, a, b, x[6], A3, 0xa8304613, 1); + b = MD5Step(b, c, d, a, x[7], A4, 0xfd469501, 1); + a = MD5Step(a, b, c, d, x[8], A1, 0x698098d8, 1); + d = MD5Step(d, a, b, c, x[9], A2, 0x8b44f7af, 1); + c = MD5Step(c, d, a, b, x[10], A3, 0xffff5bb1, 1); + b = MD5Step(b, c, d, a, x[11], A4, 0x895cd7be, 1); + a = MD5Step(a, b, c, d, x[12], A1, 0x6b901122, 1); + d = MD5Step(d, a, b, c, x[13], A2, 0xfd987193, 1); + c = MD5Step(c, d, a, b, x[14], A3, 0xa679438e, 1); + b = MD5Step(b, c, d, a, x[15], A4, 0x49b40821, 1); + + // Round 2 + a = MD5Step(a, b, c, d, x[1], B1, 0xf61e2562, 2); + d = MD5Step(d, a, b, c, x[6], B2, 0xc040b340, 2); + c = MD5Step(c, d, a, b, x[11], B3, 0x265e5a51, 2); + b = MD5Step(b, c, d, a, x[0], B4, 0xe9b6c7aa, 2); + a = MD5Step(a, b, c, d, x[5], B1, 0xd62f105d, 2); + d = MD5Step(d, a, b, c, x[10], B2, 0x2441453, 2); + c = MD5Step(c, d, a, b, x[15], B3, 0xd8a1e681, 2); + b = MD5Step(b, c, d, a, x[4], B4, 0xe7d3fbc8, 2); + a = MD5Step(a, b, c, d, x[9], B1, 0x21e1cde6, 2); + d = MD5Step(d, a, b, c, x[14], B2, 0xc33707d6, 2); + c = MD5Step(c, d, a, b, x[3], B3, 0xf4d50d87, 2); + b = MD5Step(b, c, d, a, x[8], B4, 0x455a14ed, 2); + a = MD5Step(a, b, c, d, x[13], B1, 0xa9e3e905, 2); + d = MD5Step(d, a, b, c, x[2], B2, 0xfcefa3f8, 2); + c = MD5Step(c, d, a, b, x[7], B3, 0x676f02d9, 2); + b = MD5Step(b, c, d, a, x[12], B4, 0x8d2a4c8a, 2); + + // Round 3 + a = MD5Step(a, b, c, d, x[5], C1, 0xfffa3942, 3); + d = MD5Step(d, a, b, c, x[8], C2, 0x8771f681, 3); + c = MD5Step(c, d, a, b, x[11], C3, 0x6d9d6122, 3); + b = MD5Step(b, c, d, a, x[14], C4, 0xfde5380c, 3); + a = MD5Step(a, b, c, d, x[1], C1, 0xa4beea44, 3); + d = MD5Step(d, a, b, c, x[4], C2, 0x4bdecfa9, 3); + c = MD5Step(c, d, a, b, x[7], C3, 0xf6bb4b60, 3); + b = MD5Step(b, c, d, a, x[10], C4, 0xbebfbc70, 3); + a = MD5Step(a, b, c, d, x[13], C1, 0x289b7ec6, 3); + d = MD5Step(d, a, b, c, x[0], C2, 0xeaa127fa, 3); + c = MD5Step(c, d, a, b, x[3], C3, 0xd4ef3085, 3); + b = MD5Step(b, c, d, a, x[6], C4, 0x4881d05, 3); + a = MD5Step(a, b, c, d, x[9], C1, 0xd9d4d039, 3); + d = MD5Step(d, a, b, c, x[12], C2, 0xe6db99e5, 3); + c = MD5Step(c, d, a, b, x[15], C3, 0x1fa27cf8, 3); + b = MD5Step(b, c, d, a, x[2], C4, 0xc4ac5665, 3); + + // Round 4 + a = MD5Step(a, b, c, d, x[0], D1, 0xf4292244, 4); + d = MD5Step(d, a, b, c, x[7], D2, 0x432aff97, 4); + c = MD5Step(c, d, a, b, x[14], D3, 0xab9423a7, 4); + b = MD5Step(b, c, d, a, x[5], D4, 0xfc93a039, 4); + a = MD5Step(a, b, c, d, x[12], D1, 0x655b59c3, 4); + d = MD5Step(d, a, b, c, x[3], D2, 0x8f0ccc92, 4); + c = MD5Step(c, d, a, b, x[10], D3, 0xffeff47d, 4); + b = MD5Step(b, c, d, a, x[1], D4, 0x85845dd1, 4); + a = MD5Step(a, b, c, d, x[8], D1, 0x6fa87e4f, 4); + d = MD5Step(d, a, b, c, x[15], D2, 0xfe2ce6e0, 4); + c = MD5Step(c, d, a, b, x[6], D3, 0xa3014314, 4); + b = MD5Step(b, c, d, a, x[13], D4, 0x4e0811a1, 4); + a = MD5Step(a, b, c, d, x[4], D1, 0xf7537e82, 4); + d = MD5Step(d, a, b, c, x[11], D2, 0xbd3af235, 4); + c = MD5Step(c, d, a, b, x[2], D3, 0x2ad7d2bb, 4); + b = MD5Step(b, c, d, a, x[9], D4, 0xeb86d391, 4); + + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + } + + /** + * Encodes the input array. input must be a multiple of + * four. + * + * @param input the array to encode + * + * @param length the length of the array to encode + */ + private byte[] MD5Encode(int input[], int length) { + int i, j; + byte output[] = new byte[length]; + + for (i = 0, j = 0; j < length; i++, j += 4) { + output[j] = (byte)(input[i] & 0xff); + output[j + 1] = (byte)((input[i] >>> 8) & 0xff); + output[j + 2] = (byte)((input[i] >>> 16) & 0xff); + output[j + 3] = (byte)((input[i] >>> 24) & 0xff); + } + + return output; + } + + /** + * Decodes the array. The input array must be a multiple of + * four. Results are undefined if this is not true. + * + * @param input the array to decode + * + * @param index the starting position into the input array to start decoding + * + * @param posn + * + * @param length + */ + private int[] MD5Decode(byte input[], int offset, int posn, int length) { + int output[] = new int[length / 4]; + int i, j; + int limit = length + posn + offset; + + for (i = 0, j = offset + posn; j < limit; i++, j += 4) + output[i] = ((input[j]) & 0xff) | (((input[j + 1]) & 0xff) << 8) + | (((input[j + 2]) & 0xff) << 16) | (((input[j + 3]) & 0xff) << 24); + + return output; + } +} diff --git a/obex/javax/obex/ObexSession.java b/obex/javax/obex/ObexSession.java new file mode 100644 index 0000000..b3da89c --- /dev/null +++ b/obex/javax/obex/ObexSession.java @@ -0,0 +1,63 @@ +/* + * 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 ObexSession 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". + *

+ * + * This interface serves as the common super class for + * ClientSession and ServerSession. + * + * @version 0.3 November 28, 2008 + */ +public interface ObexSession { + + /* + public InputStream openInputStream() throws IOException; + + public DataInputStream openDataInputStream() throws IOException; + + public OutputStream openOutputStream() throws IOException; + + public DataOutputStream openDataOutputStream() throws IOException;*/ + + public void close() throws IOException; + +} diff --git a/obex/javax/obex/ObexTransport.java b/obex/javax/obex/ObexTransport.java new file mode 100644 index 0000000..b1734ff --- /dev/null +++ b/obex/javax/obex/ObexTransport.java @@ -0,0 +1,78 @@ +/* + * 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 ObexTransport 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 + * ObexConnection. Each kind of medium shall have its own + * implementation to wrap and follow the same interface. + *

+ * See section 1.2.2 of IrDA Object Exchange Protocol specification. + *

+ * 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. + * + * @version 0.3 November 28, 2008 + */ +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; + //ObexSession accept() throws IOException; + +} diff --git a/obex/javax/obex/Operation.java b/obex/javax/obex/Operation.java new file mode 100644 index 0000000..120852a --- /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 Operation 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 IOException is thrown on + * the next read from the input stream, write to the output stream, or call to + * sendHeaders(). + *

+ * Definition of methods inherited from ContentConnection + *

+ * getEncoding() will always return null. + *
getLength() will return the length specified by the OBEX Length + * header or -1 if the OBEX Length header was not included. + *
getType() will return the value specified in the OBEX Type + * header or null if the OBEX Type header was not included.
+ *

+ * How Headers are Handled + *

+ * As headers are received, they may be retrieved through the + * getReceivedHeaders() method. If new headers are set during the + * operation, the new headers will be sent during the next packet exchange. + *

+ * PUT example + *

+ *

+ * 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();
+ * }
+ * 
+ *

+ * GET example + *

+ *

+ * 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;
+ * }
+ * 
+ *

Client PUT Operation Flow

+ * For PUT operations, a call to close() the OutputStream + * returned from openOutputStream() or openDataOutputStream() + * 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 + * getResponseCode() will do an implicit close on the + * OutputStream and therefore signal that the request is done. + *

Client GET Operation Flow

+ * For GET operation, a call to openInputStream() or + * openDataInputStream() will signal that the request is done. (In OBEX + * terms, the final bit in the request will be set.) A call to + * getResponseCode() will cause an implicit close on the + * InputStream. No further data may be read at this point. + * + * @version 0.3 November 28, 2008 + */ +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 close() will be called by this method. + * + * @exception IOException if the transaction has already ended or if an + * OBEX server calls this method + */ + public 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 Operation + * + * @exception IOException if this Operation has been closed + */ + public HeaderSet getReceivedHeaders() 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 + * + * @exception IOException if this Operation has been closed + * or the transaction has ended and no further messages will be exchanged + * + * @exception IllegalArgumentException if headers was not created + * by a call to ServerRequestHandler.createHeaderSet() or + * ClientSession.createHeaderSet() + * + * @exception NullPointerException if headers if null + */ + public void sendHeaders(HeaderSet headers) throws IOException; + + /** + * Returns the response code received from the server. Response codes + * are defined in the ResponseCodes class. + * + * @see ResponseCodes + * + * @return the response code retrieved from the server + * + * @exception IOException if an error occurred in the transport layer during + * the transaction; if this object was created by an OBEX server + */ + public int getResponseCode() throws IOException; + + public String getEncoding(); + + public long getLength(); + + public String getType(); + + public InputStream openInputStream() throws IOException; + + public DataInputStream openDataInputStream() throws IOException; + + public OutputStream openOutputStream() throws IOException; + + public DataOutputStream openDataOutputStream() throws IOException; + + public void close() throws IOException; + + public int getMaxPacketSize(); +} diff --git a/obex/javax/obex/PasswordAuthentication.java b/obex/javax/obex/PasswordAuthentication.java new file mode 100644 index 0000000..82f9623 --- /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. + * + * @version 0.3 November 28, 2008 + */ +public class PasswordAuthentication { + + private byte[] userName; + + private byte[] password; + + /** + * Creates a new PasswordAuthentication with the user name + * and password provided. + * + * @param userName the user name to include; this may be null + * + * @param password the password to include in the response + * + * @exception NullPointerException if password is + * null + */ + public PasswordAuthentication(byte[] userName, byte[] password) { + if (userName != null) { + this.userName = new byte[userName.length]; + System.arraycopy(userName, 0, this.userName, 0, userName.length); + } + + this.password = new byte[password.length]; + System.arraycopy(password, 0, this.password, 0, password.length); + } + + /** + * Retrieves the user name that was specified in the constructor. + * The user name may be null. + * + * @return the user name + */ + public byte[] getUserName() { + return this.userName; + } + + /** + * Retrieves the password. + * + * @return the password + */ + public byte[] getPassword() { + return this.password; + } +} diff --git a/obex/javax/obex/PrivateInputStream.java b/obex/javax/obex/PrivateInputStream.java new file mode 100644 index 0000000..1b83bb0 --- /dev/null +++ b/obex/javax/obex/PrivateInputStream.java @@ -0,0 +1,196 @@ +/* + * 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.*; + +/** + * This object provides an input stream to the Operation objects used in this + * package. + * + * OPTIMIZATION: Include the other read() methods defined in InputStream. + * + * @version 0.3 November 28, 2008 + */ +public class PrivateInputStream extends InputStream { + + private BaseStream parent; + + private byte[] data; + + private int index; + + private boolean isOpen; + + public PrivateInputStream() { + + } + + /** + * Creates an input stream for the Operation to read from + * + * @param p the connection this input stream is for + */ + public PrivateInputStream(BaseStream p) { + parent = p; + data = new byte[0]; + index = 0; + isOpen = 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 + * + * @exception IOException if an I/O error occurs + */ + @Override + public synchronized int available() throws IOException { + ensureOpen(); + return data.length - index; + } + + /** + * 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 + * + * @exception IOException if an I/O error occurs + */ + @Override + public synchronized int read() throws IOException { + ensureOpen(); + while (data.length == index) { + if (!parent.continueOperation(true, true)) { + return -1; + } + } + return (data[index++] & 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 NullPointerException("buffer is null"); + } + if ((offset | length) < 0 || length > b.length - offset) { + throw new ArrayIndexOutOfBoundsException("index outof bound"); + } + ensureOpen(); + + int currentDataLength = data.length - index; + int remainReadLength = length; + int offset1 = offset; + int result = 0; + + while (currentDataLength <= remainReadLength) { + System.arraycopy(data, index, b, offset1, currentDataLength); + index += currentDataLength; + offset1 += currentDataLength; + result += currentDataLength; + remainReadLength -= currentDataLength; + + if (!parent.continueOperation(true, true)) { + return result == 0 ? -1 : result; + } + currentDataLength = data.length - index; + } + if (remainReadLength > 0) { + System.arraycopy(data, index, b, offset1, remainReadLength); + index += remainReadLength; + result += remainReadLength; + } + return result; + } + + /** + * Allows the OperationImpl 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) + (data.length - index); + byte[] temp = new byte[length]; + + System.arraycopy(data, index, temp, 0, data.length - index); + System.arraycopy(body, start, temp, data.length - index, body.length - start); + + data = temp; + index = 0; + notifyAll(); + } + + /** + * Verifies that this stream is open + * + * @exception IOException if the stream is not open + */ + private void ensureOpen() throws IOException { + parent.ensureOpen(); + if (!isOpen) { + throw new IOException("Input stream is closed"); + } + } + + /** + * Closes the input stream. If the input stream is already closed, do + * nothing. + * + * @exception IOException this will never happen + */ + @Override + public void close() throws IOException { + isOpen = false; + parent.streamClosed(true); + } +} diff --git a/obex/javax/obex/PrivateOutputStream.java b/obex/javax/obex/PrivateOutputStream.java new file mode 100644 index 0000000..03d2363 --- /dev/null +++ b/obex/javax/obex/PrivateOutputStream.java @@ -0,0 +1,197 @@ +/* + * 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.*; + +/** + * This object provides an output stream to the Operation objects used in this + * package. + * + * @version 0.3 November 28, 2008 + */ +class PrivateOutputStream extends OutputStream { + + private BaseStream parent; + + private ByteArrayOutputStream output; + + private boolean isClosed; + + private int maxPacketSize; + + /** + * Creates an empty PrivateOutputStream to write to. + * + * @param p the connection that this stream runs over + */ + public PrivateOutputStream(BaseStream p, int maxSize) { + parent = p; + output = new ByteArrayOutputStream(); + maxPacketSize = maxSize; + } + + /** + * Determines how many bytes have been written to the output stream. + * + * @return the number of bytes written to the output stream + */ + protected int size() { + return output.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 + * + * @exception IOException if an I/O error occurs + */ + @Override + public synchronized void write(int b) throws IOException { + ensureOpen(); + parent.ensureNotDone(); + output.write(b); + if (output.size() == maxPacketSize) { + parent.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 NullPointerException("buffer is null"); + } + if ((offset | count) < 0 || count > buffer.length - offset) { + throw new IndexOutOfBoundsException("index outof bound"); + } + + ensureOpen(); + parent.ensureNotDone(); + if (count < maxPacketSize) { + output.write(buffer, offset, count); + } else { + while (remainLength >= maxPacketSize) { + output.write(buffer, offset1, maxPacketSize); + offset1 += maxPacketSize; + remainLength = count - offset1; + parent.continueOperation(true, false); + } + if (remainLength > 0) { + output.write(buffer, offset1, remainLength); + } + } + } + + /** + * Reads the bytes that have been written to this stream. + * + * @return the byte array that is written + */ + protected synchronized byte[] readBytes() { + if (output.size() > 0) { + byte[] result = output.toByteArray(); + output.reset(); + return result; + } else { + return null; + } + } + + /** + * 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 + */ + protected synchronized byte[] readBytes(int size) { + if (output.size() > 0) { + byte[] temp = output.toByteArray(); + output.reset(); + byte[] result = new byte[size]; + System.arraycopy(temp, 0, result, 0, size); + if (temp.length != size) { + output.write(temp, size, temp.length - size); + } + return result; + } else { + return null; + } + } + + /** + * Verifies that this stream is open + * + * @exception IOException if the stream is not open + */ + private void ensureOpen() throws IOException { + parent.ensureOpen(); + if (isClosed) { + throw new IOException("Output stream is closed"); + } + } + + /** + * Closes the output stream. If the input stream is already closed, do + * nothing. + * + * @exception IOException this will never happen + */ + @Override + public void close() throws IOException { + isClosed = true; + parent.streamClosed(false); + } + + /** + * Determines if the connection is closed + * + * @return true if the connection is closed; + * false if the connection is open + */ + protected boolean isClosed() { + return isClosed; + } +} diff --git a/obex/javax/obex/ResponseCodes.java b/obex/javax/obex/ResponseCodes.java new file mode 100644 index 0000000..7496738 --- /dev/null +++ b/obex/javax/obex/ResponseCodes.java @@ -0,0 +1,324 @@ +/* + * 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 ResponseCodes class contains the list of valid + * response codes a server may send to a client. + *

+ * IMPORTANT NOTE + *

+ * It is important to note that these values are different then those defined + * in javax.microedition.io.HttpConnection. The values in this + * interface represent the values defined in the IrOBEX specification. The + * values in javax.microedition.io.HttpConnection represent values + * defined in the HTTP specification. + *

+ * OBEX_DATABASE_FULL and OBEX_DATABASE_LOCKED require + * further description since they are not defined in HTTP. The server will send + * an OBEX_DATABASE_FULL message when the client requests that + * something be placed into a database but the database is full (cannot take + * more data). OBEX_DATABASE_LOCKED will be returned when the + * client wishes to access a database, database table, or database record that + * has been locked. + * + * @version 0.3 November 28, 2008 + */ +public class ResponseCodes { + + /** + * Defines the OBEX SUCCESS response code. + *

+ * The value of OBEX_HTTP_OK is 0xA0 (160). + */ + public static final int OBEX_HTTP_OK = 0xA0; + + /** + * Defines the OBEX CREATED response code. + *

+ * The value of OBEX_HTTP_CREATED is 0xA1 (161). + */ + public static final int OBEX_HTTP_CREATED = 0xA1; + + /** + * Defines the OBEX ACCEPTED response code. + *

+ * The value of OBEX_HTTP_ACCEPTED is 0xA2 (162). + */ + public static final int OBEX_HTTP_ACCEPTED = 0xA2; + + /** + * Defines the OBEX NON-AUTHORITATIVE INFORMATION response code. + *

+ * The value of OBEX_HTTP_NOT_AUTHORITATIVE is 0xA3 (163). + */ + public static final int OBEX_HTTP_NOT_AUTHORITATIVE = 0xA3; + + /** + * Defines the OBEX NO CONTENT response code. + *

+ * The value of OBEX_HTTP_NO_CONTENT is 0xA4 (164). + */ + public static final int OBEX_HTTP_NO_CONTENT = 0xA4; + + /** + * Defines the OBEX RESET CONTENT response code. + *

+ * The value of OBEX_HTTP_RESET is 0xA5 (165). + */ + public static final int OBEX_HTTP_RESET = 0xA5; + + /** + * Defines the OBEX PARTIAL CONTENT response code. + *

+ * The value of OBEX_HTTP_PARTIAL is 0xA6 (166). + */ + public static final int OBEX_HTTP_PARTIAL = 0xA6; + + /** + * Defines the OBEX MULTIPLE_CHOICES response code. + *

+ * The value of OBEX_HTTP_MULT_CHOICE is 0xB0 (176). + */ + public static final int OBEX_HTTP_MULT_CHOICE = 0xB0; + + /** + * Defines the OBEX MOVED PERMANENTLY response code. + *

+ * The value of OBEX_HTTP_MOVED_PERM is 0xB1 (177). + */ + public static final int OBEX_HTTP_MOVED_PERM = 0xB1; + + /** + * Defines the OBEX MOVED TEMPORARILY response code. + *

+ * The value of OBEX_HTTP_MOVED_TEMP is 0xB2 (178). + */ + public static final int OBEX_HTTP_MOVED_TEMP = 0xB2; + + /** + * Defines the OBEX SEE OTHER response code. + *

+ * The value of OBEX_HTTP_SEE_OTHER is 0xB3 (179). + */ + public static final int OBEX_HTTP_SEE_OTHER = 0xB3; + + /** + * Defines the OBEX NOT MODIFIED response code. + *

+ * The value of OBEX_HTTP_NOT_MODIFIED is 0xB4 (180). + */ + public static final int OBEX_HTTP_NOT_MODIFIED = 0xB4; + + /** + * Defines the OBEX USE PROXY response code. + *

+ * The value of OBEX_HTTP_USE_PROXY is 0xB5 (181). + */ + public static final int OBEX_HTTP_USE_PROXY = 0xB5; + + /** + * Defines the OBEX BAD REQUEST response code. + *

+ * The value of OBEX_HTTP_BAD_REQUEST is 0xC0 (192). + */ + public static final int OBEX_HTTP_BAD_REQUEST = 0xC0; + + /** + * Defines the OBEX UNAUTHORIZED response code. + *

+ * The value of OBEX_HTTP_UNAUTHORIZED is 0xC1 (193). + */ + public static final int OBEX_HTTP_UNAUTHORIZED = 0xC1; + + /** + * Defines the OBEX PAYMENT REQUIRED response code. + *

+ * The value of OBEX_HTTP_PAYMENT_REQUIRED is 0xC2 (194). + */ + public static final int OBEX_HTTP_PAYMENT_REQUIRED = 0xC2; + + /** + * Defines the OBEX FORBIDDEN response code. + *

+ * The value of OBEX_HTTP_FORBIDDEN is 0xC3 (195). + */ + public static final int OBEX_HTTP_FORBIDDEN = 0xC3; + + /** + * Defines the OBEX NOT FOUND response code. + *

+ * The value of OBEX_HTTP_NOT_FOUND is 0xC4 (196). + */ + public static final int OBEX_HTTP_NOT_FOUND = 0xC4; + + /** + * Defines the OBEX METHOD NOT ALLOWED response code. + *

+ * The value of OBEX_HTTP_BAD_METHOD is 0xC5 (197). + */ + public static final int OBEX_HTTP_BAD_METHOD = 0xC5; + + /** + * Defines the OBEX NOT ACCEPTABLE response code. + *

+ * The value of OBEX_HTTP_NOT_ACCEPTABLE is 0xC6 (198). + */ + public static final int OBEX_HTTP_NOT_ACCEPTABLE = 0xC6; + + /** + * Defines the OBEX PROXY AUTHENTICATION REQUIRED response code. + *

+ * The value of OBEX_HTTP_PROXY_AUTH is 0xC7 (199). + */ + public static final int OBEX_HTTP_PROXY_AUTH = 0xC7; + + /** + * Defines the OBEX REQUEST TIME OUT response code. + *

+ * The value of OBEX_HTTP_TIMEOUT is 0xC8 (200). + */ + public static final int OBEX_HTTP_TIMEOUT = 0xC8; + + /** + * Defines the OBEX METHOD CONFLICT response code. + *

+ * The value of OBEX_HTTP_CONFLICT is 0xC9 (201). + */ + public static final int OBEX_HTTP_CONFLICT = 0xC9; + + /** + * Defines the OBEX METHOD GONE response code. + *

+ * The value of OBEX_HTTP_GONE is 0xCA (202). + */ + public static final int OBEX_HTTP_GONE = 0xCA; + + /** + * Defines the OBEX METHOD LENGTH REQUIRED response code. + *

+ * The value of OBEX_HTTP_LENGTH_REQUIRED is 0xCB (203). + */ + public static final int OBEX_HTTP_LENGTH_REQUIRED = 0xCB; + + /** + * Defines the OBEX PRECONDITION FAILED response code. + *

+ * The value of OBEX_HTTP_PRECON_FAILED is 0xCC (204). + */ + public static final int OBEX_HTTP_PRECON_FAILED = 0xCC; + + /** + * Defines the OBEX REQUESTED ENTITY TOO LARGE response code. + *

+ * The value of OBEX_HTTP_ENTITY_TOO_LARGE is 0xCD (205). + */ + public static final int OBEX_HTTP_ENTITY_TOO_LARGE = 0xCD; + + /** + * Defines the OBEX REQUESTED URL TOO LARGE response code. + *

+ * The value of OBEX_HTTP_REQ_TOO_LARGE is 0xCE (206). + */ + public static final int OBEX_HTTP_REQ_TOO_LARGE = 0xCE; + + /** + * Defines the OBEX UNSUPPORTED MEDIA TYPE response code. + *

+ * The value of OBEX_HTTP_UNSUPPORTED_TYPE is 0xCF (207). + */ + public static final int OBEX_HTTP_UNSUPPORTED_TYPE = 0xCF; + + /** + * Defines the OBEX INTERNAL SERVER ERROR response code. + *

+ * The value of OBEX_HTTP_INTERNAL_ERROR is 0xD0 (208). + */ + public static final int OBEX_HTTP_INTERNAL_ERROR = 0xD0; + + /** + * Defines the OBEX NOT IMPLEMENTED response code. + *

+ * The value of OBEX_HTTP_NOT_IMPLEMENTED is 0xD1 (209). + */ + public static final int OBEX_HTTP_NOT_IMPLEMENTED = 0xD1; + + /** + * Defines the OBEX BAD GATEWAY response code. + *

+ * The value of OBEX_HTTP_BAD_GATEWAY is 0xD2 (210). + */ + public static final int OBEX_HTTP_BAD_GATEWAY = 0xD2; + + /** + * Defines the OBEX SERVICE UNAVAILABLE response code. + *

+ * The value of OBEX_HTTP_UNAVAILABLE is 0xD3 (211). + */ + public static final int OBEX_HTTP_UNAVAILABLE = 0xD3; + + /** + * Defines the OBEX GATEWAY TIMEOUT response code. + *

+ * The value of OBEX_HTTP_GATEWAY_TIMEOUT is 0xD4 (212). + */ + public static final int OBEX_HTTP_GATEWAY_TIMEOUT = 0xD4; + + /** + * Defines the OBEX HTTP VERSION NOT SUPPORTED response code. + *

+ * The value of OBEX_HTTP_VERSION is 0xD5 (213). + */ + public static final int OBEX_HTTP_VERSION = 0xD5; + + /** + * Defines the OBEX DATABASE FULL response code. + *

+ * The value of OBEX_DATABASE_FULL is 0xE0 (224). + */ + public static final int OBEX_DATABASE_FULL = 0xE0; + + /** + * Defines the OBEX DATABASE LOCKED response code. + *

+ * The value of OBEX_DATABASE_LOCKED is 0xE1 (225). + */ + public static final int OBEX_DATABASE_LOCKED = 0xE1; + + /** + * Constructor does nothing. + */ + private ResponseCodes() { + throw new RuntimeException("Not Implemented! Used to compile Code"); + } +} diff --git a/obex/javax/obex/ServerOperation.java b/obex/javax/obex/ServerOperation.java new file mode 100644 index 0000000..1b9c4e9 --- /dev/null +++ b/obex/javax/obex/ServerOperation.java @@ -0,0 +1,772 @@ +/* + * Copyright (c) 2008-2009, Motorola, Inc. + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * - Neither the name of the Motorola, Inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package javax.obex; + +import java.io.IOException; +import java.io.InputStream; +import java.io.DataInputStream; +import java.io.OutputStream; +import java.io.DataOutputStream; +import java.io.ByteArrayOutputStream; + +/** + * This class implements the Operation interface for server side connections. + *

+ * Request Codes + * There are four different request codes that are in this class. 0x02 is a + * PUT request that signals that the request is not complete and requires an + * additional OBEX packet. 0x82 is a PUT request that says that request is + * complete. In this case, the server can begin sending the response. The + * 0x03 is a GET request that signals that the request is not finished. When + * the server receives a 0x83, the client is signalling the server that it is + * done with its request. + * + * OPTIMIZATION: Extend the ClientOperation and reuse the methods defined + * OPTIMIZATION: in that class. + * + * @version 0.3 November 28, 2008 + */ +public class ServerOperation implements Operation, BaseStream { + + private InputStream socketInput; + + private ServerSession parent; + + private int maxPacketLength; + + private int responseSize; + + private boolean isClosed; + + boolean finalBitSet; + + private boolean endOfBody; // This variable defines when the end of body + + // header has been received. When this header + // is received, no further body data will be + // received from the client + private boolean isGet; + + boolean isAborted; + + HeaderSet requestHeaders; + + HeaderSet replyHeaders; + + PrivateInputStream privateInput; + + private PrivateOutputStream privateOutput; + + private String exceptionString; + + private ServerRequestHandler listener; + + private boolean outputStreamOpened; + + private boolean requestFinished; + + private static int BASE_PACKET_LENGTH = 3; + + private static final String TAG = "ServerOperation"; + + private boolean isHasBody; + + /** + * Creates new PutServerOperation + * + * @param p the parent that created this object + * + * @param in the input stream to read from + * + * @param out the output stream to write to + * + * @param request the initial request that was received from the client + * + * @param maxSize the max packet size that the client will accept + * + * @param listen the listener that is responding to the request + * + * @exception IOException if an IO error occurs + */ + public ServerOperation(ServerSession p, InputStream in, int request, int maxSize, + ServerRequestHandler listen) throws IOException { + + isAborted = false; + parent = p; + socketInput = in; + maxPacketLength = maxSize; + isClosed = false; + requestHeaders = new HeaderSet(); + replyHeaders = new HeaderSet(); + privateInput = new PrivateInputStream(this); + endOfBody = false; + responseSize = 3; + listener = listen; + requestFinished = false; + outputStreamOpened = false; + isHasBody = false; + int bytesReceived; + + /* + * Determine if this is a PUT request + */ + if ((request == 0x02) || (request == 0x82)) { + /* + * It is a PUT request. + */ + isGet = false; + } else { + /* + * It is a GET request. + */ + isGet = true; + } + + /* + * Determine if the final bit is set + */ + if ((request & 0x80) == 0) { + finalBitSet = false; + } else { + finalBitSet = true; + requestFinished = true; + } + + int length = in.read(); + length = (length << 8) + in.read(); + + /* + * Determine if the packet length is larger than this device can receive + */ + if (length > OBEXConstants.MAX_PACKET_SIZE_INT) { + parent.sendResponse(ResponseCodes.OBEX_HTTP_REQ_TOO_LARGE, null); + throw new IOException("Packet received was too large"); + } + + /* + * Determine if any headers were sent in the initial request + */ + if (length > 3) { + byte[] data = new byte[length - 3]; + bytesReceived = in.read(data); + + while (bytesReceived != data.length) { + bytesReceived += in.read(data, bytesReceived, data.length - bytesReceived); + } + + byte[] body = OBEXHelper.updateHeaderSet(requestHeaders, data); + + if (body != null) { + isHasBody = true; + } + + if (requestHeaders.connectionID != null) { + listener.setConnectionID(OBEXHelper.convertToLong(requestHeaders.connectionID)); + } else { + listener.setConnectionID(0); + } + + if (requestHeaders.authResp != null) { + if (!parent.handleAuthResp(requestHeaders.authResp)) { + exceptionString = "Authentication Failed"; + parent.sendResponse(ResponseCodes.OBEX_HTTP_UNAUTHORIZED, null); + isClosed = true; + requestHeaders.authResp = null; + return; + } + } + + if (requestHeaders.authChall != null) { + parent.handleAuthChall(requestHeaders); + // send the authResp to the client + replyHeaders.authResp = new byte[requestHeaders.authResp.length]; + System.arraycopy(requestHeaders.authResp, 0, replyHeaders.authResp, 0, + replyHeaders.authResp.length); + requestHeaders.authResp = null; + requestHeaders.authChall = null; + + } + + if (body != null) { + /* + * 0x49 is the end of body header. This signifies that no more + * body data will be sent from the client + */ + if (body[0] == 0x49) { + endOfBody = true; + } + //privateInput.writeBytes(body, body.length); + //byte [] body_tmp = new byte[body.length-1]; + //System.arraycopy(body,1,body_tmp,0,body.length-1); + //privateInput.writeBytes(body_tmp, body.length-1); + privateInput.writeBytes(body, 1); + } else { + while ((!isGet) && (!finalBitSet)) { + sendReply(OBEXConstants.OBEX_HTTP_CONTINUE); + if (privateInput.available() > 0) { + break; + } + } + }// if (body != null) + + }// if (length > 3) + + while ((!isGet) && (!finalBitSet) && (privateInput.available() == 0)) { + sendReply(OBEXConstants.OBEX_HTTP_CONTINUE); + if (privateInput.available() > 0) { + break; + } + } + + // wait for get request finished !!!! + while (isGet && !finalBitSet) { + sendReply(OBEXConstants.OBEX_HTTP_CONTINUE); + } + if (finalBitSet && isGet) { + requestFinished = true; + } + } + + public synchronized boolean isValidBody() { + return isHasBody; + } + + /** + * Determines if the operation should continue or should wait. If it + * should continue, this method will continue the operation. + * + * @param sendEmpty if true then this will continue the + * operation even if no headers will be sent; if false then + * this method will only continue the operation if there are headers to + * send + * @param isStream iftrue the stream is input stream or + * is outputstream + * @return true if the operation was completed; + * false if no operation took place + */ + public synchronized boolean continueOperation(boolean sendEmpty, boolean inStream) + throws IOException { + if (!isGet) { + if (!finalBitSet) { + if (sendEmpty) { + sendReply(OBEXConstants.OBEX_HTTP_CONTINUE); + return true; + } else { + if ((responseSize > 3) || (privateOutput.size() > 0)) { + sendReply(OBEXConstants.OBEX_HTTP_CONTINUE); + return true; + } else { + return false; + } + } + } else { + return false; + } + } else { + sendReply(OBEXConstants.OBEX_HTTP_CONTINUE); + return true; + } + } + + /** + * Sends a reply to the client. If the reply is a OBEX_HTTP_CONTINUE, it + * will wait for a response from the client before ending. + * + * @param type the response code to send back to the client + * + * @return true if the final bit was not set on the reply; + * false if no reply was received because the operation ended, + * an abort was received, or the final bit was set in the reply + * + * @exception IOException if an IO error occurs + */ + protected synchronized boolean sendReply(int type) throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + int bytesReceived; + + long id = listener.getConnectionID(); + if (id == -1) { + replyHeaders.connectionID = null; + } else { + replyHeaders.connectionID = OBEXHelper.convertToByteArray(id); + } + + byte[] headerArray = OBEXHelper.createHeader(replyHeaders, true); + int bodyLength = -1; + int orginalBodyLength = -1; + + if (privateOutput != null) { + bodyLength = privateOutput.size(); + orginalBodyLength = bodyLength; + } + + if ((BASE_PACKET_LENGTH + headerArray.length) > maxPacketLength) { + + int end = 0; + int start = 0; + + while (end != headerArray.length) { + end = OBEXHelper.findHeaderEnd(headerArray, start, maxPacketLength + - BASE_PACKET_LENGTH); + if (end == -1) { + + isClosed = true; + + if (privateInput != null) { + privateInput.close(); + } + + if (privateOutput != null) { + privateOutput.close(); + } + parent.sendResponse(ResponseCodes.OBEX_HTTP_INTERNAL_ERROR, null); + throw new IOException("OBEX Packet exceeds max packet size"); + } + byte[] sendHeader = new byte[end - start]; + System.arraycopy(headerArray, start, sendHeader, 0, sendHeader.length); + + parent.sendResponse(type, sendHeader); + start = end; + } + + if (bodyLength > 0) { + return true; + } else { + return false; + } + + } else { + out.write(headerArray); + } + + /* + * Determine if there is space to add a body reply. First, we need to + * verify that the client is finished sending the request. Next, there + * needs to be enough space to send the headers already defined along + * with the reply header (3 bytes) and the body header identifier + * (3 bytes). + */ + + /* if ((finalBitSet) && + ((bodyLength + 6 + headerArray.length) > maxPacketLength)) { + + exceptionString = "Header larger then can be sent in a packet"; + isClosed = true; + privateInput.close(); + if (privateOutput != null) { + privateOutput.close(); + } + parent.sendResponse(ResponseCodes.OBEX_HTTP_INTERNAL_ERROR, + null); + throw new IOException("OBEX Packet exceeds max packet size"); + } + */ + + if ((finalBitSet) || (headerArray.length < (maxPacketLength - 20))) { + if (bodyLength > 0) { + /* + * Determine if I can send the whole body or just part of + * the body. Remember that there is the 3 bytes for the + * response message and 3 bytes for the header ID and length + */ + if (bodyLength > (maxPacketLength - headerArray.length - 6)) { + bodyLength = maxPacketLength - headerArray.length - 6; + } + + byte[] body = privateOutput.readBytes(bodyLength); + + /* + * Since this is a put request if the final bit is set or + * the output stream is closed we need to send the 0x49 + * (End of Body) otherwise, we need to send 0x48 (Body) + */ + if ((finalBitSet) || (privateOutput.isClosed())) { + out.write(0x49); + } else { + out.write(0x48); + } + + bodyLength += 3; + out.write((byte)(bodyLength >> 8)); + out.write((byte)bodyLength); + out.write(body); + } + } + + if ((finalBitSet) && (type == ResponseCodes.OBEX_HTTP_OK) && (orginalBodyLength <= 0)) { + out.write(0x49); + orginalBodyLength = 3; + out.write((byte)(orginalBodyLength >> 8)); + out.write((byte)orginalBodyLength); + + } + + responseSize = 3; + parent.sendResponse(type, out.toByteArray()); + + if (type == OBEXConstants.OBEX_HTTP_CONTINUE) { + int headerID = socketInput.read(); + int length = socketInput.read(); + length = (length << 8) + socketInput.read(); + if ((headerID != 0x02) && (headerID != 0x82) && (headerID != 0x03) + && (headerID != 0x83)) { + + if (length > 3) { + byte[] temp = new byte[length]; + bytesReceived = socketInput.read(temp); + + while (bytesReceived != length) { + bytesReceived += socketInput.read(temp, bytesReceived, length + - bytesReceived); + } + } + + /* + * Determine if an ABORT was sent as the reply + */ + if (headerID == 0xFF) { + parent.sendResponse(ResponseCodes.OBEX_HTTP_OK, null); + isClosed = true; + isAborted = true; + exceptionString = "Abort Received"; + throw new IOException("Abort Received"); + } else { + parent.sendResponse(ResponseCodes.OBEX_HTTP_BAD_REQUEST, null); + isClosed = true; + exceptionString = "Bad Request Received"; + throw new IOException("Bad Request Received"); + } + } else { + + if ((headerID == 0x82) || (headerID == 0x83)) { + finalBitSet = true; + } + + /* + * Determine if the packet length is larger then this device can receive + */ + if (length > OBEXConstants.MAX_PACKET_SIZE_INT) { + parent.sendResponse(ResponseCodes.OBEX_HTTP_REQ_TOO_LARGE, null); + throw new IOException("Packet received was too large"); + } + + /* + * Determine if any headers were sent in the initial request + */ + if (length > 3) { + byte[] data = new byte[length - 3]; + bytesReceived = socketInput.read(data); + + while (bytesReceived != data.length) { + bytesReceived += socketInput.read(data, bytesReceived, data.length + - bytesReceived); + } + byte[] body = OBEXHelper.updateHeaderSet(requestHeaders, data); + if (body != null) { + isHasBody = true; + } + if (requestHeaders.connectionID != null) { + listener.setConnectionID(OBEXHelper + .convertToLong(requestHeaders.connectionID)); + } else { + listener.setConnectionID(1); + } + + if (requestHeaders.authResp != null) { + if (!parent.handleAuthResp(requestHeaders.authResp)) { + exceptionString = "Authentication Failed"; + parent.sendResponse(ResponseCodes.OBEX_HTTP_UNAUTHORIZED, null); + isClosed = true; + requestHeaders.authResp = null; + return false; + } + requestHeaders.authResp = null; + } + + if (requestHeaders.authChall != null) { + parent.handleAuthChall(requestHeaders); + // send the auhtResp to the client + replyHeaders.authResp = new byte[requestHeaders.authResp.length]; + System.arraycopy(requestHeaders.authResp, 0, replyHeaders.authResp, 0, + replyHeaders.authResp.length); + requestHeaders.authResp = null; + requestHeaders.authChall = null; + } + + if (body != null) { + if (body[0] == 0x49) { + endOfBody = true; + } + + /*byte [] body_tmp = new byte[body.length-1]; + System.arraycopy(body,1,body_tmp,0,body.length-1); + privateInput.writeBytes(body_tmp, body.length-1);*/ + privateInput.writeBytes(body, 1); + + } + } + } + return true; + } else { + return false; + } + } + + /** + * Sends an ABORT message to the server. By calling this method, the + * corresponding input and output streams will be closed along with this + * object. + * + * @exception IOException if the transaction has already ended or if an + * OBEX server called this method + */ + public void abort() throws IOException { + throw new IOException("Called from a server"); + } + + /** + * Returns the headers that have been received during the operation. + * Modifying the object returned has no effect on the headers that are + * sent or retrieved. + * + * @return the headers received during this Operation + * + * @exception IOException if this Operation has been closed + */ + public HeaderSet getReceivedHeaders() throws IOException { + ensureOpen(); + return requestHeaders; + } + + /** + * Specifies the headers that should be sent in the next OBEX message that + * is sent. + * + * @param headers the headers to send in the next message + * + * @exception IOException if this Operation has been closed + * or the transaction has ended and no further messages will be exchanged + * + * @exception IllegalArgumentException if headers was not created + * by a call to ServerRequestHandler.createHeaderSet() + */ + public void sendHeaders(HeaderSet headers) throws IOException { + ensureOpen(); + + if (headers == null) { + throw new NullPointerException("Headers may not be null"); + } + + int[] headerList = headers.getHeaderList(); + if (headerList != null) { + for (int i = 0; i < headerList.length; i++) { + replyHeaders.setHeader(headerList[i], headers.getHeader(headerList[i])); + } + + } + } + + /** + * Retrieves the response code retrieved from the server. Response codes + * are defined in the ResponseCodes interface. + * + * @return the response code retrieved from the server + * + * @exception IOException if an error occurred in the transport layer during + * the transaction; if this method is called on a HeaderSet + * object created by calling createHeaderSet in a + * ClientSession object; if this is called from a server + */ + public int getResponseCode() throws IOException { + throw new IOException("Called from a server"); + } + + /** + * Always returns null + * + * @return null + */ + 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 + * null if not known + */ + public String getType() { + try { + return (String)requestHeaders.getHeader(HeaderSet.TYPE); + } catch (IOException e) { + return null; + } + } + + /** + * Returns the length of the content which is being provided. E.g. if the + * connection is via HTTP, then the value of the content-length header + * field is returned. + * + * @return the content length of the resource that this connection's URL + * references, or -1 if the content length is not known + */ + public long getLength() { + try { + Long temp = (Long)requestHeaders.getHeader(HeaderSet.LENGTH); + + if (temp == null) { + return -1; + } else { + return temp.longValue(); + } + } catch (IOException e) { + return -1; + } + } + + public int getMaxPacketSize() { + return maxPacketLength - 6; + } + + /** + * Open and return an input stream for a connection. + * + * @return an input stream + * + * @exception IOException if an I/O error occurs + */ + public InputStream openInputStream() throws IOException { + ensureOpen(); + return privateInput; + } + + /** + * Open and return a data input stream for a connection. + * + * @return an input stream + * + * @exception IOException if an I/O error occurs + */ + public DataInputStream openDataInputStream() throws IOException { + return new DataInputStream(openInputStream()); + } + + /** + * Open and return an output stream for a connection. + * + * @return an output stream + * + * @exception IOException if an I/O error occurs + */ + public OutputStream openOutputStream() throws IOException { + ensureOpen(); + + if (outputStreamOpened) + throw new IOException("no more input streams available, stream already opened"); + + if (!requestFinished) + throw new IOException("no output streams available ,request not finished"); + + if (privateOutput == null) { + privateOutput = new PrivateOutputStream(this, maxPacketLength - 6); + } + outputStreamOpened = true; + return privateOutput; + } + + /** + * Open and return a data output stream for a connection. + * + * @return an output stream + * + * @exception IOException if an I/O error occurs + */ + public DataOutputStream openDataOutputStream() throws IOException { + return new DataOutputStream(openOutputStream()); + } + + /** + * Closes the connection and ends the transaction + * + * @exception IOException if the operation has already ended or is closed + */ + public void close() throws IOException { + ensureOpen(); + isClosed = true; + } + + /** + * Verifies that the connection is open and no exceptions should be thrown. + * + * @exception IOException if an exception needs to be thrown + */ + public void ensureOpen() throws IOException { + if (exceptionString != null) { + throw new IOException(exceptionString); + } + if (isClosed) { + throw new IOException("Operation has already ended"); + } + } + + /** + * Verifies that additional information may be sent. In other words, the + * operation is not done. + *

+ * Included to implement the BaseStream interface only. It does not do + * anything on the server side since the operation of the Operation object + * is not done until after the handler returns from its method. + * + * @exception IOException if the operation is completed + */ + public void ensureNotDone() throws IOException { + } + + /** + * Called when the output or input stream is closed. It does not do + * anything on the server side since the operation of the Operation object + * is not done until after the handler returns from its method. + * + * @param inStream true if the input stream is closed; + * false if the output stream is closed + * + * @exception 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..4e7f5b4 --- /dev/null +++ b/obex/javax/obex/ServerRequestHandler.java @@ -0,0 +1,288 @@ +/* + * 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 ServerRequestHandler class defines an event + * listener that will respond to OBEX requests made to the server. + *

+ * The onConnect(), onSetPath(), onDelete(), + * onGet(), + * and onPut() methods may return any response code defined + * in the ResponseCodes class except for + * OBEX_HTTP_CONTINUE. If OBEX_HTTP_CONTINUE or + * a value not defined in the ResponseCodes class is returned, + * the server implementation will send an OBEX_HTTP_INTERNAL_ERROR + * response to the client. + *

+ * Connection ID and Target Headers + *

+ * 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 HeaderSet 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. + *

+ * CREATE-EMPTY Requests + *

+ * A CREATE-EMPTY request allows clients to create empty objects on the server. + * When a CREATE-EMPTY request is received, the onPut() method + * will be called by the implementation. To differentiate between a normal + * PUT request and a CREATE-EMPTY request, an application must open the + * InputStream from the Operation object passed + * to the onPut() method. For a PUT request, the application + * will be able to read Body data from this InputStream. For + * a CREATE-EMPTY request, there will be no Body data to read. Therefore, + * a call to InputStream.read() will return -1. + * + * @version 0.3 November 28, 2008 + */ +public class ServerRequestHandler { + + private long connectionID; + + /** + * Creates a ServerRequestHandler. + */ + protected ServerRequestHandler() { + /* + * A connection ID of -1 implies there is no conenction ID + */ + connectionID = -1; + } + + /** + * Creates a HeaderSet object that may be used in put and get + * operations. + * + * @return the HeaderSet object to use in put and get operations + */ + public final HeaderSet createHeaderSet() { + return new HeaderSet(); + } + + /** + * Sets the connection ID header to include in the reply packets. + * + * @param id the connection ID to use; -1 if no connection ID should be + * sent + * + * @exception IllegalArgumentException if id is not in the + * range -1 to 232-1 + */ + public void setConnectionID(long id) { + if ((id < -1) || (id > 0xFFFFFFFFL)) { + throw new IllegalArgumentException("Illegal Connection ID"); + } + connectionID = id; + } + + /** + * 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 connectionID; + } + + /** + * Called when a CONNECT request is received. + *

+ * If this method is not implemented by the class that extends this + * class, onConnect() will always return an + * OBEX_HTTP_OK response code. + *

+ * The headers received in the request can be retrieved from the + * request argument. The headers that should be sent + * in the reply must be specified in the reply argument. + * + * @param request contains the headers sent by the client; + * request will never be null + * + * @param reply the headers that should be sent in the reply; + * reply will never be null + * + * @return a response code defined in ResponseCodes that will be + * returned to the client; if an invalid response code is provided, the + * OBEX_HTTP_INTERNAL_ERROR response code will be used + */ + public int onConnect(HeaderSet request, HeaderSet reply) { + return ResponseCodes.OBEX_HTTP_OK; + } + + /** + * Called when a DISCONNECT request is received. + *

+ * The headers received in the request can be retrieved from the + * request argument. The headers that should be sent + * in the reply must be specified in the reply argument. + * + * @param request contains the headers sent by the client; + * request will never be null + * + * @param reply the headers that should be sent in the reply; + * reply will never be null + */ + public void onDisconnect(HeaderSet request, HeaderSet reply) { + } + + /** + * Called when a SETPATH request is received. + *

+ * If this method is not implemented by the class that extends this + * class, onSetPath() will always return an + * OBEX_HTTP_NOT_IMPLEMENTED response code. + *

+ * The headers received in the request can be retrieved from the + * request argument. The headers that should be sent + * in the reply must be specified in the reply argument. + * + * @param request contains the headers sent by the client; + * request will never be null + * + * @param reply the headers that should be sent in the reply; + * reply will never be null + * + * @param backup true if the client requests that the server + * back up one directory before changing to the path described by + * name; false to apply the request to the present + * path + * + * @param create true if the path should be created if it does + * not already exist; false 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 ResponseCodes that will be + * returned to the client; if an invalid response code is provided, the + * OBEX_HTTP_INTERNAL_ERROR 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. + *

+ * If this method is not implemented by the class that extends this + * class, onDelete() will always return an + * OBEX_HTTP_NOT_IMPLEMENTED response code. + *

+ * The headers received in the request can be retrieved from the + * request argument. The headers that should be sent + * in the reply must be specified in the reply argument. + * + * @param request contains the headers sent by the client; + * request will never be null + * + * @param reply the headers that should be sent in the reply; + * reply will never be null + * + * @return a response code defined in ResponseCodes that will be + * returned to the client; if an invalid response code is provided, the + * OBEX_HTTP_INTERNAL_ERROR 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. + *

+ * If this method is not implemented by the class that extends this + * class, onPut() will always return an + * OBEX_HTTP_NOT_IMPLEMENTED response code. + *

+ * If an ABORT request is received during the processing of a PUT request, + * op will be closed by the implementation. + * + * @param op contains the headers sent by the client and allows new + * headers to be sent in the reply; op will never be + * null + * + * @return a response code defined in ResponseCodes that will be + * returned to the client; if an invalid response code is provided, the + * OBEX_HTTP_INTERNAL_ERROR response code will be used + */ + public int onPut(Operation op) { + return ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED; + } + + /** + * Called when a GET request is received. + *

+ * If this method is not implemented by the class that extends this + * class, onGet() will always return an + * OBEX_HTTP_NOT_IMPLEMENTED response code. + *

+ * If an ABORT request is received during the processing of a GET request, + * op will be closed by the implementation. + * + * @param op contains the headers sent by the client and allows new + * headers to be sent in the reply; op will never be + * null + * + * @return a response code defined in ResponseCodes that will be + * returned to the client; if an invalid response code is provided, the + * OBEX_HTTP_INTERNAL_ERROR response code will be used + */ + public int onGet(Operation op) { + 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. + *

+ * 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; + * null if no user name was provided in the response + */ + public void onAuthenticationFailure(byte[] userName) { + } + + /**Called by ServerSession to update the status of current transaction */ + public void updateStatus(String message) { + } + + public void onClose() { + } +} diff --git a/obex/javax/obex/ServerSession.java b/obex/javax/obex/ServerSession.java new file mode 100644 index 0000000..31c82cc --- /dev/null +++ b/obex/javax/obex/ServerSession.java @@ -0,0 +1,923 @@ +/* + * 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.*; + +/** + * This class in an implementation of the ServerSession interface. + * + * @version 0.3 November 28, 2008 + */ +public class ServerSession implements Runnable, ObexSession { + + private ObexTransport client; + + // private Socket client ; + private InputStream input; + + private OutputStream output; + + private ServerRequestHandler listener; + + private Thread processThread; + + private int maxPacketLength; + + private Authenticator authenticator; + + byte[] challengeDigest; + + public boolean isClosed; + + private static final String TAG = "ServerSession"; + + /** + * Creates new ServerSession. + * + * @param conn + * the connection to the client + * + * @param handler + * the event listener that will process requests + * + * @param auth + * the authenticator to use with this connection + * + * @exception IOException + * if an error occurred while opening the input and output + * streams + */ + public ServerSession(ObexTransport conn, ServerRequestHandler handler, Authenticator auth) + throws IOException { + authenticator = auth; + client = conn; + input = client.openInputStream(); + output = client.openOutputStream(); + listener = handler; + maxPacketLength = 256; + + isClosed = false; + processThread = new Thread(this); + processThread.start(); + } + + /* removed as they're provided to the API user. Not used internally. */ + /* + public boolean isCreatedServer() { + if (client instanceof BTConnection) + return ((BTConnection)client).isServerCreated(); + else + return false; + } + + public boolean isClosed() { + if (client instanceof BTConnection) + return ((BTConnection)client).isClosed(); + else + return false; + } + + public int getConnectionHandle() { + if (client instanceof BTConnection) + return ((BTConnection)client).getConnectionHandle(); + else + return -1; + } + + public RemoteDevice getRemoteDevice() { + if (client instanceof BTConnection) + return ((BTConnection)client).getRemoteDevice(); + else + return null; + }*/ + + /** + * Processes requests made to the server and forwards them to the + * appropriate event listener. + */ + public void run() { + try { + + boolean done = false; + while (!done && !isClosed) { + int requestType = input.read(); + switch (requestType) { + case 0x80: + handleConnectRequest(); + break; + + case 0x81: + handleDisconnectRequest(); + done = true; + break; + + case 0x03: + case 0x83: + handleGetRequest(requestType); + break; + + case 0x02: + case 0x82: + handlePutRequest(requestType); + break; + + case 0x85: + 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 = input.read(); + length = (length << 8) + input.read(); + for (int i = 3; i < length; i++) { + input.read(); + } + sendResponse(ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED, null); + + // done = true; + } + } + + } catch (NullPointerException e) { + } catch (Exception e) { + } + close(); + } + + /** + * Handles a PUT request from a client. This method will provide a + * ServerOperation object to the request handler. The + * ServerOperation 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 + * ServerOperation 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 + * + * @exception IOException + * if an error occurred at the transport layer + */ + private void handlePutRequest(int type) throws IOException { + ServerOperation client = new ServerOperation(this, input, type, maxPacketLength, listener); + try { + int response = -1; + + if ((client.finalBitSet) && !client.isValidBody()) { + response = validateResponseCode(listener.onDelete(client.requestHeaders, + client.replyHeaders)); + } else { + response = validateResponseCode(listener.onPut(client)); + } + if (response != ResponseCodes.OBEX_HTTP_OK) { + client.sendReply(response); + } else if (!client.isAborted) { + // wait for the final bit + while (!client.finalBitSet) { + client.sendReply(OBEXConstants.OBEX_HTTP_CONTINUE); + } + client.sendReply(response); + } + } catch (Exception e) { + sendResponse(ResponseCodes.OBEX_HTTP_INTERNAL_ERROR, null); + } + } + + /** + * Handles a GET request from a client. This method will provide a + * ServerOperation object to the request handler. The + * ServerOperation 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 + * ServerOperation 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 + * + * @exception IOException + * if an error occurred at the transport layer + */ + private void handleGetRequest(int type) throws IOException { + ServerOperation client = new ServerOperation(this, input, type, maxPacketLength, listener); + try { + int response = validateResponseCode(listener.onGet(client)); + + if (!client.isAborted) { + client.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 + * + * @exception IOException + * if an IO error occurs + */ + protected 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; + } + output.write(data); + output.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 HeaderSet object to pass to the + * ServerRequestHandler object. After the handler processes the + * request, this method will create a reply message to send to the server + * with the response code provided. + * + * @exception IOException + * if an error occurred at the transport layer + */ + private void handleSetPathRequest() throws IOException { + int length; + int flags; + int constants; + int totalLength = 3; + byte[] head = null; + int code = -1; + int bytesReceived; + HeaderSet request = new HeaderSet(); + HeaderSet reply = new HeaderSet(); + + length = input.read(); + length = (length << 8) + input.read(); + flags = input.read(); + constants = input.read(); + + if (length > OBEXConstants.MAX_PACKET_SIZE_INT) { + code = ResponseCodes.OBEX_HTTP_REQ_TOO_LARGE; + totalLength = 3; + } else { + if (length > 5) { + byte[] headers = new byte[length - 5]; + bytesReceived = input.read(headers); + + while (bytesReceived != headers.length) { + bytesReceived += input.read(headers, bytesReceived, headers.length + - bytesReceived); + } + + OBEXHelper.updateHeaderSet(request, headers); + + if (request.connectionID != null) { + listener.setConnectionID(OBEXHelper.convertToLong(request.connectionID)); + } else { + listener.setConnectionID(-1); + } + // the Auth chan is initiated by the server. + // client sent back the authResp . + if (request.authResp != null) { + if (!handleAuthResp(request.authResp)) { + code = ResponseCodes.OBEX_HTTP_UNAUTHORIZED; + listener.onAuthenticationFailure(OBEXHelper.getTagValue((byte)0x01, + request.authResp)); + } + request.authResp = null; + } + } + + if (code != ResponseCodes.OBEX_HTTP_UNAUTHORIZED) { + // the Auth chan is initiated by the client + // the server will send back the authResp to the client + if (request.authChall != null) { + handleAuthChall(request); + reply.authResp = new byte[request.authResp.length]; + System.arraycopy(request.authResp, 0, reply.authResp, 0, reply.authResp.length); + request.authChall = null; + request.authResp = null; + } + boolean backup = false; + boolean create = true; + if (!((flags & 1) == 0)) { + backup = true; + } + if ((flags & 2) == 0) { + create = false; + } + + try { + code = listener.onSetPath(request, reply, backup, create); + } catch (Exception e) { + sendResponse(ResponseCodes.OBEX_HTTP_INTERNAL_ERROR, null); + return; + } + + code = validateResponseCode(code); + + if (reply.nonce != null) { + challengeDigest = new byte[16]; + System.arraycopy(reply.nonce, 0, challengeDigest, 0, 16); + } else { + challengeDigest = null; + } + + long id = listener.getConnectionID(); + if (id == -1) { + reply.connectionID = null; + } else { + reply.connectionID = OBEXHelper.convertToByteArray(id); + } + + head = OBEXHelper.createHeader(reply, false); + totalLength += head.length; + + if (totalLength > maxPacketLength) { + 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 + */ + output.write(replyData); + output.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 HeaderSet object to pass to the + * ServerRequestHandler object. After the handler processes the + * request, this method will create a reply message to send to the server. + * + * @exception 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 = input.read(); + length = (length << 8) + input.read(); + + if (length > OBEXConstants.MAX_PACKET_SIZE_INT) { + code = ResponseCodes.OBEX_HTTP_REQ_TOO_LARGE; + totalLength = 3; + } else { + if (length > 3) { + byte[] headers = new byte[length - 3]; + bytesReceived = input.read(headers); + + while (bytesReceived != headers.length) { + bytesReceived += input.read(headers, bytesReceived, headers.length + - bytesReceived); + } + + OBEXHelper.updateHeaderSet(request, headers); + } + + if (request.connectionID != null) { + listener.setConnectionID(OBEXHelper.convertToLong(request.connectionID)); + } else { + listener.setConnectionID(1); + } + + if (request.authResp != null) { + if (!handleAuthResp(request.authResp)) { + code = ResponseCodes.OBEX_HTTP_UNAUTHORIZED; + listener.onAuthenticationFailure(OBEXHelper.getTagValue((byte)0x01, + request.authResp)); + } + request.authResp = null; + } + + if (code != ResponseCodes.OBEX_HTTP_UNAUTHORIZED) { + + if (request.authChall != null) { + handleAuthChall(request); + request.authChall = null; + } + + try { + listener.onDisconnect(request, reply); + } catch (Exception e) { + sendResponse(ResponseCodes.OBEX_HTTP_INTERNAL_ERROR, null); + return; + } + + /* + * Since a client will never response to an authentication + * challenge on a DISCONNECT, there is no reason to keep track + * of the challenge. + * + * if (reply.nonce != null) { challengeDigest = new byte[16]; + * System.arraycopy(reply.nonce, 0, challengeDigest, 0, 16); } + * else { challengeDigest = null; } + */ + + long id = listener.getConnectionID(); + if (id == -1) { + reply.connectionID = null; + } else { + reply.connectionID = OBEXHelper.convertToByteArray(id); + } + + head = OBEXHelper.createHeader(reply, false); + totalLength += head.length; + + if (totalLength > maxPacketLength) { + 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 + */ + output.write(replyData); + output.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 HeaderSet object to pass to the + * ServerRequestHandler object. After the handler processes the + * request, this method will create a reply message to send to the server + * with the response code provided. + * + * @exception IOException + * if an error occurred at the transport layer + */ + private void handleConnectRequest() throws IOException { + int packetLength; + int version; + 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 = input.read(); + packetLength = (packetLength << 8) + input.read(); + version = input.read(); + flags = input.read(); + maxPacketLength = input.read(); + maxPacketLength = (maxPacketLength << 8) + input.read(); + + // should we check it? + if (maxPacketLength > OBEXConstants.MAX_PACKET_SIZE_INT) { + maxPacketLength = OBEXConstants.MAX_PACKET_SIZE_INT; + } + + if (packetLength > OBEXConstants.MAX_PACKET_SIZE_INT) { + code = ResponseCodes.OBEX_HTTP_REQ_TOO_LARGE; + totalLength = 7; + } else { + if (packetLength > 7) { + byte[] headers = new byte[packetLength - 7]; + bytesReceived = input.read(headers); + + while (bytesReceived != headers.length) { + bytesReceived += input.read(headers, bytesReceived, headers.length + - bytesReceived); + } + + OBEXHelper.updateHeaderSet(request, headers); + } + + if (request.connectionID != null) { + listener.setConnectionID(OBEXHelper.convertToLong(request.connectionID)); + } else { + listener.setConnectionID(1); + } + + if (request.authResp != null) { + if (!handleAuthResp(request.authResp)) { + code = ResponseCodes.OBEX_HTTP_UNAUTHORIZED; + listener.onAuthenticationFailure(OBEXHelper.getTagValue((byte)0x01, + request.authResp)); + } + request.authResp = null; + } + + if (code != ResponseCodes.OBEX_HTTP_UNAUTHORIZED) { + if (request.authChall != null) { + handleAuthChall(request); + reply.authResp = new byte[request.authResp.length]; + System.arraycopy(request.authResp, 0, reply.authResp, 0, reply.authResp.length); + request.authChall = null; + request.authResp = null; + } + + try { + code = listener.onConnect(request, reply); + code = validateResponseCode(code); + + if (reply.nonce != null) { + challengeDigest = new byte[16]; + System.arraycopy(reply.nonce, 0, challengeDigest, 0, 16); + } else { + challengeDigest = null; + } + long id = listener.getConnectionID(); + if (id == -1) { + reply.connectionID = null; + } else { + reply.connectionID = OBEXHelper.convertToByteArray(id); + } + + head = OBEXHelper.createHeader(reply, false); + totalLength += head.length; + + if (totalLength > maxPacketLength) { + 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)(OBEXConstants.MAX_PACKET_SIZE_INT >> 8); + sendData[6] = (byte)(OBEXConstants.MAX_PACKET_SIZE_INT & 0xFF); + + if (head != null) { + System.arraycopy(head, 0, sendData, 7, head.length); + } + + output.write(sendData); + output.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 (listener != null) { + listener.onClose(); + } + try { + input.close(); + output.close(); + client.close(); + isClosed = true; + } catch (Exception e) { + } + client = null; + input = null; + output = null; + listener = null; + } + + /** + * Verifies that the response code is valid. If it is not valid, it will + * return the OBEX_HTTP_INTERNAL_ERROR response code. + * + * @param code + * the response code to check + * + * @return the valid response code or OBEX_HTTP_INTERNAL_ERROR + * if 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; + } + + /** + * 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 true if the last request should be resent; + * false if the last request should not be resent + */ + protected boolean handleAuthChall(HeaderSet header) { + if (authenticator == 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.authChall); + byte[] option = OBEXHelper.getTagValue((byte)0x01, header.authChall); + byte[] description = OBEXHelper.getTagValue((byte)0x02, header.authChall); + + String realm = ""; + if (description != null) { + byte[] realmString = new byte[description.length - 1]; + System.arraycopy(description, 1, realmString, 0, realmString.length); + + switch (description[0] & 0xFF) { + + case 0x00: + // ASCII encoding + // Fall through + case 0x01: + // ISO-8859-1 encoding + try { + realm = new String(realmString, "ISO8859_1"); + } catch (Exception e) { + throw new RuntimeException("Unsupported Encoding Scheme"); + } + break; + + case 0xFF: + // UNICODE Encoding + realm = OBEXHelper.convertToUnicode(realmString, false); + break; + + case 0x02: + // ISO-8859-2 encoding + // Fall through + case 0x03: + // ISO-8859-3 encoding + // Fall through + case 0x04: + // ISO-8859-4 encoding + // Fall through + case 0x05: + // ISO-8859-5 encoding + // Fall through + case 0x06: + // ISO-8859-6 encoding + // Fall through + case 0x07: + // ISO-8859-7 encoding + // Fall through + case 0x08: + // ISO-8859-8 encoding + // Fall through + case 0x09: + // ISO-8859-9 encoding + // Fall through + default: + throw new RuntimeException("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.authChall = null; + + try { + result = authenticator.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.authResp = new byte[38 + userName.length]; + header.authResp[36] = (byte)0x01; + header.authResp[37] = (byte)userName.length; + System.arraycopy(userName, 0, header.authResp, 38, userName.length); + } else { + header.authResp = 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.authResp[0] = (byte)0x00; + header.authResp[1] = (byte)0x10; + + System.arraycopy(OBEXHelper.computeMD5Hash(digest), 0, header.authResp, 2, 16); + + // Add the challenge + header.authResp[18] = (byte)0x02; + header.authResp[19] = (byte)0x10; + System.arraycopy(challenge, 0, header.authResp, 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 true if the response passed; false if + * the response failed + */ + protected boolean handleAuthResp(byte[] authResp) { + if (authenticator == null) { + return false; + } + // get the correct password from the application + byte[] correctPassword = authenticator.onAuthenticationResponse(OBEXHelper.getTagValue( + (byte)0x01, authResp)); + if (correctPassword == null) { + return false; + } + + byte[] temp = new byte[correctPassword.length + 16]; + + System.arraycopy(challengeDigest, 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/SessionNotifier.java b/obex/javax/obex/SessionNotifier.java new file mode 100644 index 0000000..0c9bfd5 --- /dev/null +++ b/obex/javax/obex/SessionNotifier.java @@ -0,0 +1,168 @@ +/* + * 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 SessionNotifier interface defines a connection notifier for + * server-side OBEX connections. When a SessionNotifier is + * created and calls acceptAndOpen(), it will begin listening for + * clients to create a connection at the transport layer. When the transport + * layer connection is received, the acceptAndOpen() method will + * return a javax.microedition.io.Connection that is the + * connection to the client. The acceptAndOpen() method also takes a + * ServerRequestHandler argument that will process the requests + * from the client that connects to the server. + * + * @version 0.3 November 28, 2008 + */ +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. + *

+ *

Additional Note for OBEX over Bluetooth

+ * If this method is called on a SessionNotifier object that + * does not have a ServiceRecord in the SDDB, the + * ServiceRecord for this object will be added to the SDDB. + * This method requests the BCC to put the + * local device in connectable mode so that it will respond to + * connection attempts by clients. + *

+ * The following checks are done to verify that the service record + * provided is valid. If any of these checks fail, then a + * ServiceRegistrationException is thrown. + *

+ *

+ * This method will not ensure that ServiceRecord 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 + * + * @exception IOException if an error occurs in the transport layer + * + * @exception NullPointerException if handler is + * null + * + * @exception 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. + * + * @exception BluetoothStateException if the server device could + * not be placed in connectable mode because the device user has + * configured the device to be non-connectable + */ + public 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 + * Authenticator to use to respond to authentication challenge + * and authentication response headers. + *

+ *

Additional Note for OBEX over Bluetooth

+ * If this method is called on a SessionNotifier object that + * does not have a ServiceRecord in the SDDB, the + * ServiceRecord for this object will be added to the SDDB. + * This method requests the BCC to put the + * local device in connectable mode so that it will respond to + * connection attempts by clients. + *

+ * The following checks are done to verify that the service record + * provided is valid. If any of these checks fail, then a + * ServiceRegistrationException is thrown. + *

+ *

+ * This method will not ensure that ServiceRecord 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 Authenticator to use with this connection; + * if null then no Authenticator will be used + * + * @return the connection to the client + * + * @exception IOException if an error occurs in the transport layer + * + * @exception NullPointerException if handler is + * null + * + * @exception 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. + * + * @exception BluetoothStateException if the server device could + * not be placed in connectable mode because the device user has + * configured the device to be non-connectable + */ + public ObexSession acceptAndOpen(ServerRequestHandler handler, Authenticator auth) + throws IOException; +} -- cgit v1.1