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