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 { public final class BluetoothSocket implements java.io.Closeable {
method public void close() throws java.io.IOException; method public void close() throws java.io.IOException;
method public void connect() 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 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 java.io.OutputStream getOutputStream() throws java.io.IOException;
method public android.bluetooth.BluetoothDevice getRemoteDevice(); method public android.bluetooth.BluetoothDevice getRemoteDevice();
method public boolean isConnected(); 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 { public final class BluetoothSocket implements java.io.Closeable {
method public void close() throws java.io.IOException; method public void close() throws java.io.IOException;
method public void connect() 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 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 java.io.OutputStream getOutputStream() throws java.io.IOException;
method public android.bluetooth.BluetoothDevice getRemoteDevice(); method public android.bluetooth.BluetoothDevice getRemoteDevice();
method public boolean isConnected(); 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -377,6 +378,18 @@ public final class BluetoothAdapter {
/** @hide */ /** @hide */
public static final String BLUETOOTH_MANAGER_SERVICE = "bluetooth_manager"; 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 ADDRESS_LENGTH = 17;
private static final int CONTROLLER_ENERGY_UPDATE_TIMEOUT_MILLIS = 30; private static final int CONTROLLER_ENERGY_UPDATE_TIMEOUT_MILLIS = 30;
@ -1144,6 +1157,9 @@ public final class BluetoothAdapter {
BluetoothServerSocket socket = new BluetoothServerSocket( BluetoothServerSocket socket = new BluetoothServerSocket(
BluetoothSocket.TYPE_RFCOMM, true, true, channel); BluetoothSocket.TYPE_RFCOMM, true, true, channel);
int errno = socket.mSocket.bindListen(); int errno = socket.mSocket.bindListen();
if(channel == SOCKET_CHANNEL_AUTO_STATIC_NO_SDP) {
socket.setChannel(socket.mSocket.getPort());
}
if (errno != 0) { if (errno != 0) {
//TODO(BT): Throw the same exception error code //TODO(BT): Throw the same exception error code
// that the previous code was using. // that the previous code was using.
@ -1278,6 +1294,9 @@ public final class BluetoothAdapter {
BluetoothServerSocket socket = new BluetoothServerSocket( BluetoothServerSocket socket = new BluetoothServerSocket(
BluetoothSocket.TYPE_RFCOMM, false, false, port); BluetoothSocket.TYPE_RFCOMM, false, false, port);
int errno = socket.mSocket.bindListen(); int errno = socket.mSocket.bindListen();
if(port == SOCKET_CHANNEL_AUTO_STATIC_NO_SDP) {
socket.setChannel(socket.mSocket.getPort());
}
if (errno != 0) { if (errno != 0) {
//TODO(BT): Throw the same exception error code //TODO(BT): Throw the same exception error code
// that the previous code was using. // that the previous code was using.
@ -1300,6 +1319,9 @@ public final class BluetoothAdapter {
BluetoothServerSocket socket = new BluetoothServerSocket( BluetoothServerSocket socket = new BluetoothServerSocket(
BluetoothSocket.TYPE_RFCOMM, false, true, port); BluetoothSocket.TYPE_RFCOMM, false, true, port);
int errno = socket.mSocket.bindListen(); int errno = socket.mSocket.bindListen();
if(port == SOCKET_CHANNEL_AUTO_STATIC_NO_SDP) {
socket.setChannel(socket.mSocket.getPort());
}
if (errno < 0) { if (errno < 0) {
//TODO(BT): Throw the same exception error code //TODO(BT): Throw the same exception error code
// that the previous code was using. // that the previous code was using.
@ -1329,6 +1351,30 @@ public final class BluetoothAdapter {
return socket; 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 * Read the local Out of Band Pairing Data
* <p>Requires {@link android.Manifest.permission#BLUETOOTH} * <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; 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} * Broadcast Action: This intent is used to broadcast the {@link UUID}
* wrapped as a {@link android.os.ParcelUuid} of the remote device after it * 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"; 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}, * For {@link #getPhonebookAccessPermission}, {@link #setPhonebookAccessPermission},
* {@link #getMessageAccessPermission} and {@link #setMessageAccessPermission}. * {@link #getMessageAccessPermission} and {@link #setMessageAccessPermission}.
@ -1054,14 +1067,34 @@ public final class BluetoothDevice implements Parcelable {
return false; 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 */ /** @hide */
public boolean fetchMasInstances() { public boolean sdpSearch(ParcelUuid uuid) {
if (sService == null) { 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; return false;
} }
try { try {
return sService.fetchRemoteMasInstances(this); return sService.sdpSearch(this,uuid);
} catch (RemoteException e) {Log.e(TAG, "", e);} } catch (RemoteException e) {Log.e(TAG, "", e);}
return false; return false;
} }
@ -1260,6 +1293,36 @@ public final class BluetoothDevice implements Parcelable {
null); 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 * Create an RFCOMM {@link BluetoothSocket} ready to start a secure
* outgoing connection to this remote device using SDP lookup of uuid. * 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.Handler;
import android.os.ParcelUuid; import android.os.ParcelUuid;
import android.util.Log;
import java.io.Closeable; import java.io.Closeable;
import java.io.IOException; import java.io.IOException;
@ -66,10 +67,11 @@ import java.io.IOException;
*/ */
public final class BluetoothServerSocket implements Closeable { public final class BluetoothServerSocket implements Closeable {
private static final String TAG = "BluetoothServerSocket";
/*package*/ final BluetoothSocket mSocket; /*package*/ final BluetoothSocket mSocket;
private Handler mHandler; private Handler mHandler;
private int mMessage; private int mMessage;
private final int mChannel; private int mChannel;
/** /**
* Construct a socket for incoming connections. * Construct a socket for incoming connections.
@ -84,6 +86,9 @@ public final class BluetoothServerSocket implements Closeable {
throws IOException { throws IOException {
mChannel = port; mChannel = port;
mSocket = new BluetoothSocket(type, -1, auth, encrypt, null, port, null); 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) /*package*/ BluetoothServerSocket(int type, boolean auth, boolean encrypt, ParcelUuid uuid)
throws IOException { throws IOException {
mSocket = new BluetoothSocket(type, -1, auth, encrypt, null, -1, uuid); mSocket = new BluetoothSocket(type, -1, auth, encrypt, null, -1, uuid);
// TODO: This is the same as mChannel = -1 - is this intentional?
mChannel = mSocket.getPort(); mChannel = mSocket.getPort();
} }
@ -153,6 +159,7 @@ public final class BluetoothServerSocket implements Closeable {
/*package*/ void setServiceName(String ServiceName) { /*package*/ void setServiceName(String ServiceName) {
mSocket.setServiceName(ServiceName); mSocket.setServiceName(ServiceName);
} }
/** /**
* Returns the channel on which this socket is bound. * Returns the channel on which this socket is bound.
* @hide * @hide
@ -160,4 +167,47 @@ public final class BluetoothServerSocket implements Closeable {
public int getChannel() { public int getChannel() {
return mChannel; 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.os.RemoteException;
import android.util.Log; import android.util.Log;
import java.io.BufferedInputStream;
import java.io.Closeable; import java.io.Closeable;
import java.io.FileDescriptor; import java.io.FileDescriptor;
import java.io.IOException; import java.io.IOException;
@ -29,6 +30,8 @@ import java.io.OutputStream;
import java.util.Locale; import java.util.Locale;
import java.util.UUID; import java.util.UUID;
import android.net.LocalSocket; import android.net.LocalSocket;
import java.nio.Buffer;
import java.nio.ByteOrder; import java.nio.ByteOrder;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
/** /**
@ -86,17 +89,19 @@ public final class BluetoothSocket implements Closeable {
/** @hide */ /** @hide */
public static final int MAX_RFCOMM_CHANNEL = 30; 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 */ /** Keep TYPE_ fields in sync with BluetoothSocket.cpp */
/*package*/ static final int TYPE_RFCOMM = 1; public static final int TYPE_RFCOMM = 1;
/*package*/ static final int TYPE_SCO = 2; public static final int TYPE_SCO = 2;
/*package*/ static final int TYPE_L2CAP = 3; public static final int TYPE_L2CAP = 3;
/*package*/ static final int EBADFD = 77; /*package*/ static final int EBADFD = 77;
/*package*/ static final int EADDRINUSE = 98; /*package*/ static final int EADDRINUSE = 98;
/*package*/ static final int SEC_FLAG_ENCRYPT = 1; /*package*/ static final int SEC_FLAG_ENCRYPT = 1;
/*package*/ static final int SEC_FLAG_AUTH = 1 << 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 final int mType; /* one of TYPE_RFCOMM etc */
private BluetoothDevice mDevice; /* remote device */ private BluetoothDevice mDevice; /* remote device */
@ -106,6 +111,7 @@ public final class BluetoothSocket implements Closeable {
private final BluetoothInputStream mInputStream; private final BluetoothInputStream mInputStream;
private final BluetoothOutputStream mOutputStream; private final BluetoothOutputStream mOutputStream;
private final ParcelUuid mUuid; private final ParcelUuid mUuid;
private boolean mExcludeSdp = false;
private ParcelFileDescriptor mPfd; private ParcelFileDescriptor mPfd;
private LocalSocket mSocket; private LocalSocket mSocket;
private InputStream mSocketIS; private InputStream mSocketIS;
@ -115,7 +121,11 @@ public final class BluetoothSocket implements Closeable {
private String mServiceName; private String mServiceName;
private static int PROXY_CONNECTION_TIMEOUT = 5000; 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 { private enum SocketState {
INIT, INIT,
@ -144,12 +154,14 @@ public final class BluetoothSocket implements Closeable {
*/ */
/*package*/ BluetoothSocket(int type, int fd, boolean auth, boolean encrypt, /*package*/ BluetoothSocket(int type, int fd, boolean auth, boolean encrypt,
BluetoothDevice device, int port, ParcelUuid uuid) throws IOException { 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) { if (port < 1 || port > MAX_RFCOMM_CHANNEL) {
throw new IOException("Invalid RFCOMM channel: " + port); throw new IOException("Invalid RFCOMM channel: " + port);
} }
} }
if(uuid != null) if (uuid != null)
mUuid = uuid; mUuid = uuid;
else mUuid = new ParcelUuid(new UUID(0, 0)); else mUuid = new ParcelUuid(new UUID(0, 0));
mType = type; mType = type;
@ -172,6 +184,7 @@ public final class BluetoothSocket implements Closeable {
mOutputStream = new BluetoothOutputStream(this); mOutputStream = new BluetoothOutputStream(this);
} }
private BluetoothSocket(BluetoothSocket s) { private BluetoothSocket(BluetoothSocket s) {
if (VDBG) Log.d(TAG, "Creating new Private BluetoothSocket of type: " + s.mType);
mUuid = s.mUuid; mUuid = s.mUuid;
mType = s.mType; mType = s.mType;
mAuth = s.mAuth; mAuth = s.mAuth;
@ -179,7 +192,11 @@ public final class BluetoothSocket implements Closeable {
mPort = s.mPort; mPort = s.mPort;
mInputStream = new BluetoothInputStream(this); mInputStream = new BluetoothInputStream(this);
mOutputStream = new BluetoothOutputStream(this); mOutputStream = new BluetoothOutputStream(this);
mMaxRxPacketSize = s.mMaxRxPacketSize;
mMaxTxPacketSize = s.mMaxTxPacketSize;
mServiceName = s.mServiceName; mServiceName = s.mServiceName;
mExcludeSdp = s.mExcludeSdp;
} }
private BluetoothSocket acceptSocket(String RemoteAddr) throws IOException { private BluetoothSocket acceptSocket(String RemoteAddr) throws IOException {
BluetoothSocket as = new BluetoothSocket(this); BluetoothSocket as = new BluetoothSocket(this);
@ -229,6 +246,8 @@ public final class BluetoothSocket implements Closeable {
flags |= SEC_FLAG_AUTH; flags |= SEC_FLAG_AUTH;
if(mEncrypt) if(mEncrypt)
flags |= SEC_FLAG_ENCRYPT; flags |= SEC_FLAG_ENCRYPT;
if(mExcludeSdp)
flags |= BTSOCK_FLAG_NO_SDP;
return flags; return flags;
} }
@ -298,7 +317,8 @@ public final class BluetoothSocket implements Closeable {
try { try {
if (mSocketState == SocketState.CLOSED) throw new IOException("socket closed"); 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"); if (bluetoothProxy == null) throw new IOException("Bluetooth is off");
mPfd = bluetoothProxy.connectSocket(mDevice, mType, mPfd = bluetoothProxy.connectSocket(mDevice, mType,
mUuid, mPort, getSecurityFlags()); mUuid, mPort, getSecurityFlags());
@ -370,7 +390,7 @@ public final class BluetoothSocket implements Closeable {
mSocketState = SocketState.LISTENING; mSocketState = SocketState.LISTENING;
} }
if (DBG) Log.d(TAG, "channel: " + channel); if (DBG) Log.d(TAG, "channel: " + channel);
if (mPort == -1) { if (mPort <= -1) {
mPort = channel; mPort = channel;
} // else ASSERT(mPort == channel) } // else ASSERT(mPort == channel)
ret = 0; ret = 0;
@ -391,7 +411,8 @@ public final class BluetoothSocket implements Closeable {
/*package*/ BluetoothSocket accept(int timeout) throws IOException { /*package*/ BluetoothSocket accept(int timeout) throws IOException {
BluetoothSocket acceptedSocket; 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) { if(timeout > 0) {
Log.d(TAG, "accept() set timeout (ms):" + timeout); Log.d(TAG, "accept() set timeout (ms):" + timeout);
mSocket.setSoTimeout(timeout); mSocket.setSoTimeout(timeout);
@ -427,19 +448,71 @@ public final class BluetoothSocket implements Closeable {
} }
/*package*/ int read(byte[] b, int offset, int length) throws IOException { /*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); if (VDBG) Log.d(TAG, "read in: " + mSocketIS + " len: " + length);
int ret = mSocketIS.read(b, offset, length); if(mType == TYPE_L2CAP)
if(ret < 0) {
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); throw new IOException("bt socket closed, read return: " + ret);
if (VDBG) Log.d(TAG, "read out: " + mSocketIS + " ret: " + ret); if (VDBG) Log.d(TAG, "read out: " + mSocketIS + " ret: " + ret);
return ret; return ret;
} }
/*package*/ int write(byte[] b, int offset, int length) throws IOException { /*package*/ int write(byte[] b, int offset, int length) throws IOException {
if (mSocketOS == null) throw new IOException("write is called on null OutputStream");
//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 (VDBG) Log.d(TAG, "write: " + mSocketOS + " length: " + length);
if (mType == TYPE_L2CAP) {
if(length <= mMaxTxPacketSize) {
mSocketOS.write(b, offset, length); 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 // There is no good way to confirm since the entire process is asynchronous anyway
if (VDBG) Log.d(TAG, "write out: " + mSocketOS + " length: " + length); if (VDBG) Log.d(TAG, "write out: " + mSocketOS + " length: " + length);
return length; return length;
@ -447,7 +520,8 @@ public final class BluetoothSocket implements Closeable {
@Override @Override
public void close() throws IOException { 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) if(mSocketState == SocketState.CLOSED)
return; return;
else else
@ -457,8 +531,9 @@ public final class BluetoothSocket implements Closeable {
if(mSocketState == SocketState.CLOSED) if(mSocketState == SocketState.CLOSED)
return; return;
mSocketState = SocketState.CLOSED; mSocketState = SocketState.CLOSED;
if (DBG) Log.d(TAG, "close() this: " + this + ", channel: " + mPort + ", mSocketIS: " + mSocketIS + if (DBG) Log.d(TAG, "close() this: " + this + ", channel: " + mPort +
", mSocketOS: " + mSocketOS + "mSocket: " + mSocket); ", mSocketIS: " + mSocketIS + ", mSocketOS: " + mSocketOS +
"mSocket: " + mSocket);
if(mSocket != null) { if(mSocket != null) {
if (DBG) Log.d(TAG, "Closing mSocket: " + mSocket); if (DBG) Log.d(TAG, "Closing mSocket: " + mSocket);
mSocket.shutdownInput(); mSocket.shutdownInput();
@ -480,6 +555,47 @@ public final class BluetoothSocket implements Closeable {
/*package */ int getPort() { /*package */ int getPort() {
return mPort; 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) { private String convertAddr(final byte[] addr) {
return String.format(Locale.US, "%02X:%02X:%02X:%02X:%02X:%02X", return String.format(Locale.US, "%02X:%02X:%02X:%02X:%02X:%02X",
addr[0] , addr[1], addr[2], addr[3] , addr[4], addr[5]); 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 { private String waitSocketSignal(InputStream is) throws IOException {
byte [] sig = new byte[SOCK_SIGNAL_SIZE]; byte [] sig = new byte[SOCK_SIGNAL_SIZE];
int ret = readAll(is, sig); 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); ByteBuffer bb = ByteBuffer.wrap(sig);
/* the struct in native is decorated with __attribute__((packed)), hence this is possible */
bb.order(ByteOrder.nativeOrder()); bb.order(ByteOrder.nativeOrder());
int size = bb.getShort(); int size = bb.getShort();
if(size != SOCK_SIGNAL_SIZE) if(size != SOCK_SIGNAL_SIZE)
@ -497,19 +615,36 @@ public final class BluetoothSocket implements Closeable {
bb.get(addr); bb.get(addr);
int channel = bb.getInt(); int channel = bb.getInt();
int status = 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); String RemoteAddr = convertAddr(addr);
if (VDBG) Log.d(TAG, "waitSocketSignal: sig size: " + size + ", remote 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) if(status != 0)
throw new IOException("Connection failure, status: " + status); throw new IOException("Connection failure, status: " + status);
return RemoteAddr; 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 { private int readAll(InputStream is, byte[] b) throws IOException {
int left = b.length; int left = b.length;
while(left > 0) { while(left > 0) {
int ret = is.read(b, b.length - left, left); int ret = is.read(b, b.length - left, left);
if(ret <= 0) 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; left -= ret;
if(left != 0) if(left != 0)
Log.w(TAG, "readAll() looping, read partial size: " + (b.length - left) + 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()); bb.order(ByteOrder.nativeOrder());
return bb.getInt(); 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); int getRemoteClass(in BluetoothDevice device);
ParcelUuid[] getRemoteUuids(in BluetoothDevice device); ParcelUuid[] getRemoteUuids(in BluetoothDevice device);
boolean fetchRemoteUuids(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 setPin(in BluetoothDevice device, boolean accept, int len, in byte[] pinCode);
boolean setPasskey(in BluetoothDevice device, boolean accept, int len, in byte[] 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 LOCAL_MODULE:= javax.obex
include $(BUILD_JAVA_LIBRARY) 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. * Copyright (c) 2008-2009, Motorola, Inc.
* *
* All rights reserved. * All rights reserved.
@ -40,6 +41,8 @@ import java.io.DataInputStream;
import java.io.DataOutputStream; import java.io.DataOutputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import android.util.Log;
/** /**
* This class implements the <code>Operation</code> interface. It will read and * This class implements the <code>Operation</code> interface. It will read and
* write data via puts and gets. * write data via puts and gets.
@ -47,6 +50,10 @@ import java.io.ByteArrayOutputStream;
*/ */
public final class ClientOperation implements Operation, BaseStream { public final class ClientOperation implements Operation, BaseStream {
private static final String TAG = "ClientOperation";
private static final boolean V = ObexHelper.VDBG;
private ClientSession mParent; private ClientSession mParent;
private boolean mInputOpen; private boolean mInputOpen;
@ -75,6 +82,19 @@ public final class ClientOperation implements Operation, BaseStream {
private boolean mEndOfBodySent; 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 * Creates new OperationImpl to read and write data to a server
* @param maxSize the maximum packet size * @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 * Since we are not sending any headers or returning any headers then
* we just need to write and read the same bytes * 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) { if (mReplyHeader.responseCode != ResponseCodes.OBEX_HTTP_OK) {
throw new IOException("Invalid response code from server"); throw new IOException("Invalid response code from server");
@ -215,6 +235,7 @@ public final class ClientOperation implements Operation, BaseStream {
try { try {
return (String)mReplyHeader.getHeader(HeaderSet.TYPE); return (String)mReplyHeader.getHeader(HeaderSet.TYPE);
} catch (IOException e) { } catch (IOException e) {
if(V) Log.d(TAG, "Exception occured - returning null",e);
return null; return null;
} }
} }
@ -236,6 +257,7 @@ public final class ClientOperation implements Operation, BaseStream {
return temp.longValue(); return temp.longValue();
} }
} catch (IOException e) { } catch (IOException e) {
if(V) Log.d(TAG,"Exception occured - returning -1",e);
return -1; 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 * @param opCode the request code to send to the client
* @return <code>true</code> if there is more data to send; * @return <code>true</code> if there is more data to send;
* <code>false</code> if there is no 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 * length, but it is a waste of resources if we can't send much of
* the body. * 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 end = 0;
int start = 0; int start = 0;
// split & send the headerArray in multiple packets. // split & send the headerArray in multiple packets.
while (end != headerArray.length) { while (end != headerArray.length) {
//split the headerArray //split the headerArray
end = ObexHelper.findHeaderEnd(headerArray, start, mMaxPacketSize end = ObexHelper.findHeaderEnd(headerArray, start, mMaxPacketSize
- ObexHelper.BASE_PACKET_LENGTH); - ObexHelper.BASE_PACKET_LENGTH);
// can not split // can not split
@ -459,7 +486,7 @@ public final class ClientOperation implements Operation, BaseStream {
byte[] sendHeader = new byte[end - start]; byte[] sendHeader = new byte[end - start];
System.arraycopy(headerArray, start, sendHeader, 0, sendHeader.length); 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; return false;
} }
@ -470,12 +497,20 @@ public final class ClientOperation implements Operation, BaseStream {
start = end; start = end;
} }
// Enable SRM if it should be enabled
checkForSrm();
if (bodyLength > 0) { if (bodyLength > 0) {
return true; return true;
} else { } else {
return false; return false;
} }
} else { } 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); out.write(headerArray);
} }
@ -499,11 +534,11 @@ public final class ClientOperation implements Operation, BaseStream {
* (End of Body) otherwise, we need to send 0x48 (Body) * (End of Body) otherwise, we need to send 0x48 (Body)
*/ */
if ((mPrivateOutput.isClosed()) && (!returnValue) && (!mEndOfBodySent) if ((mPrivateOutput.isClosed()) && (!returnValue) && (!mEndOfBodySent)
&& ((opCode & 0x80) != 0)) { && ((opCode & ObexHelper.OBEX_OPCODE_FINAL_BIT_MASK) != 0)) {
out.write(0x49); out.write(HeaderSet.END_OF_BODY);
mEndOfBodySent = true; mEndOfBodySent = true;
} else { } else {
out.write(0x48); out.write(HeaderSet.BODY);
} }
bodyLength += 3; bodyLength += 3;
@ -517,12 +552,11 @@ public final class ClientOperation implements Operation, BaseStream {
if (mPrivateOutputOpen && bodyLength <= 0 && !mEndOfBodySent) { if (mPrivateOutputOpen && bodyLength <= 0 && !mEndOfBodySent) {
// only 0x82 or 0x83 can send 0x49 // only 0x82 or 0x83 can send 0x49
if ((opCode & 0x80) == 0) { if ((opCode & ObexHelper.OBEX_OPCODE_FINAL_BIT_MASK) == 0) {
out.write(0x48); out.write(HeaderSet.BODY);
} else { } else {
out.write(0x49); out.write(HeaderSet.END_OF_BODY);
mEndOfBodySent = true; mEndOfBodySent = true;
} }
bodyLength = 3; bodyLength = 3;
@ -531,15 +565,20 @@ public final class ClientOperation implements Operation, BaseStream {
} }
if (out.size() == 0) { if (out.size() == 0) {
if (!mParent.sendRequest(opCode, null, mReplyHeader, mPrivateInput)) { if (!mParent.sendRequest(opCode, null, mReplyHeader, mPrivateInput, mSrmActive)) {
return false; return false;
} }
// Enable SRM if it should be enabled
checkForSrm();
return returnValue; return returnValue;
} }
if ((out.size() > 0) if ((out.size() > 0)
&& (!mParent.sendRequest(opCode, out.toByteArray(), mReplyHeader, mPrivateInput))) { && (!mParent.sendRequest(opCode, out.toByteArray(),
mReplyHeader, mPrivateInput, mSrmActive))) {
return false; return false;
} }
// Enable SRM if it should be enabled
checkForSrm();
// send all of the output data in 0x48, // send all of the output data in 0x48,
// send 0x49 with empty body // send 0x49 with empty body
@ -549,6 +588,35 @@ public final class ClientOperation implements Operation, BaseStream {
return returnValue; 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 * This method starts the processing thread results. It will send the
* initial request. If the response takes more then one packet, a thread * 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 (mGetOperation) {
if (!mOperationDone) { if (!mOperationDone) {
if (!mGetFinalFlag) {
mReplyHeader.responseCode = ResponseCodes.OBEX_HTTP_CONTINUE; mReplyHeader.responseCode = ResponseCodes.OBEX_HTTP_CONTINUE;
while ((more) && (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE)) { while ((more) && (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE)) {
more = sendRequest(0x03); 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) { if (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE) {
mParent.sendRequest(0x83, null, mReplyHeader, mPrivateInput); mParent.sendRequest(ObexHelper.OBEX_OPCODE_GET_FINAL,
null, mReplyHeader, mPrivateInput, mSrmActive);
} }
if (mReplyHeader.responseCode != ResponseCodes.OBEX_HTTP_CONTINUE) { if (mReplyHeader.responseCode != ResponseCodes.OBEX_HTTP_CONTINUE) {
mOperationDone = true; mOperationDone = true;
}
} else { } else {
more = sendRequest(0x83); checkForSrm();
if (more) {
throw new IOException("FINAL_GET forced but data did not fit into single packet!");
}
mOperationDone = true;
} }
} }
} else { } else {
// PUT operation
if (!mOperationDone) { if (!mOperationDone) {
mReplyHeader.responseCode = ResponseCodes.OBEX_HTTP_CONTINUE; mReplyHeader.responseCode = ResponseCodes.OBEX_HTTP_CONTINUE;
while ((more) && (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) { 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) { 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) public synchronized boolean continueOperation(boolean sendEmpty, boolean inStream)
throws IOException { 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 (mGetOperation) {
if ((inStream) && (!mOperationDone)) { if ((inStream) && (!mOperationDone)) {
// to deal with inputstream in get operation // 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 * Determine if that was not the last packet in the operation
*/ */
if (mReplyHeader.responseCode != ResponseCodes.OBEX_HTTP_CONTINUE) { if (mReplyHeader.responseCode != ResponseCodes.OBEX_HTTP_CONTINUE) {
mOperationDone = true; mOperationDone = true;
} else {
checkForSrm();
} }
return true; return true;
@ -636,16 +705,7 @@ public final class ClientOperation implements Operation, BaseStream {
if (mPrivateInput == null) { if (mPrivateInput == null) {
mPrivateInput = new PrivateInputStream(this); mPrivateInput = new PrivateInputStream(this);
} }
sendRequest(ObexHelper.OBEX_OPCODE_GET);
if (!mGetFinalFlag) {
sendRequest(0x03);
} else {
sendRequest(0x83);
if (mReplyHeader.responseCode != ResponseCodes.OBEX_HTTP_CONTINUE) {
mOperationDone = true;
}
}
return true; return true;
} else if (mOperationDone) { } else if (mOperationDone) {
@ -653,12 +713,13 @@ public final class ClientOperation implements Operation, BaseStream {
} }
} else { } else {
// PUT operation
if ((!inStream) && (!mOperationDone)) { if ((!inStream) && (!mOperationDone)) {
// to deal with outputstream in put operation // to deal with outputstream in put operation
if (mReplyHeader.responseCode == -1) { if (mReplyHeader.responseCode == -1) {
mReplyHeader.responseCode = ResponseCodes.OBEX_HTTP_CONTINUE; mReplyHeader.responseCode = ResponseCodes.OBEX_HTTP_CONTINUE;
} }
sendRequest(0x02); sendRequest(ObexHelper.OBEX_OPCODE_PUT);
return true; return true;
} else if ((inStream) && (!mOperationDone)) { } else if ((inStream) && (!mOperationDone)) {
// How to deal with inputstream in put operation ? // 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)) { 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) { while (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE) {
sendRequest(0x82); sendRequest(ObexHelper.OBEX_OPCODE_PUT_FINAL);
} }
mOperationDone = true; mOperationDone = true;
} else if ((inStream) && (mOperationDone)) { } else if ((inStream) && (mOperationDone)) {
@ -724,12 +785,14 @@ public final class ClientOperation implements Operation, BaseStream {
} }
while (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE) { while (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE) {
if (!sendRequest(0x83)) { if (!sendRequest(ObexHelper.OBEX_OPCODE_GET_FINAL)) {
break; break;
} }
} }
while (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE) { 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; mOperationDone = true;
} else if ((!inStream) && (!mOperationDone)) { } else if ((!inStream) && (!mOperationDone)) {
@ -752,9 +815,9 @@ public final class ClientOperation implements Operation, BaseStream {
mReplyHeader.responseCode = ResponseCodes.OBEX_HTTP_CONTINUE; mReplyHeader.responseCode = ResponseCodes.OBEX_HTTP_CONTINUE;
while ((more) && (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); // parent.sendRequest(0x83, null, replyHeaders, privateInput);
if (mReplyHeader.responseCode != ResponseCodes.OBEX_HTTP_CONTINUE) { if (mReplyHeader.responseCode != ResponseCodes.OBEX_HTTP_CONTINUE) {
mOperationDone = true; mOperationDone = true;
@ -764,5 +827,6 @@ public final class ClientOperation implements Operation, BaseStream {
} }
public void noBodyHeader(){ 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. * Copyright (c) 2008-2009, Motorola, Inc.
* *
* All rights reserved. * All rights reserved.
@ -37,12 +39,16 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import android.util.Log;
/** /**
* This class in an implementation of the OBEX ClientSession. * This class in an implementation of the OBEX ClientSession.
* @hide * @hide
*/ */
public final class ClientSession extends ObexSession { public final class ClientSession extends ObexSession {
private static final String TAG = "ClientSession";
private boolean mOpen; private boolean mOpen;
// Determines if an OBEX layer connection has been established // Determines if an OBEX layer connection has been established
@ -51,10 +57,10 @@ public final class ClientSession extends ObexSession {
private byte[] mConnectionId = null; 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. * specification.
*/ */
private int maxPacketSize = 256; private int mMaxTxPacketSize = ObexHelper.LOWER_LIMIT_MAX_PACKET_SIZE;
private boolean mRequestActive; private boolean mRequestActive;
@ -62,11 +68,33 @@ public final class ClientSession extends ObexSession {
private final OutputStream mOutput; private final OutputStream mOutput;
private final boolean mLocalSrmSupported;
private final ObexTransport mTransport;
public ClientSession(final ObexTransport trans) throws IOException { public ClientSession(final ObexTransport trans) throws IOException {
mInput = trans.openInputStream(); mInput = trans.openInputStream();
mOutput = trans.openOutputStream(); mOutput = trans.openOutputStream();
mOpen = true; mOpen = true;
mRequestActive = false; 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 { public HeaderSet connect(final HeaderSet header) throws IOException {
@ -98,23 +126,25 @@ public final class ClientSession extends ObexSession {
* Byte 7 to n: headers * Byte 7 to n: headers
*/ */
byte[] requestPacket = new byte[totalLength]; byte[] requestPacket = new byte[totalLength];
int maxRxPacketSize = ObexHelper.getMaxRxPacketSize(mTransport);
// We just need to start at byte 3 since the sendRequest() method will // We just need to start at byte 3 since the sendRequest() method will
// handle the length and 0x80. // handle the length and 0x80.
requestPacket[0] = (byte)0x10; requestPacket[0] = (byte)0x10;
requestPacket[1] = (byte)0x00; requestPacket[1] = (byte)0x00;
requestPacket[2] = (byte)(ObexHelper.MAX_PACKET_SIZE_INT >> 8); requestPacket[2] = (byte)(maxRxPacketSize >> 8);
requestPacket[3] = (byte)(ObexHelper.MAX_PACKET_SIZE_INT & 0xFF); requestPacket[3] = (byte)(maxRxPacketSize & 0xFF);
if (head != null) { if (head != null) {
System.arraycopy(head, 0, requestPacket, 4, head.length); 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) { 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(); 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. * 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); 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); head = ObexHelper.createHeader(header, false);
if ((head.length + 3) > maxPacketSize) { if ((head.length + 3) > mMaxTxPacketSize) {
throw new IOException("Packet size exceeds max packet size"); throw new IOException("Packet size exceeds max packet size");
} }
} else { } else {
@ -215,7 +256,7 @@ public final class ClientSession extends ObexSession {
} }
HeaderSet returnHeaderSet = new HeaderSet(); 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: * 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); 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 { public void setAuthenticator(Authenticator auth) throws IOException {
@ -314,7 +364,7 @@ public final class ClientSession extends ObexSession {
head = ObexHelper.createHeader(headset, false); head = ObexHelper.createHeader(headset, false);
totalLength += head.length; totalLength += head.length;
if (totalLength > maxPacketSize) { if (totalLength > mMaxTxPacketSize) {
throw new IOException("Packet size exceeds max packet size"); throw new IOException("Packet size exceeds max packet size");
} }
@ -348,7 +398,7 @@ public final class ClientSession extends ObexSession {
} }
HeaderSet returnHeaderSet = new HeaderSet(); 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: * 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 head the headers to send to the client
* @param header the header object to update with the response * @param header the header object to update with the response
* @param privateInput the input stream used by the Operation object; null * @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>true</code> if the operation completed successfully;
* <code>false</code> if an authentication response failed to pass * <code>false</code> if an authentication response failed to pass
* @throws IOException if an IO error occurs * @throws IOException if an IO error occurs
*/ */
public boolean sendRequest(int opCode, byte[] head, HeaderSet header, 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 //check header length with local max size
if (head != null) { if (head != null) {
if ((head.length + 3) > ObexHelper.MAX_PACKET_SIZE_INT) { 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 "); 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; int bytesReceived;
ByteArrayOutputStream out = new ByteArrayOutputStream(); ByteArrayOutputStream out = new ByteArrayOutputStream();
out.write((byte)opCode); out.write((byte)opCode);
@ -428,15 +498,22 @@ public final class ClientSession extends ObexSession {
out.write(head); out.write(head);
} }
if (!skipSend) {
// Write the request to the output stream and flush the stream // Write the request to the output stream and flush the stream
mOutput.write(out.toByteArray()); 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(); mOutput.flush();
}
if (!skipReceive) {
header.responseCode = mInput.read(); header.responseCode = mInput.read();
int length = ((mInput.read() << 8) | (mInput.read())); int length = ((mInput.read() << 8) | (mInput.read()));
if (length > ObexHelper.MAX_PACKET_SIZE_INT) { if (length > ObexHelper.getMaxRxPacketSize(mTransport)) {
throw new IOException("Packet received exceeds packet size limit"); throw new IOException("Packet received exceeds packet size limit");
} }
if (length > ObexHelper.BASE_PACKET_LENGTH) { if (length > ObexHelper.BASE_PACKET_LENGTH) {
@ -446,11 +523,22 @@ public final class ClientSession extends ObexSession {
int version = mInput.read(); int version = mInput.read();
@SuppressWarnings("unused") @SuppressWarnings("unused")
int flags = mInput.read(); int flags = mInput.read();
maxPacketSize = (mInput.read() << 8) + mInput.read(); mMaxTxPacketSize = (mInput.read() << 8) + mInput.read();
//check with local max size //check with local max size
if (maxPacketSize > ObexHelper.MAX_CLIENT_PACKET_SIZE) { if (mMaxTxPacketSize > ObexHelper.MAX_CLIENT_PACKET_SIZE) {
maxPacketSize = 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) { if (length > 7) {
@ -507,7 +595,8 @@ public final class ClientSession extends ObexSession {
byte[] sendHeaders = new byte[out.size() - 3]; byte[] sendHeaders = new byte[out.size() - 3];
System.arraycopy(out.toByteArray(), 3, sendHeaders, 0, sendHeaders.length); 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(); mInput.close();
mOutput.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 * This class implements the javax.obex.HeaderSet interface for OBEX over
* RFCOMM. * RFCOMM or OBEX over l2cap.
* @hide * @hide
*/ */
public final class HeaderSet { public final class HeaderSet {
@ -178,6 +178,22 @@ public final class HeaderSet {
*/ */
public static final int OBJECT_CLASS = 0x4F; 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 Long mCount; // 4 byte unsigned integer
private String mName; // null terminated Unicode text string private String mName; // null terminated Unicode text string
@ -204,7 +220,7 @@ public final class HeaderSet {
private byte[] mObjectClass; // byte sequence 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 private byte[][] mSequenceUserDefined; // byte sequence user defined
@ -212,7 +228,12 @@ public final class HeaderSet {
private Long[] mIntegerUserDefined; // 4 byte unsigned integer 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; /*package*/ byte[] nonce;
@ -234,7 +255,6 @@ public final class HeaderSet {
mByteUserDefined = new Byte[16]; mByteUserDefined = new Byte[16];
mIntegerUserDefined = new Long[16]; mIntegerUserDefined = new Long[16];
responseCode = -1; responseCode = -1;
mRandom = new SecureRandom();
} }
/** /**
@ -393,6 +413,30 @@ public final class HeaderSet {
} }
} }
break; 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: default:
// Verify that it was not a Unicode String user Defined // Verify that it was not a Unicode String user Defined
if ((headerID >= 0x30) && (headerID <= 0x3F)) { if ((headerID >= 0x30) && (headerID <= 0x3F)) {
@ -493,6 +537,10 @@ public final class HeaderSet {
return mObjectClass; return mObjectClass;
case APPLICATION_PARAMETER: case APPLICATION_PARAMETER:
return mAppParam; return mAppParam;
case SINGLE_RESPONSE_MODE:
return mSingleResponseMode;
case SINGLE_RESPONSE_MODE_PARAMETER:
return mSrmParam;
default: default:
// Verify that it was not a Unicode String user Defined // Verify that it was not a Unicode String user Defined
if ((headerID >= 0x30) && (headerID <= 0x3F)) { if ((headerID >= 0x30) && (headerID <= 0x3F)) {
@ -564,6 +612,12 @@ public final class HeaderSet {
if (mObjectClass != null) { if (mObjectClass != null) {
out.write(OBJECT_CLASS); 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++) { for (int i = 0x30; i < 0x40; i++) {
if (mUnicodeUserDefined[i - 0x30] != null) { if (mUnicodeUserDefined[i - 0x30] != null) {
@ -625,6 +679,9 @@ public final class HeaderSet {
throws IOException { throws IOException {
nonce = new byte[16]; nonce = new byte[16];
if(mRandom == null) {
mRandom = new SecureRandom();
}
for (int i = 0; i < 16; i++) { for (int i = 0; i < 16; i++) {
nonce[i] = (byte)mRandom.nextInt(); 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. * Copyright (c) 2008-2009, Motorola, Inc.
* *
* All rights reserved. * All rights reserved.
@ -42,12 +43,16 @@ import java.util.Calendar;
import java.util.Date; import java.util.Date;
import java.util.TimeZone; import java.util.TimeZone;
import android.util.Log;
/** /**
* This class defines a set of helper methods for the implementation of Obex. * This class defines a set of helper methods for the implementation of Obex.
* @hide * @hide
*/ */
public final class ObexHelper { 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 * Defines the basic packet length used by OBEX. Every OBEX packet has the
* same basic format:<BR> * same basic format:<BR>
@ -65,18 +70,24 @@ public final class ObexHelper {
* should be the Max incoming MTU minus TODO: L2CAP package headers and * should be the Max incoming MTU minus TODO: L2CAP package headers and
* RFCOMM package headers. TODO: Retrieve the max incoming MTU from TODO: * RFCOMM package headers. TODO: Retrieve the max incoming MTU from TODO:
* LocalDevice.getProperty(). * LocalDevice.getProperty().
* NOTE: This value must be larger than or equal to the L2CAP SDU
*/ */
/* /*
* android note set as 0xFFFE to match remote MPS * android note set as 0xFFFE to match remote MPS
*/ */
public static final int MAX_PACKET_SIZE_INT = 0xFFFE; 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. * Temporary workaround to be able to push files to Windows 7.
* TODO: Should be removed as soon as Microsoft updates their driver. * TODO: Should be removed as soon as Microsoft updates their driver.
*/ */
public static final int MAX_CLIENT_PACKET_SIZE = 0xFC00; 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_CONNECT = 0x80;
public static final int OBEX_OPCODE_DISCONNECT = 0x81; 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 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 * Updates the HeaderSet with the headers received in the byte array
* provided. Invalid headers are ignored. * provided. Invalid headers are ignored.
@ -314,7 +331,7 @@ public final class ObexHelper {
} }
} catch (Exception e) { } catch (Exception e) {
// Not a valid header so ignore // 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; index += 4;
break; break;
@ -322,7 +339,7 @@ public final class ObexHelper {
} }
} catch (IOException e) { } catch (IOException e) {
throw new IOException("Header was not formatted properly"); throw new IOException("Header was not formatted properly", e);
} }
return body; 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) { } catch (IOException e) {
} finally { } finally {
result = out.toByteArray(); result = out.toByteArray();
@ -702,6 +746,8 @@ public final class ObexHelper {
int index = start; int index = start;
int length = 0; int length = 0;
// TODO: Ensure SRM and SRMP headers are not split into two OBEX packets
while ((fullLength < maxSize) && (index < headerArray.length)) { while ((fullLength < maxSize) && (index < headerArray.length)) {
int headerID = (headerArray[index] < 0 ? headerArray[index] + 256 : headerArray[index]); int headerID = (headerArray[index] < 0 ? headerArray[index] + 256 : headerArray[index]);
lastLength = fullLength; lastLength = fullLength;
@ -1008,4 +1054,39 @@ public final class ObexHelper {
return authChall; 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 java.io.IOException;
import android.util.Log;
/** /**
* The <code>ObexSession</code> interface characterizes the term * The <code>ObexSession</code> interface characterizes the term
* "OBEX Connection" as defined in the IrDA Object Exchange Protocol v1.2, which * "OBEX Connection" as defined in the IrDA Object Exchange Protocol v1.2, which
@ -47,6 +49,9 @@ import java.io.IOException;
*/ */
public class ObexSession { public class ObexSession {
private static final String TAG = "ObexSession";
private static final boolean V = ObexHelper.VDBG;
protected Authenticator mAuthenticator; protected Authenticator mAuthenticator;
protected byte[] mChallengeDigest; protected byte[] mChallengeDigest;
@ -125,6 +130,7 @@ public class ObexSession {
result = mAuthenticator result = mAuthenticator
.onAuthenticationChallenge(realm, isUserIDRequired, isFullAccess); .onAuthenticationChallenge(realm, isUserIDRequired, isFullAccess);
} catch (Exception e) { } catch (Exception e) {
if (V) Log.d(TAG, "Exception occured - returning false", e);
return false; return false;
} }

View File

@ -73,4 +73,39 @@ public interface ObexTransport {
DataOutputStream openDataOutputStream() throws IOException; 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. * Copyright (c) 2008-2009, Motorola, Inc.
* *
* All rights reserved. * All rights reserved.
@ -39,6 +40,8 @@ import java.io.OutputStream;
import java.io.DataOutputStream; import java.io.DataOutputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import android.util.Log;
/** /**
* This class implements the Operation interface for server side connections. * This class implements the Operation interface for server side connections.
* <P> * <P>
@ -54,6 +57,10 @@ import java.io.ByteArrayOutputStream;
*/ */
public final class ServerOperation implements Operation, BaseStream { 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 boolean isAborted;
public HeaderSet requestHeader; public HeaderSet requestHeader;
@ -78,6 +85,8 @@ public final class ServerOperation implements Operation, BaseStream {
private PrivateOutputStream mPrivateOutput; private PrivateOutputStream mPrivateOutput;
private ObexTransport mTransport;
private boolean mPrivateOutputOpen; private boolean mPrivateOutputOpen;
private String mExceptionString; private String mExceptionString;
@ -89,6 +98,19 @@ public final class ServerOperation implements Operation, BaseStream {
private boolean mHasBody; private boolean mHasBody;
private boolean mSendBodyHeader = true; 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 * Creates new ServerOperation
@ -116,12 +138,14 @@ public final class ServerOperation implements Operation, BaseStream {
mRequestFinished = false; mRequestFinished = false;
mPrivateOutputOpen = false; mPrivateOutputOpen = false;
mHasBody = false; mHasBody = false;
int bytesReceived; ObexPacket packet;
mTransport = p.getTransport();
/* /*
* Determine if this is a PUT request * 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. * It is a PUT request.
*/ */
@ -130,13 +154,14 @@ public final class ServerOperation implements Operation, BaseStream {
/* /*
* Determine if the final bit is set * Determine if the final bit is set
*/ */
if ((request & 0x80) == 0) { if ((request & ObexHelper.OBEX_OPCODE_FINAL_BIT_MASK) == 0) {
finalBitSet = false; finalBitSet = false;
} else { } else {
finalBitSet = true; finalBitSet = true;
mRequestFinished = 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. * 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 // For Get request, final bit set is decided by server side logic
finalBitSet = false; finalBitSet = false;
if (request == 0x83) { if (request == ObexHelper.OBEX_OPCODE_GET_FINAL) {
mRequestFinished = true; mRequestFinished = true;
} }
} else { } else {
throw new IOException("ServerOperation can not handle such request"); throw new IOException("ServerOperation can not handle such request");
} }
int length = in.read(); packet = ObexPacket.read(request, mInput);
length = (length << 8) + in.read();
/* /*
* Determine if the packet length is larger than this device can receive * 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); 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 * Determine if any headers were sent in the initial request
*/ */
if (length > 3) { if (packet.mLength > 3) {
byte[] data = new byte[length - 3]; if(!handleObexPacket(packet)) {
bytesReceived = in.read(data);
while (bytesReceived != data.length) {
bytesReceived += in.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));
} 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; return;
} }
} if (!mHasBody) {
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 {
while ((!mGetOperation) && (!finalBitSet)) { while ((!mGetOperation) && (!finalBitSet)) {
sendReply(ResponseCodes.OBEX_HTTP_CONTINUE); sendReply(ResponseCodes.OBEX_HTTP_CONTINUE);
if (mPrivateInput.available() > 0) { 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() { public boolean isValidBody() {
return mHasBody; 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 * 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 * @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; * @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 * <code>false</code> if no reply was received because the operation
* ended, an abort was received, or the final bit was set in the * ended, an abort was received, the final bit was set in the
* reply * reply or SRM is active.
* @throws IOException if an IO error occurs * @throws IOException if an IO error occurs
*/ */
public synchronized boolean sendReply(int type) throws IOException { public synchronized boolean sendReply(int type) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream(); ByteArrayOutputStream out = new ByteArrayOutputStream();
int bytesReceived; boolean skipSend = false;
boolean skipReceive = false;
boolean srmRespSendPending = false;
long id = mListener.getConnectionId(); long id = mListener.getConnectionId();
if (id == -1) { if (id == -1) {
@ -293,7 +375,19 @@ public final class ServerOperation implements Operation, BaseStream {
replyHeader.mConnectionID = ObexHelper.convertToByteArray(id); 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 bodyLength = -1;
int orginalBodyLength = -1; int orginalBodyLength = -1;
@ -347,6 +441,28 @@ public final class ServerOperation implements Operation, BaseStream {
finalBitSet = true; 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 ((finalBitSet) || (headerArray.length < (mMaxPacketLength - 20))) {
if (bodyLength > 0) { if (bodyLength > 0) {
/* /*
@ -387,7 +503,7 @@ public final class ServerOperation implements Operation, BaseStream {
} }
if ((finalBitSet) && (type == ResponseCodes.OBEX_HTTP_OK) && (orginalBodyLength <= 0)) { if ((finalBitSet) && (type == ResponseCodes.OBEX_HTTP_OK) && (orginalBodyLength <= 0)) {
if(mSendBodyHeader == true) { if(mSendBodyHeader) {
out.write(0x49); out.write(0x49);
orginalBodyLength = 3; orginalBodyLength = 3;
out.write((byte)(orginalBodyLength >> 8)); out.write((byte)(orginalBodyLength >> 8));
@ -395,39 +511,35 @@ public final class ServerOperation implements Operation, BaseStream {
} }
} }
if(skipSend == false) {
mResponseSize = 3; mResponseSize = 3;
mParent.sendResponse(type, out.toByteArray()); mParent.sendResponse(type, out.toByteArray());
}
if (type == ResponseCodes.OBEX_HTTP_CONTINUE) { 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) { if(mGetOperation && skipReceive) {
byte[] temp = new byte[length - 3]; // Here we need to check for and handle abort (throw an exception).
// First three bytes already read, compensating for this // Any other signal received should be discarded silently (only on server side)
bytesReceived = mInput.read(temp); checkSrmRemoteAbort();
} else {
// Receive and handle data (only send reply if !skipSend)
// Read a complete OBEX Packet
ObexPacket packet = ObexPacket.read(mInput);
while (bytesReceived != temp.length) { int headerId = packet.mHeaderId;
bytesReceived += mInput.read(temp, bytesReceived, if ((headerId != ObexHelper.OBEX_OPCODE_PUT)
temp.length - bytesReceived); && (headerId != ObexHelper.OBEX_OPCODE_PUT_FINAL)
} && (headerId != ObexHelper.OBEX_OPCODE_GET)
} && (headerId != ObexHelper.OBEX_OPCODE_GET_FINAL)) {
/* /*
* Determine if an ABORT was sent as the reply * Determine if an ABORT was sent as the reply
*/ */
if (headerID == ObexHelper.OBEX_OPCODE_ABORT) { if (headerId == ObexHelper.OBEX_OPCODE_ABORT) {
mParent.sendResponse(ResponseCodes.OBEX_HTTP_OK, null); handleRemoteAbort();
mClosed = true;
isAborted = true;
mExceptionString = "Abort Received";
throw new IOException("Abort Received");
} else { } else {
// TODO:shall we send this if it occurs during SRM? Errata on the subject
mParent.sendResponse(ResponseCodes.OBEX_HTTP_BAD_REQUEST, null); mParent.sendResponse(ResponseCodes.OBEX_HTTP_BAD_REQUEST, null);
mClosed = true; mClosed = true;
mExceptionString = "Bad Request Received"; mExceptionString = "Bad Request Received";
@ -435,16 +547,16 @@ public final class ServerOperation implements Operation, BaseStream {
} }
} else { } else {
if ((headerID == ObexHelper.OBEX_OPCODE_PUT_FINAL)) { if ((headerId == ObexHelper.OBEX_OPCODE_PUT_FINAL)) {
finalBitSet = true; finalBitSet = true;
} else if (headerID == ObexHelper.OBEX_OPCODE_GET_FINAL) { } else if (headerId == ObexHelper.OBEX_OPCODE_GET_FINAL) {
mRequestFinished = true; mRequestFinished = true;
} }
/* /*
* Determine if the packet length is larger then this device can receive * Determine if the packet length is larger than the negotiated packet size
*/ */
if (length > ObexHelper.MAX_PACKET_SIZE_INT) { if (packet.mLength > ObexHelper.getMaxRxPacketSize(mTransport)) {
mParent.sendResponse(ResponseCodes.OBEX_HTTP_REQ_TOO_LARGE, null); 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");
} }
@ -452,50 +564,13 @@ public final class ServerOperation implements Operation, BaseStream {
/* /*
* Determine if any headers were sent in the initial request * Determine if any headers were sent in the initial request
*/ */
if (length > 3) { if (packet.mLength > 3 || (mSrmEnabled && packet.mLength == 3)) {
byte[] data = new byte[length - 3]; if(handleObexPacket(packet) == false) {
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));
} 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; 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; return true;
} else { } 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 * Sends an ABORT message to the server. By calling this method, the
* corresponding input and output streams will be closed along with this * corresponding input and output streams will be closed along with this

View File

@ -275,4 +275,13 @@ public class ServerRequestHandler {
*/ */
public void onClose() { 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. * Copyright (c) 2008-2009, Motorola, Inc.
* *
* All rights reserved. * All rights reserved.
@ -45,6 +47,7 @@ import java.io.OutputStream;
public final class ServerSession extends ObexSession implements Runnable { public final class ServerSession extends ObexSession implements Runnable {
private static final String TAG = "Obex ServerSession"; private static final String TAG = "Obex ServerSession";
private static final boolean V = ObexHelper.VDBG;
private ObexTransport mTransport; private ObexTransport mTransport;
@ -91,7 +94,9 @@ public final class ServerSession extends ObexSession implements Runnable {
boolean done = false; boolean done = false;
while (!done && !mClosed) { while (!done && !mClosed) {
if(V) Log.v(TAG, "Waiting for incoming request...");
int requestType = mInput.read(); int requestType = mInput.read();
if(V) Log.v(TAG, "Read request: " + requestType);
switch (requestType) { switch (requestType) {
case ObexHelper.OBEX_OPCODE_CONNECT: case ObexHelper.OBEX_OPCODE_CONNECT:
handleConnectRequest(); handleConnectRequest();
@ -140,9 +145,9 @@ public final class ServerSession extends ObexSession implements Runnable {
} }
} catch (NullPointerException e) { } catch (NullPointerException e) {
Log.d(TAG, e.toString()); Log.d(TAG, "Exception occured - ignoring", e);
} catch (Exception e) { } catch (Exception e) {
Log.d(TAG, e.toString()); Log.d(TAG, "Exception occured - ignoring", e);
} }
close(); close();
} }
@ -163,7 +168,7 @@ public final class ServerSession extends ObexSession implements Runnable {
int length = mInput.read(); int length = mInput.read();
length = (length << 8) + 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; code = ResponseCodes.OBEX_HTTP_REQ_TOO_LARGE;
} else { } else {
for (int i = 3; i < length; i++) { 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 *internal error should not be sent because server has already replied with
*OK response in "sendReply") *OK response in "sendReply")
*/ */
if(V) Log.d(TAG,"Exception occured - sending OBEX_HTTP_INTERNAL_ERROR reply",e);
if (!op.isAborted) { if (!op.isAborted) {
sendResponse(ResponseCodes.OBEX_HTTP_INTERNAL_ERROR, null); sendResponse(ResponseCodes.OBEX_HTTP_INTERNAL_ERROR, null);
} }
@ -243,6 +249,7 @@ public final class ServerSession extends ObexSession implements Runnable {
op.sendReply(response); op.sendReply(response);
} }
} catch (Exception e) { } catch (Exception e) {
if(V) Log.d(TAG,"Exception occured - sending OBEX_HTTP_INTERNAL_ERROR reply",e);
sendResponse(ResponseCodes.OBEX_HTTP_INTERNAL_ERROR, null); sendResponse(ResponseCodes.OBEX_HTTP_INTERNAL_ERROR, null);
} }
} }
@ -275,7 +282,7 @@ public final class ServerSession extends ObexSession implements Runnable {
data[2] = (byte)totalLength; data[2] = (byte)totalLength;
} }
op.write(data); 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(); flags = mInput.read();
constants = mInput.read(); constants = mInput.read();
if (length > ObexHelper.MAX_PACKET_SIZE_INT) { if (length > ObexHelper.getMaxRxPacketSize(mTransport)) {
code = ResponseCodes.OBEX_HTTP_REQ_TOO_LARGE; code = ResponseCodes.OBEX_HTTP_REQ_TOO_LARGE;
totalLength = 3; totalLength = 3;
} else { } else {
@ -358,6 +365,7 @@ public final class ServerSession extends ObexSession implements Runnable {
try { try {
code = mListener.onSetPath(request, reply, backup, create); code = mListener.onSetPath(request, reply, backup, create);
} catch (Exception e) { } catch (Exception e) {
if(V) Log.d(TAG,"Exception occured - sending OBEX_HTTP_INTERNAL_ERROR reply",e);
sendResponse(ResponseCodes.OBEX_HTTP_INTERNAL_ERROR, null); sendResponse(ResponseCodes.OBEX_HTTP_INTERNAL_ERROR, null);
return; return;
} }
@ -425,7 +433,7 @@ public final class ServerSession extends ObexSession implements Runnable {
length = mInput.read(); length = mInput.read();
length = (length << 8) + 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; code = ResponseCodes.OBEX_HTTP_REQ_TOO_LARGE;
totalLength = 3; totalLength = 3;
} else { } else {
@ -466,6 +474,7 @@ public final class ServerSession extends ObexSession implements Runnable {
try { try {
mListener.onDisconnect(request, reply); mListener.onDisconnect(request, reply);
} catch (Exception e) { } catch (Exception e) {
if(V) Log.d(TAG,"Exception occured - sending OBEX_HTTP_INTERNAL_ERROR reply",e);
sendResponse(ResponseCodes.OBEX_HTTP_INTERNAL_ERROR, null); sendResponse(ResponseCodes.OBEX_HTTP_INTERNAL_ERROR, null);
return; return;
} }
@ -531,23 +540,38 @@ public final class ServerSession extends ObexSession implements Runnable {
HeaderSet reply = new HeaderSet(); HeaderSet reply = new HeaderSet();
int bytesReceived; int bytesReceived;
if(V) Log.v(TAG,"handleConnectRequest()");
/* /*
* Read in the length of the OBEX packet, OBEX version, flags, and max * Read in the length of the OBEX packet, OBEX version, flags, and max
* packet length * packet length
*/ */
packetLength = mInput.read(); packetLength = mInput.read();
packetLength = (packetLength << 8) + mInput.read(); packetLength = (packetLength << 8) + mInput.read();
if(V) Log.v(TAG,"handleConnectRequest() - packetLength: " + packetLength);
version = mInput.read(); version = mInput.read();
flags = mInput.read(); flags = mInput.read();
mMaxPacketLength = mInput.read(); mMaxPacketLength = mInput.read();
mMaxPacketLength = (mMaxPacketLength << 8) + mInput.read(); mMaxPacketLength = (mMaxPacketLength << 8) + mInput.read();
if(V) Log.v(TAG,"handleConnectRequest() - version: " + version
+ " MaxLength: " + mMaxPacketLength + " flags: " + flags);
// should we check it? // should we check it?
if (mMaxPacketLength > ObexHelper.MAX_PACKET_SIZE_INT) { if (mMaxPacketLength > ObexHelper.MAX_PACKET_SIZE_INT) {
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; code = ResponseCodes.OBEX_HTTP_REQ_TOO_LARGE;
totalLength = 7; totalLength = 7;
} else { } else {
@ -614,7 +638,7 @@ public final class ServerSession extends ObexSession implements Runnable {
code = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; code = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
} }
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); if(V) Log.d(TAG,"Exception occured - sending OBEX_HTTP_INTERNAL_ERROR reply",e);
totalLength = 7; totalLength = 7;
head = null; head = null;
code = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 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 * Packet Length (Defined in MAX_PACKET_SIZE) Byte 7 to n: headers
*/ */
byte[] sendData = new byte[totalLength]; byte[] sendData = new byte[totalLength];
int maxRxLength = ObexHelper.getMaxRxPacketSize(mTransport);
sendData[0] = (byte)code; sendData[0] = (byte)code;
sendData[1] = length[2]; sendData[1] = length[2];
sendData[2] = length[3]; sendData[2] = length[3];
sendData[3] = (byte)0x10; sendData[3] = (byte)0x10;
sendData[4] = (byte)0x00; sendData[4] = (byte)0x00;
sendData[5] = (byte)(ObexHelper.MAX_PACKET_SIZE_INT >> 8); sendData[5] = (byte)(maxRxLength >> 8);
sendData[6] = (byte)(ObexHelper.MAX_PACKET_SIZE_INT & 0xFF); sendData[6] = (byte)(maxRxLength & 0xFF);
if (head != null) { if (head != null) {
System.arraycopy(head, 0, sendData, 7, head.length); System.arraycopy(head, 0, sendData, 7, head.length);
@ -659,11 +684,16 @@ public final class ServerSession extends ObexSession implements Runnable {
mListener.onClose(); mListener.onClose();
} }
try { try {
mInput.close(); /* Set state to closed before interrupting the thread by closing the streams */
mOutput.close();
mTransport.close();
mClosed = true; mClosed = true;
if(mInput != null)
mInput.close();
if(mOutput != null)
mOutput.close();
if(mTransport != null)
mTransport.close();
} catch (Exception e) { } catch (Exception e) {
if(V) Log.d(TAG,"Exception occured during close() - ignore",e);
} }
mTransport = null; mTransport = null;
mInput = null; mInput = null;
@ -702,4 +732,7 @@ public final class ServerSession extends ObexSession implements Runnable {
return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
} }
public ObexTransport getTransport() {
return mTransport;
}
} }