From 238e0f934f1f47263b384bc745ae0678c777130d Mon Sep 17 00:00:00 2001 From: Casper Bonde Date: Thu, 9 Apr 2015 09:24:48 +0200 Subject: OBEX Over L2CAP + SDP search API for BT profiles - Updated OBEX to support SRM - Added support for OBEX over l2cap and SRM. - Minor bugfixes, and reduce CPU load ALOT - Added support to send responses without body data. - Extend BluetoothSocket to support L2CAP - Added functionality to get the channel number needed to be able to create an SDP record with the channel number. - Added interface to get socket type and max packet sizes. - Added interface to perform SDP search and get the resulting SDP record data. Change-Id: I9d37a00ce73dfffc0e3ce03eab5511ba3a86e5b8 --- obex/Android.mk | 11 + obex/javax/obex/ClientOperation.java | 174 +++++++++---- obex/javax/obex/ClientSession.java | 247 ++++++++++++------ obex/javax/obex/HeaderSet.java | 65 ++++- obex/javax/obex/ObexHelper.java | 87 ++++++- obex/javax/obex/ObexPacket.java | 71 +++++ obex/javax/obex/ObexSession.java | 6 + obex/javax/obex/ObexTransport.java | 35 +++ obex/javax/obex/ServerOperation.java | 416 +++++++++++++++++++----------- obex/javax/obex/ServerRequestHandler.java | 9 + obex/javax/obex/ServerSession.java | 59 ++++- 11 files changed, 881 insertions(+), 299 deletions(-) create mode 100644 obex/javax/obex/ObexPacket.java (limited to 'obex') diff --git a/obex/Android.mk b/obex/Android.mk index fbfe9be..e7c1fd3 100644 --- a/obex/Android.mk +++ b/obex/Android.mk @@ -7,3 +7,14 @@ LOCAL_SRC_FILES := $(call all-subdir-java-files) LOCAL_MODULE:= javax.obex include $(BUILD_JAVA_LIBRARY) + + +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := optional + +LOCAL_SRC_FILES := $(call all-subdir-java-files) + +LOCAL_MODULE:= javax.obexstatic + +include $(BUILD_STATIC_JAVA_LIBRARY) \ No newline at end of file diff --git a/obex/javax/obex/ClientOperation.java b/obex/javax/obex/ClientOperation.java index 75278b5..cc20d39 100644 --- a/obex/javax/obex/ClientOperation.java +++ b/obex/javax/obex/ClientOperation.java @@ -1,5 +1,6 @@ /* - * Copyright (c) 2014 The Android Open Source Project + * Copyright (c) 2015 The Android Open Source Project + * Copyright (C) 2015 Samsung LSI * Copyright (c) 2008-2009, Motorola, Inc. * * All rights reserved. @@ -40,6 +41,8 @@ import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.ByteArrayOutputStream; +import android.util.Log; + /** * This class implements the Operation interface. It will read and * write data via puts and gets. @@ -47,6 +50,10 @@ import java.io.ByteArrayOutputStream; */ public final class ClientOperation implements Operation, BaseStream { + private static final String TAG = "ClientOperation"; + + private static final boolean V = ObexHelper.VDBG; + private ClientSession mParent; private boolean mInputOpen; @@ -75,6 +82,19 @@ public final class ClientOperation implements Operation, BaseStream { private boolean mEndOfBodySent; + private boolean mSendBodyHeader = true; + // A latch - when triggered, there is not way back ;-) + private boolean mSrmActive = false; + + // Assume SRM disabled - until support is confirmed + // by the server + private boolean mSrmEnabled = false; + // keep waiting until final-bit is received in request + // to handle the case where the SRM enable header is in + // a different OBEX packet than the SRMP header. + private boolean mSrmWaitingForRemote = true; + + /** * Creates new OperationImpl to read and write data to a server * @param maxSize the maximum packet size @@ -164,7 +184,7 @@ public final class ClientOperation implements Operation, BaseStream { * Since we are not sending any headers or returning any headers then * we just need to write and read the same bytes */ - mParent.sendRequest(ObexHelper.OBEX_OPCODE_ABORT, null, mReplyHeader, null); + mParent.sendRequest(ObexHelper.OBEX_OPCODE_ABORT, null, mReplyHeader, null, false); if (mReplyHeader.responseCode != ResponseCodes.OBEX_HTTP_OK) { throw new IOException("Invalid response code from server"); @@ -215,6 +235,7 @@ public final class ClientOperation implements Operation, BaseStream { try { return (String)mReplyHeader.getHeader(HeaderSet.TYPE); } catch (IOException e) { + if(V) Log.d(TAG, "Exception occured - returning null",e); return null; } } @@ -236,6 +257,7 @@ public final class ClientOperation implements Operation, BaseStream { return temp.longValue(); } } catch (IOException e) { + if(V) Log.d(TAG,"Exception occured - returning -1",e); return -1; } } @@ -408,7 +430,9 @@ public final class ClientOperation implements Operation, BaseStream { } /** - * Sends a request to the client of the specified type + * Sends a request to the client of the specified type. + * This function will enable SRM and set SRM active if the server + * response allows this. * @param opCode the request code to send to the client * @return true if there is more data to send; * false if there is no more data to send @@ -431,13 +455,16 @@ public final class ClientOperation implements Operation, BaseStream { * length, but it is a waste of resources if we can't send much of * the body. */ - if ((ObexHelper.BASE_PACKET_LENGTH + headerArray.length) > mMaxPacketSize) { + final int MINIMUM_BODY_LENGTH = 3; + if ((ObexHelper.BASE_PACKET_LENGTH + headerArray.length + MINIMUM_BODY_LENGTH) + > mMaxPacketSize) { int end = 0; int start = 0; // split & send the headerArray in multiple packets. while (end != headerArray.length) { //split the headerArray + end = ObexHelper.findHeaderEnd(headerArray, start, mMaxPacketSize - ObexHelper.BASE_PACKET_LENGTH); // can not split @@ -459,7 +486,7 @@ public final class ClientOperation implements Operation, BaseStream { byte[] sendHeader = new byte[end - start]; System.arraycopy(headerArray, start, sendHeader, 0, sendHeader.length); - if (!mParent.sendRequest(opCode, sendHeader, mReplyHeader, mPrivateInput)) { + if (!mParent.sendRequest(opCode, sendHeader, mReplyHeader, mPrivateInput, false)) { return false; } @@ -470,12 +497,20 @@ public final class ClientOperation implements Operation, BaseStream { start = end; } + // Enable SRM if it should be enabled + checkForSrm(); + if (bodyLength > 0) { return true; } else { return false; } } else { + /* All headers will fit into a single package */ + if(mSendBodyHeader == false) { + /* As we are not to send any body data, set the FINAL_BIT */ + opCode |= ObexHelper.OBEX_OPCODE_FINAL_BIT_MASK; + } out.write(headerArray); } @@ -499,11 +534,11 @@ public final class ClientOperation implements Operation, BaseStream { * (End of Body) otherwise, we need to send 0x48 (Body) */ if ((mPrivateOutput.isClosed()) && (!returnValue) && (!mEndOfBodySent) - && ((opCode & 0x80) != 0)) { - out.write(0x49); + && ((opCode & ObexHelper.OBEX_OPCODE_FINAL_BIT_MASK) != 0)) { + out.write(HeaderSet.END_OF_BODY); mEndOfBodySent = true; } else { - out.write(0x48); + out.write(HeaderSet.BODY); } bodyLength += 3; @@ -517,12 +552,11 @@ public final class ClientOperation implements Operation, BaseStream { if (mPrivateOutputOpen && bodyLength <= 0 && !mEndOfBodySent) { // only 0x82 or 0x83 can send 0x49 - if ((opCode & 0x80) == 0) { - out.write(0x48); + if ((opCode & ObexHelper.OBEX_OPCODE_FINAL_BIT_MASK) == 0) { + out.write(HeaderSet.BODY); } else { - out.write(0x49); + out.write(HeaderSet.END_OF_BODY); mEndOfBodySent = true; - } bodyLength = 3; @@ -531,15 +565,20 @@ public final class ClientOperation implements Operation, BaseStream { } if (out.size() == 0) { - if (!mParent.sendRequest(opCode, null, mReplyHeader, mPrivateInput)) { + if (!mParent.sendRequest(opCode, null, mReplyHeader, mPrivateInput, mSrmActive)) { return false; } + // Enable SRM if it should be enabled + checkForSrm(); return returnValue; } if ((out.size() > 0) - && (!mParent.sendRequest(opCode, out.toByteArray(), mReplyHeader, mPrivateInput))) { + && (!mParent.sendRequest(opCode, out.toByteArray(), + mReplyHeader, mPrivateInput, mSrmActive))) { return false; } + // Enable SRM if it should be enabled + checkForSrm(); // send all of the output data in 0x48, // send 0x49 with empty body @@ -549,6 +588,35 @@ public final class ClientOperation implements Operation, BaseStream { return returnValue; } + private void checkForSrm() throws IOException { + Byte srmMode = (Byte)mReplyHeader.getHeader(HeaderSet.SINGLE_RESPONSE_MODE); + if(mParent.isSrmSupported() == true && srmMode != null + && srmMode == ObexHelper.OBEX_SRM_ENABLE) { + mSrmEnabled = true; + } + /** + * Call this only when a complete obex packet have been received. + * (This is not optimal, but the current design is not really suited to + * the way SRM is specified.) + * The BT usage of SRM is not really safe - it assumes that the SRMP will fit + * into every OBEX packet, hence if another header occupies the entire packet, + * the scheme will not work - unlikely though. + */ + if(mSrmEnabled) { + mSrmWaitingForRemote = false; + Byte srmp = (Byte)mReplyHeader.getHeader(HeaderSet.SINGLE_RESPONSE_MODE_PARAMETER); + if(srmp != null && srmp == ObexHelper.OBEX_SRMP_WAIT) { + mSrmWaitingForRemote = true; + // Clear the wait header, as the absence of the header in the next packet + // indicates don't wait anymore. + mReplyHeader.setHeader(HeaderSet.SINGLE_RESPONSE_MODE_PARAMETER, null); + } + } + if((mSrmWaitingForRemote == false) && (mSrmEnabled == true)) { + mSrmActive = true; + } + } + /** * This method starts the processing thread results. It will send the * initial request. If the response takes more then one packet, a thread @@ -564,40 +632,35 @@ public final class ClientOperation implements Operation, BaseStream { if (mGetOperation) { if (!mOperationDone) { - if (!mGetFinalFlag) { - mReplyHeader.responseCode = ResponseCodes.OBEX_HTTP_CONTINUE; - while ((more) && (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE)) { - more = sendRequest(0x03); - } - - if (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE) { - mParent.sendRequest(0x83, null, mReplyHeader, mPrivateInput); - } - if (mReplyHeader.responseCode != ResponseCodes.OBEX_HTTP_CONTINUE) { - mOperationDone = true; - } - } else { - more = sendRequest(0x83); - - if (more) { - throw new IOException("FINAL_GET forced but data did not fit into single packet!"); - } - + mReplyHeader.responseCode = ResponseCodes.OBEX_HTTP_CONTINUE; + while ((more) && (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE)) { + more = sendRequest(ObexHelper.OBEX_OPCODE_GET); + } + // For GET we need to loop until all headers have been sent, + // And then we wait for the first continue package with the + // reply. + if (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE) { + mParent.sendRequest(ObexHelper.OBEX_OPCODE_GET_FINAL, + null, mReplyHeader, mPrivateInput, mSrmActive); + } + if (mReplyHeader.responseCode != ResponseCodes.OBEX_HTTP_CONTINUE) { mOperationDone = true; + } else { + checkForSrm(); } } } else { - + // PUT operation if (!mOperationDone) { mReplyHeader.responseCode = ResponseCodes.OBEX_HTTP_CONTINUE; while ((more) && (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE)) { - more = sendRequest(0x02); - + more = sendRequest(ObexHelper.OBEX_OPCODE_PUT); } } if (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE) { - mParent.sendRequest(0x82, null, mReplyHeader, mPrivateInput); + mParent.sendRequest(ObexHelper.OBEX_OPCODE_PUT_FINAL, + null, mReplyHeader, mPrivateInput, mSrmActive); } if (mReplyHeader.responseCode != ResponseCodes.OBEX_HTTP_CONTINUE) { @@ -617,15 +680,21 @@ public final class ClientOperation implements Operation, BaseStream { public synchronized boolean continueOperation(boolean sendEmpty, boolean inStream) throws IOException { + // One path to the first put operation - the other one does not need to + // handle SRM, as all will fit into one packet. + if (mGetOperation) { if ((inStream) && (!mOperationDone)) { // to deal with inputstream in get operation - mParent.sendRequest(0x83, null, mReplyHeader, mPrivateInput); + mParent.sendRequest(ObexHelper.OBEX_OPCODE_GET_FINAL, + null, mReplyHeader, mPrivateInput, mSrmActive); /* * Determine if that was not the last packet in the operation */ if (mReplyHeader.responseCode != ResponseCodes.OBEX_HTTP_CONTINUE) { mOperationDone = true; + } else { + checkForSrm(); } return true; @@ -636,16 +705,7 @@ public final class ClientOperation implements Operation, BaseStream { if (mPrivateInput == null) { mPrivateInput = new PrivateInputStream(this); } - - if (!mGetFinalFlag) { - sendRequest(0x03); - } else { - sendRequest(0x83); - - if (mReplyHeader.responseCode != ResponseCodes.OBEX_HTTP_CONTINUE) { - mOperationDone = true; - } - } + sendRequest(ObexHelper.OBEX_OPCODE_GET); return true; } else if (mOperationDone) { @@ -653,12 +713,13 @@ public final class ClientOperation implements Operation, BaseStream { } } else { + // PUT operation if ((!inStream) && (!mOperationDone)) { // to deal with outputstream in put operation if (mReplyHeader.responseCode == -1) { mReplyHeader.responseCode = ResponseCodes.OBEX_HTTP_CONTINUE; } - sendRequest(0x02); + sendRequest(ObexHelper.OBEX_OPCODE_PUT); return true; } else if ((inStream) && (!mOperationDone)) { // How to deal with inputstream in put operation ? @@ -696,7 +757,7 @@ public final class ClientOperation implements Operation, BaseStream { } while ((more) && (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE)) { - more = sendRequest(0x02); + more = sendRequest(ObexHelper.OBEX_OPCODE_PUT); } /* @@ -706,7 +767,7 @@ public final class ClientOperation implements Operation, BaseStream { */ while (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE) { - sendRequest(0x82); + sendRequest(ObexHelper.OBEX_OPCODE_PUT_FINAL); } mOperationDone = true; } else if ((inStream) && (mOperationDone)) { @@ -724,12 +785,14 @@ public final class ClientOperation implements Operation, BaseStream { } while (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE) { - if (!sendRequest(0x83)) { + if (!sendRequest(ObexHelper.OBEX_OPCODE_GET_FINAL)) { break; } } while (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE) { - mParent.sendRequest(0x83, null, mReplyHeader, mPrivateInput); + mParent.sendRequest(ObexHelper.OBEX_OPCODE_GET_FINAL, null, + mReplyHeader, mPrivateInput, false); + // Regardless of the SRM state, wait for the response. } mOperationDone = true; } else if ((!inStream) && (!mOperationDone)) { @@ -752,9 +815,9 @@ public final class ClientOperation implements Operation, BaseStream { mReplyHeader.responseCode = ResponseCodes.OBEX_HTTP_CONTINUE; while ((more) && (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE)) { - more = sendRequest(0x03); + more = sendRequest(ObexHelper.OBEX_OPCODE_GET); } - sendRequest(0x83); + sendRequest(ObexHelper.OBEX_OPCODE_GET_FINAL); // parent.sendRequest(0x83, null, replyHeaders, privateInput); if (mReplyHeader.responseCode != ResponseCodes.OBEX_HTTP_CONTINUE) { mOperationDone = true; @@ -764,5 +827,6 @@ public final class ClientOperation implements Operation, BaseStream { } public void noBodyHeader(){ + mSendBodyHeader = false; } } diff --git a/obex/javax/obex/ClientSession.java b/obex/javax/obex/ClientSession.java index 27d8976..272a920 100644 --- a/obex/javax/obex/ClientSession.java +++ b/obex/javax/obex/ClientSession.java @@ -1,4 +1,6 @@ /* + * Copyright (c) 2015 The Android Open Source Project + * Copyright (C) 2015 Samsung LSI * Copyright (c) 2008-2009, Motorola, Inc. * * All rights reserved. @@ -37,12 +39,16 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import android.util.Log; + /** * This class in an implementation of the OBEX ClientSession. * @hide */ public final class ClientSession extends ObexSession { + private static final String TAG = "ClientSession"; + private boolean mOpen; // Determines if an OBEX layer connection has been established @@ -51,10 +57,10 @@ public final class ClientSession extends ObexSession { private byte[] mConnectionId = null; /* - * The max Packet size must be at least 256 according to the OBEX + * The max Packet size must be at least 255 according to the OBEX * specification. */ - private int maxPacketSize = 256; + private int mMaxTxPacketSize = ObexHelper.LOWER_LIMIT_MAX_PACKET_SIZE; private boolean mRequestActive; @@ -62,11 +68,33 @@ public final class ClientSession extends ObexSession { private final OutputStream mOutput; + private final boolean mLocalSrmSupported; + + private final ObexTransport mTransport; + public ClientSession(final ObexTransport trans) throws IOException { mInput = trans.openInputStream(); mOutput = trans.openOutputStream(); mOpen = true; mRequestActive = false; + mLocalSrmSupported = trans.isSrmSupported(); + mTransport = trans; + } + + /** + * Create a ClientSession + * @param trans The transport to use for OBEX transactions + * @param supportsSrm True if Single Response Mode should be used e.g. if the + * supplied transport is a TCP or l2cap channel. + * @throws IOException if it occurs while opening the transport streams. + */ + public ClientSession(final ObexTransport trans, final boolean supportsSrm) throws IOException { + mInput = trans.openInputStream(); + mOutput = trans.openOutputStream(); + mOpen = true; + mRequestActive = false; + mLocalSrmSupported = supportsSrm; + mTransport = trans; } public HeaderSet connect(final HeaderSet header) throws IOException { @@ -98,23 +126,25 @@ public final class ClientSession extends ObexSession { * Byte 7 to n: headers */ byte[] requestPacket = new byte[totalLength]; + int maxRxPacketSize = ObexHelper.getMaxRxPacketSize(mTransport); // We just need to start at byte 3 since the sendRequest() method will // handle the length and 0x80. requestPacket[0] = (byte)0x10; requestPacket[1] = (byte)0x00; - requestPacket[2] = (byte)(ObexHelper.MAX_PACKET_SIZE_INT >> 8); - requestPacket[3] = (byte)(ObexHelper.MAX_PACKET_SIZE_INT & 0xFF); + requestPacket[2] = (byte)(maxRxPacketSize >> 8); + requestPacket[3] = (byte)(maxRxPacketSize & 0xFF); if (head != null) { System.arraycopy(head, 0, requestPacket, 4, head.length); } - // check with local max packet size + // Since we are not yet connected, the peer max packet size is unknown, + // hence we are only guaranteed the server will use the first 7 bytes. if ((requestPacket.length + 3) > ObexHelper.MAX_PACKET_SIZE_INT) { - throw new IOException("Packet size exceeds max packet size"); + throw new IOException("Packet size exceeds max packet size for connect"); } HeaderSet returnHeaderSet = new HeaderSet(); - sendRequest(ObexHelper.OBEX_OPCODE_CONNECT, requestPacket, returnHeaderSet, null); + sendRequest(ObexHelper.OBEX_OPCODE_CONNECT, requestPacket, returnHeaderSet, null, false); /* * Read the response from the OBEX server. @@ -158,7 +188,18 @@ public final class ClientSession extends ObexSession { System.arraycopy(mConnectionId, 0, head.mConnectionID, 0, 4); } - return new ClientOperation(maxPacketSize, this, head, true); + if(mLocalSrmSupported) { + head.setHeader(HeaderSet.SINGLE_RESPONSE_MODE, ObexHelper.OBEX_SRM_ENABLE); + /* TODO: Consider creating an interface to get the wait state. + * On an android system, I cannot see when this is to be used. + * except perhaps if we are to wait for user accept on a push message. + if(getLocalWaitState()) { + head.setHeader(HeaderSet.SINGLE_RESPONSE_MODE_PARAMETER, ObexHelper.OBEX_SRMP_WAIT); + } + */ + } + + return new ClientOperation(mMaxTxPacketSize, this, head, true); } /** @@ -202,7 +243,7 @@ public final class ClientSession extends ObexSession { } head = ObexHelper.createHeader(header, false); - if ((head.length + 3) > maxPacketSize) { + if ((head.length + 3) > mMaxTxPacketSize) { throw new IOException("Packet size exceeds max packet size"); } } else { @@ -215,7 +256,7 @@ public final class ClientSession extends ObexSession { } HeaderSet returnHeaderSet = new HeaderSet(); - sendRequest(ObexHelper.OBEX_OPCODE_DISCONNECT, head, returnHeaderSet, null); + sendRequest(ObexHelper.OBEX_OPCODE_DISCONNECT, head, returnHeaderSet, null, false); /* * An OBEX DISCONNECT reply from the server: @@ -269,7 +310,16 @@ public final class ClientSession extends ObexSession { System.arraycopy(mConnectionId, 0, head.mConnectionID, 0, 4); } - return new ClientOperation(maxPacketSize, this, head, false); + if(mLocalSrmSupported) { + head.setHeader(HeaderSet.SINGLE_RESPONSE_MODE, ObexHelper.OBEX_SRM_ENABLE); + /* TODO: Consider creating an interface to get the wait state. + * On an android system, I cannot see when this is to be used. + if(getLocalWaitState()) { + head.setHeader(HeaderSet.SINGLE_RESPONSE_MODE_PARAMETER, ObexHelper.OBEX_SRMP_WAIT); + } + */ + } + return new ClientOperation(mMaxTxPacketSize, this, head, false); } public void setAuthenticator(Authenticator auth) throws IOException { @@ -314,7 +364,7 @@ public final class ClientSession extends ObexSession { head = ObexHelper.createHeader(headset, false); totalLength += head.length; - if (totalLength > maxPacketSize) { + if (totalLength > mMaxTxPacketSize) { throw new IOException("Packet size exceeds max packet size"); } @@ -348,7 +398,7 @@ public final class ClientSession extends ObexSession { } HeaderSet returnHeaderSet = new HeaderSet(); - sendRequest(ObexHelper.OBEX_OPCODE_SETPATH, packet, returnHeaderSet, null); + sendRequest(ObexHelper.OBEX_OPCODE_SETPATH, packet, returnHeaderSet, null, false); /* * An OBEX SETPATH reply from the server: @@ -400,20 +450,40 @@ public final class ClientSession extends ObexSession { * @param head the headers to send to the client * @param header the header object to update with the response * @param privateInput the input stream used by the Operation object; null - * if this is called on a CONNECT, SETPATH or DISCONNECT return + * if this is called on a CONNECT, SETPATH or DISCONNECT + * @return * true if the operation completed successfully; * false if an authentication response failed to pass * @throws IOException if an IO error occurs */ public boolean sendRequest(int opCode, byte[] head, HeaderSet header, - PrivateInputStream privateInput) throws IOException { + PrivateInputStream privateInput, boolean srmActive) throws IOException { //check header length with local max size if (head != null) { if ((head.length + 3) > ObexHelper.MAX_PACKET_SIZE_INT) { + // TODO: This is an implementation limit - not a specification requirement. throw new IOException("header too large "); } } + boolean skipSend = false; + boolean skipReceive = false; + if (srmActive == true) { + if (opCode == ObexHelper.OBEX_OPCODE_PUT) { + // we are in the middle of a SRM PUT operation, don't expect a continue. + skipReceive = true; + } else if (opCode == ObexHelper.OBEX_OPCODE_GET) { + // We are still sending the get request, send, but don't expect continue + // until the request is transfered (the final bit is set) + skipReceive = true; + } else if (opCode == ObexHelper.OBEX_OPCODE_GET_FINAL) { + // All done sending the request, expect data from the server, without + // sending continue. + skipSend = true; + } + + } + int bytesReceived; ByteArrayOutputStream out = new ByteArrayOutputStream(); out.write((byte)opCode); @@ -428,86 +498,105 @@ public final class ClientSession extends ObexSession { out.write(head); } - // Write the request to the output stream and flush the stream - mOutput.write(out.toByteArray()); - mOutput.flush(); + if (!skipSend) { + // Write the request to the output stream and flush the stream + mOutput.write(out.toByteArray()); + // TODO: is this really needed? if this flush is implemented + // correctly, we will get a gap between each obex packet. + // which is kind of the idea behind SRM to avoid. + // Consider offloading to another thread (async action) + mOutput.flush(); + } - header.responseCode = mInput.read(); + if (!skipReceive) { + header.responseCode = mInput.read(); - int length = ((mInput.read() << 8) | (mInput.read())); + int length = ((mInput.read() << 8) | (mInput.read())); - if (length > ObexHelper.MAX_PACKET_SIZE_INT) { - throw new IOException("Packet received exceeds packet size limit"); - } - if (length > ObexHelper.BASE_PACKET_LENGTH) { - byte[] data = null; - if (opCode == ObexHelper.OBEX_OPCODE_CONNECT) { - @SuppressWarnings("unused") - int version = mInput.read(); - @SuppressWarnings("unused") - int flags = mInput.read(); - maxPacketSize = (mInput.read() << 8) + mInput.read(); + if (length > ObexHelper.getMaxRxPacketSize(mTransport)) { + throw new IOException("Packet received exceeds packet size limit"); + } + if (length > ObexHelper.BASE_PACKET_LENGTH) { + byte[] data = null; + if (opCode == ObexHelper.OBEX_OPCODE_CONNECT) { + @SuppressWarnings("unused") + int version = mInput.read(); + @SuppressWarnings("unused") + int flags = mInput.read(); + mMaxTxPacketSize = (mInput.read() << 8) + mInput.read(); + + //check with local max size + if (mMaxTxPacketSize > ObexHelper.MAX_CLIENT_PACKET_SIZE) { + mMaxTxPacketSize = ObexHelper.MAX_CLIENT_PACKET_SIZE; + } - //check with local max size - if (maxPacketSize > ObexHelper.MAX_CLIENT_PACKET_SIZE) { - maxPacketSize = ObexHelper.MAX_CLIENT_PACKET_SIZE; - } + // check with transport maximum size + if(mMaxTxPacketSize > ObexHelper.getMaxTxPacketSize(mTransport)) { + // To increase this size, increase the buffer size in L2CAP layer + // in Bluedroid. + Log.w(TAG, "An OBEX packet size of " + mMaxTxPacketSize + "was" + + " requested. Transport only allows: " + + ObexHelper.getMaxTxPacketSize(mTransport) + + " Lowering limit to this value."); + mMaxTxPacketSize = ObexHelper.getMaxTxPacketSize(mTransport); + } - if (length > 7) { - data = new byte[length - 7]; + if (length > 7) { + data = new byte[length - 7]; - bytesReceived = mInput.read(data); - while (bytesReceived != (length - 7)) { - bytesReceived += mInput.read(data, bytesReceived, data.length - - bytesReceived); + bytesReceived = mInput.read(data); + while (bytesReceived != (length - 7)) { + bytesReceived += mInput.read(data, bytesReceived, data.length + - bytesReceived); + } + } else { + return true; } } else { - return true; - } - } else { - data = new byte[length - 3]; - bytesReceived = mInput.read(data); + data = new byte[length - 3]; + bytesReceived = mInput.read(data); - while (bytesReceived != (length - 3)) { - bytesReceived += mInput.read(data, bytesReceived, data.length - bytesReceived); - } - if (opCode == ObexHelper.OBEX_OPCODE_ABORT) { - return true; + while (bytesReceived != (length - 3)) { + bytesReceived += mInput.read(data, bytesReceived, data.length - bytesReceived); + } + if (opCode == ObexHelper.OBEX_OPCODE_ABORT) { + return true; + } } - } - byte[] body = ObexHelper.updateHeaderSet(header, data); - if ((privateInput != null) && (body != null)) { - privateInput.writeBytes(body, 1); - } + byte[] body = ObexHelper.updateHeaderSet(header, data); + if ((privateInput != null) && (body != null)) { + privateInput.writeBytes(body, 1); + } - if (header.mConnectionID != null) { - mConnectionId = new byte[4]; - System.arraycopy(header.mConnectionID, 0, mConnectionId, 0, 4); - } + if (header.mConnectionID != null) { + mConnectionId = new byte[4]; + System.arraycopy(header.mConnectionID, 0, mConnectionId, 0, 4); + } - if (header.mAuthResp != null) { - if (!handleAuthResp(header.mAuthResp)) { - setRequestInactive(); - throw new IOException("Authentication Failed"); + if (header.mAuthResp != null) { + if (!handleAuthResp(header.mAuthResp)) { + setRequestInactive(); + throw new IOException("Authentication Failed"); + } } - } - if ((header.responseCode == ResponseCodes.OBEX_HTTP_UNAUTHORIZED) - && (header.mAuthChall != null)) { + if ((header.responseCode == ResponseCodes.OBEX_HTTP_UNAUTHORIZED) + && (header.mAuthChall != null)) { - if (handleAuthChall(header)) { - out.write((byte)HeaderSet.AUTH_RESPONSE); - out.write((byte)((header.mAuthResp.length + 3) >> 8)); - out.write((byte)(header.mAuthResp.length + 3)); - out.write(header.mAuthResp); - header.mAuthChall = null; - header.mAuthResp = null; + if (handleAuthChall(header)) { + out.write((byte)HeaderSet.AUTH_RESPONSE); + out.write((byte)((header.mAuthResp.length + 3) >> 8)); + out.write((byte)(header.mAuthResp.length + 3)); + out.write(header.mAuthResp); + header.mAuthChall = null; + header.mAuthResp = null; - byte[] sendHeaders = new byte[out.size() - 3]; - System.arraycopy(out.toByteArray(), 3, sendHeaders, 0, sendHeaders.length); + byte[] sendHeaders = new byte[out.size() - 3]; + System.arraycopy(out.toByteArray(), 3, sendHeaders, 0, sendHeaders.length); - return sendRequest(opCode, sendHeaders, header, privateInput); + return sendRequest(opCode, sendHeaders, header, privateInput, false); + } } } } @@ -520,4 +609,8 @@ public final class ClientSession extends ObexSession { mInput.close(); mOutput.close(); } + + public boolean isSrmSupported() { + return mLocalSrmSupported; + } } diff --git a/obex/javax/obex/HeaderSet.java b/obex/javax/obex/HeaderSet.java index 51b560a..35fe186 100644 --- a/obex/javax/obex/HeaderSet.java +++ b/obex/javax/obex/HeaderSet.java @@ -40,7 +40,7 @@ import java.security.SecureRandom; /** * This class implements the javax.obex.HeaderSet interface for OBEX over - * RFCOMM. + * RFCOMM or OBEX over l2cap. * @hide */ public final class HeaderSet { @@ -178,6 +178,22 @@ public final class HeaderSet { */ public static final int OBJECT_CLASS = 0x4F; + /** + * Represents the OBEX Single Response Mode (SRM). This header is used + * for Single response mode, introduced in OBEX 1.5. + *

+ * The value of SINGLE_RESPONSE_MODE is 0x97 (151). + */ + public static final int SINGLE_RESPONSE_MODE = 0x97; + + /** + * Represents the OBEX Single Response Mode Parameters. This header is used + * for Single response mode, introduced in OBEX 1.5. + *

+ * The value of SINGLE_RESPONSE_MODE_PARAMETER is 0x98 (152). + */ + public static final int SINGLE_RESPONSE_MODE_PARAMETER = 0x98; + private Long mCount; // 4 byte unsigned integer private String mName; // null terminated Unicode text string @@ -204,7 +220,7 @@ public final class HeaderSet { private byte[] mObjectClass; // byte sequence - private String[] mUnicodeUserDefined; //null terminated unicode string + private String[] mUnicodeUserDefined; // null terminated unicode string private byte[][] mSequenceUserDefined; // byte sequence user defined @@ -212,7 +228,12 @@ public final class HeaderSet { private Long[] mIntegerUserDefined; // 4 byte unsigned integer - private final SecureRandom mRandom; + private SecureRandom mRandom = null; + + private Byte mSingleResponseMode; // byte to indicate enable/disable/support for SRM + + private Byte mSrmParam; // byte representing the SRM parameters - only "wait" + // is supported by Bluetooth /*package*/ byte[] nonce; @@ -234,7 +255,6 @@ public final class HeaderSet { mByteUserDefined = new Byte[16]; mIntegerUserDefined = new Long[16]; responseCode = -1; - mRandom = new SecureRandom(); } /** @@ -393,6 +413,30 @@ public final class HeaderSet { } } break; + case SINGLE_RESPONSE_MODE: + if (headerValue == null) { + mSingleResponseMode = null; + } else { + if (!(headerValue instanceof Byte)) { + throw new IllegalArgumentException( + "Single Response Mode must be a Byte"); + } else { + mSingleResponseMode = (Byte)headerValue; + } + } + break; + case SINGLE_RESPONSE_MODE_PARAMETER: + if (headerValue == null) { + mSrmParam = null; + } else { + if (!(headerValue instanceof Byte)) { + throw new IllegalArgumentException( + "Single Response Mode Parameter must be a Byte"); + } else { + mSrmParam = (Byte)headerValue; + } + } + break; default: // Verify that it was not a Unicode String user Defined if ((headerID >= 0x30) && (headerID <= 0x3F)) { @@ -493,6 +537,10 @@ public final class HeaderSet { return mObjectClass; case APPLICATION_PARAMETER: return mAppParam; + case SINGLE_RESPONSE_MODE: + return mSingleResponseMode; + case SINGLE_RESPONSE_MODE_PARAMETER: + return mSrmParam; default: // Verify that it was not a Unicode String user Defined if ((headerID >= 0x30) && (headerID <= 0x3F)) { @@ -564,6 +612,12 @@ public final class HeaderSet { if (mObjectClass != null) { out.write(OBJECT_CLASS); } + if(mSingleResponseMode != null) { + out.write(SINGLE_RESPONSE_MODE); + } + if(mSrmParam != null) { + out.write(SINGLE_RESPONSE_MODE_PARAMETER); + } for (int i = 0x30; i < 0x40; i++) { if (mUnicodeUserDefined[i - 0x30] != null) { @@ -625,6 +679,9 @@ public final class HeaderSet { throws IOException { nonce = new byte[16]; + if(mRandom == null) { + mRandom = new SecureRandom(); + } for (int i = 0; i < 16; i++) { nonce[i] = (byte)mRandom.nextInt(); } diff --git a/obex/javax/obex/ObexHelper.java b/obex/javax/obex/ObexHelper.java index 0a06709..fa50943 100644 --- a/obex/javax/obex/ObexHelper.java +++ b/obex/javax/obex/ObexHelper.java @@ -1,5 +1,6 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * Copyright (C) 2015 The Android Open Source Project + * Copyright (C) 2015 Samsung LSI * Copyright (c) 2008-2009, Motorola, Inc. * * All rights reserved. @@ -42,12 +43,16 @@ import java.util.Calendar; import java.util.Date; import java.util.TimeZone; +import android.util.Log; + /** * This class defines a set of helper methods for the implementation of Obex. * @hide */ public final class ObexHelper { + private static final String TAG = "ObexHelper"; + public static final boolean VDBG = false; /** * Defines the basic packet length used by OBEX. Every OBEX packet has the * same basic format:
@@ -65,18 +70,24 @@ public final class ObexHelper { * should be the Max incoming MTU minus TODO: L2CAP package headers and * RFCOMM package headers. TODO: Retrieve the max incoming MTU from TODO: * LocalDevice.getProperty(). + * NOTE: This value must be larger than or equal to the L2CAP SDU */ /* * android note set as 0xFFFE to match remote MPS */ public static final int MAX_PACKET_SIZE_INT = 0xFFFE; + // The minimum allowed max packet size is 255 according to the OBEX specification + public static final int LOWER_LIMIT_MAX_PACKET_SIZE = 255; + /** * Temporary workaround to be able to push files to Windows 7. * TODO: Should be removed as soon as Microsoft updates their driver. */ public static final int MAX_CLIENT_PACKET_SIZE = 0xFC00; + public static final int OBEX_OPCODE_FINAL_BIT_MASK = 0x80; + public static final int OBEX_OPCODE_CONNECT = 0x80; public static final int OBEX_OPCODE_DISCONNECT = 0x81; @@ -119,6 +130,12 @@ public final class ObexHelper { public static final int OBEX_AUTH_REALM_CHARSET_UNICODE = 0xFF; + public static final byte OBEX_SRM_ENABLE = 0x01; // For BT we only need enable/disable + public static final byte OBEX_SRM_DISABLE = 0x00; + public static final byte OBEX_SRM_SUPPORT = 0x02; // Unused for now + + public static final byte OBEX_SRMP_WAIT = 0x01; // Only SRMP value used by BT + /** * Updates the HeaderSet with the headers received in the byte array * provided. Invalid headers are ignored. @@ -314,7 +331,7 @@ public final class ObexHelper { } } catch (Exception e) { // Not a valid header so ignore - throw new IOException("Header was not formatted properly"); + throw new IOException("Header was not formatted properly", e); } index += 4; break; @@ -322,7 +339,7 @@ public final class ObexHelper { } } catch (IOException e) { - throw new IOException("Header was not formatted properly"); + throw new IOException("Header was not formatted properly", e); } return body; @@ -672,6 +689,33 @@ public final class ObexHelper { } } + // TODO: + // If the SRM and SRMP header is in use, they must be send in the same OBEX packet + // But the current structure of the obex code cannot handle this, and therefore + // it makes sense to put them in the tail of the headers, since we then reduce the + // chance of enabling SRM to soon. The down side is that SRM cannot be used while + // transferring non-body headers + + // Add the SRM header + byteHeader = (Byte)headImpl.getHeader(HeaderSet.SINGLE_RESPONSE_MODE); + if (byteHeader != null) { + out.write((byte)HeaderSet.SINGLE_RESPONSE_MODE); + out.write(byteHeader.byteValue()); + if (nullOut) { + headImpl.setHeader(HeaderSet.SINGLE_RESPONSE_MODE, null); + } + } + + // Add the SRM parameter header + byteHeader = (Byte)headImpl.getHeader(HeaderSet.SINGLE_RESPONSE_MODE_PARAMETER); + if (byteHeader != null) { + out.write((byte)HeaderSet.SINGLE_RESPONSE_MODE_PARAMETER); + out.write(byteHeader.byteValue()); + if (nullOut) { + headImpl.setHeader(HeaderSet.SINGLE_RESPONSE_MODE_PARAMETER, null); + } + } + } catch (IOException e) { } finally { result = out.toByteArray(); @@ -702,6 +746,8 @@ public final class ObexHelper { int index = start; int length = 0; + // TODO: Ensure SRM and SRMP headers are not split into two OBEX packets + while ((fullLength < maxSize) && (index < headerArray.length)) { int headerID = (headerArray[index] < 0 ? headerArray[index] + 256 : headerArray[index]); lastLength = fullLength; @@ -1008,4 +1054,39 @@ public final class ObexHelper { return authChall; } + + /** + * Return the maximum allowed OBEX packet to transmit. + * OBEX packets transmitted must be smaller than this value. + * @param transport Reference to the ObexTransport in use. + * @return the maximum allowed OBEX packet to transmit + */ + public static int getMaxTxPacketSize(ObexTransport transport) { + int size = transport.getMaxTransmitPacketSize(); + return validateMaxPacketSize(size); + } + + /** + * Return the maximum allowed OBEX packet to receive - used in OBEX connect. + * @param transport + * @return he maximum allowed OBEX packet to receive + */ + public static int getMaxRxPacketSize(ObexTransport transport) { + int size = transport.getMaxReceivePacketSize(); + return validateMaxPacketSize(size); + } + + private static int validateMaxPacketSize(int size) { + if(VDBG && (size > MAX_PACKET_SIZE_INT)) Log.w(TAG, + "The packet size supported for the connection (" + size + ") is larger" + + " than the configured OBEX packet size: " + MAX_PACKET_SIZE_INT); + if(size != -1) { + if(size < LOWER_LIMIT_MAX_PACKET_SIZE) { + throw new IllegalArgumentException(size + " is less that the lower limit: " + + LOWER_LIMIT_MAX_PACKET_SIZE); + } + return size; + } + return MAX_PACKET_SIZE_INT; + } } diff --git a/obex/javax/obex/ObexPacket.java b/obex/javax/obex/ObexPacket.java new file mode 100644 index 0000000..bb6c96e --- /dev/null +++ b/obex/javax/obex/ObexPacket.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2015 The Android Open Source Project + * Copyright (c) 2015 Samsung LSI + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package javax.obex; + +import java.io.IOException; +import java.io.InputStream; + +public class ObexPacket { + public int mHeaderId; + public int mLength; + public byte[] mPayload = null; + + private ObexPacket(int headerId, int length) { + mHeaderId = headerId; + mLength = length; + } + + /** + * Create a complete OBEX packet by reading data from an InputStream. + * @param is the input stream to read from. + * @return the OBEX packet read. + * @throws IOException if an IO exception occurs during read. + */ + public static ObexPacket read(InputStream is) throws IOException { + int headerId = is.read(); + return read(headerId, is); + } + + /** + * Read the remainder of an OBEX packet, with a specified headerId. + * @param headerId the headerId already read from the stream. + * @param is the stream to read from, assuming 1 byte have already been read. + * @return the OBEX packet read. + * @throws IOException + */ + public static ObexPacket read(int headerId, InputStream is) throws IOException { + // Read the 2 byte length field from the stream + int length = is.read(); + length = (length << 8) + is.read(); + + ObexPacket newPacket = new ObexPacket(headerId, length); + + int bytesReceived; + byte[] temp = null; + if (length > 3) { + // First three bytes already read, compensating for this + temp = new byte[length - 3]; + bytesReceived = is.read(temp); + while (bytesReceived != temp.length) { + bytesReceived += is.read(temp, bytesReceived, temp.length - bytesReceived); + } + } + newPacket.mPayload = temp; + return newPacket; + } +} diff --git a/obex/javax/obex/ObexSession.java b/obex/javax/obex/ObexSession.java index a7daeb5..542b9c8 100644 --- a/obex/javax/obex/ObexSession.java +++ b/obex/javax/obex/ObexSession.java @@ -34,6 +34,8 @@ package javax.obex; import java.io.IOException; +import android.util.Log; + /** * The ObexSession interface characterizes the term * "OBEX Connection" as defined in the IrDA Object Exchange Protocol v1.2, which @@ -47,6 +49,9 @@ import java.io.IOException; */ public class ObexSession { + private static final String TAG = "ObexSession"; + private static final boolean V = ObexHelper.VDBG; + protected Authenticator mAuthenticator; protected byte[] mChallengeDigest; @@ -125,6 +130,7 @@ public class ObexSession { result = mAuthenticator .onAuthenticationChallenge(realm, isUserIDRequired, isFullAccess); } catch (Exception e) { + if (V) Log.d(TAG, "Exception occured - returning false", e); return false; } diff --git a/obex/javax/obex/ObexTransport.java b/obex/javax/obex/ObexTransport.java index 445e267..a5a75f5 100644 --- a/obex/javax/obex/ObexTransport.java +++ b/obex/javax/obex/ObexTransport.java @@ -73,4 +73,39 @@ public interface ObexTransport { DataOutputStream openDataOutputStream() throws IOException; + /** + * Must return the maximum allowed OBEX packet that can be sent over + * the transport. For L2CAP this will be the Max SDU reported by the + * peer device. + * The returned value will be used to set the outgoing OBEX packet + * size. Therefore this value shall not change. + * For RFCOMM or other transport types where the OBEX packets size + * is unrelated to the transport packet size, return -1; + * @return the maximum allowed OBEX packet that can be send over + * the transport. Or -1 in case of don't care. + */ + int getMaxTransmitPacketSize(); + + /** + * Must return the maximum allowed OBEX packet that can be received over + * the transport. For L2CAP this will be the Max SDU configured for the + * L2CAP channel. + * The returned value will be used to validate the incoming packet size + * values. + * For RFCOMM or other transport types where the OBEX packets size + * is unrelated to the transport packet size, return -1; + * @return the maximum allowed OBEX packet that can be send over + * the transport. Or -1 in case of don't care. + */ + int getMaxReceivePacketSize(); + + /** + * Shall return true if the transport in use supports SRM. + * @return + * true if SRM operation is supported, and is to be enabled. + * false if SRM operations are not supported, or should not be used. + */ + boolean isSrmSupported(); + + } diff --git a/obex/javax/obex/ServerOperation.java b/obex/javax/obex/ServerOperation.java index fc441e0..56a675a 100644 --- a/obex/javax/obex/ServerOperation.java +++ b/obex/javax/obex/ServerOperation.java @@ -1,4 +1,5 @@ -/* +/* Copyright (c) 2015 The Android Open Source Project + * Copyright (C) 2015 Samsung LSI * Copyright (c) 2008-2009, Motorola, Inc. * * All rights reserved. @@ -39,6 +40,8 @@ import java.io.OutputStream; import java.io.DataOutputStream; import java.io.ByteArrayOutputStream; +import android.util.Log; + /** * This class implements the Operation interface for server side connections. *

@@ -54,6 +57,10 @@ import java.io.ByteArrayOutputStream; */ public final class ServerOperation implements Operation, BaseStream { + private static final String TAG = "ServerOperation"; + + private static final boolean V = ObexHelper.VDBG; // Verbose debugging + public boolean isAborted; public HeaderSet requestHeader; @@ -78,6 +85,8 @@ public final class ServerOperation implements Operation, BaseStream { private PrivateOutputStream mPrivateOutput; + private ObexTransport mTransport; + private boolean mPrivateOutputOpen; private String mExceptionString; @@ -89,6 +98,19 @@ public final class ServerOperation implements Operation, BaseStream { private boolean mHasBody; private boolean mSendBodyHeader = true; + // Assume SRM disabled - needs to be explicit + // enabled by client + private boolean mSrmEnabled = false; + // A latch - when triggered, there is not way back ;-) + private boolean mSrmActive = false; + // Set to true when a SRM enable response have been send + private boolean mSrmResponseSent = false; + // keep waiting until final-bit is received in request + // to handle the case where the SRM enable header is in + // a different OBEX packet than the SRMP header. + private boolean mSrmWaitingForRemote = true; + // Why should we wait? - currently not exposed to apps. + private boolean mSrmLocalWait = false; /** * Creates new ServerOperation @@ -116,12 +138,14 @@ public final class ServerOperation implements Operation, BaseStream { mRequestFinished = false; mPrivateOutputOpen = false; mHasBody = false; - int bytesReceived; + ObexPacket packet; + mTransport = p.getTransport(); /* * Determine if this is a PUT request */ - if ((request == 0x02) || (request == 0x82)) { + if ((request == ObexHelper.OBEX_OPCODE_PUT) || + (request == ObexHelper.OBEX_OPCODE_PUT_FINAL)) { /* * It is a PUT request. */ @@ -130,13 +154,14 @@ public final class ServerOperation implements Operation, BaseStream { /* * Determine if the final bit is set */ - if ((request & 0x80) == 0) { + if ((request & ObexHelper.OBEX_OPCODE_FINAL_BIT_MASK) == 0) { finalBitSet = false; } else { finalBitSet = true; mRequestFinished = true; } - } else if ((request == 0x03) || (request == 0x83)) { + } else if ((request == ObexHelper.OBEX_OPCODE_GET) || + (request == ObexHelper.OBEX_OPCODE_GET_FINAL)) { /* * It is a GET request. */ @@ -145,71 +170,32 @@ public final class ServerOperation implements Operation, BaseStream { // For Get request, final bit set is decided by server side logic finalBitSet = false; - if (request == 0x83) { + if (request == ObexHelper.OBEX_OPCODE_GET_FINAL) { mRequestFinished = true; } } else { throw new IOException("ServerOperation can not handle such request"); } - int length = in.read(); - length = (length << 8) + in.read(); + packet = ObexPacket.read(request, mInput); /* * Determine if the packet length is larger than this device can receive */ - if (length > ObexHelper.MAX_PACKET_SIZE_INT) { + if (packet.mLength > ObexHelper.getMaxRxPacketSize(mTransport)) { mParent.sendResponse(ResponseCodes.OBEX_HTTP_REQ_TOO_LARGE, null); - throw new IOException("Packet received was too large"); + throw new IOException("Packet received was too large. Length: " + + packet.mLength + " maxLength: " + ObexHelper.getMaxRxPacketSize(mTransport)); } /* * Determine if any headers were sent in the initial request */ - if (length > 3) { - byte[] data = new byte[length - 3]; - bytesReceived = in.read(data); - - while (bytesReceived != data.length) { - bytesReceived += in.read(data, bytesReceived, data.length - bytesReceived); + if (packet.mLength > 3) { + if(!handleObexPacket(packet)) { + return; } - - byte[] body = ObexHelper.updateHeaderSet(requestHeader, data); - - if (body != null) { - mHasBody = true; - } - - if (mListener.getConnectionId() != -1 && requestHeader.mConnectionID != null) { - mListener.setConnectionId(ObexHelper.convertToLong(requestHeader.mConnectionID)); - } else { - mListener.setConnectionId(1); - } - - if (requestHeader.mAuthResp != null) { - if (!mParent.handleAuthResp(requestHeader.mAuthResp)) { - mExceptionString = "Authentication Failed"; - mParent.sendResponse(ResponseCodes.OBEX_HTTP_UNAUTHORIZED, null); - mClosed = true; - requestHeader.mAuthResp = null; - return; - } - } - - if (requestHeader.mAuthChall != null) { - mParent.handleAuthChall(requestHeader); - // send the authResp to the client - replyHeader.mAuthResp = new byte[requestHeader.mAuthResp.length]; - System.arraycopy(requestHeader.mAuthResp, 0, replyHeader.mAuthResp, 0, - replyHeader.mAuthResp.length); - requestHeader.mAuthResp = null; - requestHeader.mAuthChall = null; - - } - - if (body != null) { - mPrivateInput.writeBytes(body, 1); - } else { + if (!mHasBody) { while ((!mGetOperation) && (!finalBitSet)) { sendReply(ResponseCodes.OBEX_HTTP_CONTINUE); if (mPrivateInput.available() > 0) { @@ -232,6 +218,100 @@ public final class ServerOperation implements Operation, BaseStream { } } + /** + * Parse headers and update member variables + * @param packet the received obex packet + * @return false for failing authentication - and a OBEX_HTTP_UNAUTHORIZED + * response have been send. Else true. + * @throws IOException + */ + private boolean handleObexPacket(ObexPacket packet) throws IOException { + byte[] body = updateRequestHeaders(packet); + + if (body != null) { + mHasBody = true; + } + if (mListener.getConnectionId() != -1 && requestHeader.mConnectionID != null) { + mListener.setConnectionId(ObexHelper + .convertToLong(requestHeader.mConnectionID)); + } else { + mListener.setConnectionId(1); + } + + if (requestHeader.mAuthResp != null) { + if (!mParent.handleAuthResp(requestHeader.mAuthResp)) { + mExceptionString = "Authentication Failed"; + mParent.sendResponse(ResponseCodes.OBEX_HTTP_UNAUTHORIZED, null); + mClosed = true; + requestHeader.mAuthResp = null; + return false; + } + requestHeader.mAuthResp = null; + } + + if (requestHeader.mAuthChall != null) { + mParent.handleAuthChall(requestHeader); + // send the auhtResp to the client + replyHeader.mAuthResp = new byte[requestHeader.mAuthResp.length]; + System.arraycopy(requestHeader.mAuthResp, 0, replyHeader.mAuthResp, 0, + replyHeader.mAuthResp.length); + requestHeader.mAuthResp = null; + requestHeader.mAuthChall = null; + } + + if (body != null) { + mPrivateInput.writeBytes(body, 1); + } + return true; + } + + /** + * Update the request header set, and sniff on SRM headers to update local state. + * @param data the OBEX packet data + * @return any bytes in a body/end-of-body header returned by {@link ObexHelper.updateHeaderSet} + * @throws IOException + */ + private byte[] updateRequestHeaders(ObexPacket packet) throws IOException { + byte[] body = null; + if (packet.mPayload != null) { + body = ObexHelper.updateHeaderSet(requestHeader, packet.mPayload); + } + Byte srmMode = (Byte)requestHeader.getHeader(HeaderSet.SINGLE_RESPONSE_MODE); + if(mTransport.isSrmSupported() && srmMode != null + && srmMode == ObexHelper.OBEX_SRM_ENABLE) { + mSrmEnabled = true; + if(V) Log.d(TAG,"SRM is now ENABLED (but not active) for this operation"); + } + checkForSrmWait(packet.mHeaderId); + if((!mSrmWaitingForRemote) && (mSrmEnabled)) { + if(V) Log.d(TAG,"SRM is now ACTIVE for this operation"); + mSrmActive = true; + } + return body; + } + + /** + * Call this only when a complete request have been received. + * (This is not optimal, but the current design is not really suited to + * the way SRM is specified.) + */ + private void checkForSrmWait(int headerId){ + if (mSrmEnabled && (headerId == ObexHelper.OBEX_OPCODE_GET + || headerId == ObexHelper.OBEX_OPCODE_GET_FINAL + || headerId == ObexHelper.OBEX_OPCODE_PUT)) { + try { + mSrmWaitingForRemote = false; + Byte srmp = (Byte)requestHeader.getHeader(HeaderSet.SINGLE_RESPONSE_MODE_PARAMETER); + if(srmp != null && srmp == ObexHelper.OBEX_SRMP_WAIT) { + mSrmWaitingForRemote = true; + // Clear the wait header, as the absents of the header when the final bit is set + // indicates don't wait. + requestHeader.setHeader(HeaderSet.SINGLE_RESPONSE_MODE_PARAMETER, null); + } + } catch (IOException e) {if(V){Log.w(TAG,"Exception while extracting header",e);}} + } + } + public boolean isValidBody() { return mHasBody; } @@ -274,17 +354,19 @@ public final class ServerOperation implements Operation, BaseStream { /** * Sends a reply to the client. If the reply is a OBEX_HTTP_CONTINUE, it - * will wait for a response from the client before ending. + * will wait for a response from the client before ending unless SRM is active. * @param type the response code to send back to the client * @return true if the final bit was not set on the reply; * false if no reply was received because the operation - * ended, an abort was received, or the final bit was set in the - * reply + * ended, an abort was received, the final bit was set in the + * reply or SRM is active. * @throws IOException if an IO error occurs */ public synchronized boolean sendReply(int type) throws IOException { ByteArrayOutputStream out = new ByteArrayOutputStream(); - int bytesReceived; + boolean skipSend = false; + boolean skipReceive = false; + boolean srmRespSendPending = false; long id = mListener.getConnectionId(); if (id == -1) { @@ -293,7 +375,19 @@ public final class ServerOperation implements Operation, BaseStream { replyHeader.mConnectionID = ObexHelper.convertToByteArray(id); } - byte[] headerArray = ObexHelper.createHeader(replyHeader, true); + if(mSrmEnabled && !mSrmResponseSent) { + // As we are not ensured that the SRM enable is in the first OBEX packet + // We must check for each reply. + if(V)Log.v(TAG, "mSrmEnabled==true, sending SRM enable response."); + replyHeader.setHeader(HeaderSet.SINGLE_RESPONSE_MODE, (byte)ObexHelper.OBEX_SRM_ENABLE); + srmRespSendPending = true; + } + + if(mSrmEnabled && !mGetOperation && mSrmLocalWait) { + replyHeader.setHeader(HeaderSet.SINGLE_RESPONSE_MODE, (byte)ObexHelper.OBEX_SRMP_WAIT); + } + + byte[] headerArray = ObexHelper.createHeader(replyHeader, true); // This clears the headers int bodyLength = -1; int orginalBodyLength = -1; @@ -347,6 +441,28 @@ public final class ServerOperation implements Operation, BaseStream { finalBitSet = true; } + if(mSrmActive) { + if(!mGetOperation && type == ResponseCodes.OBEX_HTTP_CONTINUE && + mSrmResponseSent == true) { + // we are in the middle of a SRM PUT operation, don't send a continue. + skipSend = true; + } else if(mGetOperation && mRequestFinished == false && mSrmResponseSent == true) { + // We are still receiving the get request, receive, but don't send continue. + skipSend = true; + } else if(mGetOperation && mRequestFinished == true) { + // All done receiving the GET request, send data to the client, without + // expecting a continue. + skipReceive = true; + } + if(V)Log.v(TAG, "type==" + type + " skipSend==" + skipSend + + " skipReceive==" + skipReceive); + } + if(srmRespSendPending) { + if(V)Log.v(TAG, + "SRM Enabled (srmRespSendPending == true)- sending SRM Enable response"); + mSrmResponseSent = true; + } + if ((finalBitSet) || (headerArray.length < (mMaxPacketLength - 20))) { if (bodyLength > 0) { /* @@ -387,7 +503,7 @@ public final class ServerOperation implements Operation, BaseStream { } if ((finalBitSet) && (type == ResponseCodes.OBEX_HTTP_OK) && (orginalBodyLength <= 0)) { - if(mSendBodyHeader == true) { + if(mSendBodyHeader) { out.write(0x49); orginalBodyLength = 3; out.write((byte)(orginalBodyLength >> 8)); @@ -395,107 +511,66 @@ public final class ServerOperation implements Operation, BaseStream { } } - mResponseSize = 3; - mParent.sendResponse(type, out.toByteArray()); + if(skipSend == false) { + mResponseSize = 3; + mParent.sendResponse(type, out.toByteArray()); + } if (type == ResponseCodes.OBEX_HTTP_CONTINUE) { - int headerID = mInput.read(); - int length = mInput.read(); - length = (length << 8) + mInput.read(); - if ((headerID != ObexHelper.OBEX_OPCODE_PUT) - && (headerID != ObexHelper.OBEX_OPCODE_PUT_FINAL) - && (headerID != ObexHelper.OBEX_OPCODE_GET) - && (headerID != ObexHelper.OBEX_OPCODE_GET_FINAL)) { - - if (length > 3) { - byte[] temp = new byte[length - 3]; - // First three bytes already read, compensating for this - bytesReceived = mInput.read(temp); - - while (bytesReceived != temp.length) { - bytesReceived += mInput.read(temp, bytesReceived, - temp.length - bytesReceived); - } - } - /* - * Determine if an ABORT was sent as the reply - */ - if (headerID == ObexHelper.OBEX_OPCODE_ABORT) { - mParent.sendResponse(ResponseCodes.OBEX_HTTP_OK, null); - mClosed = true; - isAborted = true; - mExceptionString = "Abort Received"; - throw new IOException("Abort Received"); - } else { - mParent.sendResponse(ResponseCodes.OBEX_HTTP_BAD_REQUEST, null); - mClosed = true; - mExceptionString = "Bad Request Received"; - throw new IOException("Bad Request Received"); - } + if(mGetOperation && skipReceive) { + // Here we need to check for and handle abort (throw an exception). + // Any other signal received should be discarded silently (only on server side) + checkSrmRemoteAbort(); } else { - - if ((headerID == ObexHelper.OBEX_OPCODE_PUT_FINAL)) { - finalBitSet = true; - } else if (headerID == ObexHelper.OBEX_OPCODE_GET_FINAL) { - mRequestFinished = true; - } - - /* - * Determine if the packet length is larger then this device can receive - */ - if (length > ObexHelper.MAX_PACKET_SIZE_INT) { - mParent.sendResponse(ResponseCodes.OBEX_HTTP_REQ_TOO_LARGE, null); - throw new IOException("Packet received was too large"); - } - - /* - * Determine if any headers were sent in the initial request - */ - if (length > 3) { - byte[] data = new byte[length - 3]; - bytesReceived = mInput.read(data); - - while (bytesReceived != data.length) { - bytesReceived += mInput.read(data, bytesReceived, data.length - - bytesReceived); - } - byte[] body = ObexHelper.updateHeaderSet(requestHeader, data); - if (body != null) { - mHasBody = true; - } - if (mListener.getConnectionId() != -1 && requestHeader.mConnectionID != null) { - mListener.setConnectionId(ObexHelper - .convertToLong(requestHeader.mConnectionID)); + // Receive and handle data (only send reply if !skipSend) + // Read a complete OBEX Packet + ObexPacket packet = ObexPacket.read(mInput); + + int headerId = packet.mHeaderId; + if ((headerId != ObexHelper.OBEX_OPCODE_PUT) + && (headerId != ObexHelper.OBEX_OPCODE_PUT_FINAL) + && (headerId != ObexHelper.OBEX_OPCODE_GET) + && (headerId != ObexHelper.OBEX_OPCODE_GET_FINAL)) { + + /* + * Determine if an ABORT was sent as the reply + */ + if (headerId == ObexHelper.OBEX_OPCODE_ABORT) { + handleRemoteAbort(); } else { - mListener.setConnectionId(1); + // TODO:shall we send this if it occurs during SRM? Errata on the subject + mParent.sendResponse(ResponseCodes.OBEX_HTTP_BAD_REQUEST, null); + mClosed = true; + mExceptionString = "Bad Request Received"; + throw new IOException("Bad Request Received"); } + } else { - if (requestHeader.mAuthResp != null) { - if (!mParent.handleAuthResp(requestHeader.mAuthResp)) { - mExceptionString = "Authentication Failed"; - mParent.sendResponse(ResponseCodes.OBEX_HTTP_UNAUTHORIZED, null); - mClosed = true; - requestHeader.mAuthResp = null; - return false; - } - requestHeader.mAuthResp = null; + if ((headerId == ObexHelper.OBEX_OPCODE_PUT_FINAL)) { + finalBitSet = true; + } else if (headerId == ObexHelper.OBEX_OPCODE_GET_FINAL) { + mRequestFinished = true; } - if (requestHeader.mAuthChall != null) { - mParent.handleAuthChall(requestHeader); - // send the auhtResp to the client - replyHeader.mAuthResp = new byte[requestHeader.mAuthResp.length]; - System.arraycopy(requestHeader.mAuthResp, 0, replyHeader.mAuthResp, 0, - replyHeader.mAuthResp.length); - requestHeader.mAuthResp = null; - requestHeader.mAuthChall = null; + /* + * Determine if the packet length is larger than the negotiated packet size + */ + if (packet.mLength > ObexHelper.getMaxRxPacketSize(mTransport)) { + mParent.sendResponse(ResponseCodes.OBEX_HTTP_REQ_TOO_LARGE, null); + throw new IOException("Packet received was too large"); } - if (body != null) { - mPrivateInput.writeBytes(body, 1); + /* + * Determine if any headers were sent in the initial request + */ + if (packet.mLength > 3 || (mSrmEnabled && packet.mLength == 3)) { + if(handleObexPacket(packet) == false) { + return false; + } } } + } return true; } else { @@ -504,6 +579,53 @@ public final class ServerOperation implements Operation, BaseStream { } /** + * This method will look for an abort from the peer during a SRM transfer. + * The function will not block if no data has been received from the remote device. + * If data have been received, the function will block while reading the incoming + * OBEX package. + * An Abort request will be handled, and cause an IOException("Abort Received"). + * Other messages will be discarded silently as per GOEP specification. + * @throws IOException if an abort request have been received. + * TODO: I think this is an error in the specification. If we discard other messages, + * the peer device will most likely stall, as it will not receive the expected + * response for the message... + * I'm not sure how to understand "Receipt of invalid or unexpected SRM or SRMP + * header values shall be ignored by the receiving device." + * If any signal is received during an active SRM transfer it is unexpected regardless + * whether or not it contains SRM/SRMP headers... + */ + private void checkSrmRemoteAbort() throws IOException { + if(mInput.available() > 0) { + ObexPacket packet = ObexPacket.read(mInput); + /* + * Determine if an ABORT was sent as the reply + */ + if (packet.mHeaderId == ObexHelper.OBEX_OPCODE_ABORT) { + handleRemoteAbort(); + } else { + // TODO: should we throw an exception here anyway? - don't see how to + // ignore SRM/SRMP headers without ignoring the complete signal + // (in this particular case). + Log.w(TAG, "Received unexpected request from client - discarding...\n" + + " headerId: " + packet.mHeaderId + " length: " + packet.mLength); + } + } + } + + private void handleRemoteAbort() throws IOException { + /* TODO: To increase the speed of the abort operation in SRM, we need + * to be able to flush the L2CAP queue for the PSM in use. + * This could be implemented by introducing a control + * message to be send over the socket, that in the abort case + * could carry a flush command. */ + mParent.sendResponse(ResponseCodes.OBEX_HTTP_OK, null); + mClosed = true; + isAborted = true; + mExceptionString = "Abort Received"; + throw new IOException("Abort Received"); + } + + /** * Sends an ABORT message to the server. By calling this method, the * corresponding input and output streams will be closed along with this * object. diff --git a/obex/javax/obex/ServerRequestHandler.java b/obex/javax/obex/ServerRequestHandler.java index 0882572..09cbc2c 100644 --- a/obex/javax/obex/ServerRequestHandler.java +++ b/obex/javax/obex/ServerRequestHandler.java @@ -275,4 +275,13 @@ public class ServerRequestHandler { */ public void onClose() { } + + /** + * Override to add Single Response Mode support - e.g. if the supplied + * transport is l2cap. + * @return True if SRM is supported, else False + */ + public boolean isSrmSupported() { + return false; + } } diff --git a/obex/javax/obex/ServerSession.java b/obex/javax/obex/ServerSession.java index f1b9a0d..acee5dd 100644 --- a/obex/javax/obex/ServerSession.java +++ b/obex/javax/obex/ServerSession.java @@ -1,4 +1,6 @@ /* + * Copyright (C) 2015 The Android Open Source Project + * Copyright (c) 2015 Samsung LSI * Copyright (c) 2008-2009, Motorola, Inc. * * All rights reserved. @@ -45,6 +47,7 @@ import java.io.OutputStream; public final class ServerSession extends ObexSession implements Runnable { private static final String TAG = "Obex ServerSession"; + private static final boolean V = ObexHelper.VDBG; private ObexTransport mTransport; @@ -91,7 +94,9 @@ public final class ServerSession extends ObexSession implements Runnable { boolean done = false; while (!done && !mClosed) { + if(V) Log.v(TAG, "Waiting for incoming request..."); int requestType = mInput.read(); + if(V) Log.v(TAG, "Read request: " + requestType); switch (requestType) { case ObexHelper.OBEX_OPCODE_CONNECT: handleConnectRequest(); @@ -140,9 +145,9 @@ public final class ServerSession extends ObexSession implements Runnable { } } catch (NullPointerException e) { - Log.d(TAG, e.toString()); + Log.d(TAG, "Exception occured - ignoring", e); } catch (Exception e) { - Log.d(TAG, e.toString()); + Log.d(TAG, "Exception occured - ignoring", e); } close(); } @@ -163,7 +168,7 @@ public final class ServerSession extends ObexSession implements Runnable { int length = mInput.read(); length = (length << 8) + mInput.read(); - if (length > ObexHelper.MAX_PACKET_SIZE_INT) { + if (length > ObexHelper.getMaxRxPacketSize(mTransport)) { code = ResponseCodes.OBEX_HTTP_REQ_TOO_LARGE; } else { for (int i = 3; i < length; i++) { @@ -215,6 +220,7 @@ public final class ServerSession extends ObexSession implements Runnable { *internal error should not be sent because server has already replied with *OK response in "sendReply") */ + if(V) Log.d(TAG,"Exception occured - sending OBEX_HTTP_INTERNAL_ERROR reply",e); if (!op.isAborted) { sendResponse(ResponseCodes.OBEX_HTTP_INTERNAL_ERROR, null); } @@ -243,6 +249,7 @@ public final class ServerSession extends ObexSession implements Runnable { op.sendReply(response); } } catch (Exception e) { + if(V) Log.d(TAG,"Exception occured - sending OBEX_HTTP_INTERNAL_ERROR reply",e); sendResponse(ResponseCodes.OBEX_HTTP_INTERNAL_ERROR, null); } } @@ -275,7 +282,7 @@ public final class ServerSession extends ObexSession implements Runnable { data[2] = (byte)totalLength; } op.write(data); - op.flush(); + op.flush(); // TODO: Do we need to flush? } /** @@ -304,7 +311,7 @@ public final class ServerSession extends ObexSession implements Runnable { flags = mInput.read(); constants = mInput.read(); - if (length > ObexHelper.MAX_PACKET_SIZE_INT) { + if (length > ObexHelper.getMaxRxPacketSize(mTransport)) { code = ResponseCodes.OBEX_HTTP_REQ_TOO_LARGE; totalLength = 3; } else { @@ -358,6 +365,7 @@ public final class ServerSession extends ObexSession implements Runnable { try { code = mListener.onSetPath(request, reply, backup, create); } catch (Exception e) { + if(V) Log.d(TAG,"Exception occured - sending OBEX_HTTP_INTERNAL_ERROR reply",e); sendResponse(ResponseCodes.OBEX_HTTP_INTERNAL_ERROR, null); return; } @@ -425,7 +433,7 @@ public final class ServerSession extends ObexSession implements Runnable { length = mInput.read(); length = (length << 8) + mInput.read(); - if (length > ObexHelper.MAX_PACKET_SIZE_INT) { + if (length > ObexHelper.getMaxRxPacketSize(mTransport)) { code = ResponseCodes.OBEX_HTTP_REQ_TOO_LARGE; totalLength = 3; } else { @@ -466,6 +474,7 @@ public final class ServerSession extends ObexSession implements Runnable { try { mListener.onDisconnect(request, reply); } catch (Exception e) { + if(V) Log.d(TAG,"Exception occured - sending OBEX_HTTP_INTERNAL_ERROR reply",e); sendResponse(ResponseCodes.OBEX_HTTP_INTERNAL_ERROR, null); return; } @@ -531,23 +540,38 @@ public final class ServerSession extends ObexSession implements Runnable { HeaderSet reply = new HeaderSet(); int bytesReceived; + if(V) Log.v(TAG,"handleConnectRequest()"); + /* * Read in the length of the OBEX packet, OBEX version, flags, and max * packet length */ packetLength = mInput.read(); packetLength = (packetLength << 8) + mInput.read(); + if(V) Log.v(TAG,"handleConnectRequest() - packetLength: " + packetLength); + version = mInput.read(); flags = mInput.read(); mMaxPacketLength = mInput.read(); mMaxPacketLength = (mMaxPacketLength << 8) + mInput.read(); + if(V) Log.v(TAG,"handleConnectRequest() - version: " + version + + " MaxLength: " + mMaxPacketLength + " flags: " + flags); + // should we check it? if (mMaxPacketLength > ObexHelper.MAX_PACKET_SIZE_INT) { mMaxPacketLength = ObexHelper.MAX_PACKET_SIZE_INT; } - if (packetLength > ObexHelper.MAX_PACKET_SIZE_INT) { + if(mMaxPacketLength > ObexHelper.getMaxTxPacketSize(mTransport)) { + Log.w(TAG, "Requested MaxObexPacketSize " + mMaxPacketLength + + " is larger than the max size supported by the transport: " + + ObexHelper.getMaxTxPacketSize(mTransport) + + " Reducing to this size."); + mMaxPacketLength = ObexHelper.getMaxTxPacketSize(mTransport); + } + + if (packetLength > ObexHelper.getMaxRxPacketSize(mTransport)) { code = ResponseCodes.OBEX_HTTP_REQ_TOO_LARGE; totalLength = 7; } else { @@ -614,7 +638,7 @@ public final class ServerSession extends ObexSession implements Runnable { code = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; } } catch (Exception e) { - e.printStackTrace(); + if(V) Log.d(TAG,"Exception occured - sending OBEX_HTTP_INTERNAL_ERROR reply",e); totalLength = 7; head = null; code = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; @@ -633,13 +657,14 @@ public final class ServerSession extends ObexSession implements Runnable { * Packet Length (Defined in MAX_PACKET_SIZE) Byte 7 to n: headers */ byte[] sendData = new byte[totalLength]; + int maxRxLength = ObexHelper.getMaxRxPacketSize(mTransport); sendData[0] = (byte)code; sendData[1] = length[2]; sendData[2] = length[3]; sendData[3] = (byte)0x10; sendData[4] = (byte)0x00; - sendData[5] = (byte)(ObexHelper.MAX_PACKET_SIZE_INT >> 8); - sendData[6] = (byte)(ObexHelper.MAX_PACKET_SIZE_INT & 0xFF); + sendData[5] = (byte)(maxRxLength >> 8); + sendData[6] = (byte)(maxRxLength & 0xFF); if (head != null) { System.arraycopy(head, 0, sendData, 7, head.length); @@ -659,11 +684,16 @@ public final class ServerSession extends ObexSession implements Runnable { mListener.onClose(); } try { - mInput.close(); - mOutput.close(); - mTransport.close(); + /* Set state to closed before interrupting the thread by closing the streams */ mClosed = true; + if(mInput != null) + mInput.close(); + if(mOutput != null) + mOutput.close(); + if(mTransport != null) + mTransport.close(); } catch (Exception e) { + if(V) Log.d(TAG,"Exception occured during close() - ignore",e); } mTransport = null; mInput = null; @@ -702,4 +732,7 @@ public final class ServerSession extends ObexSession implements Runnable { return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; } + public ObexTransport getTransport() { + return mTransport; + } } -- cgit v1.1