ServerOperation.java revision 2e0da96e757a977154063f980d3f4e1abd41cf09
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 java.io.IOException; 36import java.io.InputStream; 37import java.io.DataInputStream; 38import java.io.OutputStream; 39import java.io.DataOutputStream; 40import java.io.ByteArrayOutputStream; 41 42/** 43 * This class implements the Operation interface for server side connections. 44 * <P> 45 * <STRONG>Request Codes</STRONG> 46 * There are four different request codes that are in this class. 0x02 is a 47 * PUT request that signals that the request is not complete and requires an 48 * additional OBEX packet. 0x82 is a PUT request that says that request is 49 * complete. In this case, the server can begin sending the response. The 50 * 0x03 is a GET request that signals that the request is not finished. When 51 * the server receives a 0x83, the client is signalling the server that it is 52 * done with its request. 53 * 54 * OPTIMIZATION: Extend the ClientOperation and reuse the methods defined 55 * OPTIMIZATION: in that class. 56 * 57 * @hide 58 */ 59public class ServerOperation implements Operation, BaseStream { 60 61 private InputStream socketInput; 62 63 private ServerSession parent; 64 65 private int maxPacketLength; 66 67 private int responseSize; 68 69 private boolean isClosed; 70 71 boolean finalBitSet; 72 73 // This variable defines when the end of body 74 // header has been received. When this header 75 // is received, no further body data will be 76 // received from the client 77 private boolean endOfBody; 78 79 private boolean isGet; 80 81 boolean isAborted; 82 83 HeaderSet requestHeaders; 84 85 HeaderSet replyHeaders; 86 87 PrivateInputStream privateInput; 88 89 private PrivateOutputStream privateOutput; 90 91 private String exceptionString; 92 93 private ServerRequestHandler listener; 94 95 private boolean outputStreamOpened; 96 97 private boolean requestFinished; 98 99 private static final int BASE_PACKET_LENGTH = 3; 100 101 private boolean isHasBody; 102 103 /** 104 * Creates new PutServerOperation 105 * 106 * @param p the parent that created this object 107 * 108 * @param in the input stream to read from 109 * 110 * @param out the output stream to write to 111 * 112 * @param request the initial request that was received from the client 113 * 114 * @param maxSize the max packet size that the client will accept 115 * 116 * @param listen the listener that is responding to the request 117 * 118 * @throws IOException if an IO error occurs 119 */ 120 public ServerOperation(ServerSession p, InputStream in, int request, int maxSize, 121 ServerRequestHandler listen) throws IOException { 122 123 isAborted = false; 124 parent = p; 125 socketInput = in; 126 maxPacketLength = maxSize; 127 isClosed = false; 128 requestHeaders = new HeaderSet(); 129 replyHeaders = new HeaderSet(); 130 privateInput = new PrivateInputStream(this); 131 endOfBody = false; 132 responseSize = 3; 133 listener = listen; 134 requestFinished = false; 135 outputStreamOpened = false; 136 isHasBody = false; 137 int bytesReceived; 138 139 /* 140 * Determine if this is a PUT request 141 */ 142 if ((request == 0x02) || (request == 0x82)) { 143 /* 144 * It is a PUT request. 145 */ 146 isGet = false; 147 } else { 148 /* 149 * It is a GET request. 150 */ 151 isGet = true; 152 } 153 154 /* 155 * Determine if the final bit is set 156 */ 157 if ((request & 0x80) == 0) { 158 finalBitSet = false; 159 } else { 160 finalBitSet = true; 161 requestFinished = true; 162 } 163 164 int length = in.read(); 165 length = (length << 8) + in.read(); 166 167 /* 168 * Determine if the packet length is larger than this device can receive 169 */ 170 if (length > ObexHelper.MAX_PACKET_SIZE_INT) { 171 parent.sendResponse(ResponseCodes.OBEX_HTTP_REQ_TOO_LARGE, null); 172 throw new IOException("Packet received was too large"); 173 } 174 175 /* 176 * Determine if any headers were sent in the initial request 177 */ 178 if (length > 3) { 179 byte[] data = new byte[length - 3]; 180 bytesReceived = in.read(data); 181 182 while (bytesReceived != data.length) { 183 bytesReceived += in.read(data, bytesReceived, data.length - bytesReceived); 184 } 185 186 byte[] body = ObexHelper.updateHeaderSet(requestHeaders, data); 187 188 if (body != null) { 189 isHasBody = true; 190 } 191 192 if (requestHeaders.connectionID != null) { 193 listener.setConnectionID(ObexHelper.convertToLong(requestHeaders.connectionID)); 194 } else { 195 listener.setConnectionID(0); 196 } 197 198 if (requestHeaders.authResp != null) { 199 if (!parent.handleAuthResp(requestHeaders.authResp)) { 200 exceptionString = "Authentication Failed"; 201 parent.sendResponse(ResponseCodes.OBEX_HTTP_UNAUTHORIZED, null); 202 isClosed = true; 203 requestHeaders.authResp = null; 204 return; 205 } 206 } 207 208 if (requestHeaders.authChall != null) { 209 parent.handleAuthChall(requestHeaders); 210 // send the authResp to the client 211 replyHeaders.authResp = new byte[requestHeaders.authResp.length]; 212 System.arraycopy(requestHeaders.authResp, 0, replyHeaders.authResp, 0, 213 replyHeaders.authResp.length); 214 requestHeaders.authResp = null; 215 requestHeaders.authChall = null; 216 217 } 218 219 if (body != null) { 220 /* 221 * 0x49 is the end of body header. This signifies that no more 222 * body data will be sent from the client 223 */ 224 if (body[0] == 0x49) { 225 endOfBody = true; 226 } 227 //privateInput.writeBytes(body, body.length); 228 //byte [] body_tmp = new byte[body.length-1]; 229 //System.arraycopy(body,1,body_tmp,0,body.length-1); 230 //privateInput.writeBytes(body_tmp, body.length-1); 231 privateInput.writeBytes(body, 1); 232 } else { 233 while ((!isGet) && (!finalBitSet)) { 234 sendReply(ObexHelper.OBEX_HTTP_CONTINUE); 235 if (privateInput.available() > 0) { 236 break; 237 } 238 } 239 }// if (body != null) 240 241 }// if (length > 3) 242 243 while ((!isGet) && (!finalBitSet) && (privateInput.available() == 0)) { 244 sendReply(ObexHelper.OBEX_HTTP_CONTINUE); 245 if (privateInput.available() > 0) { 246 break; 247 } 248 } 249 250 // wait for get request finished !!!! 251 while (isGet && !finalBitSet) { 252 sendReply(ObexHelper.OBEX_HTTP_CONTINUE); 253 } 254 if (finalBitSet && isGet) { 255 requestFinished = true; 256 } 257 } 258 259 public synchronized boolean isValidBody() { 260 return isHasBody; 261 } 262 263 /** 264 * Determines if the operation should continue or should wait. If it 265 * should continue, this method will continue the operation. 266 * 267 * @param sendEmpty if <code>true</code> then this will continue the 268 * operation even if no headers will be sent; if <code>false</code> then 269 * this method will only continue the operation if there are headers to 270 * send 271 * @param isStream if<code>true</code> the stream is input stream or 272 * is outputstream 273 * @return <code>true</code> if the operation was completed; 274 * <code>false</code> if no operation took place 275 */ 276 public synchronized boolean continueOperation(boolean sendEmpty, boolean inStream) 277 throws IOException { 278 if (!isGet) { 279 if (!finalBitSet) { 280 if (sendEmpty) { 281 sendReply(ObexHelper.OBEX_HTTP_CONTINUE); 282 return true; 283 } else { 284 if ((responseSize > 3) || (privateOutput.size() > 0)) { 285 sendReply(ObexHelper.OBEX_HTTP_CONTINUE); 286 return true; 287 } else { 288 return false; 289 } 290 } 291 } else { 292 return false; 293 } 294 } else { 295 sendReply(ObexHelper.OBEX_HTTP_CONTINUE); 296 return true; 297 } 298 } 299 300 /** 301 * Sends a reply to the client. If the reply is a OBEX_HTTP_CONTINUE, it 302 * will wait for a response from the client before ending. 303 * 304 * @param type the response code to send back to the client 305 * 306 * @return <code>true</code> if the final bit was not set on the reply; 307 * <code>false</code> if no reply was received because the operation ended, 308 * an abort was received, or the final bit was set in the reply 309 * 310 * @throws IOException if an IO error occurs 311 */ 312 protected synchronized boolean sendReply(int type) throws IOException { 313 ByteArrayOutputStream out = new ByteArrayOutputStream(); 314 int bytesReceived; 315 316 long id = listener.getConnectionID(); 317 if (id == -1) { 318 replyHeaders.connectionID = null; 319 } else { 320 replyHeaders.connectionID = ObexHelper.convertToByteArray(id); 321 } 322 323 byte[] headerArray = ObexHelper.createHeader(replyHeaders, true); 324 int bodyLength = -1; 325 int orginalBodyLength = -1; 326 327 if (privateOutput != null) { 328 bodyLength = privateOutput.size(); 329 orginalBodyLength = bodyLength; 330 } 331 332 if ((BASE_PACKET_LENGTH + headerArray.length) > maxPacketLength) { 333 334 int end = 0; 335 int start = 0; 336 337 while (end != headerArray.length) { 338 end = ObexHelper.findHeaderEnd(headerArray, start, maxPacketLength 339 - BASE_PACKET_LENGTH); 340 if (end == -1) { 341 342 isClosed = true; 343 344 if (privateInput != null) { 345 privateInput.close(); 346 } 347 348 if (privateOutput != null) { 349 privateOutput.close(); 350 } 351 parent.sendResponse(ResponseCodes.OBEX_HTTP_INTERNAL_ERROR, null); 352 throw new IOException("OBEX Packet exceeds max packet size"); 353 } 354 byte[] sendHeader = new byte[end - start]; 355 System.arraycopy(headerArray, start, sendHeader, 0, sendHeader.length); 356 357 parent.sendResponse(type, sendHeader); 358 start = end; 359 } 360 361 if (bodyLength > 0) { 362 return true; 363 } else { 364 return false; 365 } 366 367 } else { 368 out.write(headerArray); 369 } 370 371 /* 372 * Determine if there is space to add a body reply. First, we need to 373 * verify that the client is finished sending the request. Next, there 374 * needs to be enough space to send the headers already defined along 375 * with the reply header (3 bytes) and the body header identifier 376 * (3 bytes). 377 */ 378 379 /* if ((finalBitSet) && 380 ((bodyLength + 6 + headerArray.length) > maxPacketLength)) { 381 382 exceptionString = "Header larger then can be sent in a packet"; 383 isClosed = true; 384 privateInput.close(); 385 if (privateOutput != null) { 386 privateOutput.close(); 387 } 388 parent.sendResponse(ResponseCodes.OBEX_HTTP_INTERNAL_ERROR, 389 null); 390 throw new IOException("OBEX Packet exceeds max packet size"); 391 } 392 */ 393 394 if ((finalBitSet) || (headerArray.length < (maxPacketLength - 20))) { 395 if (bodyLength > 0) { 396 /* 397 * Determine if I can send the whole body or just part of 398 * the body. Remember that there is the 3 bytes for the 399 * response message and 3 bytes for the header ID and length 400 */ 401 if (bodyLength > (maxPacketLength - headerArray.length - 6)) { 402 bodyLength = maxPacketLength - headerArray.length - 6; 403 } 404 405 byte[] body = privateOutput.readBytes(bodyLength); 406 407 /* 408 * Since this is a put request if the final bit is set or 409 * the output stream is closed we need to send the 0x49 410 * (End of Body) otherwise, we need to send 0x48 (Body) 411 */ 412 if ((finalBitSet) || (privateOutput.isClosed())) { 413 out.write(0x49); 414 } else { 415 out.write(0x48); 416 } 417 418 bodyLength += 3; 419 out.write((byte)(bodyLength >> 8)); 420 out.write((byte)bodyLength); 421 out.write(body); 422 } 423 } 424 425 if ((finalBitSet) && (type == ResponseCodes.OBEX_HTTP_OK) && (orginalBodyLength <= 0)) { 426 out.write(0x49); 427 orginalBodyLength = 3; 428 out.write((byte)(orginalBodyLength >> 8)); 429 out.write((byte)orginalBodyLength); 430 431 } 432 433 responseSize = 3; 434 parent.sendResponse(type, out.toByteArray()); 435 436 if (type == ObexHelper.OBEX_HTTP_CONTINUE) { 437 int headerID = socketInput.read(); 438 int length = socketInput.read(); 439 length = (length << 8) + socketInput.read(); 440 if ((headerID != 0x02) && (headerID != 0x82) && (headerID != 0x03) 441 && (headerID != 0x83)) { 442 443 if (length > 3) { 444 byte[] temp = new byte[length]; 445 bytesReceived = socketInput.read(temp); 446 447 while (bytesReceived != length) { 448 bytesReceived += socketInput.read(temp, bytesReceived, length 449 - bytesReceived); 450 } 451 } 452 453 /* 454 * Determine if an ABORT was sent as the reply 455 */ 456 if (headerID == 0xFF) { 457 parent.sendResponse(ResponseCodes.OBEX_HTTP_OK, null); 458 isClosed = true; 459 isAborted = true; 460 exceptionString = "Abort Received"; 461 throw new IOException("Abort Received"); 462 } else { 463 parent.sendResponse(ResponseCodes.OBEX_HTTP_BAD_REQUEST, null); 464 isClosed = true; 465 exceptionString = "Bad Request Received"; 466 throw new IOException("Bad Request Received"); 467 } 468 } else { 469 470 if ((headerID == 0x82) || (headerID == 0x83)) { 471 finalBitSet = true; 472 } 473 474 /* 475 * Determine if the packet length is larger then this device can receive 476 */ 477 if (length > ObexHelper.MAX_PACKET_SIZE_INT) { 478 parent.sendResponse(ResponseCodes.OBEX_HTTP_REQ_TOO_LARGE, null); 479 throw new IOException("Packet received was too large"); 480 } 481 482 /* 483 * Determine if any headers were sent in the initial request 484 */ 485 if (length > 3) { 486 byte[] data = new byte[length - 3]; 487 bytesReceived = socketInput.read(data); 488 489 while (bytesReceived != data.length) { 490 bytesReceived += socketInput.read(data, bytesReceived, data.length 491 - bytesReceived); 492 } 493 byte[] body = ObexHelper.updateHeaderSet(requestHeaders, data); 494 if (body != null) { 495 isHasBody = true; 496 } 497 if (requestHeaders.connectionID != null) { 498 listener.setConnectionID(ObexHelper 499 .convertToLong(requestHeaders.connectionID)); 500 } else { 501 listener.setConnectionID(1); 502 } 503 504 if (requestHeaders.authResp != null) { 505 if (!parent.handleAuthResp(requestHeaders.authResp)) { 506 exceptionString = "Authentication Failed"; 507 parent.sendResponse(ResponseCodes.OBEX_HTTP_UNAUTHORIZED, null); 508 isClosed = true; 509 requestHeaders.authResp = null; 510 return false; 511 } 512 requestHeaders.authResp = null; 513 } 514 515 if (requestHeaders.authChall != null) { 516 parent.handleAuthChall(requestHeaders); 517 // send the auhtResp to the client 518 replyHeaders.authResp = new byte[requestHeaders.authResp.length]; 519 System.arraycopy(requestHeaders.authResp, 0, replyHeaders.authResp, 0, 520 replyHeaders.authResp.length); 521 requestHeaders.authResp = null; 522 requestHeaders.authChall = null; 523 } 524 525 if (body != null) { 526 if (body[0] == 0x49) { 527 endOfBody = true; 528 } 529 530 /*byte [] body_tmp = new byte[body.length-1]; 531 System.arraycopy(body,1,body_tmp,0,body.length-1); 532 privateInput.writeBytes(body_tmp, body.length-1);*/ 533 privateInput.writeBytes(body, 1); 534 535 } 536 } 537 } 538 return true; 539 } else { 540 return false; 541 } 542 } 543 544 /** 545 * Sends an ABORT message to the server. By calling this method, the 546 * corresponding input and output streams will be closed along with this 547 * object. 548 * 549 * @throws IOException if the transaction has already ended or if an 550 * OBEX server called this method 551 */ 552 public void abort() throws IOException { 553 throw new IOException("Called from a server"); 554 } 555 556 /** 557 * Returns the headers that have been received during the operation. 558 * Modifying the object returned has no effect on the headers that are 559 * sent or retrieved. 560 * 561 * @return the headers received during this <code>Operation</code> 562 * 563 * @throws IOException if this <code>Operation</code> has been closed 564 */ 565 public HeaderSet getReceivedHeaders() throws IOException { 566 ensureOpen(); 567 return requestHeaders; 568 } 569 570 /** 571 * Specifies the headers that should be sent in the next OBEX message that 572 * is sent. 573 * 574 * @param headers the headers to send in the next message 575 * 576 * @throws IOException if this <code>Operation</code> has been closed 577 * or the transaction has ended and no further messages will be exchanged 578 * 579 * @throws IllegalArgumentException if <code>headers</code> was not created 580 * by a call to <code>ServerRequestHandler.createHeaderSet()</code> 581 */ 582 public void sendHeaders(HeaderSet headers) throws IOException { 583 ensureOpen(); 584 585 if (headers == null) { 586 throw new NullPointerException("Headers may not be null"); 587 } 588 589 int[] headerList = headers.getHeaderList(); 590 if (headerList != null) { 591 for (int i = 0; i < headerList.length; i++) { 592 replyHeaders.setHeader(headerList[i], headers.getHeader(headerList[i])); 593 } 594 595 } 596 } 597 598 /** 599 * Retrieves the response code retrieved from the server. Response codes 600 * are defined in the <code>ResponseCodes</code> interface. 601 * 602 * @return the response code retrieved from the server 603 * 604 * @throws IOException if an error occurred in the transport layer during 605 * the transaction; if this method is called on a <code>HeaderSet</code> 606 * object created by calling <code>createHeaderSet</code> in a 607 * <code>ClientSession</code> object; if this is called from a server 608 */ 609 public int getResponseCode() throws IOException { 610 throw new IOException("Called from a server"); 611 } 612 613 /** 614 * Always returns <code>null</code> 615 * 616 * @return <code>null</code> 617 */ 618 public String getEncoding() { 619 return null; 620 } 621 622 /** 623 * Returns the type of content that the resource connected to is providing. 624 * E.g. if the connection is via HTTP, then the value of the content-type 625 * header field is returned. 626 * 627 * @return the content type of the resource that the URL references, or 628 * <code>null</code> if not known 629 */ 630 public String getType() { 631 try { 632 return (String)requestHeaders.getHeader(HeaderSet.TYPE); 633 } catch (IOException e) { 634 return null; 635 } 636 } 637 638 /** 639 * Returns the length of the content which is being provided. E.g. if the 640 * connection is via HTTP, then the value of the content-length header 641 * field is returned. 642 * 643 * @return the content length of the resource that this connection's URL 644 * references, or -1 if the content length is not known 645 */ 646 public long getLength() { 647 try { 648 Long temp = (Long)requestHeaders.getHeader(HeaderSet.LENGTH); 649 650 if (temp == null) { 651 return -1; 652 } else { 653 return temp.longValue(); 654 } 655 } catch (IOException e) { 656 return -1; 657 } 658 } 659 660 public int getMaxPacketSize() { 661 return maxPacketLength - 6; 662 } 663 664 /** 665 * Open and return an input stream for a connection. 666 * 667 * @return an input stream 668 * 669 * @throws IOException if an I/O error occurs 670 */ 671 public InputStream openInputStream() throws IOException { 672 ensureOpen(); 673 return privateInput; 674 } 675 676 /** 677 * Open and return a data input stream for a connection. 678 * 679 * @return an input stream 680 * 681 * @throws IOException if an I/O error occurs 682 */ 683 public DataInputStream openDataInputStream() throws IOException { 684 return new DataInputStream(openInputStream()); 685 } 686 687 /** 688 * Open and return an output stream for a connection. 689 * 690 * @return an output stream 691 * 692 * @throws IOException if an I/O error occurs 693 */ 694 public OutputStream openOutputStream() throws IOException { 695 ensureOpen(); 696 697 if (outputStreamOpened) 698 throw new IOException("no more input streams available, stream already opened"); 699 700 if (!requestFinished) 701 throw new IOException("no output streams available ,request not finished"); 702 703 if (privateOutput == null) { 704 privateOutput = new PrivateOutputStream(this, maxPacketLength - 6); 705 } 706 outputStreamOpened = true; 707 return privateOutput; 708 } 709 710 /** 711 * Open and return a data output stream for a connection. 712 * 713 * @return an output stream 714 * 715 * @throws IOException if an I/O error occurs 716 */ 717 public DataOutputStream openDataOutputStream() throws IOException { 718 return new DataOutputStream(openOutputStream()); 719 } 720 721 /** 722 * Closes the connection and ends the transaction 723 * 724 * @throws IOException if the operation has already ended or is closed 725 */ 726 public void close() throws IOException { 727 ensureOpen(); 728 isClosed = true; 729 } 730 731 /** 732 * Verifies that the connection is open and no exceptions should be thrown. 733 * 734 * @throws IOException if an exception needs to be thrown 735 */ 736 public void ensureOpen() throws IOException { 737 if (exceptionString != null) { 738 throw new IOException(exceptionString); 739 } 740 if (isClosed) { 741 throw new IOException("Operation has already ended"); 742 } 743 } 744 745 /** 746 * Verifies that additional information may be sent. In other words, the 747 * operation is not done. 748 * <P> 749 * Included to implement the BaseStream interface only. It does not do 750 * anything on the server side since the operation of the Operation object 751 * is not done until after the handler returns from its method. 752 * 753 * @throws IOException if the operation is completed 754 */ 755 public void ensureNotDone() throws IOException { 756 } 757 758 /** 759 * Called when the output or input stream is closed. It does not do 760 * anything on the server side since the operation of the Operation object 761 * is not done until after the handler returns from its method. 762 * 763 * @param inStream <code>true</code> if the input stream is closed; 764 * <code>false</code> if the output stream is closed 765 * 766 * @throws IOException if an IO error occurs 767 */ 768 public void streamClosed(boolean inStream) throws IOException { 769 770 } 771} 772