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