Home | History | Annotate | Download | only in obex
      1 /*
      2  * Copyright (c) 2008-2009, Motorola, Inc.
      3  *
      4  * All rights reserved.
      5  *
      6  * Redistribution and use in source and binary forms, with or without
      7  * modification, are permitted provided that the following conditions are met:
      8  *
      9  * - Redistributions of source code must retain the above copyright notice,
     10  * this list of conditions and the following disclaimer.
     11  *
     12  * - Redistributions in binary form must reproduce the above copyright notice,
     13  * this list of conditions and the following disclaimer in the documentation
     14  * and/or other materials provided with the distribution.
     15  *
     16  * - Neither the name of the Motorola, Inc. nor the names of its contributors
     17  * may be used to endorse or promote products derived from this software
     18  * without specific prior written permission.
     19  *
     20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
     21  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     22  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
     23  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
     24  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     25  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
     26  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
     27  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
     28  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
     29  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
     30  * POSSIBILITY OF SUCH DAMAGE.
     31  */
     32 
     33 package javax.obex;
     34 
     35 import java.io.ByteArrayOutputStream;
     36 import java.io.IOException;
     37 import java.io.InputStream;
     38 import java.io.OutputStream;
     39 
     40 /**
     41  * This class in an implementation of the OBEX ClientSession.
     42  * @hide
     43  */
     44 public final class ClientSession extends ObexSession {
     45 
     46     private boolean mOpen;
     47 
     48     // Determines if an OBEX layer connection has been established
     49     private boolean mObexConnected;
     50 
     51     private byte[] mConnectionId = null;
     52 
     53     /*
     54      * The max Packet size must be at least 256 according to the OBEX
     55      * specification.
     56      */
     57     private int maxPacketSize = 256;
     58 
     59     private boolean mRequestActive;
     60 
     61     private final InputStream mInput;
     62 
     63     private final OutputStream mOutput;
     64 
     65     public ClientSession(final ObexTransport trans) throws IOException {
     66         mInput = trans.openInputStream();
     67         mOutput = trans.openOutputStream();
     68         mOpen = true;
     69         mRequestActive = false;
     70     }
     71 
     72     public HeaderSet connect(final HeaderSet header) throws IOException {
     73         ensureOpen();
     74         if (mObexConnected) {
     75             throw new IOException("Already connected to server");
     76         }
     77         setRequestActive();
     78 
     79         int totalLength = 4;
     80         byte[] head = null;
     81 
     82         // Determine the header byte array
     83         if (header != null) {
     84             if (header.nonce != null) {
     85                 mChallengeDigest = new byte[16];
     86                 System.arraycopy(header.nonce, 0, mChallengeDigest, 0, 16);
     87             }
     88             head = ObexHelper.createHeader(header, false);
     89             totalLength += head.length;
     90         }
     91         /*
     92         * Write the OBEX CONNECT packet to the server.
     93         * Byte 0: 0x80
     94         * Byte 1&2: Connect Packet Length
     95         * Byte 3: OBEX Version Number (Presently, 0x10)
     96         * Byte 4: Flags (For TCP 0x00)
     97         * Byte 5&6: Max OBEX Packet Length (Defined in MAX_PACKET_SIZE)
     98         * Byte 7 to n: headers
     99         */
    100         byte[] requestPacket = new byte[totalLength];
    101         // We just need to start at  byte 3 since the sendRequest() method will
    102         // handle the length and 0x80.
    103         requestPacket[0] = (byte)0x10;
    104         requestPacket[1] = (byte)0x00;
    105         requestPacket[2] = (byte)(ObexHelper.MAX_PACKET_SIZE_INT >> 8);
    106         requestPacket[3] = (byte)(ObexHelper.MAX_PACKET_SIZE_INT & 0xFF);
    107         if (head != null) {
    108             System.arraycopy(head, 0, requestPacket, 4, head.length);
    109         }
    110 
    111         // check with local max packet size
    112         if ((requestPacket.length + 3) > ObexHelper.MAX_PACKET_SIZE_INT) {
    113             throw new IOException("Packet size exceeds max packet size");
    114         }
    115 
    116         HeaderSet returnHeaderSet = new HeaderSet();
    117         sendRequest(ObexHelper.OBEX_OPCODE_CONNECT, requestPacket, returnHeaderSet, null);
    118 
    119         /*
    120         * Read the response from the OBEX server.
    121         * Byte 0: Response Code (If successful then OBEX_HTTP_OK)
    122         * Byte 1&2: Packet Length
    123         * Byte 3: OBEX Version Number
    124         * Byte 4: Flags3
    125         * Byte 5&6: Max OBEX packet Length
    126         * Byte 7 to n: Optional HeaderSet
    127         */
    128         if (returnHeaderSet.responseCode == ResponseCodes.OBEX_HTTP_OK) {
    129             mObexConnected = true;
    130         }
    131         setRequestInactive();
    132 
    133         return returnHeaderSet;
    134     }
    135 
    136     public Operation get(HeaderSet header) throws IOException {
    137 
    138         if (!mObexConnected) {
    139             throw new IOException("Not connected to the server");
    140         }
    141         setRequestActive();
    142 
    143         ensureOpen();
    144 
    145         HeaderSet head;
    146         if (header == null) {
    147             head = new HeaderSet();
    148         } else {
    149             head = header;
    150             if (head.nonce != null) {
    151                 mChallengeDigest = new byte[16];
    152                 System.arraycopy(head.nonce, 0, mChallengeDigest, 0, 16);
    153             }
    154         }
    155         // Add the connection ID if one exists
    156         if (mConnectionId != null) {
    157             head.mConnectionID = new byte[4];
    158             System.arraycopy(mConnectionId, 0, head.mConnectionID, 0, 4);
    159         }
    160 
    161         return new ClientOperation(maxPacketSize, this, head, true);
    162     }
    163 
    164     /**
    165      * 0xCB Connection Id an identifier used for OBEX connection multiplexing
    166      */
    167     public void setConnectionID(long id) {
    168         if ((id < 0) || (id > 0xFFFFFFFFL)) {
    169             throw new IllegalArgumentException("Connection ID is not in a valid range");
    170         }
    171         mConnectionId = ObexHelper.convertToByteArray(id);
    172     }
    173 
    174     public HeaderSet delete(HeaderSet header) throws IOException {
    175 
    176         Operation op = put(header);
    177         op.getResponseCode();
    178         HeaderSet returnValue = op.getReceivedHeader();
    179         op.close();
    180 
    181         return returnValue;
    182     }
    183 
    184     public HeaderSet disconnect(HeaderSet header) throws IOException {
    185         if (!mObexConnected) {
    186             throw new IOException("Not connected to the server");
    187         }
    188         setRequestActive();
    189 
    190         ensureOpen();
    191         // Determine the header byte array
    192         byte[] head = null;
    193         if (header != null) {
    194             if (header.nonce != null) {
    195                 mChallengeDigest = new byte[16];
    196                 System.arraycopy(header.nonce, 0, mChallengeDigest, 0, 16);
    197             }
    198             // Add the connection ID if one exists
    199             if (mConnectionId != null) {
    200                 header.mConnectionID = new byte[4];
    201                 System.arraycopy(mConnectionId, 0, header.mConnectionID, 0, 4);
    202             }
    203             head = ObexHelper.createHeader(header, false);
    204 
    205             if ((head.length + 3) > maxPacketSize) {
    206                 throw new IOException("Packet size exceeds max packet size");
    207             }
    208         } else {
    209             // Add the connection ID if one exists
    210             if (mConnectionId != null) {
    211                 head = new byte[5];
    212                 head[0] = (byte)HeaderSet.CONNECTION_ID;
    213                 System.arraycopy(mConnectionId, 0, head, 1, 4);
    214             }
    215         }
    216 
    217         HeaderSet returnHeaderSet = new HeaderSet();
    218         sendRequest(ObexHelper.OBEX_OPCODE_DISCONNECT, head, returnHeaderSet, null);
    219 
    220         /*
    221          * An OBEX DISCONNECT reply from the server:
    222          * Byte 1: Response code
    223          * Bytes 2 & 3: packet size
    224          * Bytes 4 & up: headers
    225          */
    226 
    227         /* response code , and header are ignored
    228          * */
    229 
    230         synchronized (this) {
    231             mObexConnected = false;
    232             setRequestInactive();
    233         }
    234 
    235         return returnHeaderSet;
    236     }
    237 
    238     public long getConnectionID() {
    239 
    240         if (mConnectionId == null) {
    241             return -1;
    242         }
    243         return ObexHelper.convertToLong(mConnectionId);
    244     }
    245 
    246     public Operation put(HeaderSet header) throws IOException {
    247         if (!mObexConnected) {
    248             throw new IOException("Not connected to the server");
    249         }
    250         setRequestActive();
    251 
    252         ensureOpen();
    253         HeaderSet head;
    254         if (header == null) {
    255             head = new HeaderSet();
    256         } else {
    257             head = header;
    258             // when auth is initiated by client ,save the digest
    259             if (head.nonce != null) {
    260                 mChallengeDigest = new byte[16];
    261                 System.arraycopy(head.nonce, 0, mChallengeDigest, 0, 16);
    262             }
    263         }
    264 
    265         // Add the connection ID if one exists
    266         if (mConnectionId != null) {
    267 
    268             head.mConnectionID = new byte[4];
    269             System.arraycopy(mConnectionId, 0, head.mConnectionID, 0, 4);
    270         }
    271 
    272         return new ClientOperation(maxPacketSize, this, head, false);
    273     }
    274 
    275     public void setAuthenticator(Authenticator auth) throws IOException {
    276         if (auth == null) {
    277             throw new IOException("Authenticator may not be null");
    278         }
    279         mAuthenticator = auth;
    280     }
    281 
    282     public HeaderSet setPath(HeaderSet header, boolean backup, boolean create) throws IOException {
    283         if (!mObexConnected) {
    284             throw new IOException("Not connected to the server");
    285         }
    286         setRequestActive();
    287         ensureOpen();
    288 
    289         int totalLength = 2;
    290         byte[] head = null;
    291         HeaderSet headset;
    292         if (header == null) {
    293             headset = new HeaderSet();
    294         } else {
    295             headset = header;
    296             if (headset.nonce != null) {
    297                 mChallengeDigest = new byte[16];
    298                 System.arraycopy(headset.nonce, 0, mChallengeDigest, 0, 16);
    299             }
    300         }
    301 
    302         // when auth is initiated by client ,save the digest
    303         if (headset.nonce != null) {
    304             mChallengeDigest = new byte[16];
    305             System.arraycopy(headset.nonce, 0, mChallengeDigest, 0, 16);
    306         }
    307 
    308         // Add the connection ID if one exists
    309         if (mConnectionId != null) {
    310             headset.mConnectionID = new byte[4];
    311             System.arraycopy(mConnectionId, 0, headset.mConnectionID, 0, 4);
    312         }
    313 
    314         head = ObexHelper.createHeader(headset, false);
    315         totalLength += head.length;
    316 
    317         if (totalLength > maxPacketSize) {
    318             throw new IOException("Packet size exceeds max packet size");
    319         }
    320 
    321         int flags = 0;
    322         /*
    323          * The backup flag bit is bit 0 so if we add 1, this will set that bit
    324          */
    325         if (backup) {
    326             flags++;
    327         }
    328         /*
    329          * The create bit is bit 1 so if we or with 2 the bit will be set.
    330          */
    331         if (!create) {
    332             flags |= 2;
    333         }
    334 
    335         /*
    336          * An OBEX SETPATH packet to the server:
    337          * Byte 1: 0x85
    338          * Byte 2 & 3: packet size
    339          * Byte 4: flags
    340          * Byte 5: constants
    341          * Byte 6 & up: headers
    342          */
    343         byte[] packet = new byte[totalLength];
    344         packet[0] = (byte)flags;
    345         packet[1] = (byte)0x00;
    346         if (headset != null) {
    347             System.arraycopy(head, 0, packet, 2, head.length);
    348         }
    349 
    350         HeaderSet returnHeaderSet = new HeaderSet();
    351         sendRequest(ObexHelper.OBEX_OPCODE_SETPATH, packet, returnHeaderSet, null);
    352 
    353         /*
    354          * An OBEX SETPATH reply from the server:
    355          * Byte 1: Response code
    356          * Bytes 2 & 3: packet size
    357          * Bytes 4 & up: headers
    358          */
    359 
    360         setRequestInactive();
    361 
    362         return returnHeaderSet;
    363     }
    364 
    365     /**
    366      * Verifies that the connection is open.
    367      * @throws IOException if the connection is closed
    368      */
    369     public synchronized void ensureOpen() throws IOException {
    370         if (!mOpen) {
    371             throw new IOException("Connection closed");
    372         }
    373     }
    374 
    375     /**
    376      * Set request inactive. Allows Put and get operation objects to tell this
    377      * object when they are done.
    378      */
    379     /*package*/synchronized void setRequestInactive() {
    380         mRequestActive = false;
    381     }
    382 
    383     /**
    384      * Set request to active.
    385      * @throws IOException if already active
    386      */
    387     private synchronized void setRequestActive() throws IOException {
    388         if (mRequestActive) {
    389             throw new IOException("OBEX request is already being performed");
    390         }
    391         mRequestActive = true;
    392     }
    393 
    394     /**
    395      * Sends a standard request to the client. It will then wait for the reply
    396      * and update the header set object provided. If any authentication headers
    397      * (i.e. authentication challenge or authentication response) are received,
    398      * they will be processed.
    399      * @param opCode the type of request to send to the client
    400      * @param head the headers to send to the client
    401      * @param header the header object to update with the response
    402      * @param privateInput the input stream used by the Operation object; null
    403      *        if this is called on a CONNECT, SETPATH or DISCONNECT return
    404      *        <code>true</code> if the operation completed successfully;
    405      *        <code>false</code> if an authentication response failed to pass
    406      * @throws IOException if an IO error occurs
    407      */
    408     public boolean sendRequest(int opCode, byte[] head, HeaderSet header,
    409             PrivateInputStream privateInput) throws IOException {
    410         //check header length with local max size
    411         if (head != null) {
    412             if ((head.length + 3) > ObexHelper.MAX_PACKET_SIZE_INT) {
    413                 throw new IOException("header too large ");
    414             }
    415         }
    416 
    417         int bytesReceived;
    418         ByteArrayOutputStream out = new ByteArrayOutputStream();
    419         out.write((byte)opCode);
    420 
    421         // Determine if there are any headers to send
    422         if (head == null) {
    423             out.write(0x00);
    424             out.write(0x03);
    425         } else {
    426             out.write((byte)((head.length + 3) >> 8));
    427             out.write((byte)(head.length + 3));
    428             out.write(head);
    429         }
    430 
    431         // Write the request to the output stream and flush the stream
    432         mOutput.write(out.toByteArray());
    433         mOutput.flush();
    434 
    435         header.responseCode = mInput.read();
    436 
    437         int length = ((mInput.read() << 8) | (mInput.read()));
    438 
    439         if (length > ObexHelper.MAX_PACKET_SIZE_INT) {
    440             throw new IOException("Packet received exceeds packet size limit");
    441         }
    442         if (length > ObexHelper.BASE_PACKET_LENGTH) {
    443             byte[] data = null;
    444             if (opCode == ObexHelper.OBEX_OPCODE_CONNECT) {
    445                 @SuppressWarnings("unused")
    446                 int version = mInput.read();
    447                 @SuppressWarnings("unused")
    448                 int flags = mInput.read();
    449                 maxPacketSize = (mInput.read() << 8) + mInput.read();
    450 
    451                 //check with local max size
    452                 if (maxPacketSize > ObexHelper.MAX_CLIENT_PACKET_SIZE) {
    453                     maxPacketSize = ObexHelper.MAX_CLIENT_PACKET_SIZE;
    454                 }
    455 
    456                 if (length > 7) {
    457                     data = new byte[length - 7];
    458 
    459                     bytesReceived = mInput.read(data);
    460                     while (bytesReceived != (length - 7)) {
    461                         bytesReceived += mInput.read(data, bytesReceived, data.length
    462                                 - bytesReceived);
    463                     }
    464                 } else {
    465                     return true;
    466                 }
    467             } else {
    468                 data = new byte[length - 3];
    469                 bytesReceived = mInput.read(data);
    470 
    471                 while (bytesReceived != (length - 3)) {
    472                     bytesReceived += mInput.read(data, bytesReceived, data.length - bytesReceived);
    473                 }
    474                 if (opCode == ObexHelper.OBEX_OPCODE_ABORT) {
    475                     return true;
    476                 }
    477             }
    478 
    479             byte[] body = ObexHelper.updateHeaderSet(header, data);
    480             if ((privateInput != null) && (body != null)) {
    481                 privateInput.writeBytes(body, 1);
    482             }
    483 
    484             if (header.mConnectionID != null) {
    485                 mConnectionId = new byte[4];
    486                 System.arraycopy(header.mConnectionID, 0, mConnectionId, 0, 4);
    487             }
    488 
    489             if (header.mAuthResp != null) {
    490                 if (!handleAuthResp(header.mAuthResp)) {
    491                     setRequestInactive();
    492                     throw new IOException("Authentication Failed");
    493                 }
    494             }
    495 
    496             if ((header.responseCode == ResponseCodes.OBEX_HTTP_UNAUTHORIZED)
    497                     && (header.mAuthChall != null)) {
    498 
    499                 if (handleAuthChall(header)) {
    500                     out.write((byte)HeaderSet.AUTH_RESPONSE);
    501                     out.write((byte)((header.mAuthResp.length + 3) >> 8));
    502                     out.write((byte)(header.mAuthResp.length + 3));
    503                     out.write(header.mAuthResp);
    504                     header.mAuthChall = null;
    505                     header.mAuthResp = null;
    506 
    507                     byte[] sendHeaders = new byte[out.size() - 3];
    508                     System.arraycopy(out.toByteArray(), 3, sendHeaders, 0, sendHeaders.length);
    509 
    510                     return sendRequest(opCode, sendHeaders, header, privateInput);
    511                 }
    512             }
    513         }
    514 
    515         return true;
    516     }
    517 
    518     public void close() throws IOException {
    519         mOpen = false;
    520         mInput.close();
    521         mOutput.close();
    522     }
    523 }
    524