ServerSession.java revision 05ff98bbefda39b9ff26f8bca132cfd0248745c6
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 33package javax.obex; 34 35import android.util.Log; 36 37import java.io.InputStream; 38import java.io.IOException; 39import java.io.OutputStream; 40 41/** 42 * This class in an implementation of the OBEX ServerSession. 43 * @hide 44 */ 45public final class ServerSession extends ObexSession implements Runnable { 46 47 private static final String TAG = "Obex ServerSession"; 48 49 private ObexTransport mTransport; 50 51 private InputStream mInput; 52 53 private OutputStream mOutput; 54 55 private ServerRequestHandler mListener; 56 57 private Thread mProcessThread; 58 59 private int mMaxPacketLength; 60 61 private boolean mClosed; 62 63 /** 64 * Creates new ServerSession. 65 * @param trans the connection to the client 66 * @param handler the event listener that will process requests 67 * @param auth the authenticator to use with this connection 68 * @throws IOException if an error occurred while opening the input and 69 * output streams 70 */ 71 public ServerSession(ObexTransport trans, ServerRequestHandler handler, Authenticator auth) 72 throws IOException { 73 mAuthenticator = auth; 74 mTransport = trans; 75 mInput = mTransport.openInputStream(); 76 mOutput = mTransport.openOutputStream(); 77 mListener = handler; 78 mMaxPacketLength = 256; 79 80 mClosed = false; 81 mProcessThread = new Thread(this); 82 mProcessThread.start(); 83 } 84 85 /** 86 * Processes requests made to the server and forwards them to the 87 * appropriate event listener. 88 */ 89 public void run() { 90 try { 91 92 boolean done = false; 93 while (!done && !mClosed) { 94 int requestType = mInput.read(); 95 switch (requestType) { 96 case ObexHelper.OBEX_OPCODE_CONNECT: 97 handleConnectRequest(); 98 break; 99 100 case ObexHelper.OBEX_OPCODE_DISCONNECT: 101 handleDisconnectRequest(); 102 done = true; 103 break; 104 105 case ObexHelper.OBEX_OPCODE_GET: 106 case ObexHelper.OBEX_OPCODE_GET_FINAL: 107 handleGetRequest(requestType); 108 break; 109 110 case ObexHelper.OBEX_OPCODE_PUT: 111 case ObexHelper.OBEX_OPCODE_PUT_FINAL: 112 handlePutRequest(requestType); 113 break; 114 115 case ObexHelper.OBEX_OPCODE_SETPATH: 116 handleSetPathRequest(); 117 break; 118 119 case -1: 120 done = true; 121 break; 122 123 default: 124 125 /* 126 * Received a request type that is not recognized so I am 127 * just going to read the packet and send a not implemented 128 * to the client 129 */ 130 int length = mInput.read(); 131 length = (length << 8) + mInput.read(); 132 for (int i = 3; i < length; i++) { 133 mInput.read(); 134 } 135 sendResponse(ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED, null); 136 } 137 } 138 139 } catch (NullPointerException e) { 140 Log.d(TAG, e.toString()); 141 } catch (Exception e) { 142 Log.d(TAG, e.toString()); 143 } 144 close(); 145 } 146 147 /** 148 * Handles a PUT request from a client. This method will provide a 149 * <code>ServerOperation</code> object to the request handler. The 150 * <code>ServerOperation</code> object will handle the rest of the request. 151 * It will also send replies and receive requests until the final reply 152 * should be sent. When the final reply should be sent, this method will get 153 * the response code to use and send the reply. The 154 * <code>ServerOperation</code> object will always reply with a 155 * OBEX_HTTP_CONTINUE reply. It will only reply if further information is 156 * needed. 157 * @param type the type of request received; either 0x02 or 0x82 158 * @throws IOException if an error occurred at the transport layer 159 */ 160 private void handlePutRequest(int type) throws IOException { 161 ServerOperation op = new ServerOperation(this, mInput, type, mMaxPacketLength, mListener); 162 try { 163 int response = -1; 164 165 if ((op.finalBitSet) && !op.isValidBody()) { 166 response = validateResponseCode(mListener 167 .onDelete(op.requestHeader, op.replyHeader)); 168 } else { 169 response = validateResponseCode(mListener.onPut(op)); 170 } 171 if (response != ResponseCodes.OBEX_HTTP_OK) { 172 op.sendReply(response); 173 } else if (!op.isAborted) { 174 // wait for the final bit 175 while (!op.finalBitSet) { 176 op.sendReply(ResponseCodes.OBEX_HTTP_CONTINUE); 177 } 178 op.sendReply(response); 179 } 180 } catch (Exception e) { 181 /*To fix bugs in aborted cases, 182 *(client abort file transfer prior to the last packet which has the end of body header, 183 *internal error should not be sent because server has already replied with 184 *OK response in "sendReply") 185 */ 186 if (!op.isAborted) { 187 sendResponse(ResponseCodes.OBEX_HTTP_INTERNAL_ERROR, null); 188 } 189 } 190 } 191 192 /** 193 * Handles a GET request from a client. This method will provide a 194 * <code>ServerOperation</code> object to the request handler. The 195 * <code>ServerOperation</code> object will handle the rest of the request. 196 * It will also send replies and receive requests until the final reply 197 * should be sent. When the final reply should be sent, this method will get 198 * the response code to use and send the reply. The 199 * <code>ServerOperation</code> object will always reply with a 200 * OBEX_HTTP_CONTINUE reply. It will only reply if further information is 201 * needed. 202 * @param type the type of request received; either 0x03 or 0x83 203 * @throws IOException if an error occurred at the transport layer 204 */ 205 private void handleGetRequest(int type) throws IOException { 206 ServerOperation op = new ServerOperation(this, mInput, type, mMaxPacketLength, mListener); 207 try { 208 int response = validateResponseCode(mListener.onGet(op)); 209 210 if (!op.isAborted) { 211 op.sendReply(response); 212 } 213 } catch (Exception e) { 214 sendResponse(ResponseCodes.OBEX_HTTP_INTERNAL_ERROR, null); 215 } 216 } 217 218 /** 219 * Send standard response. 220 * @param code the response code to send 221 * @param header the headers to include in the response 222 * @throws IOException if an IO error occurs 223 */ 224 public void sendResponse(int code, byte[] header) throws IOException { 225 int totalLength = 3; 226 byte[] data = null; 227 228 if (header != null) { 229 totalLength += header.length; 230 data = new byte[totalLength]; 231 data[0] = (byte)code; 232 data[1] = (byte)(totalLength >> 8); 233 data[2] = (byte)totalLength; 234 System.arraycopy(header, 0, data, 3, header.length); 235 } else { 236 data = new byte[totalLength]; 237 data[0] = (byte)code; 238 data[1] = (byte)0x00; 239 data[2] = (byte)totalLength; 240 } 241 mOutput.write(data); 242 mOutput.flush(); 243 } 244 245 /** 246 * Handles a SETPATH request from a client. This method will read the rest 247 * of the request from the client. Assuming the request is valid, it will 248 * create a <code>HeaderSet</code> object to pass to the 249 * <code>ServerRequestHandler</code> object. After the handler processes the 250 * request, this method will create a reply message to send to the server 251 * with the response code provided. 252 * @throws IOException if an error occurred at the transport layer 253 */ 254 private void handleSetPathRequest() throws IOException { 255 int length; 256 int flags; 257 @SuppressWarnings("unused") 258 int constants; 259 int totalLength = 3; 260 byte[] head = null; 261 int code = -1; 262 int bytesReceived; 263 HeaderSet request = new HeaderSet(); 264 HeaderSet reply = new HeaderSet(); 265 266 length = mInput.read(); 267 length = (length << 8) + mInput.read(); 268 flags = mInput.read(); 269 constants = mInput.read(); 270 271 if (length > ObexHelper.MAX_PACKET_SIZE_INT) { 272 code = ResponseCodes.OBEX_HTTP_REQ_TOO_LARGE; 273 totalLength = 3; 274 } else { 275 if (length > 5) { 276 byte[] headers = new byte[length - 5]; 277 bytesReceived = mInput.read(headers); 278 279 while (bytesReceived != headers.length) { 280 bytesReceived += mInput.read(headers, bytesReceived, headers.length 281 - bytesReceived); 282 } 283 284 ObexHelper.updateHeaderSet(request, headers); 285 286 if (request.mConnectionID != null) { 287 mListener.setConnectionId(ObexHelper.convertToLong(request.mConnectionID)); 288 } else { 289 mListener.setConnectionId(-1); 290 } 291 // the Auth chan is initiated by the server, client sent back the authResp . 292 if (request.mAuthResp != null) { 293 if (!handleAuthResp(request.mAuthResp)) { 294 code = ResponseCodes.OBEX_HTTP_UNAUTHORIZED; 295 mListener.onAuthenticationFailure(ObexHelper.getTagValue((byte)0x01, 296 request.mAuthResp)); 297 } 298 request.mAuthResp = null; 299 } 300 } 301 302 if (code != ResponseCodes.OBEX_HTTP_UNAUTHORIZED) { 303 // the Auth challenge is initiated by the client 304 // the server will send back the authResp to the client 305 if (request.mAuthChall != null) { 306 handleAuthChall(request); 307 reply.mAuthResp = new byte[request.mAuthResp.length]; 308 System.arraycopy(request.mAuthResp, 0, reply.mAuthResp, 0, 309 reply.mAuthResp.length); 310 request.mAuthChall = null; 311 request.mAuthResp = null; 312 } 313 boolean backup = false; 314 boolean create = true; 315 if (!((flags & 1) == 0)) { 316 backup = true; 317 } 318 if ((flags & 2) == 0) { 319 create = false; 320 } 321 322 try { 323 code = mListener.onSetPath(request, reply, backup, create); 324 } catch (Exception e) { 325 sendResponse(ResponseCodes.OBEX_HTTP_INTERNAL_ERROR, null); 326 return; 327 } 328 329 code = validateResponseCode(code); 330 331 if (reply.nonce != null) { 332 mChallengeDigest = new byte[16]; 333 System.arraycopy(reply.nonce, 0, mChallengeDigest, 0, 16); 334 } else { 335 mChallengeDigest = null; 336 } 337 338 long id = mListener.getConnectionId(); 339 if (id == -1) { 340 reply.mConnectionID = null; 341 } else { 342 reply.mConnectionID = ObexHelper.convertToByteArray(id); 343 } 344 345 head = ObexHelper.createHeader(reply, false); 346 totalLength += head.length; 347 348 if (totalLength > mMaxPacketLength) { 349 totalLength = 3; 350 head = null; 351 code = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 352 } 353 } 354 } 355 356 // Compute Length of OBEX SETPATH packet 357 byte[] replyData = new byte[totalLength]; 358 replyData[0] = (byte)code; 359 replyData[1] = (byte)(totalLength >> 8); 360 replyData[2] = (byte)totalLength; 361 if (head != null) { 362 System.arraycopy(head, 0, replyData, 3, head.length); 363 } 364 /* 365 * Write the OBEX SETPATH packet to the server. Byte 0: response code 366 * Byte 1&2: Connect Packet Length Byte 3 to n: headers 367 */ 368 mOutput.write(replyData); 369 mOutput.flush(); 370 } 371 372 /** 373 * Handles a disconnect request from a client. This method will read the 374 * rest of the request from the client. Assuming the request is valid, it 375 * will create a <code>HeaderSet</code> object to pass to the 376 * <code>ServerRequestHandler</code> object. After the handler processes the 377 * request, this method will create a reply message to send to the server. 378 * @throws IOException if an error occurred at the transport layer 379 */ 380 private void handleDisconnectRequest() throws IOException { 381 int length; 382 int code = ResponseCodes.OBEX_HTTP_OK; 383 int totalLength = 3; 384 byte[] head = null; 385 int bytesReceived; 386 HeaderSet request = new HeaderSet(); 387 HeaderSet reply = new HeaderSet(); 388 389 length = mInput.read(); 390 length = (length << 8) + mInput.read(); 391 392 if (length > ObexHelper.MAX_PACKET_SIZE_INT) { 393 code = ResponseCodes.OBEX_HTTP_REQ_TOO_LARGE; 394 totalLength = 3; 395 } else { 396 if (length > 3) { 397 byte[] headers = new byte[length - 3]; 398 bytesReceived = mInput.read(headers); 399 400 while (bytesReceived != headers.length) { 401 bytesReceived += mInput.read(headers, bytesReceived, headers.length 402 - bytesReceived); 403 } 404 405 ObexHelper.updateHeaderSet(request, headers); 406 } 407 408 if (request.mConnectionID != null) { 409 mListener.setConnectionId(ObexHelper.convertToLong(request.mConnectionID)); 410 } else { 411 mListener.setConnectionId(1); 412 } 413 414 if (request.mAuthResp != null) { 415 if (!handleAuthResp(request.mAuthResp)) { 416 code = ResponseCodes.OBEX_HTTP_UNAUTHORIZED; 417 mListener.onAuthenticationFailure(ObexHelper.getTagValue((byte)0x01, 418 request.mAuthResp)); 419 } 420 request.mAuthResp = null; 421 } 422 423 if (code != ResponseCodes.OBEX_HTTP_UNAUTHORIZED) { 424 425 if (request.mAuthChall != null) { 426 handleAuthChall(request); 427 request.mAuthChall = null; 428 } 429 430 try { 431 mListener.onDisconnect(request, reply); 432 } catch (Exception e) { 433 sendResponse(ResponseCodes.OBEX_HTTP_INTERNAL_ERROR, null); 434 return; 435 } 436 437 long id = mListener.getConnectionId(); 438 if (id == -1) { 439 reply.mConnectionID = null; 440 } else { 441 reply.mConnectionID = ObexHelper.convertToByteArray(id); 442 } 443 444 head = ObexHelper.createHeader(reply, false); 445 totalLength += head.length; 446 447 if (totalLength > mMaxPacketLength) { 448 totalLength = 3; 449 head = null; 450 code = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 451 } 452 } 453 } 454 455 // Compute Length of OBEX CONNECT packet 456 byte[] replyData; 457 if (head != null) { 458 replyData = new byte[3 + head.length]; 459 } else { 460 replyData = new byte[3]; 461 } 462 replyData[0] = (byte)code; 463 replyData[1] = (byte)(totalLength >> 8); 464 replyData[2] = (byte)totalLength; 465 if (head != null) { 466 System.arraycopy(head, 0, replyData, 3, head.length); 467 } 468 /* 469 * Write the OBEX DISCONNECT packet to the server. Byte 0: response code 470 * Byte 1&2: Connect Packet Length Byte 3 to n: headers 471 */ 472 mOutput.write(replyData); 473 mOutput.flush(); 474 } 475 476 /** 477 * Handles a connect request from a client. This method will read the rest 478 * of the request from the client. Assuming the request is valid, it will 479 * create a <code>HeaderSet</code> object to pass to the 480 * <code>ServerRequestHandler</code> object. After the handler processes the 481 * request, this method will create a reply message to send to the server 482 * with the response code provided. 483 * @throws IOException if an error occurred at the transport layer 484 */ 485 private void handleConnectRequest() throws IOException { 486 int packetLength; 487 @SuppressWarnings("unused") 488 int version; 489 @SuppressWarnings("unused") 490 int flags; 491 int totalLength = 7; 492 byte[] head = null; 493 int code = -1; 494 HeaderSet request = new HeaderSet(); 495 HeaderSet reply = new HeaderSet(); 496 int bytesReceived; 497 498 /* 499 * Read in the length of the OBEX packet, OBEX version, flags, and max 500 * packet length 501 */ 502 packetLength = mInput.read(); 503 packetLength = (packetLength << 8) + mInput.read(); 504 version = mInput.read(); 505 flags = mInput.read(); 506 mMaxPacketLength = mInput.read(); 507 mMaxPacketLength = (mMaxPacketLength << 8) + mInput.read(); 508 509 // should we check it? 510 if (mMaxPacketLength > ObexHelper.MAX_PACKET_SIZE_INT) { 511 mMaxPacketLength = ObexHelper.MAX_PACKET_SIZE_INT; 512 } 513 514 if (packetLength > ObexHelper.MAX_PACKET_SIZE_INT) { 515 code = ResponseCodes.OBEX_HTTP_REQ_TOO_LARGE; 516 totalLength = 7; 517 } else { 518 if (packetLength > 7) { 519 byte[] headers = new byte[packetLength - 7]; 520 bytesReceived = mInput.read(headers); 521 522 while (bytesReceived != headers.length) { 523 bytesReceived += mInput.read(headers, bytesReceived, headers.length 524 - bytesReceived); 525 } 526 527 ObexHelper.updateHeaderSet(request, headers); 528 } 529 530 if (request.mConnectionID != null) { 531 mListener.setConnectionId(ObexHelper.convertToLong(request.mConnectionID)); 532 } else { 533 mListener.setConnectionId(1); 534 } 535 536 if (request.mAuthResp != null) { 537 if (!handleAuthResp(request.mAuthResp)) { 538 code = ResponseCodes.OBEX_HTTP_UNAUTHORIZED; 539 mListener.onAuthenticationFailure(ObexHelper.getTagValue((byte)0x01, 540 request.mAuthResp)); 541 } 542 request.mAuthResp = null; 543 } 544 545 if (code != ResponseCodes.OBEX_HTTP_UNAUTHORIZED) { 546 if (request.mAuthChall != null) { 547 handleAuthChall(request); 548 reply.mAuthResp = new byte[request.mAuthResp.length]; 549 System.arraycopy(request.mAuthResp, 0, reply.mAuthResp, 0, 550 reply.mAuthResp.length); 551 request.mAuthChall = null; 552 request.mAuthResp = null; 553 } 554 555 try { 556 code = mListener.onConnect(request, reply); 557 code = validateResponseCode(code); 558 559 if (reply.nonce != null) { 560 mChallengeDigest = new byte[16]; 561 System.arraycopy(reply.nonce, 0, mChallengeDigest, 0, 16); 562 } else { 563 mChallengeDigest = null; 564 } 565 long id = mListener.getConnectionId(); 566 if (id == -1) { 567 reply.mConnectionID = null; 568 } else { 569 reply.mConnectionID = ObexHelper.convertToByteArray(id); 570 } 571 572 head = ObexHelper.createHeader(reply, false); 573 totalLength += head.length; 574 575 if (totalLength > mMaxPacketLength) { 576 totalLength = 7; 577 head = null; 578 code = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 579 } 580 } catch (Exception e) { 581 e.printStackTrace(); 582 totalLength = 7; 583 head = null; 584 code = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 585 } 586 587 } 588 } 589 590 // Compute Length of OBEX CONNECT packet 591 byte[] length = ObexHelper.convertToByteArray(totalLength); 592 593 /* 594 * Write the OBEX CONNECT packet to the server. Byte 0: response code 595 * Byte 1&2: Connect Packet Length Byte 3: OBEX Version Number 596 * (Presently, 0x10) Byte 4: Flags (For TCP 0x00) Byte 5&6: Max OBEX 597 * Packet Length (Defined in MAX_PACKET_SIZE) Byte 7 to n: headers 598 */ 599 byte[] sendData = new byte[totalLength]; 600 sendData[0] = (byte)code; 601 sendData[1] = length[2]; 602 sendData[2] = length[3]; 603 sendData[3] = (byte)0x10; 604 sendData[4] = (byte)0x00; 605 sendData[5] = (byte)(ObexHelper.MAX_PACKET_SIZE_INT >> 8); 606 sendData[6] = (byte)(ObexHelper.MAX_PACKET_SIZE_INT & 0xFF); 607 608 if (head != null) { 609 System.arraycopy(head, 0, sendData, 7, head.length); 610 } 611 612 mOutput.write(sendData); 613 mOutput.flush(); 614 } 615 616 /** 617 * Closes the server session - in detail close I/O streams and the 618 * underlying transport layer. Internal flag is also set so that later 619 * attempt to read/write will throw an exception. 620 */ 621 public synchronized void close() { 622 if (mListener != null) { 623 mListener.onClose(); 624 } 625 try { 626 mInput.close(); 627 mOutput.close(); 628 mTransport.close(); 629 mClosed = true; 630 } catch (Exception e) { 631 } 632 mTransport = null; 633 mInput = null; 634 mOutput = null; 635 mListener = null; 636 } 637 638 /** 639 * Verifies that the response code is valid. If it is not valid, it will 640 * return the <code>OBEX_HTTP_INTERNAL_ERROR</code> response code. 641 * @param code the response code to check 642 * @return the valid response code or <code>OBEX_HTTP_INTERNAL_ERROR</code> 643 * if <code>code</code> is not valid 644 */ 645 private int validateResponseCode(int code) { 646 647 if ((code >= ResponseCodes.OBEX_HTTP_OK) && (code <= ResponseCodes.OBEX_HTTP_PARTIAL)) { 648 return code; 649 } 650 if ((code >= ResponseCodes.OBEX_HTTP_MULT_CHOICE) 651 && (code <= ResponseCodes.OBEX_HTTP_USE_PROXY)) { 652 return code; 653 } 654 if ((code >= ResponseCodes.OBEX_HTTP_BAD_REQUEST) 655 && (code <= ResponseCodes.OBEX_HTTP_UNSUPPORTED_TYPE)) { 656 return code; 657 } 658 if ((code >= ResponseCodes.OBEX_HTTP_INTERNAL_ERROR) 659 && (code <= ResponseCodes.OBEX_HTTP_VERSION)) { 660 return code; 661 } 662 if ((code >= ResponseCodes.OBEX_DATABASE_FULL) 663 && (code <= ResponseCodes.OBEX_DATABASE_LOCKED)) { 664 return code; 665 } 666 return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 667 } 668 669} 670