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
34package javax.obex;
35
36import java.io.ByteArrayOutputStream;
37import java.io.IOException;
38import java.util.Calendar;
39import java.security.SecureRandom;
40
41/**
42 * This class implements the javax.obex.HeaderSet interface for OBEX over
43 * RFCOMM or OBEX over l2cap.
44 * @hide
45 */
46public 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    /**
182     * Represents the OBEX Single Response Mode (SRM). This header is used
183     * for Single response mode, introduced in OBEX 1.5.
184     * <P>
185     * The value of <code>SINGLE_RESPONSE_MODE</code> is 0x97 (151).
186     */
187    public static final int SINGLE_RESPONSE_MODE = 0x97;
188
189    /**
190     * Represents the OBEX Single Response Mode Parameters. This header is used
191     * for Single response mode, introduced in OBEX 1.5.
192     * <P>
193     * The value of <code>SINGLE_RESPONSE_MODE_PARAMETER</code> is 0x98 (152).
194     */
195    public static final int SINGLE_RESPONSE_MODE_PARAMETER = 0x98;
196
197    private Long mCount; // 4 byte unsigned integer
198
199    private String mName; // null terminated Unicode text string
200
201    private boolean mEmptyName;
202
203    private String mType; // null terminated ASCII text string
204
205    private Long mLength; // 4 byte unsigend integer
206
207    private Calendar mIsoTime; // String of the form YYYYMMDDTHHMMSSZ
208
209    private Calendar mByteTime; // 4 byte unsigned integer
210
211    private String mDescription; // null terminated Unicode text String
212
213    private byte[] mTarget; // byte sequence
214
215    private byte[] mHttpHeader; // byte sequence
216
217    private byte[] mWho; // length prefixed byte sequence
218
219    private byte[] mAppParam; // byte sequence of the form tag length value
220
221    private byte[] mObjectClass; // byte sequence
222
223    private String[] mUnicodeUserDefined; // null terminated unicode string
224
225    private byte[][] mSequenceUserDefined; // byte sequence user defined
226
227    private Byte[] mByteUserDefined; // 1 byte
228
229    private Long[] mIntegerUserDefined; // 4 byte unsigned integer
230
231    private SecureRandom mRandom = null;
232
233    private Byte mSingleResponseMode; // byte to indicate enable/disable/support for SRM
234
235    private Byte mSrmParam; // byte representing the SRM parameters - only "wait"
236                            // is supported by Bluetooth
237
238    /*package*/ byte[] nonce;
239
240    public byte[] mAuthChall; // The authentication challenge header
241
242    public byte[] mAuthResp; // The authentication response header
243
244    public byte[] mConnectionID; // THe connection ID
245
246    public int responseCode;
247
248    /**
249     * Creates new <code>HeaderSet</code> object.
250     * @param size the max packet size for this connection
251     */
252    public HeaderSet() {
253        mUnicodeUserDefined = new String[16];
254        mSequenceUserDefined = new byte[16][];
255        mByteUserDefined = new Byte[16];
256        mIntegerUserDefined = new Long[16];
257        responseCode = -1;
258    }
259
260    /**
261     * Sets flag for special "value" of NAME header which should be empty. This
262     * is not the same as NAME header with empty string in which case it will
263     * have length of 5 bytes. It should be 3 bytes with only header id and
264     * length field.
265     */
266    public void setEmptyNameHeader() {
267        mName = null;
268        mEmptyName = true;
269    }
270
271    /**
272     * Gets flag for special "value" of NAME header which should be empty. See
273     * above.
274     */
275    public boolean getEmptyNameHeader() {
276        return mEmptyName;
277    }
278
279    /**
280     * Sets the value of the header identifier to the value provided. The type
281     * of object must correspond to the Java type defined in the description of
282     * this interface. If <code>null</code> is passed as the
283     * <code>headerValue</code> then the header will be removed from the set of
284     * headers to include in the next request.
285     * @param headerID the identifier to include in the message
286     * @param headerValue the value of the header identifier
287     * @throws IllegalArgumentException if the header identifier provided is not
288     *         one defined in this interface or a user-defined header; if the
289     *         type of <code>headerValue</code> is not the correct Java type as
290     *         defined in the description of this interface\
291     */
292    public void setHeader(int headerID, Object headerValue) {
293        long temp = -1;
294
295        switch (headerID) {
296            case COUNT:
297                if (!(headerValue instanceof Long)) {
298                    if (headerValue == null) {
299                        mCount = null;
300                        break;
301                    }
302                    throw new IllegalArgumentException("Count must be a Long");
303                }
304                temp = ((Long)headerValue).longValue();
305                if ((temp < 0L) || (temp > 0xFFFFFFFFL)) {
306                    throw new IllegalArgumentException("Count must be between 0 and 0xFFFFFFFF");
307                }
308                mCount = (Long)headerValue;
309                break;
310            case NAME:
311                if ((headerValue != null) && (!(headerValue instanceof String))) {
312                    throw new IllegalArgumentException("Name must be a String");
313                }
314                mEmptyName = false;
315                mName = (String)headerValue;
316                break;
317            case TYPE:
318                if ((headerValue != null) && (!(headerValue instanceof String))) {
319                    throw new IllegalArgumentException("Type must be a String");
320                }
321                mType = (String)headerValue;
322                break;
323            case LENGTH:
324                if (!(headerValue instanceof Long)) {
325                    if (headerValue == null) {
326                        mLength = null;
327                        break;
328                    }
329                    throw new IllegalArgumentException("Length must be a Long");
330                }
331                temp = ((Long)headerValue).longValue();
332                if ((temp < 0L) || (temp > 0xFFFFFFFFL)) {
333                    throw new IllegalArgumentException("Length must be between 0 and 0xFFFFFFFF");
334                }
335                mLength = (Long)headerValue;
336                break;
337            case TIME_ISO_8601:
338                if ((headerValue != null) && (!(headerValue instanceof Calendar))) {
339                    throw new IllegalArgumentException("Time ISO 8601 must be a Calendar");
340                }
341                mIsoTime = (Calendar)headerValue;
342                break;
343            case TIME_4_BYTE:
344                if ((headerValue != null) && (!(headerValue instanceof Calendar))) {
345                    throw new IllegalArgumentException("Time 4 Byte must be a Calendar");
346                }
347                mByteTime = (Calendar)headerValue;
348                break;
349            case DESCRIPTION:
350                if ((headerValue != null) && (!(headerValue instanceof String))) {
351                    throw new IllegalArgumentException("Description must be a String");
352                }
353                mDescription = (String)headerValue;
354                break;
355            case TARGET:
356                if (headerValue == null) {
357                    mTarget = null;
358                } else {
359                    if (!(headerValue instanceof byte[])) {
360                        throw new IllegalArgumentException("Target must be a byte array");
361                    } else {
362                        mTarget = new byte[((byte[])headerValue).length];
363                        System.arraycopy(headerValue, 0, mTarget, 0, mTarget.length);
364                    }
365                }
366                break;
367            case HTTP:
368                if (headerValue == null) {
369                    mHttpHeader = null;
370                } else {
371                    if (!(headerValue instanceof byte[])) {
372                        throw new IllegalArgumentException("HTTP must be a byte array");
373                    } else {
374                        mHttpHeader = new byte[((byte[])headerValue).length];
375                        System.arraycopy(headerValue, 0, mHttpHeader, 0, mHttpHeader.length);
376                    }
377                }
378                break;
379            case WHO:
380                if (headerValue == null) {
381                    mWho = null;
382                } else {
383                    if (!(headerValue instanceof byte[])) {
384                        throw new IllegalArgumentException("WHO must be a byte array");
385                    } else {
386                        mWho = new byte[((byte[])headerValue).length];
387                        System.arraycopy(headerValue, 0, mWho, 0, mWho.length);
388                    }
389                }
390                break;
391            case OBJECT_CLASS:
392                if (headerValue == null) {
393                    mObjectClass = null;
394                } else {
395                    if (!(headerValue instanceof byte[])) {
396                        throw new IllegalArgumentException("Object Class must be a byte array");
397                    } else {
398                        mObjectClass = new byte[((byte[])headerValue).length];
399                        System.arraycopy(headerValue, 0, mObjectClass, 0, mObjectClass.length);
400                    }
401                }
402                break;
403            case APPLICATION_PARAMETER:
404                if (headerValue == null) {
405                    mAppParam = null;
406                } else {
407                    if (!(headerValue instanceof byte[])) {
408                        throw new IllegalArgumentException(
409                                "Application Parameter must be a byte array");
410                    } else {
411                        mAppParam = new byte[((byte[])headerValue).length];
412                        System.arraycopy(headerValue, 0, mAppParam, 0, mAppParam.length);
413                    }
414                }
415                break;
416            case SINGLE_RESPONSE_MODE:
417                if (headerValue == null) {
418                    mSingleResponseMode = null;
419                } else {
420                    if (!(headerValue instanceof Byte)) {
421                        throw new IllegalArgumentException(
422                                "Single Response Mode must be a Byte");
423                    } else {
424                        mSingleResponseMode = (Byte)headerValue;
425                    }
426                }
427                break;
428            case SINGLE_RESPONSE_MODE_PARAMETER:
429                if (headerValue == null) {
430                    mSrmParam = null;
431                } else {
432                    if (!(headerValue instanceof Byte)) {
433                        throw new IllegalArgumentException(
434                                "Single Response Mode Parameter must be a Byte");
435                    } else {
436                        mSrmParam = (Byte)headerValue;
437                    }
438                }
439                break;
440            default:
441                // Verify that it was not a Unicode String user Defined
442                if ((headerID >= 0x30) && (headerID <= 0x3F)) {
443                    if ((headerValue != null) && (!(headerValue instanceof String))) {
444                        throw new IllegalArgumentException(
445                                "Unicode String User Defined must be a String");
446                    }
447                    mUnicodeUserDefined[headerID - 0x30] = (String)headerValue;
448
449                    break;
450                }
451                // Verify that it was not a byte sequence user defined value
452                if ((headerID >= 0x70) && (headerID <= 0x7F)) {
453
454                    if (headerValue == null) {
455                        mSequenceUserDefined[headerID - 0x70] = null;
456                    } else {
457                        if (!(headerValue instanceof byte[])) {
458                            throw new IllegalArgumentException(
459                                    "Byte Sequence User Defined must be a byte array");
460                        } else {
461                            mSequenceUserDefined[headerID - 0x70] = new byte[((byte[])headerValue).length];
462                            System.arraycopy(headerValue, 0, mSequenceUserDefined[headerID - 0x70],
463                                    0, mSequenceUserDefined[headerID - 0x70].length);
464                        }
465                    }
466                    break;
467                }
468                // Verify that it was not a Byte user Defined
469                if ((headerID >= 0xB0) && (headerID <= 0xBF)) {
470                    if ((headerValue != null) && (!(headerValue instanceof Byte))) {
471                        throw new IllegalArgumentException("ByteUser Defined must be a Byte");
472                    }
473                    mByteUserDefined[headerID - 0xB0] = (Byte)headerValue;
474
475                    break;
476                }
477                // Verify that is was not the 4 byte unsigned integer user
478                // defined header
479                if ((headerID >= 0xF0) && (headerID <= 0xFF)) {
480                    if (!(headerValue instanceof Long)) {
481                        if (headerValue == null) {
482                            mIntegerUserDefined[headerID - 0xF0] = null;
483                            break;
484                        }
485                        throw new IllegalArgumentException("Integer User Defined must be a Long");
486                    }
487                    temp = ((Long)headerValue).longValue();
488                    if ((temp < 0L) || (temp > 0xFFFFFFFFL)) {
489                        throw new IllegalArgumentException(
490                                "Integer User Defined must be between 0 and 0xFFFFFFFF");
491                    }
492                    mIntegerUserDefined[headerID - 0xF0] = (Long)headerValue;
493                    break;
494                }
495                throw new IllegalArgumentException("Invalid Header Identifier");
496        }
497    }
498
499    /**
500     * Retrieves the value of the header identifier provided. The type of the
501     * Object returned is defined in the description of this interface.
502     * @param headerID the header identifier whose value is to be returned
503     * @return the value of the header provided or <code>null</code> if the
504     *         header identifier specified is not part of this
505     *         <code>HeaderSet</code> object
506     * @throws IllegalArgumentException if the <code>headerID</code> is not one
507     *         defined in this interface or any of the user-defined headers
508     * @throws IOException if an error occurred in the transport layer during
509     *         the operation or if the connection has been closed
510     */
511    public Object getHeader(int headerID) throws IOException {
512
513        switch (headerID) {
514            case COUNT:
515                return mCount;
516            case NAME:
517                return mName;
518            case TYPE:
519                return mType;
520            case LENGTH:
521                return mLength;
522            case TIME_ISO_8601:
523                return mIsoTime;
524            case TIME_4_BYTE:
525                return mByteTime;
526            case DESCRIPTION:
527                return mDescription;
528            case TARGET:
529                return mTarget;
530            case HTTP:
531                return mHttpHeader;
532            case WHO:
533                return mWho;
534            case CONNECTION_ID:
535                return mConnectionID;
536            case OBJECT_CLASS:
537                return mObjectClass;
538            case APPLICATION_PARAMETER:
539                return mAppParam;
540            case SINGLE_RESPONSE_MODE:
541                return mSingleResponseMode;
542            case SINGLE_RESPONSE_MODE_PARAMETER:
543                return mSrmParam;
544            default:
545                // Verify that it was not a Unicode String user Defined
546                if ((headerID >= 0x30) && (headerID <= 0x3F)) {
547                    return mUnicodeUserDefined[headerID - 0x30];
548                }
549                // Verify that it was not a byte sequence user defined header
550                if ((headerID >= 0x70) && (headerID <= 0x7F)) {
551                    return mSequenceUserDefined[headerID - 0x70];
552                }
553                // Verify that it was not a byte user defined header
554                if ((headerID >= 0xB0) && (headerID <= 0xBF)) {
555                    return mByteUserDefined[headerID - 0xB0];
556                }
557                // Verify that it was not a integer user defined header
558                if ((headerID >= 0xF0) && (headerID <= 0xFF)) {
559                    return mIntegerUserDefined[headerID - 0xF0];
560                }
561                throw new IllegalArgumentException("Invalid Header Identifier");
562        }
563    }
564
565    /**
566     * Retrieves the list of headers that may be retrieved via the
567     * <code>getHeader</code> method that will not return <code>null</code>. In
568     * other words, this method returns all the headers that are available in
569     * this object.
570     * @see #getHeader
571     * @return the array of headers that are set in this object or
572     *         <code>null</code> if no headers are available
573     * @throws IOException if an error occurred in the transport layer during
574     *         the operation or the connection has been closed
575     */
576    public int[] getHeaderList() throws IOException {
577        ByteArrayOutputStream out = new ByteArrayOutputStream();
578
579        if (mCount != null) {
580            out.write(COUNT);
581        }
582        if (mName != null) {
583            out.write(NAME);
584        }
585        if (mType != null) {
586            out.write(TYPE);
587        }
588        if (mLength != null) {
589            out.write(LENGTH);
590        }
591        if (mIsoTime != null) {
592            out.write(TIME_ISO_8601);
593        }
594        if (mByteTime != null) {
595            out.write(TIME_4_BYTE);
596        }
597        if (mDescription != null) {
598            out.write(DESCRIPTION);
599        }
600        if (mTarget != null) {
601            out.write(TARGET);
602        }
603        if (mHttpHeader != null) {
604            out.write(HTTP);
605        }
606        if (mWho != null) {
607            out.write(WHO);
608        }
609        if (mAppParam != null) {
610            out.write(APPLICATION_PARAMETER);
611        }
612        if (mObjectClass != null) {
613            out.write(OBJECT_CLASS);
614        }
615        if(mSingleResponseMode != null) {
616            out.write(SINGLE_RESPONSE_MODE);
617        }
618        if(mSrmParam != null) {
619            out.write(SINGLE_RESPONSE_MODE_PARAMETER);
620        }
621
622        for (int i = 0x30; i < 0x40; i++) {
623            if (mUnicodeUserDefined[i - 0x30] != null) {
624                out.write(i);
625            }
626        }
627
628        for (int i = 0x70; i < 0x80; i++) {
629            if (mSequenceUserDefined[i - 0x70] != null) {
630                out.write(i);
631            }
632        }
633
634        for (int i = 0xB0; i < 0xC0; i++) {
635            if (mByteUserDefined[i - 0xB0] != null) {
636                out.write(i);
637            }
638        }
639
640        for (int i = 0xF0; i < 0x100; i++) {
641            if (mIntegerUserDefined[i - 0xF0] != null) {
642                out.write(i);
643            }
644        }
645
646        byte[] headers = out.toByteArray();
647        out.close();
648
649        if ((headers == null) || (headers.length == 0)) {
650            return null;
651        }
652
653        int[] result = new int[headers.length];
654        for (int i = 0; i < headers.length; i++) {
655            // Convert the byte to a positive integer.  That is, an integer
656            // between 0 and 256.
657            result[i] = headers[i] & 0xFF;
658        }
659
660        return result;
661    }
662
663    /**
664     * Sets the authentication challenge header. The <code>realm</code> will be
665     * encoded based upon the default encoding scheme used by the implementation
666     * to encode strings. Therefore, the encoding scheme used to encode the
667     * <code>realm</code> is application dependent.
668     * @param realm a short description that describes what password to use; if
669     *        <code>null</code> no realm will be sent in the authentication
670     *        challenge header
671     * @param userID if <code>true</code>, a user ID is required in the reply;
672     *        if <code>false</code>, no user ID is required
673     * @param access if <code>true</code> then full access will be granted if
674     *        successful; if <code>false</code> then read-only access will be
675     *        granted if successful
676     * @throws IOException
677     */
678    public void createAuthenticationChallenge(String realm, boolean userID, boolean access)
679            throws IOException {
680
681        nonce = new byte[16];
682        if(mRandom == null) {
683            mRandom = new SecureRandom();
684        }
685        for (int i = 0; i < 16; i++) {
686            nonce[i] = (byte)mRandom.nextInt();
687        }
688
689        mAuthChall = ObexHelper.computeAuthenticationChallenge(nonce, realm, access, userID);
690    }
691
692    /**
693     * Returns the response code received from the server. Response codes are
694     * defined in the <code>ResponseCodes</code> class.
695     * @see ResponseCodes
696     * @return the response code retrieved from the server
697     * @throws IOException if an error occurred in the transport layer during
698     *         the transaction; if this method is called on a
699     *         <code>HeaderSet</code> object created by calling
700     *         <code>createHeaderSet()</code> in a <code>ClientSession</code>
701     *         object; if this object was created by an OBEX server
702     */
703    public int getResponseCode() throws IOException {
704        if (responseCode == -1) {
705            throw new IOException("May not be called on a server");
706        } else {
707            return responseCode;
708        }
709    }
710}
711