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