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
This commit is contained in:
Casper Bonde
2015-04-09 09:24:48 +02:00
committed by Andre Eisenbach
parent 31a94f48bf
commit 238e0f934f
23 changed files with 1811 additions and 331 deletions

View File

@ -6505,10 +6505,16 @@ package android.bluetooth {
public final class BluetoothSocket implements java.io.Closeable {
method public void close() throws java.io.IOException;
method public void connect() throws java.io.IOException;
method public int getConnectionType();
method public java.io.InputStream getInputStream() throws java.io.IOException;
method public int getMaxReceivePacketSize();
method public int getMaxTransmitPacketSize();
method public java.io.OutputStream getOutputStream() throws java.io.IOException;
method public android.bluetooth.BluetoothDevice getRemoteDevice();
method public boolean isConnected();
field public static final int TYPE_L2CAP = 3; // 0x3
field public static final int TYPE_RFCOMM = 1; // 0x1
field public static final int TYPE_SCO = 2; // 0x2
}
}

View File

@ -6721,10 +6721,16 @@ package android.bluetooth {
public final class BluetoothSocket implements java.io.Closeable {
method public void close() throws java.io.IOException;
method public void connect() throws java.io.IOException;
method public int getConnectionType();
method public java.io.InputStream getInputStream() throws java.io.IOException;
method public int getMaxReceivePacketSize();
method public int getMaxTransmitPacketSize();
method public java.io.OutputStream getOutputStream() throws java.io.IOException;
method public android.bluetooth.BluetoothDevice getRemoteDevice();
method public boolean isConnected();
field public static final int TYPE_L2CAP = 3; // 0x3
field public static final int TYPE_RFCOMM = 1; // 0x1
field public static final int TYPE_SCO = 2; // 0x2
}
}

View File

