e126bd500e
Apparently there's a race condition in the OBEX transport that causes the connection to phones via Bluetooth to be shut down prematurely. Some phones react by doing a slow sync instead of an incremental sync the next time. Waiting during shutdown should address the problem (however, it was not possible to confirm this).
795 lines
30 KiB
C++
795 lines
30 KiB
C++
/*
|
|
* Copyright (C) 2009 Intel Corporation
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) version 3.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
|
* 02110-1301 USA
|
|
*/
|
|
|
|
#include <config.h>
|
|
|
|
#ifdef ENABLE_OBEX
|
|
#ifdef ENABLE_BLUETOOTH
|
|
#include <bluetooth/bluetooth.h>
|
|
#include <bluetooth/sdp.h>
|
|
#include <bluetooth/sdp_lib.h>
|
|
#include <bluetooth/rfcomm.h>
|
|
#endif
|
|
|
|
#include <synthesis/syerror.h>
|
|
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#include <errno.h>
|
|
#include <sys/socket.h>
|
|
#include <glib.h>
|
|
#include <syncevo/SuspendFlags.h>
|
|
#include <syncevo/ObexTransportAgent.h>
|
|
|
|
#include <syncevo/declarations.h>
|
|
|
|
SE_BEGIN_CXX
|
|
|
|
ObexTransportAgent::ObexTransportAgent (OBEX_TRANS_TYPE type, GMainLoop *loop) :
|
|
m_status(INACTIVE),
|
|
m_transType(type),
|
|
m_context(g_main_context_ref(loop ?
|
|
g_main_loop_get_context(loop) :
|
|
g_main_context_default())),
|
|
m_address(""),
|
|
m_port(-1),
|
|
m_buffer(NULL),
|
|
m_bufferSize(0),
|
|
m_timeoutSeconds(0),
|
|
m_disconnecting(false),
|
|
m_connectStatus(START)
|
|
{
|
|
}
|
|
|
|
ObexTransportAgent::~ObexTransportAgent()
|
|
{
|
|
}
|
|
|
|
/*
|
|
* Only setURL if the address/port has not been initialized;
|
|
* because the URL is not likely be changed during a
|
|
* sync session.
|
|
* For Bluetooth devices the URL maybe splitted as two parts:
|
|
* address and channel, the delimiter is '+'.
|
|
**/
|
|
void ObexTransportAgent::setURL(const std::string &url)
|
|
{
|
|
if(m_address.empty()){
|
|
if(m_transType == OBEX_BLUETOOTH) {
|
|
size_t pos = url.find_last_of('+');
|
|
if (pos != std::string::npos) {
|
|
m_address = url.substr(0,pos);
|
|
m_port = atoi(url.substr(pos+1, std::string::npos).c_str());
|
|
if(!m_port) {
|
|
SE_THROW_EXCEPTION(TransportException, "ObexTransport: Malformed url");
|
|
}
|
|
} else {
|
|
m_address = url;
|
|
}
|
|
if(m_address.empty()) {
|
|
SE_THROW_EXCEPTION (TransportException, "ObexTransport: Malformed url");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void ObexTransportAgent::setContentType(const std::string &type) {
|
|
m_contentType = type;
|
|
}
|
|
|
|
|
|
void ObexTransportAgent::setTimeout(int seconds)
|
|
{
|
|
m_timeoutSeconds = seconds;
|
|
}
|
|
|
|
void ObexTransportAgent::connect() {
|
|
m_obexReady = false;
|
|
if(m_transType == OBEX_BLUETOOTH) {
|
|
if(m_port == -1) {
|
|
//use sdp to detect the appropriate channel
|
|
//Do not use BDADDR_ANY to avoid a warning
|
|
bdaddr_t bdaddr, anyaddr ={{0,0,0,0,0,0}};
|
|
str2ba (m_address.c_str(), &bdaddr);
|
|
m_sdp.set(sdp_connect(&anyaddr, &bdaddr, SDP_NON_BLOCKING),
|
|
"ObexTransport Bluetooth sdp connect failed");
|
|
m_connectStatus = SDP_START;
|
|
GIOChannelPtr sdpIO(g_io_channel_unix_new(sdp_get_socket(m_sdp)),
|
|
"sdp socket channel");
|
|
m_sdpEvent.set(g_io_add_watch(sdpIO, (GIOCondition) (G_IO_IN|G_IO_OUT|G_IO_HUP|G_IO_ERR|G_IO_NVAL), sdp_source_cb, static_cast <void*> (this)),
|
|
"IO watch for SDP");
|
|
} else {
|
|
m_connectStatus = ADDR_READY;
|
|
connectInit();
|
|
}
|
|
}
|
|
/*Wait until connection is sucessfully set up*/
|
|
wait(true);
|
|
if (m_connectStatus != CONNECTED) {
|
|
SE_THROW_EXCEPTION (TransportException, "ObexTransport: connection setup failed");
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Called when the address of remote peer is available, maybe via some
|
|
* discovery mechanism.
|
|
*/
|
|
void ObexTransportAgent::connectInit () {
|
|
if (m_connectStatus != ADDR_READY) {
|
|
SE_THROW_EXCEPTION (TransportException, "ObexTransport: address info for remote peer not ready");
|
|
}
|
|
if(m_transType == OBEX_BLUETOOTH) {
|
|
bdaddr_t bdaddr;
|
|
str2ba (m_address.c_str(), &bdaddr);
|
|
if(m_port == -1) {
|
|
SE_THROW_EXCEPTION (TransportException,
|
|
"ObexTransport: no channel found for Bluetooth peer");
|
|
}
|
|
int sockfd = socket (AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM);
|
|
if (sockfd == -1) {
|
|
SE_THROW_EXCEPTION (TransportException, "Error creating Bluetooth socket");
|
|
}
|
|
cxxptr<Socket> sockObj (new Socket (sockfd));
|
|
SE_LOG_DEV(NULL, "Connecting Bluetooth device with address %s and channel %d",
|
|
m_address.c_str(), m_port);
|
|
// Init the OBEX handle
|
|
ObexPtr handle(OBEX_Init (OBEX_TRANS_FD, obex_event, 0),
|
|
"Obex Handle");
|
|
|
|
OBEX_SetUserData (handle, static_cast<void *> (this));
|
|
// Connect the device, do not use BtOBEX_TransportConnect as it is
|
|
// blocking.
|
|
sockaddr_rc any;
|
|
bdaddr_t anyaddr ={{0,0,0,0,0,0}};
|
|
memset(&any, 0, sizeof(any));
|
|
any.rc_family = AF_BLUETOOTH;
|
|
bacpy (&any.rc_bdaddr, &anyaddr);
|
|
any.rc_channel = 0;
|
|
if (bind (sockfd, (struct sockaddr *) &any, sizeof (sockaddr_rc)) <0){
|
|
SE_THROW_EXCEPTION (TransportException, "ObexTransport: Socket bind failed");
|
|
}
|
|
//set socket to non-blocking
|
|
int flags = fcntl (sockfd, F_GETFL);
|
|
fcntl(sockfd, F_SETFL, flags & ~O_NONBLOCK);
|
|
//create the io channel
|
|
GIOChannelPtr channel(g_io_channel_unix_new (sockfd),
|
|
"socket io channel");
|
|
GLibEvent obexEvent(g_io_add_watch (channel, (GIOCondition) (G_IO_IN|G_IO_OUT|G_IO_HUP|G_IO_ERR|G_IO_NVAL), obex_fd_source_cb, static_cast<void *> (this)),
|
|
"socket io channel watch");
|
|
|
|
//connect to remote device
|
|
sockaddr_rc peer;
|
|
memset(&peer, 0, sizeof(peer));
|
|
peer.rc_family = AF_BLUETOOTH;
|
|
bacpy (&peer.rc_bdaddr , &bdaddr);
|
|
peer.rc_channel = m_port;
|
|
|
|
if (::connect (sockfd, (struct sockaddr *) &peer, sizeof (sockaddr_rc)) == -1) {
|
|
//check the error status
|
|
if (errno == EINPROGRESS || errno == EAGAIN){
|
|
m_connectStatus = INIT0;
|
|
//copy sockobj so that it will not close the underlying socket
|
|
m_sock = sockObj;
|
|
m_obexEvent = obexEvent;
|
|
m_channel = channel;
|
|
m_handle = handle;
|
|
return;
|
|
} else {
|
|
SE_LOG_ERROR(NULL, "connect failed with error code %d", errno);
|
|
SE_THROW_EXCEPTION (TransportException, "ObexTransport: connect request failed with error");
|
|
}
|
|
}
|
|
|
|
// Put Connect Cmd to init the session
|
|
m_connectStatus = INIT1;
|
|
m_sock = sockObj;
|
|
m_obexEvent = obexEvent;
|
|
m_channel = channel;
|
|
m_handle = handle;
|
|
connectReq();
|
|
}//Bluetooth
|
|
else {
|
|
m_status = FAILED;
|
|
SE_LOG_ERROR(NULL, "ObexTransport: unsuported transport type");
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* After OBEX Handle is inited and the device is connected,
|
|
* send the connect cmd to initialize the session
|
|
*/
|
|
void ObexTransportAgent::connectReq() {
|
|
if (m_connectStatus!=INIT1 || !m_handle) {
|
|
SE_THROW_EXCEPTION (TransportException, "ObexTransport: OBEX Handle not inited or device not connected");
|
|
}
|
|
|
|
//set up transport mtu
|
|
OBEX_SetTransportMTU (m_handle, DEFAULT_RX_MTU, DEFAULT_TX_MTU);
|
|
|
|
// set up the fd transport
|
|
if (FdOBEX_TransportSetup (m_handle, m_sock->get(), m_sock->get(), OBEX_MAXIMUM_MTU) <0) {
|
|
SE_THROW_EXCEPTION (TransportException, "ObexTransport: Fd transport set up failed");
|
|
}
|
|
|
|
obex_object_t *connect = newCmd(OBEX_CMD_CONNECT);
|
|
/* Add the header for the sync target */
|
|
obex_headerdata_t header;
|
|
header.bs = (unsigned char *) "SYNCML-SYNC";
|
|
OBEX_ObjectAddHeader(m_handle, connect, OBEX_HDR_TARGET, header, strlen ((char *) header.bs), OBEX_FL_FIT_ONE_PACKET);
|
|
m_obexReady = false;
|
|
m_requestStart = time (NULL);
|
|
if (OBEX_Request (m_handle, connect) <0) {
|
|
SE_THROW_EXCEPTION (TransportException, "ObexTransport: OBEX connect init failed");
|
|
}
|
|
m_connectStatus = INIT2;
|
|
}
|
|
|
|
void ObexTransportAgent::shutdown() {
|
|
SE_LOG_DEV(NULL, "ObexTransportAgent::shutdown()");
|
|
|
|
// This gets called directly after a send() of the final SyncML message.
|
|
// We must ensure that this final message goes out before cancelling
|
|
// anything. Strictly speaking, we shouldn't block here. But the state
|
|
// machine is complex enough already as it is, so better block for the
|
|
// the (short!) final message to be sent.
|
|
wait(true);
|
|
|
|
//reset up obex fd soruce
|
|
GLibEvent obexEventSource;
|
|
if (m_channel) {
|
|
obexEventSource.set(g_io_add_watch(m_channel,
|
|
(GIOCondition) (G_IO_IN|G_IO_OUT|G_IO_HUP|G_IO_ERR|G_IO_NVAL),
|
|
obex_fd_source_cb, static_cast<void *> (this)),
|
|
"OBEX shutdown event");
|
|
}
|
|
|
|
if (m_handle) {
|
|
SE_LOG_DEV(NULL, "ObexTransportAgent::shutdown: cancel request");
|
|
/* It might be true there is an ongoing OBEX request undergoing,
|
|
* must cancel it before sending another cmd */
|
|
OBEX_CancelRequest (m_handle, 0);
|
|
}
|
|
|
|
//block a while waiting for the disconnect response, we will disconnects
|
|
//always even without a response.
|
|
m_obexReady = false;
|
|
if (!m_disconnecting) {
|
|
m_disconnecting = true;
|
|
if (m_handle) {
|
|
obex_object_t *disconnect = newCmd(OBEX_CMD_DISCONNECT);
|
|
//add header "connection id"
|
|
obex_headerdata_t header;
|
|
header.bq4 = m_connectId;
|
|
SE_LOG_DEV(NULL, "ObexTransportAgent::shutdown: send OBEX_CMD_DISCONNECT");
|
|
OBEX_ObjectAddHeader (m_handle, disconnect, OBEX_HDR_CONNECTION, header, sizeof
|
|
(m_connectId), OBEX_FL_FIT_ONE_PACKET);
|
|
if (OBEX_Request (m_handle, disconnect) <0) {
|
|
m_status = FAILED;
|
|
SE_THROW_EXCEPTION (TransportException, "ObexTransport: OBEX disconnect cmd failed");
|
|
}
|
|
}
|
|
}
|
|
m_obexEvent = obexEventSource;
|
|
}
|
|
|
|
/**
|
|
* Send the request to peer
|
|
*/
|
|
void ObexTransportAgent::send(const char *data, size_t len) {
|
|
SE_LOG_DEV(NULL, "ObexTransport send is called (%ld bytes)", (long)len);
|
|
cxxptr<Socket> sockObj = m_sock;
|
|
GIOChannelPtr channel = m_channel;
|
|
if(m_connectStatus != CONNECTED) {
|
|
SE_THROW_EXCEPTION (TransportException, "ObexTransport send: underlying transport is not conncted");
|
|
}
|
|
obex_object_t *put = newCmd(OBEX_CMD_PUT);
|
|
//add header "connection id"
|
|
obex_headerdata_t header;
|
|
header.bq4 = m_connectId;
|
|
OBEX_ObjectAddHeader (m_handle, put, OBEX_HDR_CONNECTION, header, sizeof
|
|
(m_connectId), OBEX_FL_FIT_ONE_PACKET);
|
|
//add header "target"
|
|
header.bs = reinterpret_cast<unsigned char *> (const_cast <char *>(m_contentType.c_str()));
|
|
OBEX_ObjectAddHeader (m_handle, put, OBEX_HDR_TYPE, header, m_contentType.size()+1, 0);
|
|
//add header "length"
|
|
header.bq4 = len;
|
|
OBEX_ObjectAddHeader (m_handle, put, OBEX_HDR_LENGTH, header, sizeof (size_t), 0);
|
|
//add header "body"
|
|
header.bs = reinterpret_cast <const uint8_t *> (data);
|
|
OBEX_ObjectAddHeader (m_handle, put, OBEX_HDR_BODY, header, len, 0);
|
|
|
|
//reset up the OBEX fd source
|
|
GLibEvent obexEventSource(g_io_add_watch (channel, (GIOCondition) (G_IO_IN|G_IO_OUT|G_IO_HUP|G_IO_ERR|G_IO_NVAL), obex_fd_source_cb, static_cast<void *> (this)),
|
|
"OBEX fd event");
|
|
|
|
//send the request
|
|
m_status = ACTIVE;
|
|
m_requestStart = time (NULL);
|
|
m_obexReady = false;
|
|
if (OBEX_Request (m_handle, put) < 0) {
|
|
SE_THROW_EXCEPTION (TransportException, "ObexTransport: send failed");
|
|
}
|
|
m_sock = sockObj;
|
|
m_channel = channel;
|
|
m_obexEvent = obexEventSource;
|
|
}
|
|
|
|
/*
|
|
* Abort the transport session; don't wait for anything.
|
|
*/
|
|
void ObexTransportAgent::cancel() {
|
|
m_requestStart = 0;
|
|
m_connectStatus = END;
|
|
if (m_handle) {
|
|
OBEX_TransportDisconnect(m_handle);
|
|
}
|
|
if (m_disconnecting) {
|
|
SE_LOG_WARNING(NULL, "Cancel disconncting process");
|
|
if (m_status != CLOSED) {
|
|
m_status = FAILED;
|
|
}
|
|
} else {
|
|
// called during normal operations;
|
|
// set m_disconnecting like the abort handler
|
|
// did before it was removed
|
|
m_disconnecting = true;
|
|
m_status = CANCELED;
|
|
// remove the event source from the agent before calling shutdown(),
|
|
// will be freed when this function returns
|
|
GLibEvent obexEventSource = m_obexEvent;
|
|
shutdown();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 1)Wait until the connection is set up.
|
|
* 2)Wait until the response is ready, which means:
|
|
* waits for the Put request being successfully sent
|
|
* sends the Get request to pull response
|
|
* waits until Get response is successfully received
|
|
*
|
|
* Runs the main loop manually so that it does not block other components.
|
|
*/
|
|
TransportAgent::Status ObexTransportAgent::wait(bool noReply) {
|
|
SE_LOG_DEV(NULL, "ObexTransportAgent::wait(%s)", noReply ? "no reply" : "reply");
|
|
|
|
while (!m_obexReady) {
|
|
SE_LOG_DEV(NULL, "ObexTransportAgent::wait(): iteration");
|
|
g_main_context_iteration (m_context, TRUE);
|
|
if (m_status == FAILED ||
|
|
m_status == CANCELED) {
|
|
m_obexEvent.set(0);
|
|
m_sock.set(0);
|
|
m_channel.set(0);
|
|
if (m_status == FAILED) {
|
|
SE_THROW_EXCEPTION(TransportException, "OBEX/Bluetooth transport error or problem on the peer");
|
|
} else {
|
|
SE_THROW_EXCEPTION_STATUS(StatusException,
|
|
"transport aborted",
|
|
SyncMLStatus(sysync::LOCERR_USERABORT));
|
|
}
|
|
}
|
|
}
|
|
SE_LOG_DEV(NULL, "ObexTransportAgent::wait(): is ready");
|
|
|
|
//remove the obex event source here
|
|
//only at this point we can be sure obexEvent is propertely set up
|
|
cxxptr<Socket> sockObj = m_sock;
|
|
GLibEvent obexEvent = m_obexEvent;
|
|
GIOChannelPtr channel = m_channel;
|
|
|
|
if (!noReply) {
|
|
//send the Get request
|
|
obex_object_t *get = newCmd(OBEX_CMD_GET);
|
|
//add header "connection id"
|
|
obex_headerdata_t header;
|
|
header.bq4 = m_connectId;
|
|
OBEX_ObjectAddHeader (m_handle, get, OBEX_HDR_CONNECTION, header, sizeof
|
|
(m_connectId), OBEX_FL_FIT_ONE_PACKET);
|
|
//add header "target"
|
|
header.bs = reinterpret_cast <const unsigned char *> (m_contentType.c_str());
|
|
OBEX_ObjectAddHeader (m_handle, get, OBEX_HDR_TYPE, header, m_contentType.size()+1, 0);
|
|
|
|
//send the request
|
|
m_obexReady = false;
|
|
if (OBEX_Request (m_handle, get) < 0) {
|
|
SE_THROW_EXCEPTION (TransportException, "ObexTransport: get failed");
|
|
}
|
|
|
|
while (!m_obexReady) {
|
|
g_main_context_iteration (m_context, TRUE);
|
|
if (m_status == FAILED) {
|
|
SE_THROW_EXCEPTION (TransportException,
|
|
"ObexTransprotAgent: Underlying transport error");
|
|
}
|
|
}
|
|
}
|
|
|
|
if(m_status != CLOSED) {
|
|
m_sock = sockObj;
|
|
m_channel = channel;
|
|
}
|
|
return m_status;
|
|
}
|
|
|
|
/**
|
|
* read the response from the buffer
|
|
*/
|
|
void ObexTransportAgent::getReply(const char *&data, size_t &len, std::string &contentType){
|
|
cxxptr<Socket> sockObj = m_sock;
|
|
GIOChannelPtr channel = m_channel;
|
|
if (m_status != GOT_REPLY || !m_buffer || !m_bufferSize) {
|
|
SE_THROW_EXCEPTION (TransportException, "");
|
|
}
|
|
data = m_buffer;
|
|
len = m_bufferSize;
|
|
// There is no content type sent back from the peer according to the spec
|
|
contentType = "";
|
|
m_sock = sockObj;
|
|
m_channel = channel;
|
|
}
|
|
|
|
gboolean ObexTransportAgent::sdp_source_cb (GIOChannel *io, GIOCondition cond, void *udata)
|
|
{
|
|
return static_cast <ObexTransportAgent*> (udata) -> sdp_source_cb_impl(io, cond);
|
|
}
|
|
|
|
gboolean ObexTransportAgent::obex_fd_source_cb (GIOChannel *io, GIOCondition cond, void *udata)
|
|
{
|
|
return static_cast <ObexTransportAgent*> (udata) -> obex_fd_source_cb_impl(io, cond);
|
|
}
|
|
|
|
void ObexTransportAgent::sdp_callback (uint8_t type, uint16_t status, uint8_t *rsp, size_t size, void *udata)
|
|
{
|
|
return static_cast <ObexTransportAgent *> (udata) -> sdp_callback_impl(type, status, rsp, size);
|
|
}
|
|
|
|
void ObexTransportAgent::obex_event (obex_t *handle, obex_object_t *object, int mode, int event, int obex_cmd, int obex_rsp){
|
|
(static_cast<ObexTransportAgent *> (OBEX_GetUserData (handle)))
|
|
->obex_callback(object, mode, event, obex_cmd, obex_rsp);
|
|
}
|
|
|
|
gboolean ObexTransportAgent::sdp_source_cb_impl (GIOChannel *io, GIOCondition cond)
|
|
{
|
|
try {
|
|
if(cond & (G_IO_HUP | G_IO_ERR | G_IO_NVAL)){
|
|
SE_THROW_EXCEPTION (TransportException, "SDP connection end unexpectedly");
|
|
} else if ((cond &G_IO_OUT) && m_connectStatus == SDP_START) {
|
|
m_connectStatus = SDP_REQ;
|
|
sdp_set_notify (m_sdp, sdp_callback, static_cast <void *> (this));
|
|
const unsigned char syncml_client_uuid[] = {
|
|
0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x10, 0x00,
|
|
0x80, 0x00, 0x00, 0x02, 0xEE, 0x00, 0x00, 0x02};
|
|
uint32_t range = 0x0000ffff;
|
|
uuid_t uuid;
|
|
sdp_list_t *services, *attrs;
|
|
sdp_uuid128_create(&uuid, syncml_client_uuid);
|
|
services = sdp_list_append(NULL, &uuid);
|
|
attrs = sdp_list_append(NULL, &range);
|
|
if (sdp_service_search_attr_async(m_sdp, services, SDP_ATTR_REQ_RANGE, attrs) < 0) {
|
|
sdp_list_free (attrs, NULL);
|
|
sdp_list_free (services, NULL);
|
|
SE_THROW_EXCEPTION(TransportException, "ObexTransport: Bluetooth sdp service search failed");
|
|
}
|
|
sdp_list_free(attrs, NULL);
|
|
sdp_list_free(services, NULL);
|
|
return TRUE;
|
|
} else if ((cond &G_IO_IN) && m_connectStatus == SDP_REQ) {
|
|
sdp_process (m_sdp);
|
|
m_sdp.set(0);
|
|
return FALSE;
|
|
} else {
|
|
return TRUE;
|
|
}
|
|
} catch (...) {
|
|
handleException("sdp_source_cb");
|
|
}
|
|
m_sdp.set(0);
|
|
return FALSE;
|
|
}
|
|
|
|
void ObexTransportAgent::sdp_callback_impl (uint8_t type, uint16_t status, uint8_t *rsp, size_t size)
|
|
{
|
|
size_t scanned;
|
|
int bufSize = size;
|
|
int channel = -1;
|
|
|
|
try {
|
|
m_connectStatus = SDP_DONE;
|
|
if (status || type != SDP_SVC_SEARCH_ATTR_RSP) {
|
|
SE_THROW_EXCEPTION (TransportException, "ObexTransportAgent: Bluetooth service search failed");
|
|
}
|
|
|
|
int seqSize = 0;
|
|
uint8_t dtdp;
|
|
#if defined(HAVE_BLUEZ_SAFE)
|
|
scanned = sdp_extract_seqtype_safe(rsp, bufSize, &dtdp, &seqSize);
|
|
#elif defined(HAVE_BLUEZ_BUFSIZE)
|
|
scanned = sdp_extract_seqtype(rsp, bufSize, &dtdp, &seqSize);
|
|
#else
|
|
scanned = sdp_extract_seqtype(rsp, &dtdp, &seqSize);
|
|
#endif
|
|
if (!scanned || !seqSize) {
|
|
SE_THROW_EXCEPTION (TransportException, "ObexTransportAgent: Bluetooth service search failed");
|
|
}
|
|
|
|
rsp += scanned;
|
|
bufSize -= scanned;
|
|
do {
|
|
sdp_record_t *rec;
|
|
sdp_list_t *protos;
|
|
int recSize;
|
|
|
|
recSize = 0;
|
|
#if defined(HAVE_BLUEZ_SAFE)
|
|
rec = sdp_extract_pdu_safe(rsp, bufSize, &recSize);
|
|
#elif defined(HAVE_BLUEZ_BUFSIZE)
|
|
rec = sdp_extract_pdu(rsp, bufSize, &recSize);
|
|
#else
|
|
rec = sdp_extract_pdu(rsp, &recSize);
|
|
#endif
|
|
if (!rec) {
|
|
SE_THROW_EXCEPTION (TransportException, "ObexTransportAgent: sdp_extract_pdu failed");
|
|
}
|
|
if (!recSize) {
|
|
sdp_record_free(rec);
|
|
SE_THROW_EXCEPTION (TransportException, "ObexTransportAgent: sdp_extract_pdu failed");
|
|
}
|
|
if (!sdp_get_access_protos(rec, &protos)) {
|
|
channel = sdp_get_proto_port(protos, RFCOMM_UUID);
|
|
sdp_list_foreach(protos,
|
|
(sdp_list_func_t) sdp_list_free, NULL);
|
|
sdp_list_free(protos, NULL);
|
|
}
|
|
sdp_record_free(rec);
|
|
if (channel > 0) {
|
|
break;
|
|
}
|
|
scanned += recSize;
|
|
rsp += recSize;
|
|
bufSize -= recSize;
|
|
} while (scanned < size && bufSize > 0);
|
|
|
|
if (channel <= 0) {
|
|
SE_THROW_EXCEPTION (TransportException, "ObexTransportAgent: Bluetooth service search failed");
|
|
}
|
|
|
|
m_port = channel;
|
|
m_connectStatus = ADDR_READY;
|
|
connectInit();
|
|
} catch (...) {
|
|
handleException("sdp_callback_impl");
|
|
}
|
|
}
|
|
|
|
/* obex event source callbackg*/
|
|
gboolean ObexTransportAgent::obex_fd_source_cb_impl (GIOChannel *io, GIOCondition cond)
|
|
{
|
|
cxxptr<Socket> sockObj = m_sock;
|
|
GIOChannelPtr channel = m_channel;
|
|
|
|
if (m_status == CLOSED) {
|
|
return TRUE;
|
|
}
|
|
|
|
try {
|
|
if (cond & (G_IO_HUP | G_IO_ERR | G_IO_NVAL)) {
|
|
SE_THROW_EXCEPTION (TransportException,
|
|
StringPrintf("obex_fd_source_cb_impl: got event %s%s%s",
|
|
(cond & G_IO_HUP) ?"HUP":"",
|
|
(cond & G_IO_ERR) ?"ERR":"",
|
|
(cond & G_IO_NVAL) ?"NVAL":""));
|
|
}
|
|
|
|
if (m_connectStatus == INIT0 && (cond & G_IO_OUT)) {
|
|
int status = -1;
|
|
socklen_t optLen = sizeof (int);
|
|
if (!getsockopt (sockObj->get(), SOL_SOCKET, SO_ERROR, &status, &optLen) && status == 0) {
|
|
m_connectStatus = INIT1;
|
|
m_sock = sockObj;
|
|
m_channel = channel;
|
|
connectReq();
|
|
return TRUE;
|
|
} else {
|
|
SE_THROW_EXCEPTION (TransportException, "OBEXTransport: socket connect failed");
|
|
}
|
|
}
|
|
|
|
time_t now = time(NULL);
|
|
if (m_timeoutSeconds &&
|
|
(m_requestStart != 0) &&
|
|
(now >= m_timeoutSeconds +m_requestStart)) {
|
|
m_sock = sockObj;
|
|
m_channel = channel;
|
|
//timeout
|
|
m_status = TIME_OUT;
|
|
//currently we will not support transport resend for
|
|
//OBEX transport ??
|
|
cancel();
|
|
return TRUE;
|
|
}
|
|
|
|
if (OBEX_HandleInput (m_handle, OBEX_POLL_INTERVAL) <0 && errno != EINTR) {
|
|
//transport error
|
|
//no way to recovery, simply abort
|
|
//disconnect without sending disconnect request
|
|
//The failure may already has been processed during
|
|
//OBEX callback, thus no need to handle it again if status is
|
|
//already marked as FAILED
|
|
if (m_status != FAILED){
|
|
cancel();
|
|
}
|
|
}
|
|
m_sock = sockObj;
|
|
m_channel = channel;
|
|
return TRUE;
|
|
} catch (...) {
|
|
handleException("obex_fd_source_cb_impl");
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
/**
|
|
* Obex Event Callback
|
|
*/
|
|
void ObexTransportAgent::obex_callback (obex_object_t *object, int mode, int event, int obex_cmd, int obex_rsp) {
|
|
try {
|
|
switch (event) {
|
|
case OBEX_EV_PROGRESS:
|
|
SE_LOG_DEV(NULL, "OBEX_EV_PROGRESS");
|
|
break;
|
|
case OBEX_EV_REQDONE:
|
|
SE_LOG_DEV(NULL, "OBEX_EV_REQDONE");
|
|
m_obexReady = true;
|
|
m_requestStart = 0;
|
|
if (obex_rsp != OBEX_RSP_SUCCESS) {
|
|
SE_LOG_ERROR(NULL, "OBEX Request %d got a failed response %s",
|
|
obex_cmd,OBEX_ResponseToString(obex_rsp));
|
|
m_status = FAILED;
|
|
return;
|
|
} else {
|
|
switch (obex_cmd) {
|
|
case OBEX_CMD_CONNECT:
|
|
{
|
|
uint8_t headertype = 0;
|
|
obex_headerdata_t header;
|
|
uint32_t len;
|
|
|
|
while (OBEX_ObjectGetNextHeader (m_handle, object, &headertype, &header, &len)) {
|
|
if (headertype == OBEX_HDR_CONNECTION) {
|
|
m_connectId = header.bq4;
|
|
} else if (headertype == OBEX_HDR_WHO) {
|
|
SE_LOG_DEV(NULL, "OBEX Transport: get header who from connect response with value %.*s",
|
|
len, header.bs);
|
|
} else {
|
|
SE_LOG_WARNING(NULL, "OBEX Transport: Unknow header from connect response");
|
|
}
|
|
}
|
|
if (m_connectId == 0) {
|
|
m_status = FAILED;
|
|
SE_LOG_ERROR(NULL, "No connection id received from connect response");
|
|
}
|
|
m_connectStatus = CONNECTED;
|
|
|
|
break;
|
|
}
|
|
case OBEX_CMD_DISCONNECT:
|
|
{
|
|
if (m_connectStatus == CONNECTED) {
|
|
m_connectStatus = END;
|
|
OBEX_TransportDisconnect (m_handle);
|
|
m_status = CLOSED;
|
|
}
|
|
break;
|
|
}
|
|
case OBEX_CMD_GET:
|
|
{
|
|
int length = 0;
|
|
uint8_t headertype = 0;
|
|
obex_headerdata_t header;
|
|
uint32_t len;
|
|
while (OBEX_ObjectGetNextHeader (m_handle, object, &headertype, &header, &len)) {
|
|
if (headertype == OBEX_HDR_LENGTH) {
|
|
length = header.bq4;
|
|
} else if (headertype == OBEX_HDR_BODY) {
|
|
if (length ==0) {
|
|
length = len;
|
|
SE_LOG_DEV(NULL, "No length header for get response is recevied, using body size %d", len);
|
|
}
|
|
if (length ==0) {
|
|
m_status = FAILED;
|
|
SE_LOG_ERROR(NULL, "ObexTransport: Get zero sized response body for Get");
|
|
}
|
|
m_buffer.set(new char[length], "buffer");
|
|
m_bufferSize = length;
|
|
memcpy (m_buffer, header.bs, length);
|
|
} else {
|
|
SE_LOG_WARNING(NULL, "Unknow header received for Get cmd");
|
|
}
|
|
}
|
|
if( !length || !m_buffer) {
|
|
m_status = FAILED;
|
|
SE_LOG_ERROR(NULL, "Get Cmd response have no body");
|
|
}
|
|
m_status = GOT_REPLY;
|
|
break;
|
|
}
|
|
case OBEX_CMD_PUT:
|
|
SE_LOG_DEV(NULL, "ObexTransport send completed");
|
|
break;
|
|
}
|
|
}//else
|
|
break;
|
|
case OBEX_EV_LINKERR:
|
|
SE_LOG_DEV(NULL, "OBEX_EV_LINKERR");
|
|
{
|
|
if (obex_rsp == 0 && m_disconnecting) {
|
|
//disconnct event
|
|
m_connectStatus = END;
|
|
OBEX_TransportDisconnect (m_handle);
|
|
//a normal case, same as OBEX_EV_DONE
|
|
m_obexReady = true;
|
|
m_status = CLOSED;
|
|
} else if (obex_rsp !=0) {
|
|
SE_LOG_ERROR(NULL, "ObexTransport Error %d", obex_rsp);
|
|
m_status = FAILED;
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
case OBEX_EV_STREAMEMPTY:
|
|
SE_LOG_DEV(NULL, "OBEX_EV_STREAMEMPTY");
|
|
break;
|
|
case OBEX_EV_STREAMAVAIL:
|
|
SE_LOG_DEV(NULL, "OBEX_EV_STREAMAVAIL");
|
|
break;
|
|
}
|
|
} catch (...) {
|
|
handleException("obex_callback");
|
|
}
|
|
}
|
|
|
|
void ObexTransportAgent::handleException(const char *where)
|
|
{
|
|
SE_LOG_DEBUG(NULL, "ObexTransport: exception thrown in %s", where);
|
|
if (m_status == FAILED ||
|
|
m_status == CANCELED ) {
|
|
// don't change anything, don't bother the user with error
|
|
// messages - something went already wrong
|
|
} else {
|
|
// log error and remember put transport into error state
|
|
Exception::handle();
|
|
m_status = FAILED;
|
|
}
|
|
}
|
|
|
|
obex_object_t* ObexTransportAgent::newCmd(uint8_t cmd) {
|
|
obex_object_t *cmdObject = OBEX_ObjectNew (m_handle, cmd);
|
|
if(!cmdObject) {
|
|
m_status = FAILED;
|
|
SE_LOG_ERROR(NULL, "ObexTransport: OBEX Object New failed");
|
|
return NULL;
|
|
} else {
|
|
return cmdObject;
|
|
}
|
|
}
|
|
|
|
#endif //ENABLE_OBEX
|
|
SE_END_CXX
|