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