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