diff options
Diffstat (limited to 'obex/javax/obex/ClientSession.java')
-rw-r--r-- | obex/javax/obex/ClientSession.java | 783 |
1 files changed, 783 insertions, 0 deletions
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(); + } +} |