summaryrefslogtreecommitdiffstats
path: root/obex
diff options
context:
space:
mode:
authorCasper Bonde <c.bonde@samsung.com>2015-04-09 09:24:48 +0200
committerAndre Eisenbach <eisenbach@google.com>2015-04-11 01:49:11 +0000
commit238e0f934f1f47263b384bc745ae0678c777130d (patch)
treeaad2caae161654624cbe93003ed8027f96aaf923 /obex
parent31a94f48bf8014cf6a1127bd23cf9a8541a9abed (diff)
downloadframeworks_base-238e0f934f1f47263b384bc745ae0678c777130d.zip
frameworks_base-238e0f934f1f47263b384bc745ae0678c777130d.tar.gz
frameworks_base-238e0f934f1f47263b384bc745ae0678c777130d.tar.bz2
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
Diffstat (limited to 'obex')
-rw-r--r--obex/Android.mk11
-rw-r--r--obex/javax/obex/ClientOperation.java174
-rw-r--r--obex/javax/obex/ClientSession.java247
-rw-r--r--obex/javax/obex/HeaderSet.java65
-rw-r--r--obex/javax/obex/ObexHelper.java87
-rw-r--r--obex/javax/obex/ObexPacket.java71
-rw-r--r--obex/javax/obex/ObexSession.java6
-rw-r--r--obex/javax/obex/ObexTransport.java35
-rw-r--r--obex/javax/obex/ServerOperation.java416
-rw-r--r--obex/javax/obex/ServerRequestHandler.java9
-rw-r--r--obex/javax/obex/ServerSession.java59
11 files changed, 881 insertions, 299 deletions
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 <code>Operation</code> 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 <code>true</code> if there is more data to send;
* <code>false</code> 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
* <code>true</code> if the operation completed successfully;
* <code>false</code> 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.
+ * <P>
+ * The value of <code>SINGLE_RESPONSE_MODE</code> 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.
+ * <P>
+ * The value of <code>SINGLE_RESPONSE_MODE_PARAMETER</code> 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:<BR>
@@ -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 <code>ObexSession</code> 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
+ * <code>true</code> if SRM operation is supported, and is to be enabled.
+ * <code>false</code> 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.
* <P>
@@ -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 <code>true</code> if the final bit was not set on the reply;
* <code>false</code> 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;
+ }
}