Home | History | Annotate | Download | only in obex
      1 /*
      2  * Copyright (c) 2014 The Android Open Source Project
      3  * Copyright (c) 2008-2009, Motorola, Inc.
      4  *
      5  * All rights reserved.
      6  *
      7  * Redistribution and use in source and binary forms, with or without
      8  * modification, are permitted provided that the following conditions are met:
      9  *
     10  * - Redistributions of source code must retain the above copyright notice,
     11  * this list of conditions and the following disclaimer.
     12  *
     13  * - Redistributions in binary form must reproduce the above copyright notice,
     14  * this list of conditions and the following disclaimer in the documentation
     15  * and/or other materials provided with the distribution.
     16  *
     17  * - Neither the name of the Motorola, Inc. nor the names of its contributors
     18  * may be used to endorse or promote products derived from this software
     19  * without specific prior written permission.
     20  *
     21  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
     22  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     23  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
     24  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
     25  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     26  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
     27  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
     28  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
     29  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
     30  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
     31  * POSSIBILITY OF SUCH DAMAGE.
     32  */
     33 
     34 package javax.obex;
     35 
     36 import java.io.ByteArrayOutputStream;
     37 import java.io.IOException;
     38 import java.util.Calendar;
     39 import java.security.SecureRandom;
     40 
     41 /**
     42  * This class implements the javax.obex.HeaderSet interface for OBEX over
     43  * RFCOMM.
     44  * @hide
     45  */
     46 public final class HeaderSet {
     47 
     48     /**
     49      * Represents the OBEX Count header. This allows the connection statement to
     50      * tell the server how many objects it plans to send or retrieve.
     51      * <P>
     52      * The value of <code>COUNT</code> is 0xC0 (192).
     53      */
     54     public static final int COUNT = 0xC0;
     55 
     56     /**
     57      * Represents the OBEX Name header. This specifies the name of the object.
     58      * <P>
     59      * The value of <code>NAME</code> is 0x01 (1).
     60      */
     61     public static final int NAME = 0x01;
     62 
     63     /**
     64      * Represents the OBEX Type header. This allows a request to specify the
     65      * type of the object (e.g. text, html, binary, etc.).
     66      * <P>
     67      * The value of <code>TYPE</code> is 0x42 (66).
     68      */
     69     public static final int TYPE = 0x42;
     70 
     71     /**
     72      * Represents the OBEX Length header. This is the length of the object in
     73      * bytes.
     74      * <P>
     75      * The value of <code>LENGTH</code> is 0xC3 (195).
     76      */
     77     public static final int LENGTH = 0xC3;
     78 
     79     /**
     80      * Represents the OBEX Time header using the ISO 8601 standards. This is the
     81      * preferred time header.
     82      * <P>
     83      * The value of <code>TIME_ISO_8601</code> is 0x44 (68).
     84      */
     85     public static final int TIME_ISO_8601 = 0x44;
     86 
     87     /**
     88      * Represents the OBEX Time header using the 4 byte representation. This is
     89      * only included for backwards compatibility. It represents the number of
     90      * seconds since January 1, 1970.
     91      * <P>
     92      * The value of <code>TIME_4_BYTE</code> is 0xC4 (196).
     93      */
     94     public static final int TIME_4_BYTE = 0xC4;
     95 
     96     /**
     97      * Represents the OBEX Description header. This is a text description of the
     98      * object.
     99      * <P>
    100      * The value of <code>DESCRIPTION</code> is 0x05 (5).
    101      */
    102     public static final int DESCRIPTION = 0x05;
    103 
    104     /**
    105      * Represents the OBEX Target header. This is the name of the service an
    106      * operation is targeted to.
    107      * <P>
    108      * The value of <code>TARGET</code> is 0x46 (70).
    109      */
    110     public static final int TARGET = 0x46;
    111 
    112     /**
    113      * Represents the OBEX HTTP header. This allows an HTTP 1.X header to be
    114      * included in a request or reply.
    115      * <P>
    116      * The value of <code>HTTP</code> is 0x47 (71).
    117      */
    118     public static final int HTTP = 0x47;
    119 
    120     /**
    121      * Represents the OBEX BODY header.
    122      * <P>
    123      * The value of <code>BODY</code> is 0x48 (72).
    124      */
    125     public static final int BODY = 0x48;
    126 
    127     /**
    128      * Represents the OBEX End of BODY header.
    129      * <P>
    130      * The value of <code>BODY</code> is 0x49 (73).
    131      */
    132     public static final int END_OF_BODY = 0x49;
    133 
    134     /**
    135      * Represents the OBEX Who header. Identifies the OBEX application to
    136      * determine if the two peers are talking to each other.
    137      * <P>
    138      * The value of <code>WHO</code> is 0x4A (74).
    139      */
    140     public static final int WHO = 0x4A;
    141 
    142     /**
    143      * Represents the OBEX Connection ID header. Identifies used for OBEX
    144      * connection multiplexing.
    145      * <P>
    146      * The value of <code>CONNECTION_ID</code> is 0xCB (203).
    147      */
    148 
    149     public static final int CONNECTION_ID = 0xCB;
    150 
    151     /**
    152      * Represents the OBEX Application Parameter header. This header specifies
    153      * additional application request and response information.
    154      * <P>
    155      * The value of <code>APPLICATION_PARAMETER</code> is 0x4C (76).
    156      */
    157     public static final int APPLICATION_PARAMETER = 0x4C;
    158 
    159     /**
    160      * Represents the OBEX authentication digest-challenge.
    161      * <P>
    162      * The value of <code>AUTH_CHALLENGE</code> is 0x4D (77).
    163      */
    164     public static final int AUTH_CHALLENGE = 0x4D;
    165 
    166     /**
    167      * Represents the OBEX authentication digest-response.
    168      * <P>
    169      * The value of <code>AUTH_RESPONSE</code> is 0x4E (78).
    170      */
    171     public static final int AUTH_RESPONSE = 0x4E;
    172 
    173     /**
    174      * Represents the OBEX Object Class header. This header specifies the OBEX
    175      * object class of the object.
    176      * <P>
    177      * The value of <code>OBJECT_CLASS</code> is 0x4F (79).
    178      */
    179     public static final int OBJECT_CLASS = 0x4F;
    180 
    181     private Long mCount; // 4 byte unsigned integer
    182 
    183     private String mName; // null terminated Unicode text string
    184 
    185     private boolean mEmptyName;
    186 
    187     private String mType; // null terminated ASCII text string
    188 
    189     private Long mLength; // 4 byte unsigend integer
    190 
    191     private Calendar mIsoTime; // String of the form YYYYMMDDTHHMMSSZ
    192 
    193     private Calendar mByteTime; // 4 byte unsigned integer
    194 
    195     private String mDescription; // null terminated Unicode text String
    196 
    197     private byte[] mTarget; // byte sequence
    198 
    199     private byte[] mHttpHeader; // byte sequence
    200 
    201     private byte[] mWho; // length prefixed byte sequence
    202 
    203     private byte[] mAppParam; // byte sequence of the form tag length value
    204 
    205     private byte[] mObjectClass; // byte sequence
    206 
    207     private String[] mUnicodeUserDefined; //null terminated unicode string
    208 
    209     private byte[][] mSequenceUserDefined; // byte sequence user defined
    210 
    211     private Byte[] mByteUserDefined; // 1 byte
    212 
    213     private Long[] mIntegerUserDefined; // 4 byte unsigned integer
    214 
    215     private final SecureRandom mRandom;
    216 
    217     /*package*/ byte[] nonce;
    218 
    219     public byte[] mAuthChall; // The authentication challenge header
    220 
    221     public byte[] mAuthResp; // The authentication response header
    222 
    223     public byte[] mConnectionID; // THe connection ID
    224 
    225     public int responseCode;
    226 
    227     /**
    228      * Creates new <code>HeaderSet</code> object.
    229      * @param size the max packet size for this connection
    230      */
    231     public HeaderSet() {
    232         mUnicodeUserDefined = new String[16];
    233         mSequenceUserDefined = new byte[16][];
    234         mByteUserDefined = new Byte[16];
    235         mIntegerUserDefined = new Long[16];
    236         responseCode = -1;
    237         mRandom = new SecureRandom();
    238     }
    239 
    240     /**
    241      * Sets flag for special "value" of NAME header which should be empty. This
    242      * is not the same as NAME header with empty string in which case it will
    243      * have length of 5 bytes. It should be 3 bytes with only header id and
    244      * length field.
    245      */
    246     public void setEmptyNameHeader() {
    247         mName = null;
    248         mEmptyName = true;
    249     }
    250 
    251     /**
    252      * Gets flag for special "value" of NAME header which should be empty. See
    253      * above.
    254      */
    255     public boolean getEmptyNameHeader() {
    256         return mEmptyName;
    257     }
    258 
    259     /**
    260      * Sets the value of the header identifier to the value provided. The type
    261      * of object must correspond to the Java type defined in the description of
    262      * this interface. If <code>null</code> is passed as the
    263      * <code>headerValue</code> then the header will be removed from the set of
    264      * headers to include in the next request.
    265      * @param headerID the identifier to include in the message
    266      * @param headerValue the value of the header identifier
    267      * @throws IllegalArgumentException if the header identifier provided is not
    268      *         one defined in this interface or a user-defined header; if the
    269      *         type of <code>headerValue</code> is not the correct Java type as
    270      *         defined in the description of this interface\
    271      */
    272     public void setHeader(int headerID, Object headerValue) {
    273         long temp = -1;
    274 
    275         switch (headerID) {
    276             case COUNT:
    277                 if (!(headerValue instanceof Long)) {
    278                     if (headerValue == null) {
    279                         mCount = null;
    280                         break;
    281                     }
    282                     throw new IllegalArgumentException("Count must be a Long");
    283                 }
    284                 temp = ((Long)headerValue).longValue();
    285                 if ((temp < 0L) || (temp > 0xFFFFFFFFL)) {
    286                     throw new IllegalArgumentException("Count must be between 0 and 0xFFFFFFFF");
    287                 }
    288                 mCount = (Long)headerValue;
    289                 break;
    290             case NAME:
    291                 if ((headerValue != null) && (!(headerValue instanceof String))) {
    292                     throw new IllegalArgumentException("Name must be a String");
    293                 }
    294                 mEmptyName = false;
    295                 mName = (String)headerValue;
    296                 break;
    297             case TYPE:
    298                 if ((headerValue != null) && (!(headerValue instanceof String))) {
    299                     throw new IllegalArgumentException("Type must be a String");
    300                 }
    301                 mType = (String)headerValue;
    302                 break;
    303             case LENGTH:
    304                 if (!(headerValue instanceof Long)) {
    305                     if (headerValue == null) {
    306                         mLength = null;
    307                         break;
    308                     }
    309                     throw new IllegalArgumentException("Length must be a Long");
    310                 }
    311                 temp = ((Long)headerValue).longValue();
    312                 if ((temp < 0L) || (temp > 0xFFFFFFFFL)) {
    313                     throw new IllegalArgumentException("Length must be between 0 and 0xFFFFFFFF");
    314                 }
    315                 mLength = (Long)headerValue;
    316                 break;
    317             case TIME_ISO_8601:
    318                 if ((headerValue != null) && (!(headerValue instanceof Calendar))) {
    319                     throw new IllegalArgumentException("Time ISO 8601 must be a Calendar");
    320                 }
    321                 mIsoTime = (Calendar)headerValue;
    322                 break;
    323             case TIME_4_BYTE:
    324                 if ((headerValue != null) && (!(headerValue instanceof Calendar))) {
    325                     throw new IllegalArgumentException("Time 4 Byte must be a Calendar");
    326                 }
    327                 mByteTime = (Calendar)headerValue;
    328                 break;
    329             case DESCRIPTION:
    330                 if ((headerValue != null) && (!(headerValue instanceof String))) {
    331                     throw new IllegalArgumentException("Description must be a String");
    332                 }
    333                 mDescription = (String)headerValue;
    334                 break;
    335             case TARGET:
    336                 if (headerValue == null) {
    337                     mTarget = null;
    338                 } else {
    339                     if (!(headerValue instanceof byte[])) {
    340                         throw new IllegalArgumentException("Target must be a byte array");
    341                     } else {
    342                         mTarget = new byte[((byte[])headerValue).length];
    343                         System.arraycopy(headerValue, 0, mTarget, 0, mTarget.length);
    344                     }
    345                 }
    346                 break;
    347             case HTTP:
    348                 if (headerValue == null) {
    349                     mHttpHeader = null;
    350                 } else {
    351                     if (!(headerValue instanceof byte[])) {
    352                         throw new IllegalArgumentException("HTTP must be a byte array");
    353                     } else {
    354                         mHttpHeader = new byte[((byte[])headerValue).length];
    355                         System.arraycopy(headerValue, 0, mHttpHeader, 0, mHttpHeader.length);
    356                     }
    357                 }
    358                 break;
    359             case WHO:
    360                 if (headerValue == null) {
    361                     mWho = null;
    362                 } else {
    363                     if (!(headerValue instanceof byte[])) {
    364                         throw new IllegalArgumentException("WHO must be a byte array");
    365                     } else {
    366                         mWho = new byte[((byte[])headerValue).length];
    367                         System.arraycopy(headerValue, 0, mWho, 0, mWho.length);
    368                     }
    369                 }
    370                 break;
    371             case OBJECT_CLASS:
    372                 if (headerValue == null) {
    373                     mObjectClass = null;
    374                 } else {
    375                     if (!(headerValue instanceof byte[])) {
    376                         throw new IllegalArgumentException("Object Class must be a byte array");
    377                     } else {
    378                         mObjectClass = new byte[((byte[])headerValue).length];
    379                         System.arraycopy(headerValue, 0, mObjectClass, 0, mObjectClass.length);
    380                     }
    381                 }
    382                 break;
    383             case APPLICATION_PARAMETER:
    384                 if (headerValue == null) {
    385                     mAppParam = null;
    386                 } else {
    387                     if (!(headerValue instanceof byte[])) {
    388                         throw new IllegalArgumentException(
    389                                 "Application Parameter must be a byte array");
    390                     } else {
    391                         mAppParam = new byte[((byte[])headerValue).length];
    392                         System.arraycopy(headerValue, 0, mAppParam, 0, mAppParam.length);
    393                     }
    394                 }
    395                 break;
    396             default:
    397                 // Verify that it was not a Unicode String user Defined
    398                 if ((headerID >= 0x30) && (headerID <= 0x3F)) {
    399                     if ((headerValue != null) && (!(headerValue instanceof String))) {
    400                         throw new IllegalArgumentException(
    401                                 "Unicode String User Defined must be a String");
    402                     }
    403                     mUnicodeUserDefined[headerID - 0x30] = (String)headerValue;
    404 
    405                     break;
    406                 }
    407                 // Verify that it was not a byte sequence user defined value
    408                 if ((headerID >= 0x70) && (headerID <= 0x7F)) {
    409 
    410                     if (headerValue == null) {
    411                         mSequenceUserDefined[headerID - 0x70] = null;
    412                     } else {
    413                         if (!(headerValue instanceof byte[])) {
    414                             throw new IllegalArgumentException(
    415                                     "Byte Sequence User Defined must be a byte array");
    416                         } else {
    417                             mSequenceUserDefined[headerID - 0x70] = new byte[((byte[])headerValue).length];
    418                             System.arraycopy(headerValue, 0, mSequenceUserDefined[headerID - 0x70],
    419                                     0, mSequenceUserDefined[headerID - 0x70].length);
    420                         }
    421                     }
    422                     break;
    423                 }
    424                 // Verify that it was not a Byte user Defined
    425                 if ((headerID >= 0xB0) && (headerID <= 0xBF)) {
    426                     if ((headerValue != null) && (!(headerValue instanceof Byte))) {
    427                         throw new IllegalArgumentException("ByteUser Defined must be a Byte");
    428                     }
    429                     mByteUserDefined[headerID - 0xB0] = (Byte)headerValue;
    430 
    431                     break;
    432                 }
    433                 // Verify that is was not the 4 byte unsigned integer user
    434                 // defined header
    435                 if ((headerID >= 0xF0) && (headerID <= 0xFF)) {
    436                     if (!(headerValue instanceof Long)) {
    437                         if (headerValue == null) {
    438                             mIntegerUserDefined[headerID - 0xF0] = null;
    439                             break;
    440                         }
    441                         throw new IllegalArgumentException("Integer User Defined must be a Long");
    442                     }
    443                     temp = ((Long)headerValue).longValue();
    444                     if ((temp < 0L) || (temp > 0xFFFFFFFFL)) {
    445                         throw new IllegalArgumentException(
    446                                 "Integer User Defined must be between 0 and 0xFFFFFFFF");
    447                     }
    448                     mIntegerUserDefined[headerID - 0xF0] = (Long)headerValue;
    449                     break;
    450                 }
    451                 throw new IllegalArgumentException("Invalid Header Identifier");
    452         }
    453     }
    454 
    455     /**
    456      * Retrieves the value of the header identifier provided. The type of the
    457      * Object returned is defined in the description of this interface.
    458      * @param headerID the header identifier whose value is to be returned
    459      * @return the value of the header provided or <code>null</code> if the
    460      *         header identifier specified is not part of this
    461      *         <code>HeaderSet</code> object
    462      * @throws IllegalArgumentException if the <code>headerID</code> is not one
    463      *         defined in this interface or any of the user-defined headers
    464      * @throws IOException if an error occurred in the transport layer during
    465      *         the operation or if the connection has been closed
    466      */
    467     public Object getHeader(int headerID) throws IOException {
    468 
    469         switch (headerID) {
    470             case COUNT:
    471                 return mCount;
    472             case NAME:
    473                 return mName;
    474             case TYPE:
    475                 return mType;
    476             case LENGTH:
    477                 return mLength;
    478             case TIME_ISO_8601:
    479                 return mIsoTime;
    480             case TIME_4_BYTE:
    481                 return mByteTime;
    482             case DESCRIPTION:
    483                 return mDescription;
    484             case TARGET:
    485                 return mTarget;
    486             case HTTP:
    487                 return mHttpHeader;
    488             case WHO:
    489                 return mWho;
    490             case CONNECTION_ID:
    491                 return mConnectionID;
    492             case OBJECT_CLASS:
    493                 return mObjectClass;
    494             case APPLICATION_PARAMETER:
    495                 return mAppParam;
    496             default:
    497                 // Verify that it was not a Unicode String user Defined
    498                 if ((headerID >= 0x30) && (headerID <= 0x3F)) {
    499                     return mUnicodeUserDefined[headerID - 0x30];
    500                 }
    501                 // Verify that it was not a byte sequence user defined header
    502                 if ((headerID >= 0x70) && (headerID <= 0x7F)) {
    503                     return mSequenceUserDefined[headerID - 0x70];
    504                 }
    505                 // Verify that it was not a byte user defined header
    506                 if ((headerID >= 0xB0) && (headerID <= 0xBF)) {
    507                     return mByteUserDefined[headerID - 0xB0];
    508                 }
    509                 // Verify that it was not a integer user defined header
    510                 if ((headerID >= 0xF0) && (headerID <= 0xFF)) {
    511                     return mIntegerUserDefined[headerID - 0xF0];
    512                 }
    513                 throw new IllegalArgumentException("Invalid Header Identifier");
    514         }
    515     }
    516 
    517     /**
    518      * Retrieves the list of headers that may be retrieved via the
    519      * <code>getHeader</code> method that will not return <code>null</code>. In
    520      * other words, this method returns all the headers that are available in
    521      * this object.
    522      * @see #getHeader
    523      * @return the array of headers that are set in this object or
    524      *         <code>null</code> if no headers are available
    525      * @throws IOException if an error occurred in the transport layer during
    526      *         the operation or the connection has been closed
    527      */
    528     public int[] getHeaderList() throws IOException {
    529         ByteArrayOutputStream out = new ByteArrayOutputStream();
    530 
    531         if (mCount != null) {
    532             out.write(COUNT);
    533         }
    534         if (mName != null) {
    535             out.write(NAME);
    536         }
    537         if (mType != null) {
    538             out.write(TYPE);
    539         }
    540         if (mLength != null) {
    541             out.write(LENGTH);
    542         }
    543         if (mIsoTime != null) {
    544             out.write(TIME_ISO_8601);
    545         }
    546         if (mByteTime != null) {
    547             out.write(TIME_4_BYTE);
    548         }
    549         if (mDescription != null) {
    550             out.write(DESCRIPTION);
    551         }
    552         if (mTarget != null) {
    553             out.write(TARGET);
    554         }
    555         if (mHttpHeader != null) {
    556             out.write(HTTP);
    557         }
    558         if (mWho != null) {
    559             out.write(WHO);
    560         }
    561         if (mAppParam != null) {
    562             out.write(APPLICATION_PARAMETER);
    563         }
    564         if (mObjectClass != null) {
    565             out.write(OBJECT_CLASS);
    566         }
    567 
    568         for (int i = 0x30; i < 0x40; i++) {
    569             if (mUnicodeUserDefined[i - 0x30] != null) {
    570                 out.write(i);
    571             }
    572         }
    573 
    574         for (int i = 0x70; i < 0x80; i++) {
    575             if (mSequenceUserDefined[i - 0x70] != null) {
    576                 out.write(i);
    577             }
    578         }
    579 
    580         for (int i = 0xB0; i < 0xC0; i++) {
    581             if (mByteUserDefined[i - 0xB0] != null) {
    582                 out.write(i);
    583             }
    584         }
    585 
    586         for (int i = 0xF0; i < 0x100; i++) {
    587             if (mIntegerUserDefined[i - 0xF0] != null) {
    588                 out.write(i);
    589             }
    590         }
    591 
    592         byte[] headers = out.toByteArray();
    593         out.close();
    594 
    595         if ((headers == null) || (headers.length == 0)) {
    596             return null;
    597         }
    598 
    599         int[] result = new int[headers.length];
    600         for (int i = 0; i < headers.length; i++) {
    601             // Convert the byte to a positive integer.  That is, an integer
    602             // between 0 and 256.
    603             result[i] = headers[i] & 0xFF;
    604         }
    605 
    606         return result;
    607     }
    608 
    609     /**
    610      * Sets the authentication challenge header. The <code>realm</code> will be
    611      * encoded based upon the default encoding scheme used by the implementation
    612      * to encode strings. Therefore, the encoding scheme used to encode the
    613      * <code>realm</code> is application dependent.
    614      * @param realm a short description that describes what password to use; if
    615      *        <code>null</code> no realm will be sent in the authentication
    616      *        challenge header
    617      * @param userID if <code>true</code>, a user ID is required in the reply;
    618      *        if <code>false</code>, no user ID is required
    619      * @param access if <code>true</code> then full access will be granted if
    620      *        successful; if <code>false</code> then read-only access will be
    621      *        granted if successful
    622      * @throws IOException
    623      */
    624     public void createAuthenticationChallenge(String realm, boolean userID, boolean access)
    625             throws IOException {
    626 
    627         nonce = new byte[16];
    628         for (int i = 0; i < 16; i++) {
    629             nonce[i] = (byte)mRandom.nextInt();
    630         }
    631 
    632         mAuthChall = ObexHelper.computeAuthenticationChallenge(nonce, realm, access, userID);
    633     }
    634 
    635     /**
    636      * Returns the response code received from the server. Response codes are
    637      * defined in the <code>ResponseCodes</code> class.
    638      * @see ResponseCodes
    639      * @return the response code retrieved from the server
    640      * @throws IOException if an error occurred in the transport layer during
    641      *         the transaction; if this method is called on a
    642      *         <code>HeaderSet</code> object created by calling
    643      *         <code>createHeaderSet()</code> in a <code>ClientSession</code>
    644      *         object; if this object was created by an OBEX server
    645      */
    646     public int getResponseCode() throws IOException {
    647         if (responseCode == -1) {
    648             throw new IOException("May not be called on a server");
    649         } else {
    650             return responseCode;
    651         }
    652     }
    653 }
    654