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:
committed by
Andre Eisenbach
parent
31a94f48bf
commit
238e0f934f
@ -6505,10 +6505,16 @@ package android.bluetooth {
|
||||
public final class BluetoothSocket implements java.io.Closeable {
|
||||
method public void close() throws java.io.IOException;
|
||||
method public void connect() throws java.io.IOException;
|
||||
method public int getConnectionType();
|
||||
method public java.io.InputStream getInputStream() throws java.io.IOException;
|
||||
method public int getMaxReceivePacketSize();
|
||||
method public int getMaxTransmitPacketSize();
|
||||
method public java.io.OutputStream getOutputStream() throws java.io.IOException;
|
||||
method public android.bluetooth.BluetoothDevice getRemoteDevice();
|
||||
method public boolean isConnected();
|
||||
field public static final int TYPE_L2CAP = 3; // 0x3
|
||||
field public static final int TYPE_RFCOMM = 1; // 0x1
|
||||
field public static final int TYPE_SCO = 2; // 0x2
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -6721,10 +6721,16 @@ package android.bluetooth {
|
||||
public final class BluetoothSocket implements java.io.Closeable {
|
||||
method public void close() throws java.io.IOException;
|
||||
method public void connect() throws java.io.IOException;
|
||||
method public int getConnectionType();
|
||||
method public java.io.InputStream getInputStream() throws java.io.IOException;
|
||||
method public int getMaxReceivePacketSize();
|
||||
method public int getMaxTransmitPacketSize();
|
||||
method public java.io.OutputStream getOutputStream() throws java.io.IOException;
|
||||
method public android.bluetooth.BluetoothDevice getRemoteDevice();
|
||||
method public boolean isConnected();
|
||||
field public static final int TYPE_L2CAP = 3; // 0x3
|
||||
field public static final int TYPE_RFCOMM = 1; // 0x1
|
||||
field public static final int TYPE_SCO = 2; // 0x2
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2009-2014 The Android Open Source Project
|
||||
* Copyright (C) 2009-2015 The Android Open Source Project
|
||||
* Copyright (C) 2015 Samsung LSI
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -377,6 +378,18 @@ public final class BluetoothAdapter {
|
||||
/** @hide */
|
||||
public static final String BLUETOOTH_MANAGER_SERVICE = "bluetooth_manager";
|
||||
|
||||
|
||||
/** When creating a ServerSocket using listenUsingRfcommOn() or
|
||||
* listenUsingL2capOn() use SOCKET_CHANNEL_AUTO_STATIC to create
|
||||
* a ServerSocket that auto assigns a channel number to the first
|
||||
* bluetooth socket.
|
||||
* The channel number assigned to this first Bluetooth Socket will
|
||||
* be stored in the ServerSocket, and reused for subsequent Bluetooth
|
||||
* sockets.
|
||||
* @hide */
|
||||
public static final int SOCKET_CHANNEL_AUTO_STATIC_NO_SDP = -2;
|
||||
|
||||
|
||||
private static final int ADDRESS_LENGTH = 17;
|
||||
|
||||
private static final int CONTROLLER_ENERGY_UPDATE_TIMEOUT_MILLIS = 30;
|
||||
@ -1144,6 +1157,9 @@ public final class BluetoothAdapter {
|
||||
BluetoothServerSocket socket = new BluetoothServerSocket(
|
||||
BluetoothSocket.TYPE_RFCOMM, true, true, channel);
|
||||
int errno = socket.mSocket.bindListen();
|
||||
if(channel == SOCKET_CHANNEL_AUTO_STATIC_NO_SDP) {
|
||||
socket.setChannel(socket.mSocket.getPort());
|
||||
}
|
||||
if (errno != 0) {
|
||||
//TODO(BT): Throw the same exception error code
|
||||
// that the previous code was using.
|
||||
@ -1278,6 +1294,9 @@ public final class BluetoothAdapter {
|
||||
BluetoothServerSocket socket = new BluetoothServerSocket(
|
||||
BluetoothSocket.TYPE_RFCOMM, false, false, port);
|
||||
int errno = socket.mSocket.bindListen();
|
||||
if(port == SOCKET_CHANNEL_AUTO_STATIC_NO_SDP) {
|
||||
socket.setChannel(socket.mSocket.getPort());
|
||||
}
|
||||
if (errno != 0) {
|
||||
//TODO(BT): Throw the same exception error code
|
||||
// that the previous code was using.
|
||||
@ -1300,6 +1319,9 @@ public final class BluetoothAdapter {
|
||||
BluetoothServerSocket socket = new BluetoothServerSocket(
|
||||
BluetoothSocket.TYPE_RFCOMM, false, true, port);
|
||||
int errno = socket.mSocket.bindListen();
|
||||
if(port == SOCKET_CHANNEL_AUTO_STATIC_NO_SDP) {
|
||||
socket.setChannel(socket.mSocket.getPort());
|
||||
}
|
||||
if (errno < 0) {
|
||||
//TODO(BT): Throw the same exception error code
|
||||
// that the previous code was using.
|
||||
@ -1329,6 +1351,30 @@ public final class BluetoothAdapter {
|
||||
return socket;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an encrypted, authenticated, L2CAP server socket.
|
||||
* Call #accept to retrieve connections to this socket.
|
||||
* @return An L2CAP BluetoothServerSocket
|
||||
* @throws IOException On error, for example Bluetooth not available, or
|
||||
* insufficient permissions.
|
||||
* @hide
|
||||
*/
|
||||
public BluetoothServerSocket listenUsingL2capOn(int port) throws IOException {
|
||||
BluetoothServerSocket socket = new BluetoothServerSocket(
|
||||
BluetoothSocket.TYPE_L2CAP, true, true, port);
|
||||
int errno = socket.mSocket.bindListen();
|
||||
if(port == SOCKET_CHANNEL_AUTO_STATIC_NO_SDP) {
|
||||
socket.setChannel(socket.mSocket.getPort());
|
||||
}
|
||||
if (errno != 0) {
|
||||
//TODO(BT): Throw the same exception error code
|
||||
// that the previous code was using.
|
||||
//socket.mSocket.throwErrnoNative(errno);
|
||||
throw new IOException("Error: " + errno);
|
||||
}
|
||||
return socket;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the local Out of Band Pairing Data
|
||||
* <p>Requires {@link android.Manifest.permission#BLUETOOTH}
|
||||
|
@ -302,6 +302,12 @@ public final class BluetoothDevice implements Parcelable {
|
||||
*/
|
||||
public static final int DEVICE_TYPE_DUAL = 3;
|
||||
|
||||
|
||||
/** @hide */
|
||||
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
|
||||
public static final String ACTION_SDP_RECORD =
|
||||
"android.bluetooth.device.action.SDP_RECORD";
|
||||
|
||||
/**
|
||||
* Broadcast Action: This intent is used to broadcast the {@link UUID}
|
||||
* wrapped as a {@link android.os.ParcelUuid} of the remote device after it
|
||||
@ -526,6 +532,13 @@ public final class BluetoothDevice implements Parcelable {
|
||||
*/
|
||||
public static final String EXTRA_UUID = "android.bluetooth.device.extra.UUID";
|
||||
|
||||
/** @hide */
|
||||
public static final String EXTRA_SDP_RECORD =
|
||||
"android.bluetooth.device.extra.SDP_RECORD";
|
||||
|
||||
/** @hide */
|
||||
public static final String EXTRA_SDP_SEARCH_STATUS =
|
||||
"android.bluetooth.device.extra.SDP_SEARCH_STATUS";
|
||||
/**
|
||||
* For {@link #getPhonebookAccessPermission}, {@link #setPhonebookAccessPermission},
|
||||
* {@link #getMessageAccessPermission} and {@link #setMessageAccessPermission}.
|
||||
@ -1054,14 +1067,34 @@ public final class BluetoothDevice implements Parcelable {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a service discovery on the remote device to get the SDP records associated
|
||||
* with the specified UUID.
|
||||
*
|
||||
* <p>This API is asynchronous and {@link #ACTION_SDP_RECORD} intent is sent,
|
||||
* with the SDP records found on the remote end. If there is an error
|
||||
* in getting the SDP records or if the process takes a long time,
|
||||
* {@link #ACTION_SDP_RECORD} intent is sent with an status value in
|
||||
* {@link #EXTRA_SDP_SEARCH_STATUS} different from 0.
|
||||
* Detailed status error codes can be found by members of the Bluetooth package in
|
||||
* the AbstractionLayer class.
|
||||
* <p>Requires {@link android.Manifest.permission#BLUETOOTH}.
|
||||
* The SDP record data will be stored in the intent as {@link #EXTRA_SDP_RECORD}.
|
||||
* The object type will match one of the SdpXxxRecord types, depending on the UUID searched
|
||||
* for.
|
||||
*
|
||||
* @return False if the sanity check fails, True if the process
|
||||
* of initiating an ACL connection to the remote device
|
||||
* was started.
|
||||
*/
|
||||
/** @hide */
|
||||
public boolean fetchMasInstances() {
|
||||
public boolean sdpSearch(ParcelUuid uuid) {
|
||||
if (sService == null) {
|
||||
Log.e(TAG, "BT not enabled. Cannot query remote device for MAS instances");
|
||||
Log.e(TAG, "BT not enabled. Cannot query remote device sdp records");
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
return sService.fetchRemoteMasInstances(this);
|
||||
return sService.sdpSearch(this,uuid);
|
||||
} catch (RemoteException e) {Log.e(TAG, "", e);}
|
||||
return false;
|
||||
}
|
||||
@ -1260,6 +1293,36 @@ public final class BluetoothDevice implements Parcelable {
|
||||
null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an L2cap {@link BluetoothSocket} ready to start a secure
|
||||
* outgoing connection to this remote device on given channel.
|
||||
* <p>The remote device will be authenticated and communication on this
|
||||
* socket will be encrypted.
|
||||
* <p> Use this socket only if an authenticated socket link is possible.
|
||||
* Authentication refers to the authentication of the link key to
|
||||
* prevent man-in-the-middle type of attacks.
|
||||
* For example, for Bluetooth 2.1 devices, if any of the devices does not
|
||||
* have an input and output capability or just has the ability to
|
||||
* display a numeric key, a secure socket connection is not possible.
|
||||
* In such a case, use {#link createInsecureRfcommSocket}.
|
||||
* For more details, refer to the Security Model section 5.2 (vol 3) of
|
||||
* Bluetooth Core Specification version 2.1 + EDR.
|
||||
* <p>Use {@link BluetoothSocket#connect} to initiate the outgoing
|
||||
* connection.
|
||||
* <p>Valid L2CAP PSM channels are in range 1 to 2^16.
|
||||
* <p>Requires {@link android.Manifest.permission#BLUETOOTH}
|
||||
*
|
||||
* @param channel L2cap PSM/channel to connect to
|
||||
* @return a RFCOMM BluetoothServerSocket ready for an outgoing connection
|
||||
* @throws IOException on error, for example Bluetooth not available, or
|
||||
* insufficient permissions
|
||||
* @hide
|
||||
*/
|
||||
public BluetoothSocket createL2capSocket(int channel) throws IOException {
|
||||
return new BluetoothSocket(BluetoothSocket.TYPE_L2CAP, -1, true, true, this, channel,
|
||||
null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an RFCOMM {@link BluetoothSocket} ready to start a secure
|
||||
* outgoing connection to this remote device using SDP lookup of uuid.
|
||||
|
@ -18,6 +18,7 @@ package android.bluetooth;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.os.ParcelUuid;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
@ -66,10 +67,11 @@ import java.io.IOException;
|
||||
*/
|
||||
public final class BluetoothServerSocket implements Closeable {
|
||||
|
||||
private static final String TAG = "BluetoothServerSocket";
|
||||
/*package*/ final BluetoothSocket mSocket;
|
||||
private Handler mHandler;
|
||||
private int mMessage;
|
||||
private final int mChannel;
|
||||
private int mChannel;
|
||||
|
||||
/**
|
||||
* Construct a socket for incoming connections.
|
||||
@ -84,6 +86,9 @@ public final class BluetoothServerSocket implements Closeable {
|
||||
throws IOException {
|
||||
mChannel = port;
|
||||
mSocket = new BluetoothSocket(type, -1, auth, encrypt, null, port, null);
|
||||
if(port == BluetoothAdapter.SOCKET_CHANNEL_AUTO_STATIC_NO_SDP) {
|
||||
mSocket.setExcludeSdp(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -98,6 +103,7 @@ public final class BluetoothServerSocket implements Closeable {
|
||||
/*package*/ BluetoothServerSocket(int type, boolean auth, boolean encrypt, ParcelUuid uuid)
|
||||
throws IOException {
|
||||
mSocket = new BluetoothSocket(type, -1, auth, encrypt, null, -1, uuid);
|
||||
// TODO: This is the same as mChannel = -1 - is this intentional?
|
||||
mChannel = mSocket.getPort();
|
||||
}
|
||||
|
||||
@ -153,6 +159,7 @@ public final class BluetoothServerSocket implements Closeable {
|
||||
/*package*/ void setServiceName(String ServiceName) {
|
||||
mSocket.setServiceName(ServiceName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the channel on which this socket is bound.
|
||||
* @hide
|
||||
@ -160,4 +167,47 @@ public final class BluetoothServerSocket implements Closeable {
|
||||
public int getChannel() {
|
||||
return mChannel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the channel on which future sockets are bound.
|
||||
* Currently used only when a channel is auto generated.
|
||||
*/
|
||||
/*package*/ void setChannel(int newChannel) {
|
||||
/* TODO: From a design/architecture perspective this is wrong.
|
||||
* The bind operation should be conducted through this class
|
||||
* and the resulting port should be kept in mChannel, and
|
||||
* not set from BluetoothAdapter. */
|
||||
if(mSocket != null) {
|
||||
if(mSocket.getPort() != newChannel) {
|
||||
Log.w(TAG,"The port set is different that the underlying port. mSocket.getPort(): "
|
||||
+ mSocket.getPort() + " requested newChannel: " + newChannel);
|
||||
}
|
||||
}
|
||||
mChannel = newChannel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("ServerSocket: Type: ");
|
||||
switch(mSocket.getConnectionType()) {
|
||||
case BluetoothSocket.TYPE_RFCOMM:
|
||||
{
|
||||
sb.append("TYPE_RFCOMM");
|
||||
break;
|
||||
}
|
||||
case BluetoothSocket.TYPE_L2CAP:
|
||||
{
|
||||
sb.append("TYPE_L2CAP");
|
||||
break;
|
||||
}
|
||||
case BluetoothSocket.TYPE_SCO:
|
||||
{
|
||||
sb.append("TYPE_SCO");
|
||||
break;
|
||||
}
|
||||
}
|
||||
sb.append(" Channel: ").append(mChannel);
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ import android.os.ParcelFileDescriptor;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.Closeable;
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.IOException;
|
||||
@ -29,6 +30,8 @@ import java.io.OutputStream;
|
||||
import java.util.Locale;
|
||||
import java.util.UUID;
|
||||
import android.net.LocalSocket;
|
||||
|
||||
import java.nio.Buffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.ByteBuffer;
|
||||
/**
|
||||
@ -86,17 +89,19 @@ public final class BluetoothSocket implements Closeable {
|
||||
|
||||
/** @hide */
|
||||
public static final int MAX_RFCOMM_CHANNEL = 30;
|
||||
/*package*/ static final int MAX_L2CAP_PACKAGE_SIZE = 0xFFFF;
|
||||
|
||||
/** Keep TYPE_ fields in sync with BluetoothSocket.cpp */
|
||||
/*package*/ static final int TYPE_RFCOMM = 1;
|
||||
/*package*/ static final int TYPE_SCO = 2;
|
||||
/*package*/ static final int TYPE_L2CAP = 3;
|
||||
public static final int TYPE_RFCOMM = 1;
|
||||
public static final int TYPE_SCO = 2;
|
||||
public static final int TYPE_L2CAP = 3;
|
||||
|
||||
/*package*/ static final int EBADFD = 77;
|
||||
/*package*/ static final int EADDRINUSE = 98;
|
||||
|
||||
/*package*/ static final int SEC_FLAG_ENCRYPT = 1;
|
||||
/*package*/ static final int SEC_FLAG_AUTH = 1 << 1;
|
||||
/*package*/ static final int BTSOCK_FLAG_NO_SDP = 1 << 2;
|
||||
|
||||
private final int mType; /* one of TYPE_RFCOMM etc */
|
||||
private BluetoothDevice mDevice; /* remote device */
|
||||
@ -106,6 +111,7 @@ public final class BluetoothSocket implements Closeable {
|
||||
private final BluetoothInputStream mInputStream;
|
||||
private final BluetoothOutputStream mOutputStream;
|
||||
private final ParcelUuid mUuid;
|
||||
private boolean mExcludeSdp = false;
|
||||
private ParcelFileDescriptor mPfd;
|
||||
private LocalSocket mSocket;
|
||||
private InputStream mSocketIS;
|
||||
@ -115,7 +121,11 @@ public final class BluetoothSocket implements Closeable {
|
||||
private String mServiceName;
|
||||
private static int PROXY_CONNECTION_TIMEOUT = 5000;
|
||||
|
||||
private static int SOCK_SIGNAL_SIZE = 16;
|
||||
private static int SOCK_SIGNAL_SIZE = 20;
|
||||
|
||||
private ByteBuffer mL2capBuffer = null;
|
||||
private int mMaxTxPacketSize = 0; // The l2cap maximum packet size supported by the peer.
|
||||
private int mMaxRxPacketSize = 0; // The l2cap maximum packet size that can be received.
|
||||
|
||||
private enum SocketState {
|
||||
INIT,
|
||||
@ -144,12 +154,14 @@ public final class BluetoothSocket implements Closeable {
|
||||
*/
|
||||
/*package*/ BluetoothSocket(int type, int fd, boolean auth, boolean encrypt,
|
||||
BluetoothDevice device, int port, ParcelUuid uuid) throws IOException {
|
||||
if (type == BluetoothSocket.TYPE_RFCOMM && uuid == null && fd == -1) {
|
||||
if (VDBG) Log.d(TAG, "Creating new BluetoothSocket of type: " + type);
|
||||
if (type == BluetoothSocket.TYPE_RFCOMM && uuid == null && fd == -1
|
||||
&& port != BluetoothAdapter.SOCKET_CHANNEL_AUTO_STATIC_NO_SDP) {
|
||||
if (port < 1 || port > MAX_RFCOMM_CHANNEL) {
|
||||
throw new IOException("Invalid RFCOMM channel: " + port);
|
||||
}
|
||||
}
|
||||
if(uuid != null)
|
||||
if (uuid != null)
|
||||
mUuid = uuid;
|
||||
else mUuid = new ParcelUuid(new UUID(0, 0));
|
||||
mType = type;
|
||||
@ -172,6 +184,7 @@ public final class BluetoothSocket implements Closeable {
|
||||
mOutputStream = new BluetoothOutputStream(this);
|
||||
}
|
||||
private BluetoothSocket(BluetoothSocket s) {
|
||||
if (VDBG) Log.d(TAG, "Creating new Private BluetoothSocket of type: " + s.mType);
|
||||
mUuid = s.mUuid;
|
||||
mType = s.mType;
|
||||
mAuth = s.mAuth;
|
||||
@ -179,7 +192,11 @@ public final class BluetoothSocket implements Closeable {
|
||||
mPort = s.mPort;
|
||||
mInputStream = new BluetoothInputStream(this);
|
||||
mOutputStream = new BluetoothOutputStream(this);
|
||||
mMaxRxPacketSize = s.mMaxRxPacketSize;
|
||||
mMaxTxPacketSize = s.mMaxTxPacketSize;
|
||||
|
||||
mServiceName = s.mServiceName;
|
||||
mExcludeSdp = s.mExcludeSdp;
|
||||
}
|
||||
private BluetoothSocket acceptSocket(String RemoteAddr) throws IOException {
|
||||
BluetoothSocket as = new BluetoothSocket(this);
|
||||
@ -229,6 +246,8 @@ public final class BluetoothSocket implements Closeable {
|
||||
flags |= SEC_FLAG_AUTH;
|
||||
if(mEncrypt)
|
||||
flags |= SEC_FLAG_ENCRYPT;
|
||||
if(mExcludeSdp)
|
||||
flags |= BTSOCK_FLAG_NO_SDP;
|
||||
return flags;
|
||||
}
|
||||
|
||||
@ -298,7 +317,8 @@ public final class BluetoothSocket implements Closeable {
|
||||
|
||||
try {
|
||||
if (mSocketState == SocketState.CLOSED) throw new IOException("socket closed");
|
||||
IBluetooth bluetoothProxy = BluetoothAdapter.getDefaultAdapter().getBluetoothService(null);
|
||||
IBluetooth bluetoothProxy =
|
||||
BluetoothAdapter.getDefaultAdapter().getBluetoothService(null);
|
||||
if (bluetoothProxy == null) throw new IOException("Bluetooth is off");
|
||||
mPfd = bluetoothProxy.connectSocket(mDevice, mType,
|
||||
mUuid, mPort, getSecurityFlags());
|
||||
@ -370,7 +390,7 @@ public final class BluetoothSocket implements Closeable {
|
||||
mSocketState = SocketState.LISTENING;
|
||||
}
|
||||
if (DBG) Log.d(TAG, "channel: " + channel);
|
||||
if (mPort == -1) {
|
||||
if (mPort <= -1) {
|
||||
mPort = channel;
|
||||
} // else ASSERT(mPort == channel)
|
||||
ret = 0;
|
||||
@ -391,7 +411,8 @@ public final class BluetoothSocket implements Closeable {
|
||||
|
||||
/*package*/ BluetoothSocket accept(int timeout) throws IOException {
|
||||
BluetoothSocket acceptedSocket;
|
||||
if (mSocketState != SocketState.LISTENING) throw new IOException("bt socket is not in listen state");
|
||||
if (mSocketState != SocketState.LISTENING)
|
||||
throw new IOException("bt socket is not in listen state");
|
||||
if(timeout > 0) {
|
||||
Log.d(TAG, "accept() set timeout (ms):" + timeout);
|
||||
mSocket.setSoTimeout(timeout);
|
||||
@ -427,27 +448,80 @@ public final class BluetoothSocket implements Closeable {
|
||||
}
|
||||
|
||||
/*package*/ int read(byte[] b, int offset, int length) throws IOException {
|
||||
if (mSocketIS == null) throw new IOException("read is called on null InputStream");
|
||||
int ret = 0;
|
||||
if (VDBG) Log.d(TAG, "read in: " + mSocketIS + " len: " + length);
|
||||
int ret = mSocketIS.read(b, offset, length);
|
||||
if(ret < 0)
|
||||
if(mType == TYPE_L2CAP)
|
||||
{
|
||||
int bytesToRead = length;
|
||||
if (VDBG) Log.v(TAG, "l2cap: read(): offset: " + offset + " length:" + length
|
||||
+ "mL2capBuffer= " + mL2capBuffer);
|
||||
if (mL2capBuffer == null) {
|
||||
createL2capRxBuffer();
|
||||
}
|
||||
if (mL2capBuffer.remaining() == 0) {
|
||||
if (VDBG) Log.v(TAG, "l2cap buffer empty, refilling...");
|
||||
if (fillL2capRxBuffer() == -1) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
if (bytesToRead > mL2capBuffer.remaining()) {
|
||||
bytesToRead = mL2capBuffer.remaining();
|
||||
}
|
||||
if(VDBG) Log.v(TAG, "get(): offset: " + offset
|
||||
+ " bytesToRead: " + bytesToRead);
|
||||
mL2capBuffer.get(b, offset, bytesToRead);
|
||||
ret = bytesToRead;
|
||||
}else {
|
||||
if (VDBG) Log.v(TAG, "default: read(): offset: " + offset + " length:" + length);
|
||||
ret = mSocketIS.read(b, offset, length);
|
||||
}
|
||||
if (ret < 0)
|
||||
throw new IOException("bt socket closed, read return: " + ret);
|
||||
if (VDBG) Log.d(TAG, "read out: " + mSocketIS + " ret: " + ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*package*/ int write(byte[] b, int offset, int length) throws IOException {
|
||||
if (mSocketOS == null) throw new IOException("write is called on null OutputStream");
|
||||
if (VDBG) Log.d(TAG, "write: " + mSocketOS + " length: " + length);
|
||||
mSocketOS.write(b, offset, length);
|
||||
// There is no good way to confirm since the entire process is asynchronous anyway
|
||||
if (VDBG) Log.d(TAG, "write out: " + mSocketOS + " length: " + length);
|
||||
return length;
|
||||
|
||||
//TODO: Since bindings can exist between the SDU size and the
|
||||
// protocol, we might need to throw an exception instead of just
|
||||
// splitting the write into multiple smaller writes.
|
||||
// Rfcomm uses dynamic allocation, and should not have any bindings
|
||||
// to the actual message length.
|
||||
if (VDBG) Log.d(TAG, "write: " + mSocketOS + " length: " + length);
|
||||
if (mType == TYPE_L2CAP) {
|
||||
if(length <= mMaxTxPacketSize) {
|
||||
mSocketOS.write(b, offset, length);
|
||||
} else {
|
||||
int tmpOffset = offset;
|
||||
int tmpLength = mMaxTxPacketSize;
|
||||
int endIndex = offset + length;
|
||||
boolean done = false;
|
||||
if(DBG) Log.w(TAG, "WARNING: Write buffer larger than L2CAP packet size!\n"
|
||||
+ "Packet will be divided into SDU packets of size "
|
||||
+ mMaxTxPacketSize);
|
||||
do{
|
||||
mSocketOS.write(b, tmpOffset, tmpLength);
|
||||
tmpOffset += mMaxTxPacketSize;
|
||||
if((tmpOffset + mMaxTxPacketSize) > endIndex) {
|
||||
tmpLength = endIndex - tmpOffset;
|
||||
done = true;
|
||||
}
|
||||
} while(!done);
|
||||
|
||||
}
|
||||
} else {
|
||||
mSocketOS.write(b, offset, length);
|
||||
}
|
||||
// There is no good way to confirm since the entire process is asynchronous anyway
|
||||
if (VDBG) Log.d(TAG, "write out: " + mSocketOS + " length: " + length);
|
||||
return length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
if (DBG) Log.d(TAG, "close() in, this: " + this + ", channel: " + mPort + ", state: " + mSocketState);
|
||||
if (DBG) Log.d(TAG, "close() in, this: " + this + ", channel: " + mPort + ", state: "
|
||||
+ mSocketState);
|
||||
if(mSocketState == SocketState.CLOSED)
|
||||
return;
|
||||
else
|
||||
@ -457,8 +531,9 @@ public final class BluetoothSocket implements Closeable {
|
||||
if(mSocketState == SocketState.CLOSED)
|
||||
return;
|
||||
mSocketState = SocketState.CLOSED;
|
||||
if (DBG) Log.d(TAG, "close() this: " + this + ", channel: " + mPort + ", mSocketIS: " + mSocketIS +
|
||||
", mSocketOS: " + mSocketOS + "mSocket: " + mSocket);
|
||||
if (DBG) Log.d(TAG, "close() this: " + this + ", channel: " + mPort +
|
||||
", mSocketIS: " + mSocketIS + ", mSocketOS: " + mSocketOS +
|
||||
"mSocket: " + mSocket);
|
||||
if(mSocket != null) {
|
||||
if (DBG) Log.d(TAG, "Closing mSocket: " + mSocket);
|
||||
mSocket.shutdownInput();
|
||||
@ -480,6 +555,47 @@ public final class BluetoothSocket implements Closeable {
|
||||
/*package */ int getPort() {
|
||||
return mPort;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the maximum supported Transmit packet size for the underlying transport.
|
||||
* Use this to optimize the writes done to the output socket, to avoid sending
|
||||
* half full packets.
|
||||
* @return the maximum supported Transmit packet size for the underlying transport.
|
||||
*/
|
||||
public int getMaxTransmitPacketSize(){
|
||||
return mMaxTxPacketSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the maximum supported Receive packet size for the underlying transport.
|
||||
* Use this to optimize the reads done on the input stream, as any call to read
|
||||
* will return a maximum of this amount of bytes - or for some transports a
|
||||
* multiple of this value.
|
||||
* @return the maximum supported Receive packet size for the underlying transport.
|
||||
*/
|
||||
public int getMaxReceivePacketSize(){
|
||||
return mMaxRxPacketSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the type of the underlying connection
|
||||
* @return one of TYPE_
|
||||
*/
|
||||
public int getConnectionType() {
|
||||
return mType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change if a SDP entry should be automatically created.
|
||||
* Must be called before calling .bind, for the call to have any effect.
|
||||
* @param mExcludeSdp <li>TRUE - do not auto generate SDP record.
|
||||
* <li>FALSE - default - auto generate SPP SDP record.
|
||||
* @hide
|
||||
*/
|
||||
public void setExcludeSdp(boolean excludeSdp) {
|
||||
this.mExcludeSdp = excludeSdp;
|
||||
}
|
||||
|
||||
private String convertAddr(final byte[] addr) {
|
||||
return String.format(Locale.US, "%02X:%02X:%02X:%02X:%02X:%02X",
|
||||
addr[0] , addr[1], addr[2], addr[3] , addr[4], addr[5]);
|
||||
@ -487,8 +603,10 @@ public final class BluetoothSocket implements Closeable {
|
||||
private String waitSocketSignal(InputStream is) throws IOException {
|
||||
byte [] sig = new byte[SOCK_SIGNAL_SIZE];
|
||||
int ret = readAll(is, sig);
|
||||
if (VDBG) Log.d(TAG, "waitSocketSignal read 16 bytes signal ret: " + ret);
|
||||
if (VDBG) Log.d(TAG, "waitSocketSignal read " + SOCK_SIGNAL_SIZE +
|
||||
" bytes signal ret: " + ret);
|
||||
ByteBuffer bb = ByteBuffer.wrap(sig);
|
||||
/* the struct in native is decorated with __attribute__((packed)), hence this is possible */
|
||||
bb.order(ByteOrder.nativeOrder());
|
||||
int size = bb.getShort();
|
||||
if(size != SOCK_SIGNAL_SIZE)
|
||||
@ -497,19 +615,36 @@ public final class BluetoothSocket implements Closeable {
|
||||
bb.get(addr);
|
||||
int channel = bb.getInt();
|
||||
int status = bb.getInt();
|
||||
mMaxTxPacketSize = (bb.getShort() & 0xffff); // Convert to unsigned value
|
||||
mMaxRxPacketSize = (bb.getShort() & 0xffff); // Convert to unsigned value
|
||||
String RemoteAddr = convertAddr(addr);
|
||||
if (VDBG) Log.d(TAG, "waitSocketSignal: sig size: " + size + ", remote addr: "
|
||||
+ RemoteAddr + ", channel: " + channel + ", status: " + status);
|
||||
+ RemoteAddr + ", channel: " + channel + ", status: " + status
|
||||
+ " MaxRxPktSize: " + mMaxRxPacketSize + " MaxTxPktSize: " + mMaxTxPacketSize);
|
||||
if(status != 0)
|
||||
throw new IOException("Connection failure, status: " + status);
|
||||
return RemoteAddr;
|
||||
}
|
||||
|
||||
private void createL2capRxBuffer(){
|
||||
if(mType == TYPE_L2CAP) {
|
||||
// Allocate the buffer to use for reads.
|
||||
if(VDBG) Log.v(TAG, " Creating mL2capBuffer: mMaxPacketSize: " + mMaxRxPacketSize);
|
||||
mL2capBuffer = ByteBuffer.wrap(new byte[mMaxRxPacketSize]);
|
||||
if(VDBG) Log.v(TAG, "mL2capBuffer.remaining()" + mL2capBuffer.remaining());
|
||||
mL2capBuffer.limit(0); // Ensure we do a real read at the first read-request
|
||||
if(VDBG) Log.v(TAG, "mL2capBuffer.remaining() after limit(0):" +
|
||||
mL2capBuffer.remaining());
|
||||
}
|
||||
}
|
||||
|
||||
private int readAll(InputStream is, byte[] b) throws IOException {
|
||||
int left = b.length;
|
||||
while(left > 0) {
|
||||
int ret = is.read(b, b.length - left, left);
|
||||
if(ret <= 0)
|
||||
throw new IOException("read failed, socket might closed or timeout, read ret: " + ret);
|
||||
throw new IOException("read failed, socket might closed or timeout, read ret: "
|
||||
+ ret);
|
||||
left -= ret;
|
||||
if(left != 0)
|
||||
Log.w(TAG, "readAll() looping, read partial size: " + (b.length - left) +
|
||||
@ -526,4 +661,18 @@ public final class BluetoothSocket implements Closeable {
|
||||
bb.order(ByteOrder.nativeOrder());
|
||||
return bb.getInt();
|
||||
}
|
||||
|
||||
private int fillL2capRxBuffer() throws IOException {
|
||||
mL2capBuffer.rewind();
|
||||
int ret = mSocketIS.read(mL2capBuffer.array());
|
||||
if(ret == -1) {
|
||||
// reached end of stream - return -1
|
||||
mL2capBuffer.limit(0);
|
||||
return -1;
|
||||
}
|
||||
mL2capBuffer.limit(ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -68,7 +68,7 @@ interface IBluetooth
|
||||
int getRemoteClass(in BluetoothDevice device);
|
||||
ParcelUuid[] getRemoteUuids(in BluetoothDevice device);
|
||||
boolean fetchRemoteUuids(in BluetoothDevice device);
|
||||
boolean fetchRemoteMasInstances(in BluetoothDevice device);
|
||||
boolean sdpSearch(in BluetoothDevice device, in ParcelUuid uuid);
|
||||
|
||||
boolean setPin(in BluetoothDevice device, boolean accept, int len, in byte[] pinCode);
|
||||
boolean setPasskey(in BluetoothDevice device, boolean accept, int len, in byte[]
|
||||
|
147
core/java/android/bluetooth/SdpMasRecord.java
Normal file
147
core/java/android/bluetooth/SdpMasRecord.java
Normal 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];
|
||||
}
|
||||
};
|
||||
}
|
112
core/java/android/bluetooth/SdpMnsRecord.java
Normal file
112
core/java/android/bluetooth/SdpMnsRecord.java
Normal 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];
|
||||
}
|
||||
};
|
||||
}
|
118
core/java/android/bluetooth/SdpOppOpsRecord.java
Normal file
118
core/java/android/bluetooth/SdpOppOpsRecord.java
Normal 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];
|
||||
}
|
||||
};
|
||||
|
||||
}
|
125
core/java/android/bluetooth/SdpPseRecord.java
Normal file
125
core/java/android/bluetooth/SdpPseRecord.java
Normal 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];
|
||||
}
|
||||
};
|
||||
}
|
76
core/java/android/bluetooth/SdpRecord.java
Normal file
76
core/java/android/bluetooth/SdpRecord.java
Normal 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;
|
||||
}
|
||||
}
|
@ -7,3 +7,14 @@ LOCAL_SRC_FILES := $(call all-subdir-java-files)
|
||||
LOCAL_MODULE:= javax.obex
|
||||
|
||||
include $(BUILD_JAVA_LIBRARY)
|
||||
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
LOCAL_MODULE_TAGS := optional
|
||||
|
||||
LOCAL_SRC_FILES := $(call all-subdir-java-files)
|
||||
|
||||
LOCAL_MODULE:= javax.obexstatic
|
||||
|
||||
include $(BUILD_STATIC_JAVA_LIBRARY)
|
@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (c) 2014 The Android Open Source Project
|
||||
* Copyright (c) 2015 The Android Open Source Project
|
||||
* Copyright (C) 2015 Samsung LSI
|
||||
* Copyright (c) 2008-2009, Motorola, Inc.
|
||||
*
|
||||
* All rights reserved.
|
||||
@ -40,6 +41,8 @@ import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* This class implements the <code>Operation</code> interface. It will read and
|
||||
* write data via puts and gets.
|
||||
@ -47,6 +50,10 @@ import java.io.ByteArrayOutputStream;
|
||||
*/
|
||||
public final class ClientOperation implements Operation, BaseStream {
|
||||
|
||||
private static final String TAG = "ClientOperation";
|
||||
|
||||
private static final boolean V = ObexHelper.VDBG;
|
||||
|
||||
private ClientSession mParent;
|
||||
|
||||
private boolean mInputOpen;
|
||||
@ -75,6 +82,19 @@ public final class ClientOperation implements Operation, BaseStream {
|
||||
|
||||
private boolean mEndOfBodySent;
|
||||
|
||||
private boolean mSendBodyHeader = true;
|
||||
// A latch - when triggered, there is not way back ;-)
|
||||
private boolean mSrmActive = false;
|
||||
|
||||
// Assume SRM disabled - until support is confirmed
|
||||
// by the server
|
||||
private boolean mSrmEnabled = false;
|
||||
// keep waiting until final-bit is received in request
|
||||
// to handle the case where the SRM enable header is in
|
||||
// a different OBEX packet than the SRMP header.
|
||||
private boolean mSrmWaitingForRemote = true;
|
||||
|
||||
|
||||
/**
|
||||
* Creates new OperationImpl to read and write data to a server
|
||||
* @param maxSize the maximum packet size
|
||||
@ -164,7 +184,7 @@ public final class ClientOperation implements Operation, BaseStream {
|
||||
* Since we are not sending any headers or returning any headers then
|
||||
* we just need to write and read the same bytes
|
||||
*/
|
||||
mParent.sendRequest(ObexHelper.OBEX_OPCODE_ABORT, null, mReplyHeader, null);
|
||||
mParent.sendRequest(ObexHelper.OBEX_OPCODE_ABORT, null, mReplyHeader, null, false);
|
||||
|
||||
if (mReplyHeader.responseCode != ResponseCodes.OBEX_HTTP_OK) {
|
||||
throw new IOException("Invalid response code from server");
|
||||
@ -215,6 +235,7 @@ public final class ClientOperation implements Operation, BaseStream {
|
||||
try {
|
||||
return (String)mReplyHeader.getHeader(HeaderSet.TYPE);
|
||||
} catch (IOException e) {
|
||||
if(V) Log.d(TAG, "Exception occured - returning null",e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -236,6 +257,7 @@ public final class ClientOperation implements Operation, BaseStream {
|
||||
return temp.longValue();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
if(V) Log.d(TAG,"Exception occured - returning -1",e);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
@ -408,7 +430,9 @@ public final class ClientOperation implements Operation, BaseStream {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a request to the client of the specified type
|
||||
* Sends a request to the client of the specified type.
|
||||
* This function will enable SRM and set SRM active if the server
|
||||
* response allows this.
|
||||
* @param opCode the request code to send to the client
|
||||
* @return <code>true</code> if there is more data to send;
|
||||
* <code>false</code> if there is no more data to send
|
||||
@ -431,13 +455,16 @@ public final class ClientOperation implements Operation, BaseStream {
|
||||
* length, but it is a waste of resources if we can't send much of
|
||||
* the body.
|
||||
*/
|
||||
if ((ObexHelper.BASE_PACKET_LENGTH + headerArray.length) > mMaxPacketSize) {
|
||||
final int MINIMUM_BODY_LENGTH = 3;
|
||||
if ((ObexHelper.BASE_PACKET_LENGTH + headerArray.length + MINIMUM_BODY_LENGTH)
|
||||
> mMaxPacketSize) {
|
||||
int end = 0;
|
||||
int start = 0;
|
||||
// split & send the headerArray in multiple packets.
|
||||
|
||||
while (end != headerArray.length) {
|
||||
//split the headerArray
|
||||
|
||||
end = ObexHelper.findHeaderEnd(headerArray, start, mMaxPacketSize
|
||||
- ObexHelper.BASE_PACKET_LENGTH);
|
||||
// can not split
|
||||
@ -459,7 +486,7 @@ public final class ClientOperation implements Operation, BaseStream {
|
||||
|
||||
byte[] sendHeader = new byte[end - start];
|
||||
System.arraycopy(headerArray, start, sendHeader, 0, sendHeader.length);
|
||||
if (!mParent.sendRequest(opCode, sendHeader, mReplyHeader, mPrivateInput)) {
|
||||
if (!mParent.sendRequest(opCode, sendHeader, mReplyHeader, mPrivateInput, false)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -470,12 +497,20 @@ public final class ClientOperation implements Operation, BaseStream {
|
||||
start = end;
|
||||
}
|
||||
|
||||
// Enable SRM if it should be enabled
|
||||
checkForSrm();
|
||||
|
||||
if (bodyLength > 0) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
/* All headers will fit into a single package */
|
||||
if(mSendBodyHeader == false) {
|
||||
/* As we are not to send any body data, set the FINAL_BIT */
|
||||
opCode |= ObexHelper.OBEX_OPCODE_FINAL_BIT_MASK;
|
||||
}
|
||||
out.write(headerArray);
|
||||
}
|
||||
|
||||
@ -499,11 +534,11 @@ public final class ClientOperation implements Operation, BaseStream {
|
||||
* (End of Body) otherwise, we need to send 0x48 (Body)
|
||||
*/
|
||||
if ((mPrivateOutput.isClosed()) && (!returnValue) && (!mEndOfBodySent)
|
||||
&& ((opCode & 0x80) != 0)) {
|
||||
out.write(0x49);
|
||||
&& ((opCode & ObexHelper.OBEX_OPCODE_FINAL_BIT_MASK) != 0)) {
|
||||
out.write(HeaderSet.END_OF_BODY);
|
||||
mEndOfBodySent = true;
|
||||
} else {
|
||||
out.write(0x48);
|
||||
out.write(HeaderSet.BODY);
|
||||
}
|
||||
|
||||
bodyLength += 3;
|
||||
@ -517,12 +552,11 @@ public final class ClientOperation implements Operation, BaseStream {
|
||||
|
||||
if (mPrivateOutputOpen && bodyLength <= 0 && !mEndOfBodySent) {
|
||||
// only 0x82 or 0x83 can send 0x49
|
||||
if ((opCode & 0x80) == 0) {
|
||||
out.write(0x48);
|
||||
if ((opCode & ObexHelper.OBEX_OPCODE_FINAL_BIT_MASK) == 0) {
|
||||
out.write(HeaderSet.BODY);
|
||||
} else {
|
||||
out.write(0x49);
|
||||
out.write(HeaderSet.END_OF_BODY);
|
||||
mEndOfBodySent = true;
|
||||
|
||||
}
|
||||
|
||||
bodyLength = 3;
|
||||
@ -531,15 +565,20 @@ public final class ClientOperation implements Operation, BaseStream {
|
||||
}
|
||||
|
||||
if (out.size() == 0) {
|
||||
if (!mParent.sendRequest(opCode, null, mReplyHeader, mPrivateInput)) {
|
||||
if (!mParent.sendRequest(opCode, null, mReplyHeader, mPrivateInput, mSrmActive)) {
|
||||
return false;
|
||||
}
|
||||
// Enable SRM if it should be enabled
|
||||
checkForSrm();
|
||||
return returnValue;
|
||||
}
|
||||
if ((out.size() > 0)
|
||||
&& (!mParent.sendRequest(opCode, out.toByteArray(), mReplyHeader, mPrivateInput))) {
|
||||
&& (!mParent.sendRequest(opCode, out.toByteArray(),
|
||||
mReplyHeader, mPrivateInput, mSrmActive))) {
|
||||
return false;
|
||||
}
|
||||
// Enable SRM if it should be enabled
|
||||
checkForSrm();
|
||||
|
||||
// send all of the output data in 0x48,
|
||||
// send 0x49 with empty body
|
||||
@ -549,6 +588,35 @@ public final class ClientOperation implements Operation, BaseStream {
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
private void checkForSrm() throws IOException {
|
||||
Byte srmMode = (Byte)mReplyHeader.getHeader(HeaderSet.SINGLE_RESPONSE_MODE);
|
||||
if(mParent.isSrmSupported() == true && srmMode != null
|
||||
&& srmMode == ObexHelper.OBEX_SRM_ENABLE) {
|
||||
mSrmEnabled = true;
|
||||
}
|
||||
/**
|
||||
* Call this only when a complete obex packet have been received.
|
||||
* (This is not optimal, but the current design is not really suited to
|
||||
* the way SRM is specified.)
|
||||
* The BT usage of SRM is not really safe - it assumes that the SRMP will fit
|
||||
* into every OBEX packet, hence if another header occupies the entire packet,
|
||||
* the scheme will not work - unlikely though.
|
||||
*/
|
||||
if(mSrmEnabled) {
|
||||
mSrmWaitingForRemote = false;
|
||||
Byte srmp = (Byte)mReplyHeader.getHeader(HeaderSet.SINGLE_RESPONSE_MODE_PARAMETER);
|
||||
if(srmp != null && srmp == ObexHelper.OBEX_SRMP_WAIT) {
|
||||
mSrmWaitingForRemote = true;
|
||||
// Clear the wait header, as the absence of the header in the next packet
|
||||
// indicates don't wait anymore.
|
||||
mReplyHeader.setHeader(HeaderSet.SINGLE_RESPONSE_MODE_PARAMETER, null);
|
||||
}
|
||||
}
|
||||
if((mSrmWaitingForRemote == false) && (mSrmEnabled == true)) {
|
||||
mSrmActive = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method starts the processing thread results. It will send the
|
||||
* initial request. If the response takes more then one packet, a thread
|
||||
@ -564,40 +632,35 @@ public final class ClientOperation implements Operation, BaseStream {
|
||||
|
||||
if (mGetOperation) {
|
||||
if (!mOperationDone) {
|
||||
if (!mGetFinalFlag) {
|
||||
mReplyHeader.responseCode = ResponseCodes.OBEX_HTTP_CONTINUE;
|
||||
while ((more) && (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE)) {
|
||||
more = sendRequest(0x03);
|
||||
}
|
||||
|
||||
if (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE) {
|
||||
mParent.sendRequest(0x83, null, mReplyHeader, mPrivateInput);
|
||||
}
|
||||
if (mReplyHeader.responseCode != ResponseCodes.OBEX_HTTP_CONTINUE) {
|
||||
mOperationDone = true;
|
||||
}
|
||||
} else {
|
||||
more = sendRequest(0x83);
|
||||
|
||||
if (more) {
|
||||
throw new IOException("FINAL_GET forced but data did not fit into single packet!");
|
||||
}
|
||||
|
||||
mReplyHeader.responseCode = ResponseCodes.OBEX_HTTP_CONTINUE;
|
||||
while ((more) && (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE)) {
|
||||
more = sendRequest(ObexHelper.OBEX_OPCODE_GET);
|
||||
}
|
||||
// For GET we need to loop until all headers have been sent,
|
||||
// And then we wait for the first continue package with the
|
||||
// reply.
|
||||
if (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE) {
|
||||
mParent.sendRequest(ObexHelper.OBEX_OPCODE_GET_FINAL,
|
||||
null, mReplyHeader, mPrivateInput, mSrmActive);
|
||||
}
|
||||
if (mReplyHeader.responseCode != ResponseCodes.OBEX_HTTP_CONTINUE) {
|
||||
mOperationDone = true;
|
||||
} else {
|
||||
checkForSrm();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
// PUT operation
|
||||
if (!mOperationDone) {
|
||||
mReplyHeader.responseCode = ResponseCodes.OBEX_HTTP_CONTINUE;
|
||||
while ((more) && (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE)) {
|
||||
more = sendRequest(0x02);
|
||||
|
||||
more = sendRequest(ObexHelper.OBEX_OPCODE_PUT);
|
||||
}
|
||||
}
|
||||
|
||||
if (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE) {
|
||||
mParent.sendRequest(0x82, null, mReplyHeader, mPrivateInput);
|
||||
mParent.sendRequest(ObexHelper.OBEX_OPCODE_PUT_FINAL,
|
||||
null, mReplyHeader, mPrivateInput, mSrmActive);
|
||||
}
|
||||
|
||||
if (mReplyHeader.responseCode != ResponseCodes.OBEX_HTTP_CONTINUE) {
|
||||
@ -617,15 +680,21 @@ public final class ClientOperation implements Operation, BaseStream {
|
||||
public synchronized boolean continueOperation(boolean sendEmpty, boolean inStream)
|
||||
throws IOException {
|
||||
|
||||
// One path to the first put operation - the other one does not need to
|
||||
// handle SRM, as all will fit into one packet.
|
||||
|
||||
if (mGetOperation) {
|
||||
if ((inStream) && (!mOperationDone)) {
|
||||
// to deal with inputstream in get operation
|
||||
mParent.sendRequest(0x83, null, mReplyHeader, mPrivateInput);
|
||||
mParent.sendRequest(ObexHelper.OBEX_OPCODE_GET_FINAL,
|
||||
null, mReplyHeader, mPrivateInput, mSrmActive);
|
||||
/*
|
||||
* Determine if that was not the last packet in the operation
|
||||
*/
|
||||
if (mReplyHeader.responseCode != ResponseCodes.OBEX_HTTP_CONTINUE) {
|
||||
mOperationDone = true;
|
||||
} else {
|
||||
checkForSrm();
|
||||
}
|
||||
|
||||
return true;
|
||||
@ -636,16 +705,7 @@ public final class ClientOperation implements Operation, BaseStream {
|
||||
if (mPrivateInput == null) {
|
||||
mPrivateInput = new PrivateInputStream(this);
|
||||
}
|
||||
|
||||
if (!mGetFinalFlag) {
|
||||
sendRequest(0x03);
|
||||
} else {
|
||||
sendRequest(0x83);
|
||||
|
||||
if (mReplyHeader.responseCode != ResponseCodes.OBEX_HTTP_CONTINUE) {
|
||||
mOperationDone = true;
|
||||
}
|
||||
}
|
||||
sendRequest(ObexHelper.OBEX_OPCODE_GET);
|
||||
return true;
|
||||
|
||||
} else if (mOperationDone) {
|
||||
@ -653,12 +713,13 @@ public final class ClientOperation implements Operation, BaseStream {
|
||||
}
|
||||
|
||||
} else {
|
||||
// PUT operation
|
||||
if ((!inStream) && (!mOperationDone)) {
|
||||
// to deal with outputstream in put operation
|
||||
if (mReplyHeader.responseCode == -1) {
|
||||
mReplyHeader.responseCode = ResponseCodes.OBEX_HTTP_CONTINUE;
|
||||
}
|
||||
sendRequest(0x02);
|
||||
sendRequest(ObexHelper.OBEX_OPCODE_PUT);
|
||||
return true;
|
||||
} else if ((inStream) && (!mOperationDone)) {
|
||||
// How to deal with inputstream in put operation ?
|
||||
@ -696,7 +757,7 @@ public final class ClientOperation implements Operation, BaseStream {
|
||||
}
|
||||
|
||||
while ((more) && (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE)) {
|
||||
more = sendRequest(0x02);
|
||||
more = sendRequest(ObexHelper.OBEX_OPCODE_PUT);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -706,7 +767,7 @@ public final class ClientOperation implements Operation, BaseStream {
|
||||
*/
|
||||
while (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE) {
|
||||
|
||||
sendRequest(0x82);
|
||||
sendRequest(ObexHelper.OBEX_OPCODE_PUT_FINAL);
|
||||
}
|
||||
mOperationDone = true;
|
||||
} else if ((inStream) && (mOperationDone)) {
|
||||
@ -724,12 +785,14 @@ public final class ClientOperation implements Operation, BaseStream {
|
||||
}
|
||||
|
||||
while (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE) {
|
||||
if (!sendRequest(0x83)) {
|
||||
if (!sendRequest(ObexHelper.OBEX_OPCODE_GET_FINAL)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
while (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE) {
|
||||
mParent.sendRequest(0x83, null, mReplyHeader, mPrivateInput);
|
||||
mParent.sendRequest(ObexHelper.OBEX_OPCODE_GET_FINAL, null,
|
||||
mReplyHeader, mPrivateInput, false);
|
||||
// Regardless of the SRM state, wait for the response.
|
||||
}
|
||||
mOperationDone = true;
|
||||
} else if ((!inStream) && (!mOperationDone)) {
|
||||
@ -752,9 +815,9 @@ public final class ClientOperation implements Operation, BaseStream {
|
||||
|
||||
mReplyHeader.responseCode = ResponseCodes.OBEX_HTTP_CONTINUE;
|
||||
while ((more) && (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE)) {
|
||||
more = sendRequest(0x03);
|
||||
more = sendRequest(ObexHelper.OBEX_OPCODE_GET);
|
||||
}
|
||||
sendRequest(0x83);
|
||||
sendRequest(ObexHelper.OBEX_OPCODE_GET_FINAL);
|
||||
// parent.sendRequest(0x83, null, replyHeaders, privateInput);
|
||||
if (mReplyHeader.responseCode != ResponseCodes.OBEX_HTTP_CONTINUE) {
|
||||
mOperationDone = true;
|
||||
@ -764,5 +827,6 @@ public final class ClientOperation implements Operation, BaseStream {
|
||||
}
|
||||
|
||||
public void noBodyHeader(){
|
||||
mSendBodyHeader = false;
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,6 @@
|
||||
/*
|
||||
* Copyright (c) 2015 The Android Open Source Project
|
||||
* Copyright (C) 2015 Samsung LSI
|
||||
* Copyright (c) 2008-2009, Motorola, Inc.
|
||||
*
|
||||
* All rights reserved.
|
||||
@ -37,12 +39,16 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* This class in an implementation of the OBEX ClientSession.
|
||||
* @hide
|
||||
*/
|
||||
public final class ClientSession extends ObexSession {
|
||||
|
||||
private static final String TAG = "ClientSession";
|
||||
|
||||
private boolean mOpen;
|
||||
|
||||
// Determines if an OBEX layer connection has been established
|
||||
@ -51,10 +57,10 @@ public final class ClientSession extends ObexSession {
|
||||
private byte[] mConnectionId = null;
|
||||
|
||||
/*
|
||||
* The max Packet size must be at least 256 according to the OBEX
|
||||
* The max Packet size must be at least 255 according to the OBEX
|
||||
* specification.
|
||||
*/
|
||||
private int maxPacketSize = 256;
|
||||
private int mMaxTxPacketSize = ObexHelper.LOWER_LIMIT_MAX_PACKET_SIZE;
|
||||
|
||||
private boolean mRequestActive;
|
||||
|
||||
@ -62,11 +68,33 @@ public final class ClientSession extends ObexSession {
|
||||
|
||||
private final OutputStream mOutput;
|
||||
|
||||
private final boolean mLocalSrmSupported;
|
||||
|
||||
private final ObexTransport mTransport;
|
||||
|
||||
public ClientSession(final ObexTransport trans) throws IOException {
|
||||
mInput = trans.openInputStream();
|
||||
mOutput = trans.openOutputStream();
|
||||
mOpen = true;
|
||||
mRequestActive = false;
|
||||
mLocalSrmSupported = trans.isSrmSupported();
|
||||
mTransport = trans;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a ClientSession
|
||||
* @param trans The transport to use for OBEX transactions
|
||||
* @param supportsSrm True if Single Response Mode should be used e.g. if the
|
||||
* supplied transport is a TCP or l2cap channel.
|
||||
* @throws IOException if it occurs while opening the transport streams.
|
||||
*/
|
||||
public ClientSession(final ObexTransport trans, final boolean supportsSrm) throws IOException {
|
||||
mInput = trans.openInputStream();
|
||||
mOutput = trans.openOutputStream();
|
||||
mOpen = true;
|
||||
mRequestActive = false;
|
||||
mLocalSrmSupported = supportsSrm;
|
||||
mTransport = trans;
|
||||
}
|
||||
|
||||
public HeaderSet connect(final HeaderSet header) throws IOException {
|
||||
@ -98,23 +126,25 @@ public final class ClientSession extends ObexSession {
|
||||
* Byte 7 to n: headers
|
||||
*/
|
||||
byte[] requestPacket = new byte[totalLength];
|
||||
int maxRxPacketSize = ObexHelper.getMaxRxPacketSize(mTransport);
|
||||
// We just need to start at byte 3 since the sendRequest() method will
|
||||
// handle the length and 0x80.
|
||||
requestPacket[0] = (byte)0x10;
|
||||
requestPacket[1] = (byte)0x00;
|
||||
requestPacket[2] = (byte)(ObexHelper.MAX_PACKET_SIZE_INT >> 8);
|
||||
requestPacket[3] = (byte)(ObexHelper.MAX_PACKET_SIZE_INT & 0xFF);
|
||||
requestPacket[2] = (byte)(maxRxPacketSize >> 8);
|
||||
requestPacket[3] = (byte)(maxRxPacketSize & 0xFF);
|
||||
if (head != null) {
|
||||
System.arraycopy(head, 0, requestPacket, 4, head.length);
|
||||
}
|
||||
|
||||
// check with local max packet size
|
||||
// Since we are not yet connected, the peer max packet size is unknown,
|
||||
// hence we are only guaranteed the server will use the first 7 bytes.
|
||||
if ((requestPacket.length + 3) > ObexHelper.MAX_PACKET_SIZE_INT) {
|
||||
throw new IOException("Packet size exceeds max packet size");
|
||||
throw new IOException("Packet size exceeds max packet size for connect");
|
||||
}
|
||||
|
||||
HeaderSet returnHeaderSet = new HeaderSet();
|
||||
sendRequest(ObexHelper.OBEX_OPCODE_CONNECT, requestPacket, returnHeaderSet, null);
|
||||
sendRequest(ObexHelper.OBEX_OPCODE_CONNECT, requestPacket, returnHeaderSet, null, false);
|
||||
|
||||
/*
|
||||
* Read the response from the OBEX server.
|
||||
@ -158,7 +188,18 @@ public final class ClientSession extends ObexSession {
|
||||
System.arraycopy(mConnectionId, 0, head.mConnectionID, 0, 4);
|
||||
}
|
||||
|
||||
return new ClientOperation(maxPacketSize, this, head, true);
|
||||
if(mLocalSrmSupported) {
|
||||
head.setHeader(HeaderSet.SINGLE_RESPONSE_MODE, ObexHelper.OBEX_SRM_ENABLE);
|
||||
/* TODO: Consider creating an interface to get the wait state.
|
||||
* On an android system, I cannot see when this is to be used.
|
||||
* except perhaps if we are to wait for user accept on a push message.
|
||||
if(getLocalWaitState()) {
|
||||
head.setHeader(HeaderSet.SINGLE_RESPONSE_MODE_PARAMETER, ObexHelper.OBEX_SRMP_WAIT);
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
return new ClientOperation(mMaxTxPacketSize, this, head, true);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -202,7 +243,7 @@ public final class ClientSession extends ObexSession {
|
||||
}
|
||||
head = ObexHelper.createHeader(header, false);
|
||||
|
||||
if ((head.length + 3) > maxPacketSize) {
|
||||
if ((head.length + 3) > mMaxTxPacketSize) {
|
||||
throw new IOException("Packet size exceeds max packet size");
|
||||
}
|
||||
} else {
|
||||
@ -215,7 +256,7 @@ public final class ClientSession extends ObexSession {
|
||||
}
|
||||
|
||||
HeaderSet returnHeaderSet = new HeaderSet();
|
||||
sendRequest(ObexHelper.OBEX_OPCODE_DISCONNECT, head, returnHeaderSet, null);
|
||||
sendRequest(ObexHelper.OBEX_OPCODE_DISCONNECT, head, returnHeaderSet, null, false);
|
||||
|
||||
/*
|
||||
* An OBEX DISCONNECT reply from the server:
|
||||
@ -269,7 +310,16 @@ public final class ClientSession extends ObexSession {
|
||||
System.arraycopy(mConnectionId, 0, head.mConnectionID, 0, 4);
|
||||
}
|
||||
|
||||
return new ClientOperation(maxPacketSize, this, head, false);
|
||||
if(mLocalSrmSupported) {
|
||||
head.setHeader(HeaderSet.SINGLE_RESPONSE_MODE, ObexHelper.OBEX_SRM_ENABLE);
|
||||
/* TODO: Consider creating an interface to get the wait state.
|
||||
* On an android system, I cannot see when this is to be used.
|
||||
if(getLocalWaitState()) {
|
||||
head.setHeader(HeaderSet.SINGLE_RESPONSE_MODE_PARAMETER, ObexHelper.OBEX_SRMP_WAIT);
|
||||
}
|
||||
*/
|
||||
}
|
||||
return new ClientOperation(mMaxTxPacketSize, this, head, false);
|
||||
}
|
||||
|
||||
public void setAuthenticator(Authenticator auth) throws IOException {
|
||||
@ -314,7 +364,7 @@ public final class ClientSession extends ObexSession {
|
||||
head = ObexHelper.createHeader(headset, false);
|
||||
totalLength += head.length;
|
||||
|
||||
if (totalLength > maxPacketSize) {
|
||||
if (totalLength > mMaxTxPacketSize) {
|
||||
throw new IOException("Packet size exceeds max packet size");
|
||||
}
|
||||
|
||||
@ -348,7 +398,7 @@ public final class ClientSession extends ObexSession {
|
||||
}
|
||||
|
||||
HeaderSet returnHeaderSet = new HeaderSet();
|
||||
sendRequest(ObexHelper.OBEX_OPCODE_SETPATH, packet, returnHeaderSet, null);
|
||||
sendRequest(ObexHelper.OBEX_OPCODE_SETPATH, packet, returnHeaderSet, null, false);
|
||||
|
||||
/*
|
||||
* An OBEX SETPATH reply from the server:
|
||||
@ -400,20 +450,40 @@ public final class ClientSession extends ObexSession {
|
||||
* @param head the headers to send to the client
|
||||
* @param header the header object to update with the response
|
||||
* @param privateInput the input stream used by the Operation object; null
|
||||
* if this is called on a CONNECT, SETPATH or DISCONNECT return
|
||||
* if this is called on a CONNECT, SETPATH or DISCONNECT
|
||||
* @return
|
||||
* <code>true</code> if the operation completed successfully;
|
||||
* <code>false</code> if an authentication response failed to pass
|
||||
* @throws IOException if an IO error occurs
|
||||
*/
|
||||
public boolean sendRequest(int opCode, byte[] head, HeaderSet header,
|
||||
PrivateInputStream privateInput) throws IOException {
|
||||
PrivateInputStream privateInput, boolean srmActive) throws IOException {
|
||||
//check header length with local max size
|
||||
if (head != null) {
|
||||
if ((head.length + 3) > ObexHelper.MAX_PACKET_SIZE_INT) {
|
||||
// TODO: This is an implementation limit - not a specification requirement.
|
||||
throw new IOException("header too large ");
|
||||
}
|
||||
}
|
||||
|
||||
boolean skipSend = false;
|
||||
boolean skipReceive = false;
|
||||
if (srmActive == true) {
|
||||
if (opCode == ObexHelper.OBEX_OPCODE_PUT) {
|
||||
// we are in the middle of a SRM PUT operation, don't expect a continue.
|
||||
skipReceive = true;
|
||||
} else if (opCode == ObexHelper.OBEX_OPCODE_GET) {
|
||||
// We are still sending the get request, send, but don't expect continue
|
||||
// until the request is transfered (the final bit is set)
|
||||
skipReceive = true;
|
||||
} else if (opCode == ObexHelper.OBEX_OPCODE_GET_FINAL) {
|
||||
// All done sending the request, expect data from the server, without
|
||||
// sending continue.
|
||||
skipSend = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int bytesReceived;
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
out.write((byte)opCode);
|
||||
@ -428,86 +498,105 @@ public final class ClientSession extends ObexSession {
|
||||
out.write(head);
|
||||
}
|
||||
|
||||
// Write the request to the output stream and flush the stream
|
||||
mOutput.write(out.toByteArray());
|
||||
mOutput.flush();
|
||||
|
||||
header.responseCode = mInput.read();
|
||||
|
||||
int length = ((mInput.read() << 8) | (mInput.read()));
|
||||
|
||||
if (length > ObexHelper.MAX_PACKET_SIZE_INT) {
|
||||
throw new IOException("Packet received exceeds packet size limit");
|
||||
if (!skipSend) {
|
||||
// Write the request to the output stream and flush the stream
|
||||
mOutput.write(out.toByteArray());
|
||||
// TODO: is this really needed? if this flush is implemented
|
||||
// correctly, we will get a gap between each obex packet.
|
||||
// which is kind of the idea behind SRM to avoid.
|
||||
// Consider offloading to another thread (async action)
|
||||
mOutput.flush();
|
||||
}
|
||||
if (length > ObexHelper.BASE_PACKET_LENGTH) {
|
||||
byte[] data = null;
|
||||
if (opCode == ObexHelper.OBEX_OPCODE_CONNECT) {
|
||||
@SuppressWarnings("unused")
|
||||
int version = mInput.read();
|
||||
@SuppressWarnings("unused")
|
||||
int flags = mInput.read();
|
||||
maxPacketSize = (mInput.read() << 8) + mInput.read();
|
||||
|
||||
//check with local max size
|
||||
if (maxPacketSize > ObexHelper.MAX_CLIENT_PACKET_SIZE) {
|
||||
maxPacketSize = ObexHelper.MAX_CLIENT_PACKET_SIZE;
|
||||
}
|
||||
if (!skipReceive) {
|
||||
header.responseCode = mInput.read();
|
||||
|
||||
if (length > 7) {
|
||||
data = new byte[length - 7];
|
||||
int length = ((mInput.read() << 8) | (mInput.read()));
|
||||
|
||||
bytesReceived = mInput.read(data);
|
||||
while (bytesReceived != (length - 7)) {
|
||||
bytesReceived += mInput.read(data, bytesReceived, data.length
|
||||
- bytesReceived);
|
||||
if (length > ObexHelper.getMaxRxPacketSize(mTransport)) {
|
||||
throw new IOException("Packet received exceeds packet size limit");
|
||||
}
|
||||
if (length > ObexHelper.BASE_PACKET_LENGTH) {
|
||||
byte[] data = null;
|
||||
if (opCode == ObexHelper.OBEX_OPCODE_CONNECT) {
|
||||
@SuppressWarnings("unused")
|
||||
int version = mInput.read();
|
||||
@SuppressWarnings("unused")
|
||||
int flags = mInput.read();
|
||||
mMaxTxPacketSize = (mInput.read() << 8) + mInput.read();
|
||||
|
||||
//check with local max size
|
||||
if (mMaxTxPacketSize > ObexHelper.MAX_CLIENT_PACKET_SIZE) {
|
||||
mMaxTxPacketSize = ObexHelper.MAX_CLIENT_PACKET_SIZE;
|
||||
}
|
||||
|
||||
// check with transport maximum size
|
||||
if(mMaxTxPacketSize > ObexHelper.getMaxTxPacketSize(mTransport)) {
|
||||
// To increase this size, increase the buffer size in L2CAP layer
|
||||
// in Bluedroid.
|
||||
Log.w(TAG, "An OBEX packet size of " + mMaxTxPacketSize + "was"
|
||||
+ " requested. Transport only allows: "
|
||||
+ ObexHelper.getMaxTxPacketSize(mTransport)
|
||||
+ " Lowering limit to this value.");
|
||||
mMaxTxPacketSize = ObexHelper.getMaxTxPacketSize(mTransport);
|
||||
}
|
||||
|
||||
if (length > 7) {
|
||||
data = new byte[length - 7];
|
||||
|
||||
bytesReceived = mInput.read(data);
|
||||
while (bytesReceived != (length - 7)) {
|
||||
bytesReceived += mInput.read(data, bytesReceived, data.length
|
||||
- bytesReceived);
|
||||
}
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
return true;
|
||||
data = new byte[length - 3];
|
||||
bytesReceived = mInput.read(data);
|
||||
|
||||
while (bytesReceived != (length - 3)) {
|
||||
bytesReceived += mInput.read(data, bytesReceived, data.length - bytesReceived);
|
||||
}
|
||||
if (opCode == ObexHelper.OBEX_OPCODE_ABORT) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
data = new byte[length - 3];
|
||||
bytesReceived = mInput.read(data);
|
||||
|
||||
while (bytesReceived != (length - 3)) {
|
||||
bytesReceived += mInput.read(data, bytesReceived, data.length - bytesReceived);
|
||||
byte[] body = ObexHelper.updateHeaderSet(header, data);
|
||||
if ((privateInput != null) && (body != null)) {
|
||||
privateInput.writeBytes(body, 1);
|
||||
}
|
||||
if (opCode == ObexHelper.OBEX_OPCODE_ABORT) {
|
||||
return true;
|
||||
|
||||
if (header.mConnectionID != null) {
|
||||
mConnectionId = new byte[4];
|
||||
System.arraycopy(header.mConnectionID, 0, mConnectionId, 0, 4);
|
||||
}
|
||||
}
|
||||
|
||||
byte[] body = ObexHelper.updateHeaderSet(header, data);
|
||||
if ((privateInput != null) && (body != null)) {
|
||||
privateInput.writeBytes(body, 1);
|
||||
}
|
||||
|
||||
if (header.mConnectionID != null) {
|
||||
mConnectionId = new byte[4];
|
||||
System.arraycopy(header.mConnectionID, 0, mConnectionId, 0, 4);
|
||||
}
|
||||
|
||||
if (header.mAuthResp != null) {
|
||||
if (!handleAuthResp(header.mAuthResp)) {
|
||||
setRequestInactive();
|
||||
throw new IOException("Authentication Failed");
|
||||
if (header.mAuthResp != null) {
|
||||
if (!handleAuthResp(header.mAuthResp)) {
|
||||
setRequestInactive();
|
||||
throw new IOException("Authentication Failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((header.responseCode == ResponseCodes.OBEX_HTTP_UNAUTHORIZED)
|
||||
&& (header.mAuthChall != null)) {
|
||||
if ((header.responseCode == ResponseCodes.OBEX_HTTP_UNAUTHORIZED)
|
||||
&& (header.mAuthChall != null)) {
|
||||
|
||||
if (handleAuthChall(header)) {
|
||||
out.write((byte)HeaderSet.AUTH_RESPONSE);
|
||||
out.write((byte)((header.mAuthResp.length + 3) >> 8));
|
||||
out.write((byte)(header.mAuthResp.length + 3));
|
||||
out.write(header.mAuthResp);
|
||||
header.mAuthChall = null;
|
||||
header.mAuthResp = null;
|
||||
if (handleAuthChall(header)) {
|
||||
out.write((byte)HeaderSet.AUTH_RESPONSE);
|
||||
out.write((byte)((header.mAuthResp.length + 3) >> 8));
|
||||
out.write((byte)(header.mAuthResp.length + 3));
|
||||
out.write(header.mAuthResp);
|
||||
header.mAuthChall = null;
|
||||
header.mAuthResp = null;
|
||||
|
||||
byte[] sendHeaders = new byte[out.size() - 3];
|
||||
System.arraycopy(out.toByteArray(), 3, sendHeaders, 0, sendHeaders.length);
|
||||
byte[] sendHeaders = new byte[out.size() - 3];
|
||||
System.arraycopy(out.toByteArray(), 3, sendHeaders, 0, sendHeaders.length);
|
||||
|
||||
return sendRequest(opCode, sendHeaders, header, privateInput);
|
||||
return sendRequest(opCode, sendHeaders, header, privateInput, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -520,4 +609,8 @@ public final class ClientSession extends ObexSession {
|
||||
mInput.close();
|
||||
mOutput.close();
|
||||
}
|
||||
|
||||
public boolean isSrmSupported() {
|
||||
return mLocalSrmSupported;
|
||||
}
|
||||
}
|
||||
|
@ -40,7 +40,7 @@ import java.security.SecureRandom;
|
||||
|
||||
/**
|
||||
* This class implements the javax.obex.HeaderSet interface for OBEX over
|
||||
* RFCOMM.
|
||||
* RFCOMM or OBEX over l2cap.
|
||||
* @hide
|
||||
*/
|
||||
public final class HeaderSet {
|
||||
@ -178,6 +178,22 @@ public final class HeaderSet {
|
||||
*/
|
||||
public static final int OBJECT_CLASS = 0x4F;
|
||||
|
||||
/**
|
||||
* Represents the OBEX Single Response Mode (SRM). This header is used
|
||||
* for Single response mode, introduced in OBEX 1.5.
|
||||
* <P>
|
||||
* The value of <code>SINGLE_RESPONSE_MODE</code> is 0x97 (151).
|
||||
*/
|
||||
public static final int SINGLE_RESPONSE_MODE = 0x97;
|
||||
|
||||
/**
|
||||
* Represents the OBEX Single Response Mode Parameters. This header is used
|
||||
* for Single response mode, introduced in OBEX 1.5.
|
||||
* <P>
|
||||
* The value of <code>SINGLE_RESPONSE_MODE_PARAMETER</code> is 0x98 (152).
|
||||
*/
|
||||
public static final int SINGLE_RESPONSE_MODE_PARAMETER = 0x98;
|
||||
|
||||
private Long mCount; // 4 byte unsigned integer
|
||||
|
||||
private String mName; // null terminated Unicode text string
|
||||
@ -204,7 +220,7 @@ public final class HeaderSet {
|
||||
|
||||
private byte[] mObjectClass; // byte sequence
|
||||
|
||||
private String[] mUnicodeUserDefined; //null terminated unicode string
|
||||
private String[] mUnicodeUserDefined; // null terminated unicode string
|
||||
|
||||
private byte[][] mSequenceUserDefined; // byte sequence user defined
|
||||
|
||||
@ -212,7 +228,12 @@ public final class HeaderSet {
|
||||
|
||||
private Long[] mIntegerUserDefined; // 4 byte unsigned integer
|
||||
|
||||
private final SecureRandom mRandom;
|
||||
private SecureRandom mRandom = null;
|
||||
|
||||
private Byte mSingleResponseMode; // byte to indicate enable/disable/support for SRM
|
||||
|
||||
private Byte mSrmParam; // byte representing the SRM parameters - only "wait"
|
||||
// is supported by Bluetooth
|
||||
|
||||
/*package*/ byte[] nonce;
|
||||
|
||||
@ -234,7 +255,6 @@ public final class HeaderSet {
|
||||
mByteUserDefined = new Byte[16];
|
||||
mIntegerUserDefined = new Long[16];
|
||||
responseCode = -1;
|
||||
mRandom = new SecureRandom();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -393,6 +413,30 @@ public final class HeaderSet {
|
||||
}
|
||||
}
|
||||
break;
|
||||
case SINGLE_RESPONSE_MODE:
|
||||
if (headerValue == null) {
|
||||
mSingleResponseMode = null;
|
||||
} else {
|
||||
if (!(headerValue instanceof Byte)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Single Response Mode must be a Byte");
|
||||
} else {
|
||||
mSingleResponseMode = (Byte)headerValue;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case SINGLE_RESPONSE_MODE_PARAMETER:
|
||||
if (headerValue == null) {
|
||||
mSrmParam = null;
|
||||
} else {
|
||||
if (!(headerValue instanceof Byte)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Single Response Mode Parameter must be a Byte");
|
||||
} else {
|
||||
mSrmParam = (Byte)headerValue;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// Verify that it was not a Unicode String user Defined
|
||||
if ((headerID >= 0x30) && (headerID <= 0x3F)) {
|
||||
@ -493,6 +537,10 @@ public final class HeaderSet {
|
||||
return mObjectClass;
|
||||
case APPLICATION_PARAMETER:
|
||||
return mAppParam;
|
||||
case SINGLE_RESPONSE_MODE:
|
||||
return mSingleResponseMode;
|
||||
case SINGLE_RESPONSE_MODE_PARAMETER:
|
||||
return mSrmParam;
|
||||
default:
|
||||
// Verify that it was not a Unicode String user Defined
|
||||
if ((headerID >= 0x30) && (headerID <= 0x3F)) {
|
||||
@ -564,6 +612,12 @@ public final class HeaderSet {
|
||||
if (mObjectClass != null) {
|
||||
out.write(OBJECT_CLASS);
|
||||
}
|
||||
if(mSingleResponseMode != null) {
|
||||
out.write(SINGLE_RESPONSE_MODE);
|
||||
}
|
||||
if(mSrmParam != null) {
|
||||
out.write(SINGLE_RESPONSE_MODE_PARAMETER);
|
||||
}
|
||||
|
||||
for (int i = 0x30; i < 0x40; i++) {
|
||||
if (mUnicodeUserDefined[i - 0x30] != null) {
|
||||
@ -625,6 +679,9 @@ public final class HeaderSet {
|
||||
throws IOException {
|
||||
|
||||
nonce = new byte[16];
|
||||
if(mRandom == null) {
|
||||
mRandom = new SecureRandom();
|
||||
}
|
||||
for (int i = 0; i < 16; i++) {
|
||||
nonce[i] = (byte)mRandom.nextInt();
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2014 The Android Open Source Project
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
* Copyright (C) 2015 Samsung LSI
|
||||
* Copyright (c) 2008-2009, Motorola, Inc.
|
||||
*
|
||||
* All rights reserved.
|
||||
@ -42,12 +43,16 @@ import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* This class defines a set of helper methods for the implementation of Obex.
|
||||
* @hide
|
||||
*/
|
||||
public final class ObexHelper {
|
||||
|
||||
private static final String TAG = "ObexHelper";
|
||||
public static final boolean VDBG = false;
|
||||
/**
|
||||
* Defines the basic packet length used by OBEX. Every OBEX packet has the
|
||||
* same basic format:<BR>
|
||||
@ -65,18 +70,24 @@ public final class ObexHelper {
|
||||
* should be the Max incoming MTU minus TODO: L2CAP package headers and
|
||||
* RFCOMM package headers. TODO: Retrieve the max incoming MTU from TODO:
|
||||
* LocalDevice.getProperty().
|
||||
* NOTE: This value must be larger than or equal to the L2CAP SDU
|
||||
*/
|
||||
/*
|
||||
* android note set as 0xFFFE to match remote MPS
|
||||
*/
|
||||
public static final int MAX_PACKET_SIZE_INT = 0xFFFE;
|
||||
|
||||
// The minimum allowed max packet size is 255 according to the OBEX specification
|
||||
public static final int LOWER_LIMIT_MAX_PACKET_SIZE = 255;
|
||||
|
||||
/**
|
||||
* Temporary workaround to be able to push files to Windows 7.
|
||||
* TODO: Should be removed as soon as Microsoft updates their driver.
|
||||
*/
|
||||
public static final int MAX_CLIENT_PACKET_SIZE = 0xFC00;
|
||||
|
||||
public static final int OBEX_OPCODE_FINAL_BIT_MASK = 0x80;
|
||||
|
||||
public static final int OBEX_OPCODE_CONNECT = 0x80;
|
||||
|
||||
public static final int OBEX_OPCODE_DISCONNECT = 0x81;
|
||||
@ -119,6 +130,12 @@ public final class ObexHelper {
|
||||
|
||||
public static final int OBEX_AUTH_REALM_CHARSET_UNICODE = 0xFF;
|
||||
|
||||
public static final byte OBEX_SRM_ENABLE = 0x01; // For BT we only need enable/disable
|
||||
public static final byte OBEX_SRM_DISABLE = 0x00;
|
||||
public static final byte OBEX_SRM_SUPPORT = 0x02; // Unused for now
|
||||
|
||||
public static final byte OBEX_SRMP_WAIT = 0x01; // Only SRMP value used by BT
|
||||
|
||||
/**
|
||||
* Updates the HeaderSet with the headers received in the byte array
|
||||
* provided. Invalid headers are ignored.
|
||||
@ -314,7 +331,7 @@ public final class ObexHelper {
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// Not a valid header so ignore
|
||||
throw new IOException("Header was not formatted properly");
|
||||
throw new IOException("Header was not formatted properly", e);
|
||||
}
|
||||
index += 4;
|
||||
break;
|
||||
@ -322,7 +339,7 @@ public final class ObexHelper {
|
||||
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new IOException("Header was not formatted properly");
|
||||
throw new IOException("Header was not formatted properly", e);
|
||||
}
|
||||
|
||||
return body;
|
||||
@ -672,6 +689,33 @@ public final class ObexHelper {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO:
|
||||
// If the SRM and SRMP header is in use, they must be send in the same OBEX packet
|
||||
// But the current structure of the obex code cannot handle this, and therefore
|
||||
// it makes sense to put them in the tail of the headers, since we then reduce the
|
||||
// chance of enabling SRM to soon. The down side is that SRM cannot be used while
|
||||
// transferring non-body headers
|
||||
|
||||
// Add the SRM header
|
||||
byteHeader = (Byte)headImpl.getHeader(HeaderSet.SINGLE_RESPONSE_MODE);
|
||||
if (byteHeader != null) {
|
||||
out.write((byte)HeaderSet.SINGLE_RESPONSE_MODE);
|
||||
out.write(byteHeader.byteValue());
|
||||
if (nullOut) {
|
||||
headImpl.setHeader(HeaderSet.SINGLE_RESPONSE_MODE, null);
|
||||
}
|
||||
}
|
||||
|
||||
// Add the SRM parameter header
|
||||
byteHeader = (Byte)headImpl.getHeader(HeaderSet.SINGLE_RESPONSE_MODE_PARAMETER);
|
||||
if (byteHeader != null) {
|
||||
out.write((byte)HeaderSet.SINGLE_RESPONSE_MODE_PARAMETER);
|
||||
out.write(byteHeader.byteValue());
|
||||
if (nullOut) {
|
||||
headImpl.setHeader(HeaderSet.SINGLE_RESPONSE_MODE_PARAMETER, null);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (IOException e) {
|
||||
} finally {
|
||||
result = out.toByteArray();
|
||||
@ -702,6 +746,8 @@ public final class ObexHelper {
|
||||
int index = start;
|
||||
int length = 0;
|
||||
|
||||
// TODO: Ensure SRM and SRMP headers are not split into two OBEX packets
|
||||
|
||||
while ((fullLength < maxSize) && (index < headerArray.length)) {
|
||||
int headerID = (headerArray[index] < 0 ? headerArray[index] + 256 : headerArray[index]);
|
||||
lastLength = fullLength;
|
||||
@ -1008,4 +1054,39 @@ public final class ObexHelper {
|
||||
|
||||
return authChall;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the maximum allowed OBEX packet to transmit.
|
||||
* OBEX packets transmitted must be smaller than this value.
|
||||
* @param transport Reference to the ObexTransport in use.
|
||||
* @return the maximum allowed OBEX packet to transmit
|
||||
*/
|
||||
public static int getMaxTxPacketSize(ObexTransport transport) {
|
||||
int size = transport.getMaxTransmitPacketSize();
|
||||
return validateMaxPacketSize(size);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the maximum allowed OBEX packet to receive - used in OBEX connect.
|
||||
* @param transport
|
||||
* @return he maximum allowed OBEX packet to receive
|
||||
*/
|
||||
public static int getMaxRxPacketSize(ObexTransport transport) {
|
||||
int size = transport.getMaxReceivePacketSize();
|
||||
return validateMaxPacketSize(size);
|
||||
}
|
||||
|
||||
private static int validateMaxPacketSize(int size) {
|
||||
if(VDBG && (size > MAX_PACKET_SIZE_INT)) Log.w(TAG,
|
||||
"The packet size supported for the connection (" + size + ") is larger"
|
||||
+ " than the configured OBEX packet size: " + MAX_PACKET_SIZE_INT);
|
||||
if(size != -1) {
|
||||
if(size < LOWER_LIMIT_MAX_PACKET_SIZE) {
|
||||
throw new IllegalArgumentException(size + " is less that the lower limit: "
|
||||
+ LOWER_LIMIT_MAX_PACKET_SIZE);
|
||||
}
|
||||
return size;
|
||||
}
|
||||
return MAX_PACKET_SIZE_INT;
|
||||
}
|
||||
}
|
||||
|
71
obex/javax/obex/ObexPacket.java
Normal file
71
obex/javax/obex/ObexPacket.java
Normal 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;
|
||||
}
|
||||
}
|
@ -34,6 +34,8 @@ package javax.obex;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* The <code>ObexSession</code> interface characterizes the term
|
||||
* "OBEX Connection" as defined in the IrDA Object Exchange Protocol v1.2, which
|
||||
@ -47,6 +49,9 @@ import java.io.IOException;
|
||||
*/
|
||||
public class ObexSession {
|
||||
|
||||
private static final String TAG = "ObexSession";
|
||||
private static final boolean V = ObexHelper.VDBG;
|
||||
|
||||
protected Authenticator mAuthenticator;
|
||||
|
||||
protected byte[] mChallengeDigest;
|
||||
@ -125,6 +130,7 @@ public class ObexSession {
|
||||
result = mAuthenticator
|
||||
.onAuthenticationChallenge(realm, isUserIDRequired, isFullAccess);
|
||||
} catch (Exception e) {
|
||||
if (V) Log.d(TAG, "Exception occured - returning false", e);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -73,4 +73,39 @@ public interface ObexTransport {
|
||||
|
||||
DataOutputStream openDataOutputStream() throws IOException;
|
||||
|
||||
/**
|
||||
* Must return the maximum allowed OBEX packet that can be sent over
|
||||
* the transport. For L2CAP this will be the Max SDU reported by the
|
||||
* peer device.
|
||||
* The returned value will be used to set the outgoing OBEX packet
|
||||
* size. Therefore this value shall not change.
|
||||
* For RFCOMM or other transport types where the OBEX packets size
|
||||
* is unrelated to the transport packet size, return -1;
|
||||
* @return the maximum allowed OBEX packet that can be send over
|
||||
* the transport. Or -1 in case of don't care.
|
||||
*/
|
||||
int getMaxTransmitPacketSize();
|
||||
|
||||
/**
|
||||
* Must return the maximum allowed OBEX packet that can be received over
|
||||
* the transport. For L2CAP this will be the Max SDU configured for the
|
||||
* L2CAP channel.
|
||||
* The returned value will be used to validate the incoming packet size
|
||||
* values.
|
||||
* For RFCOMM or other transport types where the OBEX packets size
|
||||
* is unrelated to the transport packet size, return -1;
|
||||
* @return the maximum allowed OBEX packet that can be send over
|
||||
* the transport. Or -1 in case of don't care.
|
||||
*/
|
||||
int getMaxReceivePacketSize();
|
||||
|
||||
/**
|
||||
* Shall return true if the transport in use supports SRM.
|
||||
* @return
|
||||
* <code>true</code> if SRM operation is supported, and is to be enabled.
|
||||
* <code>false</code> if SRM operations are not supported, or should not be used.
|
||||
*/
|
||||
boolean isSrmSupported();
|
||||
|
||||
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
/*
|
||||
/* Copyright (c) 2015 The Android Open Source Project
|
||||
* Copyright (C) 2015 Samsung LSI
|
||||
* Copyright (c) 2008-2009, Motorola, Inc.
|
||||
*
|
||||
* All rights reserved.
|
||||
@ -39,6 +40,8 @@ import java.io.OutputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* This class implements the Operation interface for server side connections.
|
||||
* <P>
|
||||
@ -54,6 +57,10 @@ import java.io.ByteArrayOutputStream;
|
||||
*/
|
||||
public final class ServerOperation implements Operation, BaseStream {
|
||||
|
||||
private static final String TAG = "ServerOperation";
|
||||
|
||||
private static final boolean V = ObexHelper.VDBG; // Verbose debugging
|
||||
|
||||
public boolean isAborted;
|
||||
|
||||
public HeaderSet requestHeader;
|
||||
@ -78,6 +85,8 @@ public final class ServerOperation implements Operation, BaseStream {
|
||||
|
||||
private PrivateOutputStream mPrivateOutput;
|
||||
|
||||
private ObexTransport mTransport;
|
||||
|
||||
private boolean mPrivateOutputOpen;
|
||||
|
||||
private String mExceptionString;
|
||||
@ -89,6 +98,19 @@ public final class ServerOperation implements Operation, BaseStream {
|
||||
private boolean mHasBody;
|
||||
|
||||
private boolean mSendBodyHeader = true;
|
||||
// Assume SRM disabled - needs to be explicit
|
||||
// enabled by client
|
||||
private boolean mSrmEnabled = false;
|
||||
// A latch - when triggered, there is not way back ;-)
|
||||
private boolean mSrmActive = false;
|
||||
// Set to true when a SRM enable response have been send
|
||||
private boolean mSrmResponseSent = false;
|
||||
// keep waiting until final-bit is received in request
|
||||
// to handle the case where the SRM enable header is in
|
||||
// a different OBEX packet than the SRMP header.
|
||||
private boolean mSrmWaitingForRemote = true;
|
||||
// Why should we wait? - currently not exposed to apps.
|
||||
private boolean mSrmLocalWait = false;
|
||||
|
||||
/**
|
||||
* Creates new ServerOperation
|
||||
@ -116,12 +138,14 @@ public final class ServerOperation implements Operation, BaseStream {
|
||||
mRequestFinished = false;
|
||||
mPrivateOutputOpen = false;
|
||||
mHasBody = false;
|
||||
int bytesReceived;
|
||||
ObexPacket packet;
|
||||
mTransport = p.getTransport();
|
||||
|
||||
/*
|
||||
* Determine if this is a PUT request
|
||||
*/
|
||||
if ((request == 0x02) || (request == 0x82)) {
|
||||
if ((request == ObexHelper.OBEX_OPCODE_PUT) ||
|
||||
(request == ObexHelper.OBEX_OPCODE_PUT_FINAL)) {
|
||||
/*
|
||||
* It is a PUT request.
|
||||
*/
|
||||
@ -130,13 +154,14 @@ public final class ServerOperation implements Operation, BaseStream {
|
||||
/*
|
||||
* Determine if the final bit is set
|
||||
*/
|
||||
if ((request & 0x80) == 0) {
|
||||
if ((request & ObexHelper.OBEX_OPCODE_FINAL_BIT_MASK) == 0) {
|
||||
finalBitSet = false;
|
||||
} else {
|
||||
finalBitSet = true;
|
||||
mRequestFinished = true;
|
||||
}
|
||||
} else if ((request == 0x03) || (request == 0x83)) {
|
||||
} else if ((request == ObexHelper.OBEX_OPCODE_GET) ||
|
||||
(request == ObexHelper.OBEX_OPCODE_GET_FINAL)) {
|
||||
/*
|
||||
* It is a GET request.
|
||||
*/
|
||||
@ -145,71 +170,32 @@ public final class ServerOperation implements Operation, BaseStream {
|
||||
// For Get request, final bit set is decided by server side logic
|
||||
finalBitSet = false;
|
||||
|
||||
if (request == 0x83) {
|
||||
if (request == ObexHelper.OBEX_OPCODE_GET_FINAL) {
|
||||
mRequestFinished = true;
|
||||
}
|
||||
} else {
|
||||
throw new IOException("ServerOperation can not handle such request");
|
||||
}
|
||||
|
||||
int length = in.read();
|
||||
length = (length << 8) + in.read();
|
||||
packet = ObexPacket.read(request, mInput);
|
||||
|
||||
/*
|
||||
* Determine if the packet length is larger than this device can receive
|
||||
*/
|
||||
if (length > ObexHelper.MAX_PACKET_SIZE_INT) {
|
||||
if (packet.mLength > ObexHelper.getMaxRxPacketSize(mTransport)) {
|
||||
mParent.sendResponse(ResponseCodes.OBEX_HTTP_REQ_TOO_LARGE, null);
|
||||
throw new IOException("Packet received was too large");
|
||||
throw new IOException("Packet received was too large. Length: "
|
||||
+ packet.mLength + " maxLength: " + ObexHelper.getMaxRxPacketSize(mTransport));
|
||||
}
|
||||
|
||||
/*
|
||||
* Determine if any headers were sent in the initial request
|
||||
*/
|
||||
if (length > 3) {
|
||||
byte[] data = new byte[length - 3];
|
||||
bytesReceived = in.read(data);
|
||||
|
||||
while (bytesReceived != data.length) {
|
||||
bytesReceived += in.read(data, bytesReceived, data.length - bytesReceived);
|
||||
if (packet.mLength > 3) {
|
||||
if(!handleObexPacket(packet)) {
|
||||
return;
|
||||
}
|
||||
|
||||
byte[] body = ObexHelper.updateHeaderSet(requestHeader, data);
|
||||
|
||||
if (body != null) {
|
||||
mHasBody = true;
|
||||
}
|
||||
|
||||
if (mListener.getConnectionId() != -1 && requestHeader.mConnectionID != null) {
|
||||
mListener.setConnectionId(ObexHelper.convertToLong(requestHeader.mConnectionID));
|
||||
} else {
|
||||
mListener.setConnectionId(1);
|
||||
}
|
||||
|
||||
if (requestHeader.mAuthResp != null) {
|
||||
if (!mParent.handleAuthResp(requestHeader.mAuthResp)) {
|
||||
mExceptionString = "Authentication Failed";
|
||||
mParent.sendResponse(ResponseCodes.OBEX_HTTP_UNAUTHORIZED, null);
|
||||
mClosed = true;
|
||||
requestHeader.mAuthResp = null;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (requestHeader.mAuthChall != null) {
|
||||
mParent.handleAuthChall(requestHeader);
|
||||
// send the authResp to the client
|
||||
replyHeader.mAuthResp = new byte[requestHeader.mAuthResp.length];
|
||||
System.arraycopy(requestHeader.mAuthResp, 0, replyHeader.mAuthResp, 0,
|
||||
replyHeader.mAuthResp.length);
|
||||
requestHeader.mAuthResp = null;
|
||||
requestHeader.mAuthChall = null;
|
||||
|
||||
}
|
||||
|
||||
if (body != null) {
|
||||
mPrivateInput.writeBytes(body, 1);
|
||||
} else {
|
||||
if (!mHasBody) {
|
||||
while ((!mGetOperation) && (!finalBitSet)) {
|
||||
sendReply(ResponseCodes.OBEX_HTTP_CONTINUE);
|
||||
if (mPrivateInput.available() > 0) {
|
||||
@ -232,6 +218,100 @@ public final class ServerOperation implements Operation, BaseStream {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse headers and update member variables
|
||||
* @param packet the received obex packet
|
||||
* @return false for failing authentication - and a OBEX_HTTP_UNAUTHORIZED
|
||||
* response have been send. Else true.
|
||||
* @throws IOException
|
||||
*/
|
||||
private boolean handleObexPacket(ObexPacket packet) throws IOException {
|
||||
byte[] body = updateRequestHeaders(packet);
|
||||
|
||||
if (body != null) {
|
||||
mHasBody = true;
|
||||
}
|
||||
if (mListener.getConnectionId() != -1 && requestHeader.mConnectionID != null) {
|
||||
mListener.setConnectionId(ObexHelper
|
||||
.convertToLong(requestHeader.mConnectionID));
|
||||
} else {
|
||||
mListener.setConnectionId(1);
|
||||
}
|
||||
|
||||
if (requestHeader.mAuthResp != null) {
|
||||
if (!mParent.handleAuthResp(requestHeader.mAuthResp)) {
|
||||
mExceptionString = "Authentication Failed";
|
||||
mParent.sendResponse(ResponseCodes.OBEX_HTTP_UNAUTHORIZED, null);
|
||||
mClosed = true;
|
||||
requestHeader.mAuthResp = null;
|
||||
return false;
|
||||
}
|
||||
requestHeader.mAuthResp = null;
|
||||
}
|
||||
|
||||
if (requestHeader.mAuthChall != null) {
|
||||
mParent.handleAuthChall(requestHeader);
|
||||
// send the auhtResp to the client
|
||||
replyHeader.mAuthResp = new byte[requestHeader.mAuthResp.length];
|
||||
System.arraycopy(requestHeader.mAuthResp, 0, replyHeader.mAuthResp, 0,
|
||||
replyHeader.mAuthResp.length);
|
||||
requestHeader.mAuthResp = null;
|
||||
requestHeader.mAuthChall = null;
|
||||
}
|
||||
|
||||
if (body != null) {
|
||||
mPrivateInput.writeBytes(body, 1);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the request header set, and sniff on SRM headers to update local state.
|
||||
* @param data the OBEX packet data
|
||||
* @return any bytes in a body/end-of-body header returned by {@link ObexHelper.updateHeaderSet}
|
||||
* @throws IOException
|
||||
*/
|
||||
private byte[] updateRequestHeaders(ObexPacket packet) throws IOException {
|
||||
byte[] body = null;
|
||||
if (packet.mPayload != null) {
|
||||
body = ObexHelper.updateHeaderSet(requestHeader, packet.mPayload);
|
||||
}
|
||||
Byte srmMode = (Byte)requestHeader.getHeader(HeaderSet.SINGLE_RESPONSE_MODE);
|
||||
if(mTransport.isSrmSupported() && srmMode != null
|
||||
&& srmMode == ObexHelper.OBEX_SRM_ENABLE) {
|
||||
mSrmEnabled = true;
|
||||
if(V) Log.d(TAG,"SRM is now ENABLED (but not active) for this operation");
|
||||
}
|
||||
checkForSrmWait(packet.mHeaderId);
|
||||
if((!mSrmWaitingForRemote) && (mSrmEnabled)) {
|
||||
if(V) Log.d(TAG,"SRM is now ACTIVE for this operation");
|
||||
mSrmActive = true;
|
||||
}
|
||||
return body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this only when a complete request have been received.
|
||||
* (This is not optimal, but the current design is not really suited to
|
||||
* the way SRM is specified.)
|
||||
*/
|
||||
private void checkForSrmWait(int headerId){
|
||||
if (mSrmEnabled && (headerId == ObexHelper.OBEX_OPCODE_GET
|
||||
|| headerId == ObexHelper.OBEX_OPCODE_GET_FINAL
|
||||
|| headerId == ObexHelper.OBEX_OPCODE_PUT)) {
|
||||
try {
|
||||
mSrmWaitingForRemote = false;
|
||||
Byte srmp = (Byte)requestHeader.getHeader(HeaderSet.SINGLE_RESPONSE_MODE_PARAMETER);
|
||||
if(srmp != null && srmp == ObexHelper.OBEX_SRMP_WAIT) {
|
||||
mSrmWaitingForRemote = true;
|
||||
// Clear the wait header, as the absents of the header when the final bit is set
|
||||
// indicates don't wait.
|
||||
requestHeader.setHeader(HeaderSet.SINGLE_RESPONSE_MODE_PARAMETER, null);
|
||||
}
|
||||
} catch (IOException e) {if(V){Log.w(TAG,"Exception while extracting header",e);}}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isValidBody() {
|
||||
return mHasBody;
|
||||
}
|
||||
@ -274,17 +354,19 @@ public final class ServerOperation implements Operation, BaseStream {
|
||||
|
||||
/**
|
||||
* Sends a reply to the client. If the reply is a OBEX_HTTP_CONTINUE, it
|
||||
* will wait for a response from the client before ending.
|
||||
* will wait for a response from the client before ending unless SRM is active.
|
||||
* @param type the response code to send back to the client
|
||||
* @return <code>true</code> if the final bit was not set on the reply;
|
||||
* <code>false</code> if no reply was received because the operation
|
||||
* ended, an abort was received, or the final bit was set in the
|
||||
* reply
|
||||
* ended, an abort was received, the final bit was set in the
|
||||
* reply or SRM is active.
|
||||
* @throws IOException if an IO error occurs
|
||||
*/
|
||||
public synchronized boolean sendReply(int type) throws IOException {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
int bytesReceived;
|
||||
boolean skipSend = false;
|
||||
boolean skipReceive = false;
|
||||
boolean srmRespSendPending = false;
|
||||
|
||||
long id = mListener.getConnectionId();
|
||||
if (id == -1) {
|
||||
@ -293,7 +375,19 @@ public final class ServerOperation implements Operation, BaseStream {
|
||||
replyHeader.mConnectionID = ObexHelper.convertToByteArray(id);
|
||||
}
|
||||
|
||||
byte[] headerArray = ObexHelper.createHeader(replyHeader, true);
|
||||
if(mSrmEnabled && !mSrmResponseSent) {
|
||||
// As we are not ensured that the SRM enable is in the first OBEX packet
|
||||
// We must check for each reply.
|
||||
if(V)Log.v(TAG, "mSrmEnabled==true, sending SRM enable response.");
|
||||
replyHeader.setHeader(HeaderSet.SINGLE_RESPONSE_MODE, (byte)ObexHelper.OBEX_SRM_ENABLE);
|
||||
srmRespSendPending = true;
|
||||
}
|
||||
|
||||
if(mSrmEnabled && !mGetOperation && mSrmLocalWait) {
|
||||
replyHeader.setHeader(HeaderSet.SINGLE_RESPONSE_MODE, (byte)ObexHelper.OBEX_SRMP_WAIT);
|
||||
}
|
||||
|
||||
byte[] headerArray = ObexHelper.createHeader(replyHeader, true); // This clears the headers
|
||||
int bodyLength = -1;
|
||||
int orginalBodyLength = -1;
|
||||
|
||||
@ -347,6 +441,28 @@ public final class ServerOperation implements Operation, BaseStream {
|
||||
finalBitSet = true;
|
||||
}
|
||||
|
||||
if(mSrmActive) {
|
||||
if(!mGetOperation && type == ResponseCodes.OBEX_HTTP_CONTINUE &&
|
||||
mSrmResponseSent == true) {
|
||||
// we are in the middle of a SRM PUT operation, don't send a continue.
|
||||
skipSend = true;
|
||||
} else if(mGetOperation && mRequestFinished == false && mSrmResponseSent == true) {
|
||||
// We are still receiving the get request, receive, but don't send continue.
|
||||
skipSend = true;
|
||||
} else if(mGetOperation && mRequestFinished == true) {
|
||||
// All done receiving the GET request, send data to the client, without
|
||||
// expecting a continue.
|
||||
skipReceive = true;
|
||||
}
|
||||
if(V)Log.v(TAG, "type==" + type + " skipSend==" + skipSend
|
||||
+ " skipReceive==" + skipReceive);
|
||||
}
|
||||
if(srmRespSendPending) {
|
||||
if(V)Log.v(TAG,
|
||||
"SRM Enabled (srmRespSendPending == true)- sending SRM Enable response");
|
||||
mSrmResponseSent = true;
|
||||
}
|
||||
|
||||
if ((finalBitSet) || (headerArray.length < (mMaxPacketLength - 20))) {
|
||||
if (bodyLength > 0) {
|
||||
/*
|
||||
@ -387,7 +503,7 @@ public final class ServerOperation implements Operation, BaseStream {
|
||||
}
|
||||
|
||||
if ((finalBitSet) && (type == ResponseCodes.OBEX_HTTP_OK) && (orginalBodyLength <= 0)) {
|
||||
if(mSendBodyHeader == true) {
|
||||
if(mSendBodyHeader) {
|
||||
out.write(0x49);
|
||||
orginalBodyLength = 3;
|
||||
out.write((byte)(orginalBodyLength >> 8));
|
||||
@ -395,107 +511,66 @@ public final class ServerOperation implements Operation, BaseStream {
|
||||
}
|
||||
}
|
||||
|
||||
mResponseSize = 3;
|
||||
mParent.sendResponse(type, out.toByteArray());
|
||||
if(skipSend == false) {
|
||||
mResponseSize = 3;
|
||||
mParent.sendResponse(type, out.toByteArray());
|
||||
}
|
||||
|
||||
if (type == ResponseCodes.OBEX_HTTP_CONTINUE) {
|
||||
int headerID = mInput.read();
|
||||
int length = mInput.read();
|
||||
length = (length << 8) + mInput.read();
|
||||
if ((headerID != ObexHelper.OBEX_OPCODE_PUT)
|
||||
&& (headerID != ObexHelper.OBEX_OPCODE_PUT_FINAL)
|
||||
&& (headerID != ObexHelper.OBEX_OPCODE_GET)
|
||||
&& (headerID != ObexHelper.OBEX_OPCODE_GET_FINAL)) {
|
||||
|
||||
if (length > 3) {
|
||||
byte[] temp = new byte[length - 3];
|
||||
// First three bytes already read, compensating for this
|
||||
bytesReceived = mInput.read(temp);
|
||||
|
||||
while (bytesReceived != temp.length) {
|
||||
bytesReceived += mInput.read(temp, bytesReceived,
|
||||
temp.length - bytesReceived);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Determine if an ABORT was sent as the reply
|
||||
*/
|
||||
if (headerID == ObexHelper.OBEX_OPCODE_ABORT) {
|
||||
mParent.sendResponse(ResponseCodes.OBEX_HTTP_OK, null);
|
||||
mClosed = true;
|
||||
isAborted = true;
|
||||
mExceptionString = "Abort Received";
|
||||
throw new IOException("Abort Received");
|
||||
} else {
|
||||
mParent.sendResponse(ResponseCodes.OBEX_HTTP_BAD_REQUEST, null);
|
||||
mClosed = true;
|
||||
mExceptionString = "Bad Request Received";
|
||||
throw new IOException("Bad Request Received");
|
||||
}
|
||||
if(mGetOperation && skipReceive) {
|
||||
// Here we need to check for and handle abort (throw an exception).
|
||||
// Any other signal received should be discarded silently (only on server side)
|
||||
checkSrmRemoteAbort();
|
||||
} else {
|
||||
// Receive and handle data (only send reply if !skipSend)
|
||||
// Read a complete OBEX Packet
|
||||
ObexPacket packet = ObexPacket.read(mInput);
|
||||
|
||||
if ((headerID == ObexHelper.OBEX_OPCODE_PUT_FINAL)) {
|
||||
finalBitSet = true;
|
||||
} else if (headerID == ObexHelper.OBEX_OPCODE_GET_FINAL) {
|
||||
mRequestFinished = true;
|
||||
}
|
||||
int headerId = packet.mHeaderId;
|
||||
if ((headerId != ObexHelper.OBEX_OPCODE_PUT)
|
||||
&& (headerId != ObexHelper.OBEX_OPCODE_PUT_FINAL)
|
||||
&& (headerId != ObexHelper.OBEX_OPCODE_GET)
|
||||
&& (headerId != ObexHelper.OBEX_OPCODE_GET_FINAL)) {
|
||||
|
||||
/*
|
||||
* Determine if the packet length is larger then this device can receive
|
||||
*/
|
||||
if (length > ObexHelper.MAX_PACKET_SIZE_INT) {
|
||||
mParent.sendResponse(ResponseCodes.OBEX_HTTP_REQ_TOO_LARGE, null);
|
||||
throw new IOException("Packet received was too large");
|
||||
}
|
||||
|
||||
/*
|
||||
* Determine if any headers were sent in the initial request
|
||||
*/
|
||||
if (length > 3) {
|
||||
byte[] data = new byte[length - 3];
|
||||
bytesReceived = mInput.read(data);
|
||||
|
||||
while (bytesReceived != data.length) {
|
||||
bytesReceived += mInput.read(data, bytesReceived, data.length
|
||||
- bytesReceived);
|
||||
}
|
||||
byte[] body = ObexHelper.updateHeaderSet(requestHeader, data);
|
||||
if (body != null) {
|
||||
mHasBody = true;
|
||||
}
|
||||
if (mListener.getConnectionId() != -1 && requestHeader.mConnectionID != null) {
|
||||
mListener.setConnectionId(ObexHelper
|
||||
.convertToLong(requestHeader.mConnectionID));
|
||||
/*
|
||||
* Determine if an ABORT was sent as the reply
|
||||
*/
|
||||
if (headerId == ObexHelper.OBEX_OPCODE_ABORT) {
|
||||
handleRemoteAbort();
|
||||
} else {
|
||||
mListener.setConnectionId(1);
|
||||
// TODO:shall we send this if it occurs during SRM? Errata on the subject
|
||||
mParent.sendResponse(ResponseCodes.OBEX_HTTP_BAD_REQUEST, null);
|
||||
mClosed = true;
|
||||
mExceptionString = "Bad Request Received";
|
||||
throw new IOException("Bad Request Received");
|
||||
}
|
||||
} else {
|
||||
|
||||
if ((headerId == ObexHelper.OBEX_OPCODE_PUT_FINAL)) {
|
||||
finalBitSet = true;
|
||||
} else if (headerId == ObexHelper.OBEX_OPCODE_GET_FINAL) {
|
||||
mRequestFinished = true;
|
||||
}
|
||||
|
||||
if (requestHeader.mAuthResp != null) {
|
||||
if (!mParent.handleAuthResp(requestHeader.mAuthResp)) {
|
||||
mExceptionString = "Authentication Failed";
|
||||
mParent.sendResponse(ResponseCodes.OBEX_HTTP_UNAUTHORIZED, null);
|
||||
mClosed = true;
|
||||
requestHeader.mAuthResp = null;
|
||||
/*
|
||||
* Determine if the packet length is larger than the negotiated packet size
|
||||
*/
|
||||
if (packet.mLength > ObexHelper.getMaxRxPacketSize(mTransport)) {
|
||||
mParent.sendResponse(ResponseCodes.OBEX_HTTP_REQ_TOO_LARGE, null);
|
||||
throw new IOException("Packet received was too large");
|
||||
}
|
||||
|
||||
/*
|
||||
* Determine if any headers were sent in the initial request
|
||||
*/
|
||||
if (packet.mLength > 3 || (mSrmEnabled && packet.mLength == 3)) {
|
||||
if(handleObexPacket(packet) == false) {
|
||||
return false;
|
||||
}
|
||||
requestHeader.mAuthResp = null;
|
||||
}
|
||||
|
||||
if (requestHeader.mAuthChall != null) {
|
||||
mParent.handleAuthChall(requestHeader);
|
||||
// send the auhtResp to the client
|
||||
replyHeader.mAuthResp = new byte[requestHeader.mAuthResp.length];
|
||||
System.arraycopy(requestHeader.mAuthResp, 0, replyHeader.mAuthResp, 0,
|
||||
replyHeader.mAuthResp.length);
|
||||
requestHeader.mAuthResp = null;
|
||||
requestHeader.mAuthChall = null;
|
||||
}
|
||||
|
||||
if (body != null) {
|
||||
mPrivateInput.writeBytes(body, 1);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
@ -503,6 +578,53 @@ public final class ServerOperation implements Operation, BaseStream {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will look for an abort from the peer during a SRM transfer.
|
||||
* The function will not block if no data has been received from the remote device.
|
||||
* If data have been received, the function will block while reading the incoming
|
||||
* OBEX package.
|
||||
* An Abort request will be handled, and cause an IOException("Abort Received").
|
||||
* Other messages will be discarded silently as per GOEP specification.
|
||||
* @throws IOException if an abort request have been received.
|
||||
* TODO: I think this is an error in the specification. If we discard other messages,
|
||||
* the peer device will most likely stall, as it will not receive the expected
|
||||
* response for the message...
|
||||
* I'm not sure how to understand "Receipt of invalid or unexpected SRM or SRMP
|
||||
* header values shall be ignored by the receiving device."
|
||||
* If any signal is received during an active SRM transfer it is unexpected regardless
|
||||
* whether or not it contains SRM/SRMP headers...
|
||||
*/
|
||||
private void checkSrmRemoteAbort() throws IOException {
|
||||
if(mInput.available() > 0) {
|
||||
ObexPacket packet = ObexPacket.read(mInput);
|
||||
/*
|
||||
* Determine if an ABORT was sent as the reply
|
||||
*/
|
||||
if (packet.mHeaderId == ObexHelper.OBEX_OPCODE_ABORT) {
|
||||
handleRemoteAbort();
|
||||
} else {
|
||||
// TODO: should we throw an exception here anyway? - don't see how to
|
||||
// ignore SRM/SRMP headers without ignoring the complete signal
|
||||
// (in this particular case).
|
||||
Log.w(TAG, "Received unexpected request from client - discarding...\n"
|
||||
+ " headerId: " + packet.mHeaderId + " length: " + packet.mLength);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handleRemoteAbort() throws IOException {
|
||||
/* TODO: To increase the speed of the abort operation in SRM, we need
|
||||
* to be able to flush the L2CAP queue for the PSM in use.
|
||||
* This could be implemented by introducing a control
|
||||
* message to be send over the socket, that in the abort case
|
||||
* could carry a flush command. */
|
||||
mParent.sendResponse(ResponseCodes.OBEX_HTTP_OK, null);
|
||||
mClosed = true;
|
||||
isAborted = true;
|
||||
mExceptionString = "Abort Received";
|
||||
throw new IOException("Abort Received");
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an ABORT message to the server. By calling this method, the
|
||||
* corresponding input and output streams will be closed along with this
|
||||
|
@ -275,4 +275,13 @@ public class ServerRequestHandler {
|
||||
*/
|
||||
public void onClose() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Override to add Single Response Mode support - e.g. if the supplied
|
||||
* transport is l2cap.
|
||||
* @return True if SRM is supported, else False
|
||||
*/
|
||||
public boolean isSrmSupported() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
* Copyright (c) 2015 Samsung LSI
|
||||
* Copyright (c) 2008-2009, Motorola, Inc.
|
||||
*
|
||||
* All rights reserved.
|
||||
@ -45,6 +47,7 @@ import java.io.OutputStream;
|
||||
public final class ServerSession extends ObexSession implements Runnable {
|
||||
|
||||
private static final String TAG = "Obex ServerSession";
|
||||
private static final boolean V = ObexHelper.VDBG;
|
||||
|
||||
private ObexTransport mTransport;
|
||||
|
||||
@ -91,7 +94,9 @@ public final class ServerSession extends ObexSession implements Runnable {
|
||||
|
||||
boolean done = false;
|
||||
while (!done && !mClosed) {
|
||||
if(V) Log.v(TAG, "Waiting for incoming request...");
|
||||
int requestType = mInput.read();
|
||||
if(V) Log.v(TAG, "Read request: " + requestType);
|
||||
switch (requestType) {
|
||||
case ObexHelper.OBEX_OPCODE_CONNECT:
|
||||
handleConnectRequest();
|
||||
@ -140,9 +145,9 @@ public final class ServerSession extends ObexSession implements Runnable {
|
||||
}
|
||||
|
||||
} catch (NullPointerException e) {
|
||||
Log.d(TAG, e.toString());
|
||||
Log.d(TAG, "Exception occured - ignoring", e);
|
||||
} catch (Exception e) {
|
||||
Log.d(TAG, e.toString());
|
||||
Log.d(TAG, "Exception occured - ignoring", e);
|
||||
}
|
||||
close();
|
||||
}
|
||||
@ -163,7 +168,7 @@ public final class ServerSession extends ObexSession implements Runnable {
|
||||
|
||||
int length = mInput.read();
|
||||
length = (length << 8) + mInput.read();
|
||||
if (length > ObexHelper.MAX_PACKET_SIZE_INT) {
|
||||
if (length > ObexHelper.getMaxRxPacketSize(mTransport)) {
|
||||
code = ResponseCodes.OBEX_HTTP_REQ_TOO_LARGE;
|
||||
} else {
|
||||
for (int i = 3; i < length; i++) {
|
||||
@ -215,6 +220,7 @@ public final class ServerSession extends ObexSession implements Runnable {
|
||||
*internal error should not be sent because server has already replied with
|
||||
*OK response in "sendReply")
|
||||
*/
|
||||
if(V) Log.d(TAG,"Exception occured - sending OBEX_HTTP_INTERNAL_ERROR reply",e);
|
||||
if (!op.isAborted) {
|
||||
sendResponse(ResponseCodes.OBEX_HTTP_INTERNAL_ERROR, null);
|
||||
}
|
||||
@ -243,6 +249,7 @@ public final class ServerSession extends ObexSession implements Runnable {
|
||||
op.sendReply(response);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
if(V) Log.d(TAG,"Exception occured - sending OBEX_HTTP_INTERNAL_ERROR reply",e);
|
||||
sendResponse(ResponseCodes.OBEX_HTTP_INTERNAL_ERROR, null);
|
||||
}
|
||||
}
|
||||
@ -275,7 +282,7 @@ public final class ServerSession extends ObexSession implements Runnable {
|
||||
data[2] = (byte)totalLength;
|
||||
}
|
||||
op.write(data);
|
||||
op.flush();
|
||||
op.flush(); // TODO: Do we need to flush?
|
||||
}
|
||||
|
||||
/**
|
||||
@ -304,7 +311,7 @@ public final class ServerSession extends ObexSession implements Runnable {
|
||||
flags = mInput.read();
|
||||
constants = mInput.read();
|
||||
|
||||
if (length > ObexHelper.MAX_PACKET_SIZE_INT) {
|
||||
if (length > ObexHelper.getMaxRxPacketSize(mTransport)) {
|
||||
code = ResponseCodes.OBEX_HTTP_REQ_TOO_LARGE;
|
||||
totalLength = 3;
|
||||
} else {
|
||||
@ -358,6 +365,7 @@ public final class ServerSession extends ObexSession implements Runnable {
|
||||
try {
|
||||
code = mListener.onSetPath(request, reply, backup, create);
|
||||
} catch (Exception e) {
|
||||
if(V) Log.d(TAG,"Exception occured - sending OBEX_HTTP_INTERNAL_ERROR reply",e);
|
||||
sendResponse(ResponseCodes.OBEX_HTTP_INTERNAL_ERROR, null);
|
||||
return;
|
||||
}
|
||||
@ -425,7 +433,7 @@ public final class ServerSession extends ObexSession implements Runnable {
|
||||
length = mInput.read();
|
||||
length = (length << 8) + mInput.read();
|
||||
|
||||
if (length > ObexHelper.MAX_PACKET_SIZE_INT) {
|
||||
if (length > ObexHelper.getMaxRxPacketSize(mTransport)) {
|
||||
code = ResponseCodes.OBEX_HTTP_REQ_TOO_LARGE;
|
||||
totalLength = 3;
|
||||
} else {
|
||||
@ -466,6 +474,7 @@ public final class ServerSession extends ObexSession implements Runnable {
|
||||
try {
|
||||
mListener.onDisconnect(request, reply);
|
||||
} catch (Exception e) {
|
||||
if(V) Log.d(TAG,"Exception occured - sending OBEX_HTTP_INTERNAL_ERROR reply",e);
|
||||
sendResponse(ResponseCodes.OBEX_HTTP_INTERNAL_ERROR, null);
|
||||
return;
|
||||
}
|
||||
@ -531,23 +540,38 @@ public final class ServerSession extends ObexSession implements Runnable {
|
||||
HeaderSet reply = new HeaderSet();
|
||||
int bytesReceived;
|
||||
|
||||
if(V) Log.v(TAG,"handleConnectRequest()");
|
||||
|
||||
/*
|
||||
* Read in the length of the OBEX packet, OBEX version, flags, and max
|
||||
* packet length
|
||||
*/
|
||||
packetLength = mInput.read();
|
||||
packetLength = (packetLength << 8) + mInput.read();
|
||||
if(V) Log.v(TAG,"handleConnectRequest() - packetLength: " + packetLength);
|
||||
|
||||
version = mInput.read();
|
||||
flags = mInput.read();
|
||||
mMaxPacketLength = mInput.read();
|
||||
mMaxPacketLength = (mMaxPacketLength << 8) + mInput.read();
|
||||
|
||||
if(V) Log.v(TAG,"handleConnectRequest() - version: " + version
|
||||
+ " MaxLength: " + mMaxPacketLength + " flags: " + flags);
|
||||
|
||||
// should we check it?
|
||||
if (mMaxPacketLength > ObexHelper.MAX_PACKET_SIZE_INT) {
|
||||
mMaxPacketLength = ObexHelper.MAX_PACKET_SIZE_INT;
|
||||
}
|
||||
|
||||
if (packetLength > ObexHelper.MAX_PACKET_SIZE_INT) {
|
||||
if(mMaxPacketLength > ObexHelper.getMaxTxPacketSize(mTransport)) {
|
||||
Log.w(TAG, "Requested MaxObexPacketSize " + mMaxPacketLength
|
||||
+ " is larger than the max size supported by the transport: "
|
||||
+ ObexHelper.getMaxTxPacketSize(mTransport)
|
||||
+ " Reducing to this size.");
|
||||
mMaxPacketLength = ObexHelper.getMaxTxPacketSize(mTransport);
|
||||
}
|
||||
|
||||
if (packetLength > ObexHelper.getMaxRxPacketSize(mTransport)) {
|
||||
code = ResponseCodes.OBEX_HTTP_REQ_TOO_LARGE;
|
||||
totalLength = 7;
|
||||
} else {
|
||||
@ -614,7 +638,7 @@ public final class ServerSession extends ObexSession implements Runnable {
|
||||
code = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
if(V) Log.d(TAG,"Exception occured - sending OBEX_HTTP_INTERNAL_ERROR reply",e);
|
||||
totalLength = 7;
|
||||
head = null;
|
||||
code = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
|
||||
@ -633,13 +657,14 @@ public final class ServerSession extends ObexSession implements Runnable {
|
||||
* Packet Length (Defined in MAX_PACKET_SIZE) Byte 7 to n: headers
|
||||
*/
|
||||
byte[] sendData = new byte[totalLength];
|
||||
int maxRxLength = ObexHelper.getMaxRxPacketSize(mTransport);
|
||||
sendData[0] = (byte)code;
|
||||
sendData[1] = length[2];
|
||||
sendData[2] = length[3];
|
||||
sendData[3] = (byte)0x10;
|
||||
sendData[4] = (byte)0x00;
|
||||
sendData[5] = (byte)(ObexHelper.MAX_PACKET_SIZE_INT >> 8);
|
||||
sendData[6] = (byte)(ObexHelper.MAX_PACKET_SIZE_INT & 0xFF);
|
||||
sendData[5] = (byte)(maxRxLength >> 8);
|
||||
sendData[6] = (byte)(maxRxLength & 0xFF);
|
||||
|
||||
if (head != null) {
|
||||
System.arraycopy(head, 0, sendData, 7, head.length);
|
||||
@ -659,11 +684,16 @@ public final class ServerSession extends ObexSession implements Runnable {
|
||||
mListener.onClose();
|
||||
}
|
||||
try {
|
||||
mInput.close();
|
||||
mOutput.close();
|
||||
mTransport.close();
|
||||
/* Set state to closed before interrupting the thread by closing the streams */
|
||||
mClosed = true;
|
||||
if(mInput != null)
|
||||
mInput.close();
|
||||
if(mOutput != null)
|
||||
mOutput.close();
|
||||
if(mTransport != null)
|
||||
mTransport.close();
|
||||
} catch (Exception e) {
|
||||
if(V) Log.d(TAG,"Exception occured during close() - ignore",e);
|
||||
}
|
||||
mTransport = null;
|
||||
mInput = null;
|
||||
@ -702,4 +732,7 @@ public final class ServerSession extends ObexSession implements Runnable {
|
||||
return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
|
||||
}
|
||||
|
||||
public ObexTransport getTransport() {
|
||||
return mTransport;
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user