@ -1,5 +1,6 @@
/*
* Copyright (C) 2009-2014 The Android Open Source Project
* Copyright (C) 2009-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.
@ -377,6 +378,18 @@ public final class BluetoothAdapter {
/** @hide */
public static final String BLUETOOTH_MANAGER_SERVICE = "bluetooth_manager";
/** When creating a ServerSocket using listenUsingRfcommOn() or
* listenUsingL2capOn() use SOCKET_CHANNEL_AUTO_STATIC to create
* a ServerSocket that auto assigns a channel number to the first
* bluetooth socket.
* The channel number assigned to this first Bluetooth Socket will
* be stored in the ServerSocket, and reused for subsequent Bluetooth
* sockets.
* @hide */
public static final int SOCKET_CHANNEL_AUTO_STATIC_NO_SDP = -2;
private static final int ADDRESS_LENGTH = 17;
private static final int CONTROLLER_ENERGY_UPDATE_TIMEOUT_MILLIS = 30;
@ -1144,6 +1157,9 @@ public final class BluetoothAdapter {
BluetoothServerSocket socket = new BluetoothServerSocket(
BluetoothSocket.TYPE_RFCOMM, true, true, channel);
int errno = socket.mSocket.bindListen();
if(channel == SOCKET_CHANNEL_AUTO_STATIC_NO_SDP) {
socket.setChannel(socket.mSocket.getPort());
}
if (errno != 0) {
//TODO(BT): Throw the same exception error code
// that the previous code was using.
@ -1278,6 +1294,9 @@ public final class BluetoothAdapter {
BluetoothServerSocket socket = new BluetoothServerSocket(
BluetoothSocket.TYPE_RFCOMM, false, false, port);
int errno = socket.mSocket.bindListen();
if(port == SOCKET_CHANNEL_AUTO_STATIC_NO_SDP) {
socket.setChannel(socket.mSocket.getPort());
}
if (errno != 0) {
//TODO(BT): Throw the same exception error code
// that the previous code was using.
@ -1300,6 +1319,9 @@ public final class BluetoothAdapter {
BluetoothServerSocket socket = new BluetoothServerSocket(
BluetoothSocket.TYPE_RFCOMM, false, true, port);
int errno = socket.mSocket.bindListen();
if(port == SOCKET_CHANNEL_AUTO_STATIC_NO_SDP) {
socket.setChannel(socket.mSocket.getPort());
}
if (errno < 0) {
//TODO(BT): Throw the same exception error code
// that the previous code was using.
@ -1329,6 +1351,30 @@ public final class BluetoothAdapter {
return socket;
}
/**
* Construct an encrypted, authenticated, L2CAP server socket.
* Call #accept to retrieve connections to this socket.
* @return An L2CAP BluetoothServerSocket
* @throws IOException On error, for example Bluetooth not available, or
* insufficient permissions.
* @hide
*/
public BluetoothServerSocket listenUsingL2capOn(int port) throws IOException {
BluetoothServerSocket socket = new BluetoothServerSocket(
BluetoothSocket.TYPE_L2CAP, true, true, port);
int errno = socket.mSocket.bindListen();
if(port == SOCKET_CHANNEL_AUTO_STATIC_NO_SDP) {
socket.setChannel(socket.mSocket.getPort());
}
if (errno != 0) {
//TODO(BT): Throw the same exception error code
// that the previous code was using.
//socket.mSocket.throwErrnoNative(errno);
throw new IOException("Error: " + errno);
}
return socket;
}
/**
* Read the local Out of Band Pairing Data
* <p>Requires {@link android.Manifest.permission#BLUETOOTH}

View File

@ -302,6 +302,12 @@ public final class BluetoothDevice implements Parcelable {
*/
public static final int DEVICE_TYPE_DUAL = 3;
/** @hide */
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_SDP_RECORD =
"android.bluetooth.device.action.SDP_RECORD";
/**
* Broadcast Action: This intent is used to broadcast the {@link UUID}
* wrapped as a {@link android.os.ParcelUuid} of the remote device after it
@ -526,6 +532,13 @@ public final class BluetoothDevice implements Parcelable {
*/
public static final String EXTRA_UUID = "android.bluetooth.device.extra.UUID";
/** @hide */
public static final String EXTRA_SDP_RECORD =
"android.bluetooth.device.extra.SDP_RECORD";
/** @hide */
public static final String EXTRA_SDP_SEARCH_STATUS =
"android.bluetooth.device.extra.SDP_SEARCH_STATUS";
/**
* For {@link #getPhonebookAccessPermission}, {@link #setPhonebookAccessPermission},
* {@link #getMessageAccessPermission} and {@link #setMessageAccessPermission}.
@ -1054,14 +1067,34 @@ public final class BluetoothDevice implements Parcelable {
return false;
}
/**
* Perform a service discovery on the remote device to get the SDP records associated
* with the specified UUID.
*
* <p>This API is asynchronous and {@link #ACTION_SDP_RECORD} intent is sent,
* with the SDP records found on the remote end. If there is an error
* in getting the SDP records or if the process takes a long time,
* {@link #ACTION_SDP_RECORD} intent is sent with an status value in
* {@link #EXTRA_SDP_SEARCH_STATUS} different from 0.
* Detailed status error codes can be found by members of the Bluetooth package in
* the AbstractionLayer class.
* <p>Requires {@link android.Manifest.permission#BLUETOOTH}.
* The SDP record data will be stored in the intent as {@link #EXTRA_SDP_RECORD}.
* The object type will match one of the SdpXxxRecord types, depending on the UUID searched
* for.
*
* @return False if the sanity check fails, True if the process
* of initiating an ACL connection to the remote device
* was started.
*/
/** @hide */
public boolean fetchMasInstances() {
public boolean sdpSearch(ParcelUuid uuid) {
if (sService == null) {
Log.e(TAG, "BT not enabled. Cannot query remote device for MAS instances");
Log.e(TAG, "BT not enabled. Cannot query remote device sdp records");
return false;
}
try {
return sService.fetchRemoteMasInstances(this);
return sService.sdpSearch(this,uuid);
} catch (RemoteException e) {Log.e(TAG, "", e);}
return false;
}
@ -1260,6 +1293,36 @@ public final class BluetoothDevice implements Parcelable {
null);
}
/**
* Create an L2cap {@link BluetoothSocket} ready to start a secure
* outgoing connection to this remote device on given channel.
* <p>The remote device will be authenticated and communication on this
* socket will be encrypted.
* <p> Use this socket only if an authenticated socket link is possible.
* Authentication refers to the authentication of the link key to
* prevent man-in-the-middle type of attacks.
* For example, for Bluetooth 2.1 devices, if any of the devices does not
* have an input and output capability or just has the ability to
* display a numeric key, a secure socket connection is not possible.
* In such a case, use {#link createInsecureRfcommSocket}.
* For more details, refer to the Security Model section 5.2 (vol 3) of
* Bluetooth Core Specification version 2.1 + EDR.
* <p>Use {@link BluetoothSocket#connect} to initiate the outgoing
* connection.
* <p>Valid L2CAP PSM channels are in range 1 to 2^16.
* <p>Requires {@link android.Manifest.permission#BLUETOOTH}
*
* @param channel L2cap PSM/channel to connect to
* @return a RFCOMM BluetoothServerSocket ready for an outgoing connection
* @throws IOException on error, for example Bluetooth not available, or
* insufficient permissions
* @hide
*/
public BluetoothSocket createL2capSocket(int channel) throws IOException {
return new BluetoothSocket(BluetoothSocket.TYPE_L2CAP, -1, true, true, this, channel,
null);
}
/**
* Create an RFCOMM {@link BluetoothSocket} ready to start a secure
* outgoing connection to this remote device using SDP lookup of uuid.

View File

@ -18,6 +18,7 @@ package android.bluetooth;
import android.os.Handler;
import android.os.ParcelUuid;
import android.util.Log;
import java.io.Closeable;
import java.io.IOException;
@ -66,10 +67,11 @@ import java.io.IOException;
*/
public final class BluetoothServerSocket implements Closeable {
private static final String TAG = "BluetoothServerSocket";
/*package*/ final BluetoothSocket mSocket;
private Handler mHandler;
private int mMessage;
private final int mChannel;
private int mChannel;
/**
* Construct a socket for incoming connections.
@ -84,6 +86,9 @@ public final class BluetoothServerSocket implements Closeable {
throws IOException {
mChannel = port;
mSocket = new BluetoothSocket(type, -1, auth, encrypt, null, port, null);
if(port == BluetoothAdapter.SOCKET_CHANNEL_AUTO_STATIC_NO_SDP) {
mSocket.setExcludeSdp(true);
}
}
/**
@ -98,6 +103,7 @@ public final class BluetoothServerSocket implements Closeable {
/*package*/ BluetoothServerSocket(int type, boolean auth, boolean encrypt, ParcelUuid uuid)
throws IOException {
mSocket = new BluetoothSocket(type, -1, auth, encrypt, null, -1, uuid);
// TODO: This is the same as mChannel = -1 - is this intentional?
mChannel = mSocket.getPort();
}
@ -153,6 +159,7 @@ public final class BluetoothServerSocket implements Closeable {
/*package*/ void setServiceName(String ServiceName) {
mSocket.setServiceName(ServiceName);
}
/**
* Returns the channel on which this socket is bound.
* @hide
@ -160,4 +167,47 @@ public final class BluetoothServerSocket implements Closeable {
public int getChannel() {
return mChannel;
}
/**
* Sets the channel on which future sockets are bound.
* Currently used only when a channel is auto generated.
*/
/*package*/ void setChannel(int newChannel) {
/* TODO: From a design/architecture perspective this is wrong.
* The bind operation should be conducted through this class
* and the resulting port should be kept in mChannel, and
* not set from BluetoothAdapter. */
if(mSocket != null) {
if(mSocket.getPort() != newChannel) {
Log.w(TAG,"The port set is different that the underlying port. mSocket.getPort(): "
+ mSocket.getPort() + " requested newChannel: " + newChannel);
}
}
mChannel = newChannel;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("ServerSocket: Type: ");
switch(mSocket.getConnectionType()) {
case BluetoothSocket.TYPE_RFCOMM:
{
sb.append("TYPE_RFCOMM");
break;
}
case BluetoothSocket.TYPE_L2CAP:
{
sb.append("TYPE_L2CAP");
break;
}
case BluetoothSocket.TYPE_SCO:
{
sb.append("TYPE_SCO");
break;
}
}
sb.append(" Channel: ").append(mChannel);
return sb.toString();
}
}

View File

@ -21,6 +21,7 @@ import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.util.Log;
import java.io.BufferedInputStream;
import java.io.Closeable;
import java.io.FileDescriptor;
import java.io.IOException;
@ -29,6 +30,8 @@ import java.io.OutputStream;
import java.util.Locale;
import java.util.UUID;
import android.net.LocalSocket;
import java.nio.Buffer;
import java.nio.ByteOrder;
import java.nio.ByteBuffer;
/**
@ -86,17 +89,19 @@ public final class BluetoothSocket implements Closeable {
/** @hide */
public static final int MAX_RFCOMM_CHANNEL = 30;
/*package*/ static final int MAX_L2CAP_PACKAGE_SIZE = 0xFFFF;
/** Keep TYPE_ fields in sync with BluetoothSocket.cpp */
/*package*/ static final int TYPE_RFCOMM = 1;
/*package*/ static final int TYPE_SCO = 2;
/*package*/ static final int TYPE_L2CAP = 3;
public static final int TYPE_RFCOMM = 1;
public static final int TYPE_SCO = 2;
public static final int TYPE_L2CAP = 3;
/*package*/ static final int EBADFD = 77;
/*package*/ static final int EADDRINUSE = 98;
/*package*/ static final int SEC_FLAG_ENCRYPT = 1;
/*package*/ static final int SEC_FLAG_AUTH = 1 << 1;
/*package*/ static final int BTSOCK_FLAG_NO_SDP = 1 << 2;
private final int mType; /* one of TYPE_RFCOMM etc */
private BluetoothDevice mDevice; /* remote device */
@ -106,6 +111,7 @@ public final class BluetoothSocket implements Closeable {
private final BluetoothInputStream mInputStream;
private final BluetoothOutputStream mOutputStream;
private final ParcelUuid mUuid;
private boolean mExcludeSdp = false;
private ParcelFileDescriptor mPfd;
private LocalSocket mSocket;
private InputStream mSocketIS;
@ -115,7 +121,11 @@ public final class BluetoothSocket implements Closeable {
private String mServiceName;
private static int PROXY_CONNECTION_TIMEOUT = 5000;
private static int SOCK_SIGNAL_SIZE = 16;
private static int SOCK_SIGNAL_SIZE = 20;
private ByteBuffer mL2capBuffer = null;
private int mMaxTxPacketSize = 0; // The l2cap maximum packet size supported by the peer.
private int mMaxRxPacketSize = 0; // The l2cap maximum packet size that can be received.
private enum SocketState {
INIT,
@ -144,12 +154,14 @@ public final class BluetoothSocket implements Closeable {
*/
/*package*/ BluetoothSocket(int type, int fd, boolean auth, boolean encrypt,
BluetoothDevice device, int port, ParcelUuid uuid) throws IOException {
if (type == BluetoothSocket.TYPE_RFCOMM && uuid == null && fd == -1) {
if (VDBG) Log.d(TAG, "Creating new BluetoothSocket of type: " + type);
if (type == BluetoothSocket.TYPE_RFCOMM && uuid == null && fd == -1
&& port != BluetoothAdapter.SOCKET_CHANNEL_AUTO_STATIC_NO_SDP) {
if (port < 1 || port > MAX_RFCOMM_CHANNEL) {
throw new IOException("Invalid RFCOMM channel: " + port);
}
}
if(uuid != null)
if (uuid != null)
mUuid = uuid;
else mUuid = new ParcelUuid(new UUID(0, 0));
mType = type;
@ -172,6 +184,7 @@ public final class BluetoothSocket implements Closeable {
mOutputStream = new BluetoothOutputStream(this);
}
private BluetoothSocket(BluetoothSocket s) {
if (VDBG) Log.d(TAG, "Creating new Private BluetoothSocket of type: " + s.mType);
mUuid = s.mUuid;
mType = s.mType;
mAuth = s.mAuth;
@ -179,7 +192,11 @@ public final class BluetoothSocket implements Closeable {
mPort = s.mPort;
mInputStream = new BluetoothInputStream(this);
mOutputStream = new BluetoothOutputStream(this);
mMaxRxPacketSize = s.mMaxRxPacketSize;
mMaxTxPacketSize = s.mMaxTxPacketSize;
mServiceName = s.mServiceName;
mExcludeSdp = s.mExcludeSdp;
}
private BluetoothSocket acceptSocket(String RemoteAddr) throws IOException {
BluetoothSocket as = new BluetoothSocket(this);
@ -229,6 +246,8 @@ public final class BluetoothSocket implements Closeable {
flags |= SEC_FLAG_AUTH;
if(mEncrypt)
flags |= SEC_FLAG_ENCRYPT;
if(mExcludeSdp)
flags |= BTSOCK_FLAG_NO_SDP;
return flags;
}
@ -298,7 +317,8 @@ public final class BluetoothSocket implements Closeable {
try {
if (mSocketState == SocketState.CLOSED) throw new IOException("socket closed");
IBluetooth bluetoothProxy = BluetoothAdapter.getDefaultAdapter().getBluetoothService(null);
IBluetooth bluetoothProxy =
BluetoothAdapter.getDefaultAdapter().getBluetoothService(null);
if (bluetoothProxy == null) throw new IOException("Bluetooth is off");
mPfd = bluetoothProxy.connectSocket(mDevice, mType,
mUuid, mPort, getSecurityFlags());
@ -370,7 +390,7 @@ public final class BluetoothSocket implements Closeable {
mSocketState = SocketState.LISTENING;
}
if (DBG) Log.d(TAG, "channel: " + channel);
if (mPort == -1) {
if (mPort <= -1) {
mPort = channel;
} // else ASSERT(mPort == channel)
ret = 0;
@ -391,7 +411,8 @@ public final class BluetoothSocket implements Closeable {
/*package*/ BluetoothSocket accept(int timeout) throws IOException {
BluetoothSocket acceptedSocket;
if (mSocketState != SocketState.LISTENING) throw new IOException("bt socket is not in listen state");
if (mSocketState != SocketState.LISTENING)
throw new IOException("bt socket is not in listen state");
if(timeout > 0) {
Log.d(TAG, "accept() set timeout (ms):" + timeout);
mSocket.setSoTimeout(timeout);
@ -427,27 +448,80 @@ public final class BluetoothSocket implements Closeable {
}
/*package*/ int read(byte[] b, int offset, int length) throws IOException {
if (mSocketIS == null) throw new IOException("read is called on null InputStream");
int ret = 0;
if (VDBG) Log.d(TAG, "read in: " + mSocketIS + " len: " + length);
int ret = mSocketIS.read(b, offset, length);
if(ret < 0)
if(mType == TYPE_L2CAP)
{
int bytesToRead = length;
if (VDBG) Log.v(TAG, "l2cap: read(): offset: " + offset + " length:" + length
+ "mL2capBuffer= " + mL2capBuffer);
if (mL2capBuffer == null) {
createL2capRxBuffer();
}
if (mL2capBuffer.remaining() == 0) {
if (VDBG) Log.v(TAG, "l2cap buffer empty, refilling...");
if (fillL2capRxBuffer() == -1) {
return -1;
}
}
if (bytesToRead > mL2capBuffer.remaining()) {
bytesToRead = mL2capBuffer.remaining();
}
if(VDBG) Log.v(TAG, "get(): offset: " + offset
+ " bytesToRead: " + bytesToRead);
mL2capBuffer.get(b, offset, bytesToRead);
ret = bytesToRead;
}else {
if (VDBG) Log.v(TAG, "default: read(): offset: " + offset + " length:" + length);
ret = mSocketIS.read(b, offset, length);
}
if (ret < 0)
throw new IOException("bt socket closed, read return: " + ret);
if (VDBG) Log.d(TAG, "read out: " + mSocketIS + " ret: " + ret);
return ret;
}
/*package*/ int write(byte[] b, int offset, int length) throws IOException {
if (mSocketOS == null) throw new IOException("write is called on null OutputStream");
if (VDBG) Log.d(TAG, "write: " + mSocketOS + " length: " + length);
mSocketOS.write(b, offset, length);
// There is no good way to confirm since the entire process is asynchronous anyway
if (VDBG) Log.d(TAG, "write out: " + mSocketOS + " length: " + length);
return length;
//TODO: Since bindings can exist between the SDU size and the
// protocol, we might need to throw an exception instead of just
// splitting the write into multiple smaller writes.
// Rfcomm uses dynamic allocation, and should not have any bindings
// to the actual message length.
if (VDBG) Log.d(TAG, "write: " + mSocketOS + " length: " + length);
if (mType == TYPE_L2CAP) {
if(length <= mMaxTxPacketSize) {
mSocketOS.write(b, offset, length);
} else {
int tmpOffset = offset;
int tmpLength = mMaxTxPacketSize;
int endIndex = offset + length;
boolean done = false;
if(DBG) Log.w(TAG, "WARNING: Write buffer larger than L2CAP packet size!\n"
+ "Packet will be divided into SDU packets of size "
+ mMaxTxPacketSize);
do{
mSocketOS.write(b, tmpOffset, tmpLength);
tmpOffset += mMaxTxPacketSize;
if((tmpOffset + mMaxTxPacketSize) > endIndex) {
tmpLength = endIndex - tmpOffset;
done = true;
}
} while(!done);
}
} else {
mSocketOS.write(b, offset, length);
}
// There is no good way to confirm since the entire process is asynchronous anyway
if (VDBG) Log.d(TAG, "write out: " + mSocketOS + " length: " + length);
return length;
}
@Override
public void close() throws IOException {
if (DBG) Log.d(TAG, "close() in, this: " + this + ", channel: " + mPort + ", state: " + mSocketState);
if (DBG) Log.d(TAG, "close() in, this: " + this + ", channel: " + mPort + ", state: "
+ mSocketState);
if(mSocketState == SocketState.CLOSED)
return;
else
@ -457,8 +531,9 @@ public final class BluetoothSocket implements Closeable {
if(mSocketState == SocketState.CLOSED)
return;
mSocketState = SocketState.CLOSED;
if (DBG) Log.d(TAG, "close() this: " + this + ", channel: " + mPort + ", mSocketIS: " + mSocketIS +
", mSocketOS: " + mSocketOS + "mSocket: " + mSocket);
if (DBG) Log.d(TAG, "close() this: " + this + ", channel: " + mPort +
", mSocketIS: " + mSocketIS + ", mSocketOS: " + mSocketOS +
"mSocket: " + mSocket);
if(mSocket != null) {
if (DBG) Log.d(TAG, "Closing mSocket: " + mSocket);
mSocket.shutdownInput();
@ -480,6 +555,47 @@ public final class BluetoothSocket implements Closeable {
/*package */ int getPort() {
return mPort;
}
/**
* Get the maximum supported Transmit packet size for the underlying transport.
* Use this to optimize the writes done to the output socket, to avoid sending
* half full packets.
* @return the maximum supported Transmit packet size for the underlying transport.
*/
public int getMaxTransmitPacketSize(){
return mMaxTxPacketSize;
}
/**
* Get the maximum supported Receive packet size for the underlying transport.
* Use this to optimize the reads done on the input stream, as any call to read
* will return a maximum of this amount of bytes - or for some transports a
* multiple of this value.
* @return the maximum supported Receive packet size for the underlying transport.
*/
public int getMaxReceivePacketSize(){
return mMaxRxPacketSize;
}
/**
* Get the type of the underlying connection
* @return one of TYPE_
*/
public int getConnectionType() {
return mType;
}
/**
* Change if a SDP entry should be automatically created.
* Must be called before calling .bind, for the call to have any effect.
* @param mExcludeSdp <li>TRUE - do not auto generate SDP record.
* <li>FALSE - default - auto generate SPP SDP record.
* @hide
*/
public void setExcludeSdp(boolean excludeSdp) {
this.mExcludeSdp = excludeSdp;
}
private String convertAddr(final byte[] addr) {
return String.format(Locale.US, "%02X:%02X:%02X:%02X:%02X:%02X",
addr[0] , addr[1], addr[2], addr[3] , addr[4], addr[5]);
@ -487,8 +603,10 @@ public final class BluetoothSocket implements Closeable {
private String waitSocketSignal(InputStream is) throws IOException {
byte [] sig = new byte[SOCK_SIGNAL_SIZE];
int ret = readAll(is, sig);
if (VDBG) Log.d(TAG, "waitSocketSignal read 16 bytes signal ret: " + ret);
if (VDBG) Log.d(TAG, "waitSocketSignal read " + SOCK_SIGNAL_SIZE +
" bytes signal ret: " + ret);
ByteBuffer bb = ByteBuffer.wrap(sig);
/* the struct in native is decorated with __attribute__((packed)), hence this is possible */
bb.order(ByteOrder.nativeOrder());
int size = bb.getShort();
if(size != SOCK_SIGNAL_SIZE)
@ -497,19 +615,36 @@ public final class BluetoothSocket implements Closeable {
bb.get(addr);
int channel = bb.getInt();
int status = bb.getInt();
mMaxTxPacketSize = (bb.getShort() & 0xffff); // Convert to unsigned value
mMaxRxPacketSize = (bb.getShort() & 0xffff); // Convert to unsigned value
String RemoteAddr = convertAddr(addr);
if (VDBG) Log.d(TAG, "waitSocketSignal: sig size: " + size + ", remote addr: "
+ RemoteAddr + ", channel: " + channel + ", status: " + status);
+ RemoteAddr + ", channel: " + channel + ", status: " + status
+ " MaxRxPktSize: " + mMaxRxPacketSize + " MaxTxPktSize: " + mMaxTxPacketSize);
if(status != 0)
throw new IOException("Connection failure, status: " + status);
return RemoteAddr;
}
private void createL2capRxBuffer(){
if(mType == TYPE_L2CAP) {
// Allocate the buffer to use for reads.
if(VDBG) Log.v(TAG, " Creating mL2capBuffer: mMaxPacketSize: " + mMaxRxPacketSize);
mL2capBuffer = ByteBuffer.wrap(new byte[mMaxRxPacketSize]);
if(VDBG) Log.v(TAG, "mL2capBuffer.remaining()" + mL2capBuffer.remaining());
mL2capBuffer.limit(0); // Ensure we do a real read at the first read-request
if(VDBG) Log.v(TAG, "mL2capBuffer.remaining() after limit(0):" +
mL2capBuffer.remaining());
}
}
private int readAll(InputStream is, byte[] b) throws IOException {
int left = b.length;
while(left > 0) {
int ret = is.read(b, b.length - left, left);
if(ret <= 0)
throw new IOException("read failed, socket might closed or timeout, read ret: " + ret);
throw new IOException("read failed, socket might closed or timeout, read ret: "
+ ret);
left -= ret;
if(left != 0)
Log.w(TAG, "readAll() looping, read partial size: " + (b.length - left) +
@ -526,4 +661,18 @@ public final class BluetoothSocket implements Closeable {
bb.order(ByteOrder.nativeOrder());
return bb.getInt();
}
private int fillL2capRxBuffer() throws IOException {
mL2capBuffer.rewind();
int ret = mSocketIS.read(mL2capBuffer.array());
if(ret == -1) {
// reached end of stream - return -1
mL2capBuffer.limit(0);
return -1;
}
mL2capBuffer.limit(ret);
return ret;
}
}

View File

@ -68,7 +68,7 @@ interface IBluetooth
int getRemoteClass(in BluetoothDevice device);
ParcelUuid[] getRemoteUuids(in BluetoothDevice device);
boolean fetchRemoteUuids(in BluetoothDevice device);
boolean fetchRemoteMasInstances(in BluetoothDevice device);
boolean sdpSearch(in BluetoothDevice device, in ParcelUuid uuid);
boolean setPin(in BluetoothDevice device, boolean accept, int len, in byte[] pinCode);
boolean setPasskey(in BluetoothDevice device, boolean accept, int len, in byte[]

View File

@ -0,0 +1,147 @@
/*
* Copyright (C) 2015 Samsung System 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 android.bluetooth;
import android.os.Parcel;
import android.os.Parcelable;
/** @hide */
public class SdpMasRecord implements Parcelable {
private final int mMasInstanceId;
private final int mL2capPsm;
private final int mRfcommChannelNumber;
private final int mProfileVersion;
private final int mSupportedFeatures;
private final int mSupportedMessageTypes;
private final String mServiceName;
public static final class MessageType {
public static final int EMAIL = 0x01;
public static final int SMS_GSM = 0x02;
public static final int SMS_CDMA = 0x04;
public static final int MMS = 0x08;
}
public SdpMasRecord(int mas_instance_id,
int l2cap_psm,
int rfcomm_channel_number,
int profile_version,
int supported_features,
int supported_message_types,
String service_name){
this.mMasInstanceId = mas_instance_id;
this.mL2capPsm = l2cap_psm;
this.mRfcommChannelNumber = rfcomm_channel_number;
this.mProfileVersion = profile_version;
this.mSupportedFeatures = supported_features;
this.mSupportedMessageTypes = supported_message_types;
this.mServiceName = service_name;
}
public SdpMasRecord(Parcel in){
this.mMasInstanceId = in.readInt();
this.mL2capPsm = in.readInt();
this.mRfcommChannelNumber = in.readInt();
this.mProfileVersion = in.readInt();
this.mSupportedFeatures = in.readInt();
this.mSupportedMessageTypes = in.readInt();
this.mServiceName = in.readString();
}
@Override
public int describeContents() {
// TODO Auto-generated method stub
return 0;
}
public int getMasInstanceId() {
return mMasInstanceId;
}
public int getL2capPsm() {
return mL2capPsm;
}
public int getRfcommCannelNumber() {
return mRfcommChannelNumber;
}
public int getProfileVersion() {
return mProfileVersion;
}
public int getSupportedFeatures() {
return mSupportedFeatures;
}
public int getSupportedMessageTypes() {
return mSupportedMessageTypes;
}
public boolean msgSupported(int msg) {
return (mSupportedMessageTypes & msg) != 0;
}
public String getServiceName() {
return mServiceName;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(this.mMasInstanceId);
dest.writeInt(this.mL2capPsm);
dest.writeInt(this.mRfcommChannelNumber);
dest.writeInt(this.mProfileVersion);
dest.writeInt(this.mSupportedFeatures);
dest.writeInt(this.mSupportedMessageTypes);
dest.writeString(this.mServiceName);
}
@Override
public String toString(){
String ret = "Bluetooth MAS SDP Record:\n";
if(mMasInstanceId != -1){
ret += "Mas Instance Id: " + mMasInstanceId + "\n";
}
if(mRfcommChannelNumber != -1){
ret += "RFCOMM Chan Number: " + mRfcommChannelNumber + "\n";
}
if(mL2capPsm != -1){
ret += "L2CAP PSM: " + mL2capPsm + "\n";
}
if(mServiceName != null){
ret += "Service Name: " + mServiceName + "\n";
}
if(mProfileVersion != -1){
ret += "Profile version: " + mProfileVersion + "\n";
}
if(mSupportedMessageTypes != -1){
ret += "Supported msg types: " + mSupportedMessageTypes + "\n";
}
if(mSupportedFeatures != -1){
ret += "Supported features: " + mSupportedFeatures + "\n";
}
return ret;
}
public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
public SdpMasRecord createFromParcel(Parcel in) {
return new SdpMasRecord(in);
}
public SdpRecord[] newArray(int size) {
return new SdpRecord[size];
}
};
}

View File

@ -0,0 +1,112 @@
/*
* Copyright (C) 2015 Samsung System 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 android.bluetooth;
import android.os.Parcel;
import android.os.Parcelable;
/** @hide */
public class SdpMnsRecord implements Parcelable {
private final int mL2capPsm;
private final int mRfcommChannelNumber;
private final int mSupportedFeatures;
private final int mProfileVersion;
private final String mServiceName;
public SdpMnsRecord(int l2cap_psm,
int rfcomm_channel_number,
int profile_version,
int supported_features,
String service_name){
this.mL2capPsm = l2cap_psm;
this.mRfcommChannelNumber = rfcomm_channel_number;
this.mSupportedFeatures = supported_features;
this.mServiceName = service_name;
this.mProfileVersion = profile_version;
}
public SdpMnsRecord(Parcel in){
this.mRfcommChannelNumber = in.readInt();
this.mL2capPsm = in.readInt();
this.mServiceName = in.readString();
this.mSupportedFeatures = in.readInt();
this.mProfileVersion = in.readInt();
}
@Override
public int describeContents() {
// TODO Auto-generated method stub
return 0;
}
public int getL2capPsm() {
return mL2capPsm;
}
public int getRfcommChannelNumber() {
return mRfcommChannelNumber;
}
public int getSupportedFeatures() {
return mSupportedFeatures;
}
public String getServiceName() {
return mServiceName;
}
public int getProfileVersion() {
return mProfileVersion;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(mRfcommChannelNumber);
dest.writeInt(mL2capPsm);
dest.writeString(mServiceName);
dest.writeInt(mSupportedFeatures);
dest.writeInt(mProfileVersion);
}
public String toString(){
String ret = "Bluetooth MNS SDP Record:\n";
if(mRfcommChannelNumber != -1){
ret += "RFCOMM Chan Number: " + mRfcommChannelNumber + "\n";
}
if(mL2capPsm != -1){
ret += "L2CAP PSM: " + mL2capPsm + "\n";
}
if(mServiceName != null){
ret += "Service Name: " + mServiceName + "\n";
}
if(mSupportedFeatures != -1){
ret += "Supported features: " + mSupportedFeatures + "\n";
}
if(mProfileVersion != -1){
ret += "Profile_version: " + mProfileVersion+"\n";
}
return ret;
}
public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
public SdpMnsRecord createFromParcel(Parcel in) {
return new SdpMnsRecord(in);
}
public SdpMnsRecord[] newArray(int size) {
return new SdpMnsRecord[size];
}
};
}

View File

@ -0,0 +1,118 @@
/*
* Copyright (C) 2015 Samsung System 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 android.bluetooth;
import java.util.Arrays;
import android.os.Parcel;
import android.os.Parcelable;
/**
* Data representation of a Object Push Profile Server side SDP record.
*/
/** @hide */
public class SdpOppOpsRecord implements Parcelable {
private final String mServiceName;
private final int mRfcommChannel;
private final int mL2capPsm;
private final int mProfileVersion;
private final byte[] mFormatsList;
public SdpOppOpsRecord(String serviceName, int rfcommChannel,
int l2capPsm, int version, byte[] formatsList) {
super();
this.mServiceName = serviceName;
this.mRfcommChannel = rfcommChannel;
this.mL2capPsm = l2capPsm;
this.mProfileVersion = version;
this.mFormatsList = formatsList;
}
public String getServiceName() {
return mServiceName;
}
public int getRfcommChannel() {
return mRfcommChannel;
}
public int getL2capPsm() {
return mL2capPsm;
}
public int getProfileVersion() {
return mProfileVersion;
}
public byte[] getFormatsList() {
return mFormatsList;
}
@Override
public int describeContents() {
/* No special objects */
return 0;
}
public SdpOppOpsRecord(Parcel in){
this.mRfcommChannel = in.readInt();
this.mL2capPsm = in.readInt();
this.mProfileVersion = in.readInt();
this.mServiceName = in.readString();
int arrayLength = in.readInt();
if(arrayLength > 0) {
byte[] bytes = new byte[arrayLength];
in.readByteArray(bytes);
this.mFormatsList = bytes;
} else {
this.mFormatsList = null;
}
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(mRfcommChannel);
dest.writeInt(mL2capPsm);
dest.writeInt(mProfileVersion);
dest.writeString(mServiceName);
if(mFormatsList!= null && mFormatsList.length > 0) {
dest.writeInt(mFormatsList.length);
dest.writeByteArray(mFormatsList);
} else {
dest.writeInt(0);
}
}
public String toString(){
StringBuilder sb = new StringBuilder("Bluetooth OPP Server SDP Record:\n");
sb.append(" RFCOMM Chan Number: ").append(mRfcommChannel);
sb.append("\n L2CAP PSM: ").append(mL2capPsm);
sb.append("\n Profile version: ").append(mProfileVersion);
sb.append("\n Service Name: ").append(mServiceName);
sb.append("\n Formats List: ").append(Arrays.toString(mFormatsList));
return sb.toString();
}
public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
public SdpOppOpsRecord createFromParcel(Parcel in) {
return new SdpOppOpsRecord(in);
}
public SdpOppOpsRecord[] newArray(int size) {
return new SdpOppOpsRecord[size];
}
};
}

View File

@ -0,0 +1,125 @@
/*
* Copyright (C) 2015 Samsung System 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 android.bluetooth;
import android.os.Parcel;
import android.os.Parcelable;
/** @hide */
public class SdpPseRecord implements Parcelable {
private final int mL2capPsm;
private final int mRfcommChannelNumber;
private final int mProfileVersion;
private final int mSupportedFeatures;
private final int mSupportedRepositories;
private final String mServiceName;
public SdpPseRecord(int l2cap_psm,
int rfcomm_channel_number,
int profile_version,
int supported_features,
int supported_repositories,
String service_name){
this.mL2capPsm = l2cap_psm;
this.mRfcommChannelNumber = rfcomm_channel_number;
this.mProfileVersion = profile_version;
this.mSupportedFeatures = supported_features;
this.mSupportedRepositories = supported_repositories;
this.mServiceName = service_name;
}
public SdpPseRecord(Parcel in){
this.mRfcommChannelNumber = in.readInt();
this.mL2capPsm = in.readInt();
this.mProfileVersion = in.readInt();
this.mSupportedFeatures = in.readInt();
this.mSupportedRepositories = in.readInt();
this.mServiceName = in.readString();
}
@Override
public int describeContents() {
// TODO Auto-generated method stub
return 0;
}
public int getL2capPsm() {
return mL2capPsm;
}
public int getRfcommChannelNumber() {
return mRfcommChannelNumber;
}
public int getSupportedFeatures() {
return mSupportedFeatures;
}
public String getServiceName() {
return mServiceName;
}
public int getProfileVersion() {
return mProfileVersion;
}
public int getSupportedRepositories() {
return mSupportedRepositories;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(mRfcommChannelNumber);
dest.writeInt(mL2capPsm);
dest.writeInt(mProfileVersion);
dest.writeInt(mSupportedFeatures);
dest.writeInt(mSupportedRepositories);
dest.writeString(mServiceName);
}
public String toString(){
String ret = "Bluetooth MNS SDP Record:\n";
if(mRfcommChannelNumber != -1){
ret += "RFCOMM Chan Number: " + mRfcommChannelNumber + "\n";
}
if(mL2capPsm != -1){
ret += "L2CAP PSM: " + mL2capPsm + "\n";
}
if(mProfileVersion != -1){
ret += "profile version: " + mProfileVersion + "\n";
}
if(mServiceName != null){
ret += "Service Name: " + mServiceName + "\n";
}
if(mSupportedFeatures != -1){
ret += "Supported features: " + mSupportedFeatures + "\n";
}
if(mSupportedRepositories != -1){
ret += "Supported repositories: " + mSupportedRepositories + "\n";
}
return ret;
}
public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
public SdpPseRecord createFromParcel(Parcel in) {
return new SdpPseRecord(in);
}
public SdpPseRecord[] newArray(int size) {
return new SdpPseRecord[size];
}
};
}

View File

@ -0,0 +1,76 @@
/*
* Copyright (C) 2015 Samsung System 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 android.bluetooth;
import android.os.Parcel;
import android.os.Parcelable;
import java.util.Arrays;
/** @hide */
public class SdpRecord implements Parcelable{
private final byte[] mRawData;
private final int mRawSize;
@Override
public String toString() {
return "BluetoothSdpRecord [rawData=" + Arrays.toString(mRawData)
+ ", rawSize=" + mRawSize + "]";
}
public SdpRecord(int size_record, byte[] record){
this.mRawData = record;
this.mRawSize = size_record;
}
public SdpRecord(Parcel in){
this.mRawSize = in.readInt();
this.mRawData = new byte[mRawSize];
in.readByteArray(this.mRawData);
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(this.mRawSize);
dest.writeByteArray(this.mRawData);
}
public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
public SdpRecord createFromParcel(Parcel in) {
return new SdpRecord(in);
}
public SdpRecord[] newArray(int size) {
return new SdpRecord[size];
}
};
public byte[] getRawData() {
return mRawData;
}
public int getRawSize() {
return mRawSize;
}
}

View File

@ -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)

View File

@ -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;
}
}

View File

@ -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();
header.responseCode = 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 (!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();
}
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();
//check with local max size
if (maxPacketSize > ObexHelper.MAX_CLIENT_PACKET_SIZE) {
maxPacketSize = ObexHelper.MAX_CLIENT_PACKET_SIZE;
}
if (!skipReceive) {
header.responseCode = mInput.read();
if (length > 7) {
data = new byte[length - 7];
int length = ((mInput.read() << 8) | (mInput.read()));
bytesReceived = mInput.read(data);
while (bytesReceived != (length - 7)) {
bytesReceived += mInput.read(data, bytesReceived, data.length
- bytesReceived);
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 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];
bytesReceived = mInput.read(data);
while (bytesReceived != (length - 7)) {
bytesReceived += mInput.read(data, bytesReceived, data.length
- bytesReceived);
}
} else {
return true;
}
} else {
return true;
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;
}
}
} else {
data = new byte[length - 3];
bytesReceived = mInput.read(data);
while (bytesReceived != (length - 3)) {
bytesReceived += mInput.read(data, bytesReceived, data.length - bytesReceived);
byte[] body = ObexHelper.updateHeaderSet(header, data);
if ((privateInput != null) && (body != null)) {
privateInput.writeBytes(body, 1);
}
if (opCode == ObexHelper.OBEX_OPCODE_ABORT) {
return true;
if (header.mConnectionID != null) {
mConnectionId = new byte[4];
System.arraycopy(header.mConnectionID, 0, mConnectionId, 0, 4);
}
}
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.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;
}
}

View File

@ -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();
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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();
}

View File

@ -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 {
// Receive and handle data (only send reply if !skipSend)
// Read a complete OBEX Packet
ObexPacket packet = ObexPacket.read(mInput);
if ((headerID == ObexHelper.OBEX_OPCODE_PUT_FINAL)) {
finalBitSet = true;
} else if (headerID == ObexHelper.OBEX_OPCODE_GET_FINAL) {
mRequestFinished = true;
}
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 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));
/*
* 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 ((headerId == ObexHelper.OBEX_OPCODE_PUT_FINAL)) {
finalBitSet = true;
} else if (headerId == ObexHelper.OBEX_OPCODE_GET_FINAL) {
mRequestFinished = true;
}
if (requestHeader.mAuthResp != null) {
if (!mParent.handleAuthResp(requestHeader.mAuthResp)) {
mExceptionString = "Authentication Failed";
mParent.sendResponse(ResponseCodes.OBEX_HTTP_UNAUTHORIZED, null);
mClosed = true;
requestHeader.mAuthResp = 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");
}
/*
* Determine if any headers were sent in the initial request
*/
if (packet.mLength > 3 || (mSrmEnabled && packet.mLength == 3)) {
if(handleObexPacket(packet) == false) {
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;
} else {
@ -503,6 +578,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

View File

@ -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;
}
}

View File

@ -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;
}
}