summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--obex/javax/obex/ApplicationParameter.java135
-rw-r--r--obex/javax/obex/Authenticator.java124
-rw-r--r--obex/javax/obex/BaseStream.java83
-rw-r--r--obex/javax/obex/ClientOperation.java840
-rw-r--r--obex/javax/obex/ClientSession.java783
-rw-r--r--obex/javax/obex/HeaderSet.java610
-rw-r--r--obex/javax/obex/OBEXConstants.java74
-rw-r--r--obex/javax/obex/OBEXHelper.java1326
-rw-r--r--obex/javax/obex/ObexSession.java63
-rw-r--r--obex/javax/obex/ObexTransport.java78
-rw-r--r--obex/javax/obex/Operation.java200
-rw-r--r--obex/javax/obex/PasswordAuthentication.java85
-rw-r--r--obex/javax/obex/PrivateInputStream.java196
-rw-r--r--obex/javax/obex/PrivateOutputStream.java197
-rw-r--r--obex/javax/obex/ResponseCodes.java324
-rw-r--r--obex/javax/obex/ServerOperation.java772
-rw-r--r--obex/javax/obex/ServerRequestHandler.java288
-rw-r--r--obex/javax/obex/ServerSession.java923
-rw-r--r--obex/javax/obex/SessionNotifier.java168
19 files changed, 7269 insertions, 0 deletions
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
+ * <code>onAuthenticationChallenge()</code> or
+ * <code>onAuthenticationResponse()</code> will be called, respectively, by
+ * the implementation.
+ * <P>
+ * For more information on how the authentication procedure works in OBEX,
+ * please review the IrOBEX specification at
+ * <A HREF="http://www.irda.org">http://www.irda.org</A>.
+ * <P>
+ * <STRONG>Authentication Challenges</STRONG>
+ * <P>
+ * When a client or server receives an authentication challenge header, the
+ * <code>onAuthenticationChallenge()</code> method will be invoked by the
+ * OBEX API implementation. The application will then return the user name
+ * (if needed) and password via a <code>PasswordAuthentication</code> object.
+ * The password in this object is not sent in the authentication response.
+ * Instead, the 16-byte challenge received in the authentication challenge is
+ * combined with the password returned from the
+ * <code>onAuthenticationChallenge()</code> method and passed through the MD5
+ * hash algorithm. The resulting value is sent in the authentication response
+ * along with the user name if it was provided.
+ * <P>
+ * <STRONG>Authentication Responses</STRONG>
+ * <P>
+ * When a client or server receives an authentication response header, the
+ * <code>onAuthenticationResponse()</code> method is invoked by the API
+ * implementation with the user name received in the authentication response
+ * header. (The user name will be <code>null</code> if no user name was
+ * provided in the authentication response header.) The application must
+ * determine the correct password. This value should be returned from the
+ * <code>onAuthenticationResponse()</code> method. If the authentication
+ * request should fail without the implementation checking the password,
+ * <code>null</code> should
+ * be returned by the application. (This is needed for reasons like not
+ * recognizing the user name, etc.) If the returned value is not
+ * <code>null</code>, the OBEX API implementation will combine the password
+ * returned from the <code>onAuthenticationResponse()</code> method and
+ * challenge sent via the authentication challenge, apply the MD5 hash
+ * algorithm, and compare the result to the response hash received in the
+ * authentication response header. If the values are not equal, an
+ * <code>IOException</code> will be thrown if the client requested authentication.
+ * If the server requested authentication, the
+ * <code>onAuthenticationFailure()</code> method will be called on the
+ * <code>ServerRequestHandler</code> that failed authentication. The
+ * connection is <B>not</B> closed if authentication failed.
+ *
+ * @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
+ * <code>PasswordAuthentication</code> that contains the correct user name
+ * and password for the challenge.
+ *
+ * @param description the description of which user name and password
+ * should be used; if no description is provided in the authentication
+ * challenge or the description is encoded in an encoding scheme that is
+ * not supported, an empty string will be provided
+ *
+ * @param isUserIdRequired <code>true</code> if the user ID is required;
+ * <code>false</code> if the user ID is not required
+ *
+ * @param isFullAccess <code>true</code> if full access to the server
+ * will be granted; <code>false</code> if read only access will be
+ * granted
+ *
+ * @return a <code>PasswordAuthentication</code> object containing the
+ * user name and password used for authentication
+ */
+ 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 <code>null</code>
+ *
+ * @return the correct password for the user name provided; if
+ * <code>null</code> 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 <code>true</code> if the operation should send an
+ * empty packet or not send anything if there is no data to send
+ * @param inStream <code>true</code> if the stream is input stream or
+ * is output stream
+ * @return <code>true</code> if the operation was completed;
+ * <code>false</code> if no operation took place
+ *
+ * @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 <code>true</code> if the input stream is closed;
+ * <code>false</code> if the output stream is closed
+ *
+ * @exception IOException if an IO error occurs
+ */
+ public void streamClosed(boolean inStream) throws IOException;
+}
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 <code>Operation</code> 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:<BR>
+ * 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 <code>true</code> if this is a get request;
+ * <code>false</code. if this is a put request
+ *
+ * @exception IOExcpetion if the an IO error occured
+ */
+ public ClientOperation(InputStream in, int maxSize, ClientSession p, HeaderSet header,
+ boolean type) throws IOException {
+
+ parent = p;
+ isEndOfBodySent = false;
+ socketInput = in;
+ isClosed = false;
+ isDone = false;
+ maxPacketSize = maxSize;
+ isGet = type;
+
+ inputStreamOpened = false;
+ outputStreamOpened = false;
+ isValidateConnected = false;
+
+ privateInput = null;
+ privateOutput = null;
+
+ replyHeaders = new HeaderSet();
+
+ requestHeaders = new HeaderSet();
+
+ int[] headerList = header.getHeaderList();
+
+ if (headerList != null) {
+
+ for (int i = 0; i < headerList.length; i++) {
+ requestHeaders.setHeader(headerList[i], header.getHeader(headerList[i]));
+ }
+ }
+
+ if ((header).authChall != null) {
+ requestHeaders.authChall = new byte[(header).authChall.length];
+ System.arraycopy((header).authChall, 0, requestHeaders.authChall, 0,
+ (header).authChall.length);
+ }
+
+ if ((header).authResp != null) {
+ requestHeaders.authResp = new byte[(header).authResp.length];
+ System.arraycopy((header).authResp, 0, requestHeaders.authResp, 0,
+ (header).authResp.length);
+
+ }
+ // requestHeaders = (HeaderSet)header;
+ }
+
+ /**
+ * 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 synchronized void abort() throws IOException {
+ ensureOpen();
+ // need check again .
+ // if(isDone) {
+ // throw new IOException("Operation has already ended");
+ // }
+
+ //no compatible with sun-ri
+ if ((isDone) && (replyHeaders.responseCode != OBEXConstants.OBEX_HTTP_CONTINUE)) {
+ throw new IOException("Operation has already ended");
+ }
+
+ exceptionMessage = "Operation aborted";
+ if ((!isDone) && (replyHeaders.responseCode == OBEXConstants.OBEX_HTTP_CONTINUE)) {
+ isDone = true;
+ /*
+ * Since we are not sending any headers or returning any headers then
+ * we just need to write and read the same bytes
+ */
+ parent.sendRequest(0xFF, null, replyHeaders, null);
+
+ if (replyHeaders.responseCode != ResponseCodes.OBEX_HTTP_OK) {
+ throw new IOException("Invalid response code from server");
+ }
+
+ exceptionMessage = null;
+ }
+
+ close();
+ }
+
+ /**
+ * Retrieves the response code retrieved from the server. Response codes
+ * are defined in the <code>ResponseCodes</code> interface.
+ *
+ * @return the response code retrieved from the server
+ *
+ * @exception IOException if an error occurred in the transport layer during
+ * the transaction; if this method is called on a <code>HeaderSet</code>
+ * object created by calling <code>createHeaderSet</code> in a
+ * <code>ClientSession</code> object
+ */
+ 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 <code>null</code>
+ *
+ * @return <code>null</code>
+ */
+ public String getEncoding() {
+ return null;
+ }
+
+ /**
+ * Returns the type of content that the resource connected to is providing.
+ * E.g. if the connection is via HTTP, then the value of the content-type
+ * header field is returned.
+ *
+ * @return the content type of the resource that the URL references, or
+ * <code>null</code> if not known
+ */
+ public String getType() {
+ try {
+ return (String)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 <code>Operation</code>
+ *
+ * @exception IOException if this <code>Operation</code> 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 <code>Operation</code> has been closed
+ * or the transaction has ended and no further messages will be exchanged
+ *
+ * @exception IllegalArgumentException if <code>headers</code> was not created
+ * by a call to <code>ServerRequestHandler.createHeaderSet()</code>
+ *
+ * @exception NullPointerException if <code>headers</code> is <code>null</code>
+ */
+ 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 <code>true</code> if the transaction should end;
+ * <code>false</code> 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 <code>true</code> if there is more data to send;
+ * <code>false</code> 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 <code>true</code> if the operation should send an
+ * empty packet or not send anything if there is no data to send
+ * @param inStream <code>true</code> if the stream is input stream or
+ * is output stream
+ * @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 <code>true</code> if the input stream is closed;
+ * <code>false</code> if the output stream is closed
+ *
+ * @exception IOException if an IO error occurs
+ */
+ public void streamClosed(boolean inStream) throws IOException {
+ 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 <code>Operation</code> 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 <code>head</code>; <code>null</code>
+ * if no authentication header is included in <code>head</code>
+ *
+ * @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 <code>true</code> if the operation completed successfully;
+ * <code>false</code> 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 <code>true</code> if the last request should be resent;
+ * <code>false</code> 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 <code>true</code> if the response passed; <code>false</code> 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.
+ * <P>
+ * The value of <code>COUNT</code> is 0xC0 (192).
+ */
+ public static final int COUNT = 0xC0;
+
+ /**
+ * Represents the OBEX Name header. This specifies the name of the object.
+ * <P>
+ * The value of <code>NAME</code> is 0x01 (1).
+ */
+ public static final int NAME = 0x01;
+
+ /**
+ * Represents the OBEX Type header. This allows a request to specify the
+ * type of the object (e.g. text, html, binary, etc.).
+ * <P>
+ * The value of <code>TYPE</code> is 0x42 (66).
+ */
+ public static final int TYPE = 0x42;
+
+ /**
+ * Represents the OBEX Length header. This is the length of the object in
+ * bytes.
+ * <P>
+ * The value of <code>LENGTH</code> is 0xC3 (195).
+ */
+ public static final int LENGTH = 0xC3;
+
+ /**
+ * Represents the OBEX Time header using the ISO 8601 standards. This is
+ * the preferred time header.
+ * <P>
+ * The value of <code>TIME_ISO_8601</code> is 0x44 (68).
+ */
+ public static final int TIME_ISO_8601 = 0x44;
+
+ /**
+ * Represents the OBEX Time header using the 4 byte representation. This
+ * is only included for backwards compatibility. It represents the number
+ * of seconds since January 1, 1970.
+ * <P>
+ * The value of <code>TIME_4_BYTE</code> is 0xC4 (196).
+ */
+ public static final int TIME_4_BYTE = 0xC4;
+
+ /**
+ * Represents the OBEX Description header. This is a text description of
+ * the object.
+ * <P>
+ * The value of <code>DESCRIPTION</code> is 0x05 (5).
+ */
+ public static final int DESCRIPTION = 0x05;
+
+ /**
+ * Represents the OBEX Target header. This is the name of the service an
+ * operation is targeted to.
+ * <P>
+ * The value of <code>TARGET</code> is 0x46 (70).
+ */
+ public static final int TARGET = 0x46;
+
+ /**
+ * Represents the OBEX HTTP header. This allows an HTTP 1.X header to be
+ * included in a request or reply.
+ * <P>
+ * The value of <code>HTTP</code> is 0x47 (71).
+ */
+ public static final int HTTP = 0x47;
+
+ /**
+ * Represents the OBEX Who header. Identifies the OBEX application to
+ * determine if the two peers are talking to each other.
+ * <P>
+ * The value of <code>WHO</code> is 0x4A (74).
+ */
+ public static final int WHO = 0x4A;
+
+ /**
+ * Represents the OBEX Object Class header. This header specifies the
+ * OBEX object class of the object.
+ * <P>
+ * The value of <code>OBJECT_CLASS</code> is 0x4F (79).
+ */
+ public static final int OBJECT_CLASS = 0x4F;
+
+ /**
+ * Represents the OBEX Application Parameter header. This header specifies
+ * additional application request and response information.
+ * <P>
+ * The value of <code>APPLICATION_PARAMETER</code> is 0x4C (76).
+ */
+ public static final int APPLICATION_PARAMETER = 0x4C;
+
+ 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 <code>HeaderSet</code> 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 <code>null</code> is passed as the
+ * <code>headerValue</code> then the header will be removed from the set of
+ * headers to include in the next request.
+ *
+ * @param headerID the identifier to include in the message
+ *
+ * @param headerValue the value of the header identifier
+ *
+ * @exception IllegalArgumentException if the header identifier provided is
+ * not one defined in this interface or a user-defined header; if the type of
+ * <code>headerValue</code> is not the correct Java type as defined in the
+ * description of this interface\
+ */
+ public void setHeader(int headerID, Object headerValue) {
+ long temp = -1;
+
+ switch (headerID) {
+ case COUNT:
+ if (!(headerValue instanceof Long)) {
+ if (headerValue == null) {
+ 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 <code>null</code> if the
+ * header identifier specified is not part of this <code>HeaderSet</code>
+ * object
+ *
+ * @exception IllegalArgumentException if the <code>headerID</code> 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
+ * <code>getHeader</code> method that will not return <code>null</code>.
+ * In other words, this method returns all the headers that are available
+ * in this object.
+ *
+ * @see #getHeader
+ *
+ * @return the array of headers that are set in this object or
+ * <code>null</code> if no headers are available
+ *
+ * @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 <code>realm</code> will
+ * be encoded based upon the default encoding scheme used by the
+ * implementation to encode strings. Therefore, the encoding scheme used
+ * to encode the <code>realm</code> is application dependent.
+ *
+ * @param realm a short description that describes what password to use; if
+ * <code>null</code> no realm will be sent in the authentication challenge
+ * header
+ *
+ * @param userID if <code>true</code>, a user ID is required in the reply;
+ * if <code>false</code>, no user ID is required
+ *
+ * @param access if <code>true</code> then full access will be granted if
+ * successful; if <code>false</code> then read-only access will be granted
+ * if successful
+ */
+ 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 <code>ResponseCodes</code> 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 <code>HeaderSet</code>
+ * object created by calling <code>createHeaderSet()</code> in a
+ * <code>ClientSession</code> object; if this object was created by an OBEX
+ * server
+ */
+ public int getResponseCode() throws IOException {
+ if (responseCode == -1) {
+ throw new IOException("May not be called on a server");
+ } else {
+ return responseCode;
+ }
+ }
+}
diff --git a/obex/javax/obex/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 <code>Operation</code> 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.
+ * <P>
+ * The value of <code>OBEX_HTTP_CONTINUE</code> 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.
+ * <P>
+ * The first two bits of an OBEX Header specifies the type of object that
+ * is being sent. The table below specifies the meaning of the high
+ * bits.
+ * <TABLE>
+ * <TR><TH>Bits 8 and 7</TH><TH>Value</TH><TH>Description</TH></TR>
+ * <TR><TD>00</TD><TD>0x00</TD><TD>Null Terminated Unicode text, prefixed
+ * with 2 byte unsigned integer</TD></TR>
+ * <TR><TD>01</TD><TD>0x40</TD><TD>Byte Sequence, length prefixed with
+ * 2 byte unsigned integer</TD></TR>
+ * <TR><TD>10</TD><TD>0x80</TD><TD>1 byte quantity</TD></TR>
+ * <TR><TD>11</TD><TD>0xC0</TD><TD>4 byte quantity - transmitted in
+ * network byte order (high byte first</TD></TR>
+ * </TABLE>
+ * This method uses the information in this table to determine the type of
+ * Java object to create and passes that object with the full header
+ * to setHeader() to update the HeaderSet object. Invalid headers will
+ * cause an exception to be thrown. When it is thrown, it is ignored.
+ *
+ * @param header the HeaderSet to update
+ *
+ * @param headerArray the byte array containing headers
+ *
+ * @return the result of the last start body or end body header provided;
+ * the first byte in the result will specify if a body or end of body is
+ * received
+ *
+ * @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 <code>true</code> if the header should be set to
+ * <code>null</code> once it is added to the array or <code>false</code>
+ * if it should not be nulled out
+ *
+ * @return the header of an OBEX packet
+ */
+ public static byte[] createHeader(HeaderSet head, boolean nullOut) {
+ Long intHeader = null;
+ String stringHeader = null;
+ Calendar dateHeader = null;
+ Byte byteHeader = null;
+ StringBuffer buffer = null;
+ byte[] value = null;
+ byte[] result = null;
+ byte[] lengthArray = new byte[2];
+ int length;
+ HeaderSet headImpl = null;
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ if (!(head instanceof HeaderSet)) {
+ throw new IllegalArgumentException("Header not created by createHeaderSet");
+ }
+ headImpl = head;
+
+ try {
+ /*
+ * Determine if there is a connection ID to send. If there is,
+ * then it should be the first header in the packet.
+ */
+ if ((headImpl.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 <code>true</code> then full access will be granted if
+ * successful; if <code>false</code> then read only access will be granted
+ * if successful
+ *
+ * @param userID if <code>true</code>, a user ID is required in the reply;
+ * if <code>false</code>, no user ID is required
+ *
+ * @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 <code>x</code> <code>n</code> 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 <code>x</code> rotated to the left <code>n</code> 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. <code>input</code> 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 <code>input</code> 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 <code>ObexSession</code> interface characterizes the term
+ * "OBEX Connection" as defined in the IrDA Object Exchange Protocol v1.2, which
+ * could be the server-side view of an OBEX connection, or the client-side view
+ * of the same connection, which is established by server's accepting of a
+ * client issued "CONNECT".
+ * <P>
+ *
+ * This interface serves as the common super class for
+ * <CODE>ClientSession</CODE> and <CODE>ServerSession</CODE>.
+ *
+ * @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 <code>ObexTransport</code> interface defines the underlying transport
+ * connection which carries the OBEX protocol( such as TCP, RFCOMM device file
+ * exposed by Bluetooth or USB in kernel, RFCOMM socket emulated in Android
+ * platform, Irda). This interface provides an abstract layer to be used by the
+ * <code>ObexConnection</code>. Each kind of medium shall have its own
+ * implementation to wrap and follow the same interface.
+ * <P>
+ * See section 1.2.2 of IrDA Object Exchange Protocol specification.
+ * <P>
+ * Different kind of medium may have different construction - for example, the
+ * RFCOMM device file medium may be constructed from a file descriptor or simply
+ * a string while the TCP medium usually from a socket.
+ *
+ * @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 <code>Operation</code> interface provides ways to manipulate a single
+ * OBEX PUT or GET operation. The implementation of this interface sends
+ * OBEX packets as they are built. If during the operation the peer in the
+ * operation ends the operation, an <code>IOException</code> is thrown on
+ * the next read from the input stream, write to the output stream, or call to
+ * <code>sendHeaders()</code>.
+ * <P>
+ * <STRONG>Definition of methods inherited from <code>ContentConnection</code></STRONG>
+ * <P>
+ * <code>getEncoding()</code> will always return <code>null</code>.
+ * <BR><code>getLength()</code> will return the length specified by the OBEX Length
+ * header or -1 if the OBEX Length header was not included.
+ * <BR><code>getType()</code> will return the value specified in the OBEX Type
+ * header or <code>null</code> if the OBEX Type header was not included.<BR>
+ * <P>
+ * <STRONG>How Headers are Handled</STRONG>
+ * <P>
+ * As headers are received, they may be retrieved through the
+ * <code>getReceivedHeaders()</code> method. If new headers are set during the
+ * operation, the new headers will be sent during the next packet exchange.
+ * <P>
+ * <STRONG>PUT example</STRONG>
+ * <P>
+ * <PRE>
+ * void putObjectViaOBEX(ClientSession conn, HeaderSet head, byte[] obj)
+ * throws IOException {
+ *
+ * // Include the length header
+ * head.setHeader(head.LENGTH, new Long(obj.length));
+ *
+ * // Initiate the PUT request
+ * Operation op = conn.put(head);
+ *
+ * // Open the output stream to put the object to it
+ * DataOutputStream out = op.openDataOutputStream();
+ *
+ * // Send the object to the server
+ * out.write(obj);
+ *
+ * // End the transaction
+ * out.close();
+ * op.close();
+ * }
+ * </PRE>
+ * <P>
+ * <STRONG>GET example</STRONG>
+ * <P>
+ * <PRE>
+ * byte[] getObjectViaOBEX(ClientSession conn, HeaderSet head) throws IOException {
+ *
+ * // Send the initial GET request to the server
+ * Operation op = conn.get(head);
+ *
+ * // Retrieve the length of the object being sent back
+ * int length = op.getLength();
+ *
+ * // Create space for the object
+ * byte[] obj = new byte[length];
+ *
+ * // Get the object from the input stream
+ * DataInputStream in = trans.openDataInputStream();
+ * in.read(obj);
+ *
+ * // End the transaction
+ * in.close();
+ * op.close();
+ *
+ * return obj;
+ * }
+ * </PRE>
+ * <H3>Client PUT Operation Flow</H3>
+ * For PUT operations, a call to <code>close()</code> the <code>OutputStream</code>
+ * returned from <code>openOutputStream()</code> or <code>openDataOutputStream()</code>
+ * will signal that the request is done. (In OBEX terms, the End-Of-Body header should
+ * be sent and the final bit in the request will be set.) At this point, the
+ * reply from the server may begin to be processed. A call to
+ * <code>getResponseCode()</code> will do an implicit close on the
+ * <code>OutputStream</code> and therefore signal that the request is done.
+ * <H3>Client GET Operation Flow</H3>
+ * For GET operation, a call to <code>openInputStream()</code> or
+ * <code>openDataInputStream()</code> will signal that the request is done. (In OBEX
+ * terms, the final bit in the request will be set.) A call to
+ * <code>getResponseCode()</code> will cause an implicit close on the
+ * <code>InputStream</code>. No further data may be read at this point.
+ *
+ * @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 <code>close()</code> 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 <code>Operation</code>
+ *
+ * @exception IOException if this <code>Operation</code> 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 <code>Operation</code> has been closed
+ * or the transaction has ended and no further messages will be exchanged
+ *
+ * @exception IllegalArgumentException if <code>headers</code> was not created
+ * by a call to <code>ServerRequestHandler.createHeaderSet()</code> or
+ * <code>ClientSession.createHeaderSet()</code>
+ *
+ * @exception NullPointerException if <code>headers</code> if <code>null</code>
+ */
+ public void sendHeaders(HeaderSet headers) throws IOException;
+
+ /**
+ * Returns the response code received from the server. Response codes
+ * are defined in the <code>ResponseCodes</code> class.
+ *
+ * @see ResponseCodes
+ *
+ * @return the response code retrieved from the server
+ *
+ * @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 <code>PasswordAuthentication</code> with the user name
+ * and password provided.
+ *
+ * @param userName the user name to include; this may be <code>null</code>
+ *
+ * @param password the password to include in the response
+ *
+ * @exception NullPointerException if <code>password</code> is
+ * <code>null</code>
+ */
+ 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 <code>null</code>.
+ *
+ * @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 <code>Operation</code> 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 <code>OperationImpl</code> thread to add body data to the
+ * input stream.
+ *
+ * @param body the data to add to the stream
+ *
+ * @param start the start of the body to array to copy
+ */
+ public synchronized void writeBytes(byte[] body, int start) {
+
+ int length = (body.length - start) + (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 <code>PrivateOutputStream</code> 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 <code>true</code> if the connection is closed;
+ * <code>false</code> 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 <code>ResponseCodes</code> class contains the list of valid
+ * response codes a server may send to a client.
+ * <P>
+ * <STRONG>IMPORTANT NOTE</STRONG>
+ * <P>
+ * It is important to note that these values are different then those defined
+ * in <code>javax.microedition.io.HttpConnection</code>. The values in this
+ * interface represent the values defined in the IrOBEX specification. The
+ * values in <code>javax.microedition.io.HttpConnection</code> represent values
+ * defined in the HTTP specification.
+ * <P>
+ * <code>OBEX_DATABASE_FULL</code> and <code>OBEX_DATABASE_LOCKED</code> require
+ * further description since they are not defined in HTTP. The server will send
+ * an <code>OBEX_DATABASE_FULL</code> message when the client requests that
+ * something be placed into a database but the database is full (cannot take
+ * more data). <code>OBEX_DATABASE_LOCKED</code> will be returned when the
+ * client wishes to access a database, database table, or database record that
+ * has been locked.
+ *
+ * @version 0.3 November 28, 2008
+ */
+public class ResponseCodes {
+
+ /**
+ * Defines the OBEX SUCCESS response code.
+ * <P>
+ * The value of <code>OBEX_HTTP_OK</code> is 0xA0 (160).
+ */
+ public static final int OBEX_HTTP_OK = 0xA0;
+
+ /**
+ * Defines the OBEX CREATED response code.
+ * <P>
+ * The value of <code>OBEX_HTTP_CREATED</code> is 0xA1 (161).
+ */
+ public static final int OBEX_HTTP_CREATED = 0xA1;
+
+ /**
+ * Defines the OBEX ACCEPTED response code.
+ * <P>
+ * The value of <code>OBEX_HTTP_ACCEPTED</code> is 0xA2 (162).
+ */
+ public static final int OBEX_HTTP_ACCEPTED = 0xA2;
+
+ /**
+ * Defines the OBEX NON-AUTHORITATIVE INFORMATION response code.
+ * <P>
+ * The value of <code>OBEX_HTTP_NOT_AUTHORITATIVE</code> is 0xA3 (163).
+ */
+ public static final int OBEX_HTTP_NOT_AUTHORITATIVE = 0xA3;
+
+ /**
+ * Defines the OBEX NO CONTENT response code.
+ * <P>
+ * The value of <code>OBEX_HTTP_NO_CONTENT</code> is 0xA4 (164).
+ */
+ public static final int OBEX_HTTP_NO_CONTENT = 0xA4;
+
+ /**
+ * Defines the OBEX RESET CONTENT response code.
+ * <P>
+ * The value of <code>OBEX_HTTP_RESET</code> is 0xA5 (165).
+ */
+ public static final int OBEX_HTTP_RESET = 0xA5;
+
+ /**
+ * Defines the OBEX PARTIAL CONTENT response code.
+ * <P>
+ * The value of <code>OBEX_HTTP_PARTIAL</code> is 0xA6 (166).
+ */
+ public static final int OBEX_HTTP_PARTIAL = 0xA6;
+
+ /**
+ * Defines the OBEX MULTIPLE_CHOICES response code.
+ * <P>
+ * The value of <code>OBEX_HTTP_MULT_CHOICE</code> is 0xB0 (176).
+ */
+ public static final int OBEX_HTTP_MULT_CHOICE = 0xB0;
+
+ /**
+ * Defines the OBEX MOVED PERMANENTLY response code.
+ * <P>
+ * The value of <code>OBEX_HTTP_MOVED_PERM</code> is 0xB1 (177).
+ */
+ public static final int OBEX_HTTP_MOVED_PERM = 0xB1;
+
+ /**
+ * Defines the OBEX MOVED TEMPORARILY response code.
+ * <P>
+ * The value of <code>OBEX_HTTP_MOVED_TEMP</code> is 0xB2 (178).
+ */
+ public static final int OBEX_HTTP_MOVED_TEMP = 0xB2;
+
+ /**
+ * Defines the OBEX SEE OTHER response code.
+ * <P>
+ * The value of <code>OBEX_HTTP_SEE_OTHER</code> is 0xB3 (179).
+ */
+ public static final int OBEX_HTTP_SEE_OTHER = 0xB3;
+
+ /**
+ * Defines the OBEX NOT MODIFIED response code.
+ * <P>
+ * The value of <code>OBEX_HTTP_NOT_MODIFIED</code> is 0xB4 (180).
+ */
+ public static final int OBEX_HTTP_NOT_MODIFIED = 0xB4;
+
+ /**
+ * Defines the OBEX USE PROXY response code.
+ * <P>
+ * The value of <code>OBEX_HTTP_USE_PROXY</code> is 0xB5 (181).
+ */
+ public static final int OBEX_HTTP_USE_PROXY = 0xB5;
+
+ /**
+ * Defines the OBEX BAD REQUEST response code.
+ * <P>
+ * The value of <code>OBEX_HTTP_BAD_REQUEST</code> is 0xC0 (192).
+ */
+ public static final int OBEX_HTTP_BAD_REQUEST = 0xC0;
+
+ /**
+ * Defines the OBEX UNAUTHORIZED response code.
+ * <P>
+ * The value of <code>OBEX_HTTP_UNAUTHORIZED</code> is 0xC1 (193).
+ */
+ public static final int OBEX_HTTP_UNAUTHORIZED = 0xC1;
+
+ /**
+ * Defines the OBEX PAYMENT REQUIRED response code.
+ * <P>
+ * The value of <code>OBEX_HTTP_PAYMENT_REQUIRED</code> is 0xC2 (194).
+ */
+ public static final int OBEX_HTTP_PAYMENT_REQUIRED = 0xC2;
+
+ /**
+ * Defines the OBEX FORBIDDEN response code.
+ * <P>
+ * The value of <code>OBEX_HTTP_FORBIDDEN</code> is 0xC3 (195).
+ */
+ public static final int OBEX_HTTP_FORBIDDEN = 0xC3;
+
+ /**
+ * Defines the OBEX NOT FOUND response code.
+ * <P>
+ * The value of <code>OBEX_HTTP_NOT_FOUND</code> is 0xC4 (196).
+ */
+ public static final int OBEX_HTTP_NOT_FOUND = 0xC4;
+
+ /**
+ * Defines the OBEX METHOD NOT ALLOWED response code.
+ * <P>
+ * The value of <code>OBEX_HTTP_BAD_METHOD</code> is 0xC5 (197).
+ */
+ public static final int OBEX_HTTP_BAD_METHOD = 0xC5;
+
+ /**
+ * Defines the OBEX NOT ACCEPTABLE response code.
+ * <P>
+ * The value of <code>OBEX_HTTP_NOT_ACCEPTABLE</code> is 0xC6 (198).
+ */
+ public static final int OBEX_HTTP_NOT_ACCEPTABLE = 0xC6;
+
+ /**
+ * Defines the OBEX PROXY AUTHENTICATION REQUIRED response code.
+ * <P>
+ * The value of <code>OBEX_HTTP_PROXY_AUTH</code> is 0xC7 (199).
+ */
+ public static final int OBEX_HTTP_PROXY_AUTH = 0xC7;
+
+ /**
+ * Defines the OBEX REQUEST TIME OUT response code.
+ * <P>
+ * The value of <code>OBEX_HTTP_TIMEOUT</code> is 0xC8 (200).
+ */
+ public static final int OBEX_HTTP_TIMEOUT = 0xC8;
+
+ /**
+ * Defines the OBEX METHOD CONFLICT response code.
+ * <P>
+ * The value of <code>OBEX_HTTP_CONFLICT</code> is 0xC9 (201).
+ */
+ public static final int OBEX_HTTP_CONFLICT = 0xC9;
+
+ /**
+ * Defines the OBEX METHOD GONE response code.
+ * <P>
+ * The value of <code>OBEX_HTTP_GONE</code> is 0xCA (202).
+ */
+ public static final int OBEX_HTTP_GONE = 0xCA;
+
+ /**
+ * Defines the OBEX METHOD LENGTH REQUIRED response code.
+ * <P>
+ * The value of <code>OBEX_HTTP_LENGTH_REQUIRED</code> is 0xCB (203).
+ */
+ public static final int OBEX_HTTP_LENGTH_REQUIRED = 0xCB;
+
+ /**
+ * Defines the OBEX PRECONDITION FAILED response code.
+ * <P>
+ * The value of <code>OBEX_HTTP_PRECON_FAILED</code> is 0xCC (204).
+ */
+ public static final int OBEX_HTTP_PRECON_FAILED = 0xCC;
+
+ /**
+ * Defines the OBEX REQUESTED ENTITY TOO LARGE response code.
+ * <P>
+ * The value of <code>OBEX_HTTP_ENTITY_TOO_LARGE</code> is 0xCD (205).
+ */
+ public static final int OBEX_HTTP_ENTITY_TOO_LARGE = 0xCD;
+
+ /**
+ * Defines the OBEX REQUESTED URL TOO LARGE response code.
+ * <P>
+ * The value of <code>OBEX_HTTP_REQ_TOO_LARGE</code> is 0xCE (206).
+ */
+ public static final int OBEX_HTTP_REQ_TOO_LARGE = 0xCE;
+
+ /**
+ * Defines the OBEX UNSUPPORTED MEDIA TYPE response code.
+ * <P>
+ * The value of <code>OBEX_HTTP_UNSUPPORTED_TYPE</code> is 0xCF (207).
+ */
+ public static final int OBEX_HTTP_UNSUPPORTED_TYPE = 0xCF;
+
+ /**
+ * Defines the OBEX INTERNAL SERVER ERROR response code.
+ * <P>
+ * The value of <code>OBEX_HTTP_INTERNAL_ERROR</code> is 0xD0 (208).
+ */
+ public static final int OBEX_HTTP_INTERNAL_ERROR = 0xD0;
+
+ /**
+ * Defines the OBEX NOT IMPLEMENTED response code.
+ * <P>
+ * The value of <code>OBEX_HTTP_NOT_IMPLEMENTED</code> is 0xD1 (209).
+ */
+ public static final int OBEX_HTTP_NOT_IMPLEMENTED = 0xD1;
+
+ /**
+ * Defines the OBEX BAD GATEWAY response code.
+ * <P>
+ * The value of <code>OBEX_HTTP_BAD_GATEWAY</code> is 0xD2 (210).
+ */
+ public static final int OBEX_HTTP_BAD_GATEWAY = 0xD2;
+
+ /**
+ * Defines the OBEX SERVICE UNAVAILABLE response code.
+ * <P>
+ * The value of <code>OBEX_HTTP_UNAVAILABLE</code> is 0xD3 (211).
+ */
+ public static final int OBEX_HTTP_UNAVAILABLE = 0xD3;
+
+ /**
+ * Defines the OBEX GATEWAY TIMEOUT response code.
+ * <P>
+ * The value of <code>OBEX_HTTP_GATEWAY_TIMEOUT</code> is 0xD4 (212).
+ */
+ public static final int OBEX_HTTP_GATEWAY_TIMEOUT = 0xD4;
+
+ /**
+ * Defines the OBEX HTTP VERSION NOT SUPPORTED response code.
+ * <P>
+ * The value of <code>OBEX_HTTP_VERSION</code> is 0xD5 (213).
+ */
+ public static final int OBEX_HTTP_VERSION = 0xD5;
+
+ /**
+ * Defines the OBEX DATABASE FULL response code.
+ * <P>
+ * The value of <code>OBEX_DATABASE_FULL</code> is 0xE0 (224).
+ */
+ public static final int OBEX_DATABASE_FULL = 0xE0;
+
+ /**
+ * Defines the OBEX DATABASE LOCKED response code.
+ * <P>
+ * The value of <code>OBEX_DATABASE_LOCKED</code> is 0xE1 (225).
+ */
+ public static final int OBEX_DATABASE_LOCKED = 0xE1;
+
+ /**
+ * Constructor does nothing.
+ */
+ private ResponseCodes() {
+ 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.
+ * <P>
+ * <STRONG>Request Codes</STRONG>
+ * There are four different request codes that are in this class. 0x02 is a
+ * PUT request that signals that the request is not complete and requires an
+ * additional OBEX packet. 0x82 is a PUT request that says that request is
+ * complete. In this case, the server can begin sending the response. The
+ * 0x03 is a GET request that signals that the request is not finished. When
+ * the server receives a 0x83, the client is signalling the server that it is
+ * done with its request.
+ *
+ * OPTIMIZATION: Extend the ClientOperation and reuse the methods defined
+ * OPTIMIZATION: in that class.
+ *
+ * @version 0.3 November 28, 2008
+ */
+public class ServerOperation implements Operation, BaseStream {
+
+ private InputStream socketInput;
+
+ private ServerSession parent;
+
+ private int maxPacketLength;
+
+ private int responseSize;
+
+ private boolean isClosed;
+
+ boolean finalBitSet;
+
+ private boolean endOfBody; // This variable defines when the end of body
+
+ // header has been received. When this header
+ // is received, no further body data will be
+ // received from the client
+ private boolean isGet;
+
+ boolean isAborted;
+
+ HeaderSet requestHeaders;
+
+ HeaderSet replyHeaders;
+
+ PrivateInputStream privateInput;
+
+ private PrivateOutputStream privateOutput;
+
+ private String exceptionString;
+
+ private ServerRequestHandler listener;
+
+ private boolean outputStreamOpened;
+
+ private boolean requestFinished;
+
+ private static int BASE_PACKET_LENGTH = 3;
+
+ private static final String TAG = "ServerOperation";
+
+ private boolean isHasBody;
+
+ /**
+ * Creates new PutServerOperation
+ *
+ * @param p the parent that created this object
+ *
+ * @param in the input stream to read from
+ *
+ * @param out the output stream to write to
+ *
+ * @param request the initial request that was received from the client
+ *
+ * @param maxSize the max packet size that the client will accept
+ *
+ * @param listen the listener that is responding to the request
+ *
+ * @exception IOException if an IO error occurs
+ */
+ public ServerOperation(ServerSession p, InputStream in, int request, int maxSize,
+ ServerRequestHandler listen) throws IOException {
+
+ isAborted = false;
+ parent = p;
+ socketInput = in;
+ maxPacketLength = maxSize;
+ isClosed = false;
+ requestHeaders = new HeaderSet();
+ replyHeaders = new HeaderSet();
+ privateInput = new PrivateInputStream(this);
+ endOfBody = false;
+ responseSize = 3;
+ listener = listen;
+ requestFinished = false;
+ outputStreamOpened = false;
+ isHasBody = false;
+ int bytesReceived;
+
+ /*
+ * Determine if this is a PUT request
+ */
+ if ((request == 0x02) || (request == 0x82)) {
+ /*
+ * It is a PUT request.
+ */
+ isGet = false;
+ } else {
+ /*
+ * It is a GET request.
+ */
+ isGet = true;
+ }
+
+ /*
+ * Determine if the final bit is set
+ */
+ if ((request & 0x80) == 0) {
+ finalBitSet = false;
+ } else {
+ finalBitSet = true;
+ requestFinished = true;
+ }
+
+ int length = in.read();
+ length = (length << 8) + in.read();
+
+ /*
+ * Determine if the packet length is larger than this device can receive
+ */
+ if (length > OBEXConstants.MAX_PACKET_SIZE_INT) {
+ parent.sendResponse(ResponseCodes.OBEX_HTTP_REQ_TOO_LARGE, null);
+ throw new IOException("Packet received was too large");
+ }
+
+ /*
+ * Determine if any headers were sent in the initial request
+ */
+ if (length > 3) {
+ byte[] data = new byte[length - 3];
+ bytesReceived = in.read(data);
+
+ while (bytesReceived != data.length) {
+ bytesReceived += in.read(data, bytesReceived, data.length - bytesReceived);
+ }
+
+ byte[] body = OBEXHelper.updateHeaderSet(requestHeaders, data);
+
+ if (body != null) {
+ isHasBody = true;
+ }
+
+ if (requestHeaders.connectionID != null) {
+ listener.setConnectionID(OBEXHelper.convertToLong(requestHeaders.connectionID));
+ } else {
+ listener.setConnectionID(0);
+ }
+
+ if (requestHeaders.authResp != null) {
+ if (!parent.handleAuthResp(requestHeaders.authResp)) {
+ exceptionString = "Authentication Failed";
+ parent.sendResponse(ResponseCodes.OBEX_HTTP_UNAUTHORIZED, null);
+ isClosed = true;
+ requestHeaders.authResp = null;
+ return;
+ }
+ }
+
+ if (requestHeaders.authChall != null) {
+ parent.handleAuthChall(requestHeaders);
+ // send the authResp to the client
+ replyHeaders.authResp = new byte[requestHeaders.authResp.length];
+ System.arraycopy(requestHeaders.authResp, 0, replyHeaders.authResp, 0,
+ replyHeaders.authResp.length);
+ requestHeaders.authResp = null;
+ requestHeaders.authChall = null;
+
+ }
+
+ if (body != null) {
+ /*
+ * 0x49 is the end of body header. This signifies that no more
+ * body data will be sent from the client
+ */
+ if (body[0] == 0x49) {
+ endOfBody = true;
+ }
+ //privateInput.writeBytes(body, body.length);
+ //byte [] body_tmp = new byte[body.length-1];
+ //System.arraycopy(body,1,body_tmp,0,body.length-1);
+ //privateInput.writeBytes(body_tmp, body.length-1);
+ privateInput.writeBytes(body, 1);
+ } else {
+ while ((!isGet) && (!finalBitSet)) {
+ sendReply(OBEXConstants.OBEX_HTTP_CONTINUE);
+ if (privateInput.available() > 0) {
+ break;
+ }
+ }
+ }// if (body != null)
+
+ }// if (length > 3)
+
+ while ((!isGet) && (!finalBitSet) && (privateInput.available() == 0)) {
+ sendReply(OBEXConstants.OBEX_HTTP_CONTINUE);
+ if (privateInput.available() > 0) {
+ break;
+ }
+ }
+
+ // wait for get request finished !!!!
+ while (isGet && !finalBitSet) {
+ sendReply(OBEXConstants.OBEX_HTTP_CONTINUE);
+ }
+ if (finalBitSet && isGet) {
+ requestFinished = true;
+ }
+ }
+
+ public synchronized boolean isValidBody() {
+ return isHasBody;
+ }
+
+ /**
+ * Determines if the operation should continue or should wait. If it
+ * should continue, this method will continue the operation.
+ *
+ * @param sendEmpty if <code>true</code> then this will continue the
+ * operation even if no headers will be sent; if <code>false</code> then
+ * this method will only continue the operation if there are headers to
+ * send
+ * @param isStream if<code>true</code> the stream is input stream or
+ * is outputstream
+ * @return <code>true</code> if the operation was completed;
+ * <code>false</code> if no operation took place
+ */
+ public synchronized boolean continueOperation(boolean sendEmpty, boolean inStream)
+ throws IOException {
+ if (!isGet) {
+ if (!finalBitSet) {
+ if (sendEmpty) {
+ sendReply(OBEXConstants.OBEX_HTTP_CONTINUE);
+ return true;
+ } else {
+ if ((responseSize > 3) || (privateOutput.size() > 0)) {
+ sendReply(OBEXConstants.OBEX_HTTP_CONTINUE);
+ return true;
+ } else {
+ return false;
+ }
+ }
+ } else {
+ return false;
+ }
+ } else {
+ sendReply(OBEXConstants.OBEX_HTTP_CONTINUE);
+ return true;
+ }
+ }
+
+ /**
+ * Sends a reply to the client. If the reply is a OBEX_HTTP_CONTINUE, it
+ * will wait for a response from the client before ending.
+ *
+ * @param type the response code to send back to the client
+ *
+ * @return <code>true</code> if the final bit was not set on the reply;
+ * <code>false</code> if no reply was received because the operation ended,
+ * an abort was received, or the final bit was set in the reply
+ *
+ * @exception IOException if an IO error occurs
+ */
+ protected synchronized boolean sendReply(int type) throws IOException {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ int bytesReceived;
+
+ long id = listener.getConnectionID();
+ if (id == -1) {
+ replyHeaders.connectionID = null;
+ } else {
+ replyHeaders.connectionID = OBEXHelper.convertToByteArray(id);
+ }
+
+ byte[] headerArray = OBEXHelper.createHeader(replyHeaders, true);
+ int bodyLength = -1;
+ int orginalBodyLength = -1;
+
+ if (privateOutput != null) {
+ bodyLength = privateOutput.size();
+ orginalBodyLength = bodyLength;
+ }
+
+ if ((BASE_PACKET_LENGTH + headerArray.length) > maxPacketLength) {
+
+ int end = 0;
+ int start = 0;
+
+ while (end != headerArray.length) {
+ end = OBEXHelper.findHeaderEnd(headerArray, start, maxPacketLength
+ - BASE_PACKET_LENGTH);
+ if (end == -1) {
+
+ isClosed = true;
+
+ if (privateInput != null) {
+ privateInput.close();
+ }
+
+ if (privateOutput != null) {
+ privateOutput.close();
+ }
+ parent.sendResponse(ResponseCodes.OBEX_HTTP_INTERNAL_ERROR, null);
+ throw new IOException("OBEX Packet exceeds max packet size");
+ }
+ byte[] sendHeader = new byte[end - start];
+ System.arraycopy(headerArray, start, sendHeader, 0, sendHeader.length);
+
+ parent.sendResponse(type, sendHeader);
+ start = end;
+ }
+
+ if (bodyLength > 0) {
+ return true;
+ } else {
+ return false;
+ }
+
+ } else {
+ out.write(headerArray);
+ }
+
+ /*
+ * Determine if there is space to add a body reply. First, we need to
+ * verify that the client is finished sending the request. Next, there
+ * needs to be enough space to send the headers already defined along
+ * with the reply header (3 bytes) and the body header identifier
+ * (3 bytes).
+ */
+
+ /* if ((finalBitSet) &&
+ ((bodyLength + 6 + headerArray.length) > maxPacketLength)) {
+
+ exceptionString = "Header larger then can be sent in a packet";
+ isClosed = true;
+ privateInput.close();
+ if (privateOutput != null) {
+ privateOutput.close();
+ }
+ parent.sendResponse(ResponseCodes.OBEX_HTTP_INTERNAL_ERROR,
+ null);
+ throw new IOException("OBEX Packet exceeds max packet size");
+ }
+ */
+
+ if ((finalBitSet) || (headerArray.length < (maxPacketLength - 20))) {
+ if (bodyLength > 0) {
+ /*
+ * Determine if I can send the whole body or just part of
+ * the body. Remember that there is the 3 bytes for the
+ * response message and 3 bytes for the header ID and length
+ */
+ if (bodyLength > (maxPacketLength - headerArray.length - 6)) {
+ bodyLength = maxPacketLength - headerArray.length - 6;
+ }
+
+ byte[] body = privateOutput.readBytes(bodyLength);
+
+ /*
+ * Since this is a put request if the final bit is set or
+ * the output stream is closed we need to send the 0x49
+ * (End of Body) otherwise, we need to send 0x48 (Body)
+ */
+ if ((finalBitSet) || (privateOutput.isClosed())) {
+ out.write(0x49);
+ } else {
+ out.write(0x48);
+ }
+
+ bodyLength += 3;
+ out.write((byte)(bodyLength >> 8));
+ out.write((byte)bodyLength);
+ out.write(body);
+ }
+ }
+
+ if ((finalBitSet) && (type == ResponseCodes.OBEX_HTTP_OK) && (orginalBodyLength <= 0)) {
+ out.write(0x49);
+ orginalBodyLength = 3;
+ out.write((byte)(orginalBodyLength >> 8));
+ out.write((byte)orginalBodyLength);
+
+ }
+
+ responseSize = 3;
+ parent.sendResponse(type, out.toByteArray());
+
+ if (type == OBEXConstants.OBEX_HTTP_CONTINUE) {
+ int headerID = socketInput.read();
+ int length = socketInput.read();
+ length = (length << 8) + socketInput.read();
+ if ((headerID != 0x02) && (headerID != 0x82) && (headerID != 0x03)
+ && (headerID != 0x83)) {
+
+ if (length > 3) {
+ byte[] temp = new byte[length];
+ bytesReceived = socketInput.read(temp);
+
+ while (bytesReceived != length) {
+ bytesReceived += socketInput.read(temp, bytesReceived, length
+ - bytesReceived);
+ }
+ }
+
+ /*
+ * Determine if an ABORT was sent as the reply
+ */
+ if (headerID == 0xFF) {
+ parent.sendResponse(ResponseCodes.OBEX_HTTP_OK, null);
+ isClosed = true;
+ isAborted = true;
+ exceptionString = "Abort Received";
+ throw new IOException("Abort Received");
+ } else {
+ parent.sendResponse(ResponseCodes.OBEX_HTTP_BAD_REQUEST, null);
+ isClosed = true;
+ exceptionString = "Bad Request Received";
+ throw new IOException("Bad Request Received");
+ }
+ } else {
+
+ if ((headerID == 0x82) || (headerID == 0x83)) {
+ finalBitSet = true;
+ }
+
+ /*
+ * Determine if the packet length is larger then this device can receive
+ */
+ if (length > OBEXConstants.MAX_PACKET_SIZE_INT) {
+ parent.sendResponse(ResponseCodes.OBEX_HTTP_REQ_TOO_LARGE, null);
+ throw new IOException("Packet received was too large");
+ }
+
+ /*
+ * Determine if any headers were sent in the initial request
+ */
+ if (length > 3) {
+ byte[] data = new byte[length - 3];
+ bytesReceived = socketInput.read(data);
+
+ while (bytesReceived != data.length) {
+ bytesReceived += socketInput.read(data, bytesReceived, data.length
+ - bytesReceived);
+ }
+ byte[] body = OBEXHelper.updateHeaderSet(requestHeaders, data);
+ if (body != null) {
+ isHasBody = true;
+ }
+ if (requestHeaders.connectionID != null) {
+ listener.setConnectionID(OBEXHelper
+ .convertToLong(requestHeaders.connectionID));
+ } else {
+ listener.setConnectionID(1);
+ }
+
+ if (requestHeaders.authResp != null) {
+ if (!parent.handleAuthResp(requestHeaders.authResp)) {
+ exceptionString = "Authentication Failed";
+ parent.sendResponse(ResponseCodes.OBEX_HTTP_UNAUTHORIZED, null);
+ isClosed = true;
+ requestHeaders.authResp = null;
+ return false;
+ }
+ requestHeaders.authResp = null;
+ }
+
+ if (requestHeaders.authChall != null) {
+ parent.handleAuthChall(requestHeaders);
+ // send the auhtResp to the client
+ replyHeaders.authResp = new byte[requestHeaders.authResp.length];
+ System.arraycopy(requestHeaders.authResp, 0, replyHeaders.authResp, 0,
+ replyHeaders.authResp.length);
+ requestHeaders.authResp = null;
+ requestHeaders.authChall = null;
+ }
+
+ if (body != null) {
+ if (body[0] == 0x49) {
+ endOfBody = true;
+ }
+
+ /*byte [] body_tmp = new byte[body.length-1];
+ System.arraycopy(body,1,body_tmp,0,body.length-1);
+ privateInput.writeBytes(body_tmp, body.length-1);*/
+ privateInput.writeBytes(body, 1);
+
+ }
+ }
+ }
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Sends an ABORT message to the server. By calling this method, the
+ * corresponding input and output streams will be closed along with this
+ * object.
+ *
+ * @exception IOException if the transaction has already ended or if an
+ * OBEX server called this method
+ */
+ public void abort() throws IOException {
+ throw new IOException("Called from a server");
+ }
+
+ /**
+ * Returns the headers that have been received during the operation.
+ * Modifying the object returned has no effect on the headers that are
+ * sent or retrieved.
+ *
+ * @return the headers received during this <code>Operation</code>
+ *
+ * @exception IOException if this <code>Operation</code> has been closed
+ */
+ public HeaderSet getReceivedHeaders() throws IOException {
+ ensureOpen();
+ return requestHeaders;
+ }
+
+ /**
+ * Specifies the headers that should be sent in the next OBEX message that
+ * is sent.
+ *
+ * @param headers the headers to send in the next message
+ *
+ * @exception IOException if this <code>Operation</code> has been closed
+ * or the transaction has ended and no further messages will be exchanged
+ *
+ * @exception IllegalArgumentException if <code>headers</code> was not created
+ * by a call to <code>ServerRequestHandler.createHeaderSet()</code>
+ */
+ public void sendHeaders(HeaderSet headers) throws IOException {
+ ensureOpen();
+
+ if (headers == null) {
+ throw new NullPointerException("Headers may not be null");
+ }
+
+ int[] headerList = headers.getHeaderList();
+ if (headerList != null) {
+ for (int i = 0; i < headerList.length; i++) {
+ replyHeaders.setHeader(headerList[i], headers.getHeader(headerList[i]));
+ }
+
+ }
+ }
+
+ /**
+ * Retrieves the response code retrieved from the server. Response codes
+ * are defined in the <code>ResponseCodes</code> interface.
+ *
+ * @return the response code retrieved from the server
+ *
+ * @exception IOException if an error occurred in the transport layer during
+ * the transaction; if this method is called on a <code>HeaderSet</code>
+ * object created by calling <code>createHeaderSet</code> in a
+ * <code>ClientSession</code> object; if this is called from a server
+ */
+ public int getResponseCode() throws IOException {
+ throw new IOException("Called from a server");
+ }
+
+ /**
+ * Always returns <code>null</code>
+ *
+ * @return <code>null</code>
+ */
+ public String getEncoding() {
+ return null;
+ }
+
+ /**
+ * Returns the type of content that the resource connected to is providing.
+ * E.g. if the connection is via HTTP, then the value of the content-type
+ * header field is returned.
+ *
+ * @return the content type of the resource that the URL references, or
+ * <code>null</code> if not known
+ */
+ public String getType() {
+ try {
+ return (String)requestHeaders.getHeader(HeaderSet.TYPE);
+ } catch (IOException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Returns the length of the content which is being provided. E.g. if the
+ * connection is via HTTP, then the value of the content-length header
+ * field is returned.
+ *
+ * @return the content length of the resource that this connection's URL
+ * references, or -1 if the content length is not known
+ */
+ public long getLength() {
+ try {
+ Long temp = (Long)requestHeaders.getHeader(HeaderSet.LENGTH);
+
+ if (temp == null) {
+ return -1;
+ } else {
+ return temp.longValue();
+ }
+ } catch (IOException e) {
+ return -1;
+ }
+ }
+
+ public int getMaxPacketSize() {
+ return maxPacketLength - 6;
+ }
+
+ /**
+ * Open and return an input stream for a connection.
+ *
+ * @return an input stream
+ *
+ * @exception IOException if an I/O error occurs
+ */
+ public InputStream openInputStream() throws IOException {
+ ensureOpen();
+ return privateInput;
+ }
+
+ /**
+ * Open and return a data input stream for a connection.
+ *
+ * @return an input stream
+ *
+ * @exception IOException if an I/O error occurs
+ */
+ public DataInputStream openDataInputStream() throws IOException {
+ return new DataInputStream(openInputStream());
+ }
+
+ /**
+ * Open and return an output stream for a connection.
+ *
+ * @return an output stream
+ *
+ * @exception IOException if an I/O error occurs
+ */
+ public OutputStream openOutputStream() throws IOException {
+ ensureOpen();
+
+ if (outputStreamOpened)
+ throw new IOException("no more input streams available, stream already opened");
+
+ if (!requestFinished)
+ throw new IOException("no output streams available ,request not finished");
+
+ if (privateOutput == null) {
+ privateOutput = new PrivateOutputStream(this, maxPacketLength - 6);
+ }
+ outputStreamOpened = true;
+ return privateOutput;
+ }
+
+ /**
+ * Open and return a data output stream for a connection.
+ *
+ * @return an output stream
+ *
+ * @exception IOException if an I/O error occurs
+ */
+ public DataOutputStream openDataOutputStream() throws IOException {
+ return new DataOutputStream(openOutputStream());
+ }
+
+ /**
+ * Closes the connection and ends the transaction
+ *
+ * @exception IOException if the operation has already ended or is closed
+ */
+ public void close() throws IOException {
+ ensureOpen();
+ isClosed = true;
+ }
+
+ /**
+ * Verifies that the connection is open and no exceptions should be thrown.
+ *
+ * @exception IOException if an exception needs to be thrown
+ */
+ public void ensureOpen() throws IOException {
+ if (exceptionString != null) {
+ throw new IOException(exceptionString);
+ }
+ if (isClosed) {
+ throw new IOException("Operation has already ended");
+ }
+ }
+
+ /**
+ * Verifies that additional information may be sent. In other words, the
+ * operation is not done.
+ * <P>
+ * Included to implement the BaseStream interface only. It does not do
+ * anything on the server side since the operation of the Operation object
+ * is not done until after the handler returns from its method.
+ *
+ * @exception IOException if the operation is completed
+ */
+ public void ensureNotDone() throws IOException {
+ }
+
+ /**
+ * Called when the output or input stream is closed. It does not do
+ * anything on the server side since the operation of the Operation object
+ * is not done until after the handler returns from its method.
+ *
+ * @param inStream <code>true</code> if the input stream is closed;
+ * <code>false</code> if the output stream is closed
+ *
+ * @exception IOException if an IO error occurs
+ */
+ public void streamClosed(boolean inStream) throws IOException {
+
+ }
+}
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 <code>ServerRequestHandler</code> class defines an event
+ * listener that will respond to OBEX requests made to the server.
+ * <P>
+ * The <code>onConnect()</code>, <code>onSetPath()</code>, <code>onDelete()</code>,
+ * <code>onGet()</code>,
+ * and <code>onPut()</code> methods may return any response code defined
+ * in the <code>ResponseCodes</code> class except for
+ * <code>OBEX_HTTP_CONTINUE</code>. If <code>OBEX_HTTP_CONTINUE</code> or
+ * a value not defined in the <code>ResponseCodes</code> class is returned,
+ * the server implementation will send an <code>OBEX_HTTP_INTERNAL_ERROR</code>
+ * response to the client.
+ * <P>
+ * <STRONG>Connection ID and Target Headers</STRONG>
+ * <P>
+ * According to the IrOBEX specification, a packet may not contain a Connection
+ * ID and Target header. Since the Connection ID header is managed by the
+ * implementation, it will not send a Connection ID header, if a Connection ID
+ * was specified, in a packet that has a Target header. In other words, if an
+ * application adds a Target header to a <code>HeaderSet</code> object used
+ * in an OBEX operation and a Connection ID was specified, no Connection ID
+ * will be sent in the packet containing the Target header.
+ * <P>
+ * <STRONG>CREATE-EMPTY Requests</STRONG>
+ * <P>
+ * A CREATE-EMPTY request allows clients to create empty objects on the server.
+ * When a CREATE-EMPTY request is received, the <code>onPut()</code> method
+ * will be called by the implementation. To differentiate between a normal
+ * PUT request and a CREATE-EMPTY request, an application must open the
+ * <code>InputStream</code> from the <code>Operation</code> object passed
+ * to the <code>onPut()</code> method. For a PUT request, the application
+ * will be able to read Body data from this <code>InputStream</code>. For
+ * a CREATE-EMPTY request, there will be no Body data to read. Therefore,
+ * a call to <code>InputStream.read()</code> will return -1.
+ *
+ * @version 0.3 November 28, 2008
+ */
+public class ServerRequestHandler {
+
+ private long connectionID;
+
+ /**
+ * Creates a <code>ServerRequestHandler</code>.
+ */
+ protected ServerRequestHandler() {
+ /*
+ * A connection ID of -1 implies there is no conenction ID
+ */
+ connectionID = -1;
+ }
+
+ /**
+ * Creates a <code>HeaderSet</code> object that may be used in put and get
+ * operations.
+ *
+ * @return the <code>HeaderSet</code> 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 <code>id</code> is not in the
+ * range -1 to 2<sup>32</sup>-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.
+ * <P>
+ * If this method is not implemented by the class that extends this
+ * class, <code>onConnect()</code> will always return an
+ * <code>OBEX_HTTP_OK</code> response code.
+ * <P>
+ * The headers received in the request can be retrieved from the
+ * <code>request</code> argument. The headers that should be sent
+ * in the reply must be specified in the <code>reply</code> argument.
+ *
+ * @param request contains the headers sent by the client;
+ * <code>request</code> will never be <code>null</code>
+ *
+ * @param reply the headers that should be sent in the reply;
+ * <code>reply</code> will never be <code>null</code>
+ *
+ * @return a response code defined in <code>ResponseCodes</code> that will be
+ * returned to the client; if an invalid response code is provided, the
+ * <code>OBEX_HTTP_INTERNAL_ERROR</code> response code will be used
+ */
+ public int onConnect(HeaderSet request, HeaderSet reply) {
+ return ResponseCodes.OBEX_HTTP_OK;
+ }
+
+ /**
+ * Called when a DISCONNECT request is received.
+ * <P>
+ * The headers received in the request can be retrieved from the
+ * <code>request</code> argument. The headers that should be sent
+ * in the reply must be specified in the <code>reply</code> argument.
+ *
+ * @param request contains the headers sent by the client;
+ * <code>request</code> will never be <code>null</code>
+ *
+ * @param reply the headers that should be sent in the reply;
+ * <code>reply</code> will never be <code>null</code>
+ */
+ public void onDisconnect(HeaderSet request, HeaderSet reply) {
+ }
+
+ /**
+ * Called when a SETPATH request is received.
+ * <P>
+ * If this method is not implemented by the class that extends this
+ * class, <code>onSetPath()</code> will always return an
+ * <code>OBEX_HTTP_NOT_IMPLEMENTED</code> response code.
+ * <P>
+ * The headers received in the request can be retrieved from the
+ * <code>request</code> argument. The headers that should be sent
+ * in the reply must be specified in the <code>reply</code> argument.
+ *
+ * @param request contains the headers sent by the client;
+ * <code>request</code> will never be <code>null</code>
+ *
+ * @param reply the headers that should be sent in the reply;
+ * <code>reply</code> will never be <code>null</code>
+ *
+ * @param backup <code>true</code> if the client requests that the server
+ * back up one directory before changing to the path described by
+ * <code>name</code>; <code>false</code> to apply the request to the present
+ * path
+ *
+ * @param create <code>true</code> if the path should be created if it does
+ * not already exist; <code>false</code> if the path should not be created
+ * if it does not exist and an error code should be returned
+ *
+ * @return a response code defined in <code>ResponseCodes</code> that will be
+ * returned to the client; if an invalid response code is provided, the
+ * <code>OBEX_HTTP_INTERNAL_ERROR</code> response code will be used
+ */
+ public int onSetPath(HeaderSet request, HeaderSet reply, boolean backup, boolean create) {
+
+ return ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED;
+ }
+
+ /**
+ * Called when a DELETE request is received.
+ * <P>
+ * If this method is not implemented by the class that extends this
+ * class, <code>onDelete()</code> will always return an
+ * <code>OBEX_HTTP_NOT_IMPLEMENTED</code> response code.
+ * <P>
+ * The headers received in the request can be retrieved from the
+ * <code>request</code> argument. The headers that should be sent
+ * in the reply must be specified in the <code>reply</code> argument.
+ *
+ * @param request contains the headers sent by the client;
+ * <code>request</code> will never be <code>null</code>
+ *
+ * @param reply the headers that should be sent in the reply;
+ * <code>reply</code> will never be <code>null</code>
+ *
+ * @return a response code defined in <code>ResponseCodes</code> that will be
+ * returned to the client; if an invalid response code is provided, the
+ * <code>OBEX_HTTP_INTERNAL_ERROR</code> response code will be used
+ */
+ public int onDelete(HeaderSet request, HeaderSet reply) {
+ return ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED;
+ }
+
+ /**
+ * Called when a PUT request is received.
+ * <P>
+ * If this method is not implemented by the class that extends this
+ * class, <code>onPut()</code> will always return an
+ * <code>OBEX_HTTP_NOT_IMPLEMENTED</code> response code.
+ * <P>
+ * If an ABORT request is received during the processing of a PUT request,
+ * <code>op</code> will be closed by the implementation.
+ *
+ * @param op contains the headers sent by the client and allows new
+ * headers to be sent in the reply; <code>op</code> will never be
+ * <code>null</code>
+ *
+ * @return a response code defined in <code>ResponseCodes</code> that will be
+ * returned to the client; if an invalid response code is provided, the
+ * <code>OBEX_HTTP_INTERNAL_ERROR</code> response code will be used
+ */
+ public int onPut(Operation op) {
+ return ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED;
+ }
+
+ /**
+ * Called when a GET request is received.
+ * <P>
+ * If this method is not implemented by the class that extends this
+ * class, <code>onGet()</code> will always return an
+ * <code>OBEX_HTTP_NOT_IMPLEMENTED</code> response code.
+ * <P>
+ * If an ABORT request is received during the processing of a GET request,
+ * <code>op</code> will be closed by the implementation.
+ *
+ * @param op contains the headers sent by the client and allows new
+ * headers to be sent in the reply; <code>op</code> will never be
+ * <code>null</code>
+ *
+ * @return a response code defined in <code>ResponseCodes</code> that will be
+ * returned to the client; if an invalid response code is provided, the
+ * <code>OBEX_HTTP_INTERNAL_ERROR</code> response code will be used
+ */
+ public int onGet(Operation 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.
+ * <P>
+ * If this method is not implemented by the class that extends this class,
+ * this method will do nothing.
+ *
+ * @param userName the user name returned in the authentication response;
+ * <code>null</code> if no user name was provided in the response
+ */
+ public void onAuthenticationFailure(byte[] userName) {
+ }
+
+ /**Called by ServerSession to update the status of current transaction */
+ 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
+ * <code>ServerOperation</code> object to the request handler. The
+ * <code>ServerOperation</code> object will handle the rest of the request.
+ * It will also send replies and receive requests until the final reply
+ * should be sent. When the final reply should be sent, this method will get
+ * the response code to use and send the reply. The
+ * <code>ServerOperation</code> object will always reply with a
+ * OBEX_HTTP_CONTINUE reply. It will only reply if further information is
+ * needed.
+ *
+ * @param type
+ * the type of request received; either 0x02 or 0x82
+ *
+ * @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
+ * <code>ServerOperation</code> object to the request handler. The
+ * <code>ServerOperation</code> object will handle the rest of the request.
+ * It will also send replies and receive requests until the final reply
+ * should be sent. When the final reply should be sent, this method will get
+ * the response code to use and send the reply. The
+ * <code>ServerOperation</code> object will always reply with a
+ * OBEX_HTTP_CONTINUE reply. It will only reply if further information is
+ * needed.
+ *
+ * @param type
+ * the type of request received; either 0x03 or 0x83
+ *
+ * @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 <code>HeaderSet</code> object to pass to the
+ * <code>ServerRequestHandler</code> object. After the handler processes the
+ * request, this method will create a reply message to send to the server
+ * with the response code provided.
+ *
+ * @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 <code>HeaderSet</code> object to pass to the
+ * <code>ServerRequestHandler</code> object. After the handler processes the
+ * request, this method will create a reply message to send to the server.
+ *
+ * @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 <code>HeaderSet</code> object to pass to the
+ * <code>ServerRequestHandler</code> object. After the handler processes the
+ * request, this method will create a reply message to send to the server
+ * with the response code provided.
+ *
+ * @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 <code>OBEX_HTTP_INTERNAL_ERROR</code> response code.
+ *
+ * @param code
+ * the response code to check
+ *
+ * @return the valid response code or <code>OBEX_HTTP_INTERNAL_ERROR</code>
+ * if <code>code</code> is not valid
+ */
+ private int validateResponseCode(int code) {
+
+ if ((code >= ResponseCodes.OBEX_HTTP_OK) && (code <= ResponseCodes.OBEX_HTTP_PARTIAL)) {
+ return code;
+ }
+ if ((code >= ResponseCodes.OBEX_HTTP_MULT_CHOICE)
+ && (code <= ResponseCodes.OBEX_HTTP_USE_PROXY)) {
+ return code;
+ }
+ if ((code >= ResponseCodes.OBEX_HTTP_BAD_REQUEST)
+ && (code <= ResponseCodes.OBEX_HTTP_UNSUPPORTED_TYPE)) {
+ return code;
+ }
+ if ((code >= ResponseCodes.OBEX_HTTP_INTERNAL_ERROR)
+ && (code <= ResponseCodes.OBEX_HTTP_VERSION)) {
+ return code;
+ }
+ if ((code >= ResponseCodes.OBEX_DATABASE_FULL)
+ && (code <= ResponseCodes.OBEX_DATABASE_LOCKED)) {
+ return code;
+ }
+ return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
+ }
+
+ /**
+ * Called when the server received an authentication challenge header. This
+ * will cause the authenticator to handle the authentication challenge.
+ *
+ * @param header
+ * the header with the authentication challenge
+ *
+ * @return <code>true</code> if the last request should be resent;
+ * <code>false</code> if the last request should not be resent
+ */
+ 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 <code>true</code> if the response passed; <code>false</code> 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 <code>SessionNotifier</code> interface defines a connection notifier for
+ * server-side OBEX connections. When a <code>SessionNotifier</code> is
+ * created and calls <code>acceptAndOpen()</code>, it will begin listening for
+ * clients to create a connection at the transport layer. When the transport
+ * layer connection is received, the <code>acceptAndOpen()</code> method will
+ * return a <code>javax.microedition.io.Connection</code> that is the
+ * connection to the client. The <code>acceptAndOpen()</code> method also takes a
+ * <code>ServerRequestHandler</code> argument that will process the requests
+ * from the client that connects to the server.
+ *
+ * @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.
+ * <P>
+ * <H4>Additional Note for OBEX over Bluetooth</H4>
+ * If this method is called on a <code>SessionNotifier</code> object that
+ * does not have a <code>ServiceRecord</code> in the SDDB, the
+ * <code>ServiceRecord</code> for this object will be added to the SDDB.
+ * This method requests the BCC to put the
+ * local device in connectable mode so that it will respond to
+ * connection attempts by clients.
+ * <P>
+ * The following checks are done to verify that the service record
+ * provided is valid. If any of these checks fail, then a
+ * <code>ServiceRegistrationException</code> is thrown.
+ * <UL>
+ * <LI>ServiceClassIDList and ProtocolDescriptorList, the mandatory
+ * service attributes for a <code>btgoep</code> service record, must be
+ * present in the <code>ServiceRecord</code> associated with this notifier.
+ * <LI>L2CAP, RFCOMM and OBEX must all be in the ProtocolDescriptorList
+ * <LI>The <code>ServiceRecord</code> associated with this notifier must
+ * not have changed the RFCOMM server channel number
+ * </UL>
+ * <P>
+ * This method will not ensure that <code>ServiceRecord</code> associated
+ * with this notifier is a completely
+ * valid service record. It is the responsibility of the application to
+ * ensure that the service record follows all of the applicable
+ * syntactic and semantic rules for service record correctness.
+ *
+ * @param handler the request handler that will respond to OBEX requests
+ *
+ * @return the connection to the client
+ *
+ * @exception IOException if an error occurs in the transport layer
+ *
+ * @exception NullPointerException if <code>handler</code> is
+ * <code>null</code>
+ *
+ * @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
+ * <code>Authenticator</code> to use to respond to authentication challenge
+ * and authentication response headers.
+ * <P>
+ * <H4>Additional Note for OBEX over Bluetooth</H4>
+ * If this method is called on a <code>SessionNotifier</code> object that
+ * does not have a <code>ServiceRecord</code> in the SDDB, the
+ * <code>ServiceRecord</code> for this object will be added to the SDDB.
+ * This method requests the BCC to put the
+ * local device in connectable mode so that it will respond to
+ * connection attempts by clients.
+ * <P>
+ * The following checks are done to verify that the service record
+ * provided is valid. If any of these checks fail, then a
+ * <code>ServiceRegistrationException</code> is thrown.
+ * <UL>
+ * <LI>ServiceClassIDList and ProtocolDescriptorList, the mandatory
+ * service attributes for a <code>btgoep</code> service record, must be
+ * present in the <code>ServiceRecord</code> associated with this notifier.
+ * <LI>L2CAP, RFCOMM and OBEX must all be in the ProtocolDescriptorList
+ * <LI>The <code>ServiceRecord</code> associated with this notifier must
+ * not have changed the RFCOMM server channel number
+ * </UL>
+ * <P>
+ * This method will not ensure that <code>ServiceRecord</code> associated
+ * with this notifier is a completely
+ * valid service record. It is the responsibility of the application to
+ * ensure that the service record follows all of the applicable
+ * syntactic and semantic rules for service record correctness.
+ *
+ * @param handler the request handler that will respond to OBEX requests
+ *
+ * @param auth the <code>Authenticator</code> to use with this connection;
+ * if <code>null</code> then no <code>Authenticator</code> will be used
+ *
+ * @return the connection to the client
+ *
+ * @exception IOException if an error occurs in the transport layer
+ *
+ * @exception NullPointerException if <code>handler</code> is
+ * <code>null</code>
+ *
+ * @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;
+}