/* * * * Copyright 1990-2009 Sun Microsystems, Inc. All Rights Reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 only, as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License version 2 for more details (a copy is * included at /legal/license.txt). * * You should have received a copy of the GNU General Public License * version 2 along with this work; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa * Clara, CA 95054 or visit www.sun.com if you need additional * information or have any questions. */ package com.sun.jsr082.obex; import javax.obex.Operation; import javax.obex.HeaderSet; import javax.obex.ResponseCodes; import java.io.InputStream; import java.io.OutputStream; import java.io.IOException; import java.io.DataInputStream; import java.io.DataOutputStream; /* * The class implements server side of put/get operation. */ final class ServerOperation implements Operation { // Debug information, should be false for RR private static final boolean DEBUG = false; private Object lock = new Object(); private HeaderSetImpl recvHeaders; private ServerConnectionImpl stream; private int opcode; private boolean isGet; private boolean isAborted; private boolean requestEnd; private boolean inputStreamOpened = false; private boolean outputStreamOpened = false; private boolean inputStreamEof; private boolean responseIsSent = false; private OperationInputStream is = new OperationInputStream(); private OperationOutputStream os = new OperationOutputStream(); private byte[] head = ObexPacketStream.PACKET_CONTINUE; /* Constructor for get operation. */ ServerOperation(ServerConnectionImpl stream) throws IOException { this.stream = stream; opcode = ObexPacketStream.OPCODE_GET; isGet = true; recvHeaders = new HeaderSetImpl(HeaderSetImpl.OWNER_SERVER); int mode = waitForData(stream, recvHeaders, ObexPacketStream.OPCODE_GET); switch (mode) { case 0: isAborted = true; stream.operationClosed = true; break; case 2: requestEnd = true; // no data was received inputStreamEof = true; stream.packetBegin(head); // Response packets can contains both TARGET and CONN ID headers stream.packetAddConnectionID(stream.getConnectionID(), null); stream.packetAddAuthResponses(); stream.packetAddHeaders(null); break; } } /* Constructor for put operation. */ ServerOperation(ServerConnectionImpl stream, HeaderSetImpl recvHeaders) { // data parsing mode already on. this.stream = stream; opcode = ObexPacketStream.OPCODE_PUT; isGet = false; this.recvHeaders = recvHeaders; } public DataOutputStream openDataOutputStream() throws IOException { return new DataOutputStream(openOutputStream()); } public OutputStream openOutputStream() throws IOException { if (DEBUG) { System.out.println("server: openOutputStream()"); } synchronized (lock) { if (stream.operationClosed) { throw new IOException("operation closed"); } if (outputStreamOpened) { throw new IOException("no more output streams available"); } if (!requestEnd) { throw new IOException("input data not read out"); } outputStreamOpened = true; return os; } } public DataInputStream openDataInputStream() throws IOException { return new DataInputStream(openInputStream()); } public InputStream openInputStream() throws IOException { if (DEBUG) { System.out.println("server: openInputStream()"); } synchronized (lock) { if (stream.operationClosed) { throw new IOException("operation closed"); } if (inputStreamOpened) { throw new IOException("no more input streams available"); } inputStreamOpened = true; return is; } } public void abort() throws IOException { // forbidden on server throw new IOException("not permitted"); } public HeaderSet getReceivedHeaders() throws IOException { synchronized (lock) { if (stream.operationClosed) { throw new IOException("operation closed"); } return new HeaderSetImpl(recvHeaders); } } public int getResponseCode() throws IOException { // forbidden on server throw new IOException("not permitted"); } public String getEncoding() { return null; // acording to docs } public long getLength() { Long res = (Long)recvHeaders.getHeader(HeaderSetImpl.LENGTH); if (res == null) { return -1; } return res.longValue(); } public String getType() { return (String)recvHeaders.getHeader(HeaderSetImpl.TYPE); } public void close() { stream.operationClosed = true; } /* * Called by ServerRequestHandler to finish any activity remaining * and to return errorcode to client. */ void destroy(int status) { if (DEBUG) { System.out.println("server: destroy()"); } try { outputStreamOpened = false; if (!responseIsSent) { if (!isGet) { stream.packetBegin(head); stream.packetAddConnectionID(stream.getConnectionID(), null); stream.packetAddAuthResponses(); } stream.packetAddHeaders(null); close(); if (isAborted) status = ResponseCodes.OBEX_HTTP_OK; stream.setPacketType(status); stream.packetEnd(); // send final packet } } catch (Throwable t) { // ignore } // remaining headers will be lost stream.queuedHeaders.removeAllElements(); } public void sendHeaders(HeaderSet headers) throws IOException { if (DEBUG) { System.out.println("server: sendHeaders()"); } synchronized (lock) { if (stream.operationClosed) { throw new IOException("operation closed"); } if (headers == null) { throw new NullPointerException("null headerset"); } if (!(headers instanceof HeaderSetImpl)) { throw new IllegalArgumentException("wrong headerset class"); } HeaderSetImpl headersImpl = (HeaderSetImpl) headers; if (!headersImpl.isSendable()) { throw new IllegalArgumentException( "not created with createHeaderSet"); } stream.packetAddHeaders(headersImpl); if (requestEnd && isGet) { while (!stream.queuedHeaders.isEmpty()) { packetExchange(); } } } } private void packetExchange() throws IOException { if (DEBUG) { System.out.println("server: packetExchange()"); } if (stream.operationHeadersOverflow) { throw new IOException("operation terminated, too long headers"); } if (!requestEnd) { // reading out input stream requestEnd = stream.packetType == (opcode | ObexPacketStream.OPCODE_FINAL); // inordenary case: EOF-DATA but no final bit if (stream.isEof && !requestEnd) { while (recvHeaders.packetType == ObexPacketStream.OPCODE_PUT) { // not final - waiting for final, data not allowed // after EOFB stream.sendPacket(head, stream.getConnectionID(), null, false); stream.recvPacket(); stream.parsePacketHeaders(recvHeaders, 3); } if (recvHeaders.packetType == ObexPacketStream.OPCODE_ABORT) { stream.operationClosed = true; isAborted = true; throw new IOException("operation aborted"); } if (stream.packetType != (opcode | ObexPacketStream.OPCODE_FINAL)) { stream.operationClosed = true; stream.brokenLink(); throw new IOException("protocol error"); } } if (requestEnd) { // switch to requestEnd packetExchange mode stream.packetBegin(head); stream.packetAddConnectionID(stream.getConnectionID(), null); stream.packetAddAuthResponses(); stream.packetAddHeaders(null); return; } // stream.parseEnd(); stream.sendPacket(ObexPacketStream.PACKET_CONTINUE, stream.getConnectionID(), null, false); stream.recvPacket(); if (stream.packetType == ObexPacketStream.OPCODE_ABORT) { stream.parsePacketHeaders(recvHeaders, 3); isAborted = true; stream.operationClosed = true; throw new IOException("operation aborted"); } if ((stream.packetType & ~ObexPacketStream.OPCODE_FINAL) != opcode) { stream.operationClosed = true; stream.brokenLink(); throw new IOException("protocol error"); } stream.parsePacketDataBegin(recvHeaders, 3); return; } stream.packetEnd(); stream.recvPacket(); stream.parsePacketHeaders(recvHeaders, 3); if (stream.packetType == ObexPacketStream.OPCODE_ABORT) { // prepare response packet stream.packetBegin(ObexPacketStream.PACKET_SUCCESS); stream.packetAddConnectionID(stream.getConnectionID(), null); stream.packetAddAuthResponses(); stream.packetAddHeaders(null); isAborted = true; stream.operationClosed = true; throw new IOException("operation aborted"); } if (stream.packetType == ObexPacketStream.OPCODE_DISCONNECT) { stream.sendPacket(ObexPacketStream.PACKET_SUCCESS, stream.getConnectionID(), null, false); stream.close(); return; } if (stream.packetType != (opcode | ObexPacketStream.OPCODE_FINAL)) { stream.operationClosed = true; stream.brokenLink(); throw new IOException("protocol error"); } stream.packetBegin(head); stream.packetAddConnectionID(stream.getConnectionID(), null); stream.packetAddAuthResponses(); stream.packetAddHeaders(null); } static int waitForData(ServerConnectionImpl stream, HeaderSetImpl inputHeaderSet, int op) throws IOException { if (DEBUG) { System.out.println("server: waitForData()"); } // check of errorcode should be done before after data parsing stream.parsePacketDataBegin(inputHeaderSet, 3); // special request to check data availability int hasData = stream.parsePacketData(inputHeaderSet, null, 0, 0); // waiting for data or final bit or abort while (true) { if (stream.packetType == ObexPacketStream.OPCODE_ABORT) { return 0; } if (hasData == 1 || stream.isEof) { return 1; // has data } if (stream.packetType == (op | ObexPacketStream.OPCODE_FINAL)) { return 2; // final } if (stream.packetType != op) { stream.brokenLink(); throw new IOException("protocol error"); } stream.sendPacket(ObexPacketStream.PACKET_CONTINUE, stream.getConnectionID(), null, false); stream.recvPacket(); // check of errorcode should be done before after data parsing stream.parsePacketDataBegin(inputHeaderSet, 3); // special request to check data availability hasData = stream.parsePacketData(inputHeaderSet, null, 0, 0); } } private class OperationInputStream extends InputStream { OperationInputStream() {} public int read() throws IOException { byte[] b = new byte[1]; int len = read(b, 0, 1); if (len == -1) { return -1; } return b[0] & 0xFF; } public int read(byte[] b, int offset, int len) throws IOException { if (DEBUG) { // System.out.println("server: is.read()"); } synchronized (lock) { if (!inputStreamOpened) { throw new IOException("operation finished"); } // Nullpointer check is here if (len < 0 || offset < 0 || offset + len > b.length) { throw new ArrayIndexOutOfBoundsException(); } if (len == 0) { return 0; } if (inputStreamEof) { return -1; } int result = 0; while (true) { int rd = stream.parsePacketData(recvHeaders, b, offset, len); if (rd != 0) { offset += rd; len -= rd; result += rd; if (len == 0) { if (stream.dataOffset != stream.packetOffset) { return result; } } } else { if ((len == 0) && !stream.isEof) { return result; } } // need more data, packet is finished if (stream.isEof) { inputStreamEof = true; if (stream.dataOffset == stream.packetOffset) { requestEnd = stream.packetType == (opcode | ObexPacketStream.OPCODE_FINAL); return (result == 0) ? -1 : result; } else { return result; } } packetExchange(); } } } public void close() throws IOException { if (DEBUG) { System.out.println("server: is.close()"); } // errorcode unknown yet, // ServerRequestHandler will send errorcode packet synchronized (lock) { inputStreamOpened = false; inputStreamEof = false; } } } private class OperationOutputStream extends OutputStream { OperationOutputStream() {} public void write(int b) throws IOException { write(new byte[] { (byte)b }, 0, 1); } public void write(byte[] b, int offset, int len) throws IOException { if (DEBUG) { // System.out.println("server: os.write()"); } synchronized (lock) { if (!outputStreamOpened) { throw new IOException("operation finished"); } if (len < 0 || offset < 0 || offset + len > b.length) { throw new ArrayIndexOutOfBoundsException(); } while (len > 0) { int wr = stream.packetAddData(b, offset, len); if (wr != len) { packetExchange(); } len -= wr; offset += wr; } } } public void flush() throws IOException { if (DEBUG) { System.out.println("server: os.flush()"); } synchronized (lock) { if (!outputStreamOpened) { throw new IOException("operation finished"); } if (isGet) { packetExchange(); } } } public void close() throws IOException { if (DEBUG) { System.out.println("server: os.close()"); } synchronized (lock) { if (outputStreamOpened) { outputStreamOpened = false; boolean res = stream.packetEOFBody(); if (!res) { // error adding EOFB previous packet too long packetExchange(); stream.packetEOFBody(); } } // Unknown errorcode yet, not sending: stream.packetEnd(); } } } }