/*
 * 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(nullptr),
    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 (nullptr);
    SE_LOG_DEV(NULL, "ObexTransportAgent: OBEX_Request for OBEX_CMD_CONNECT");
    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: OBEX_Request for 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 (nullptr);
    m_obexReady = false;
    SE_LOG_DEV(NULL, "ObexTransportAgent: OBEX_Request for OBEX_CMD_PUT");
    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;
        SE_LOG_DEV(NULL, "ObexTransportAgent: OBEX_Request for OBEX_CMD_GET");
        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(nullptr, &uuid);
            attrs = sdp_list_append(nullptr, &range);
            if (sdp_service_search_attr_async(m_sdp, services, SDP_ATTR_REQ_RANGE, attrs) < 0) {
                sdp_list_free (attrs, nullptr);
                sdp_list_free (services, nullptr);
                SE_THROW_EXCEPTION(TransportException, "ObexTransport: Bluetooth sdp service search failed");
            }
            sdp_list_free(attrs, nullptr);
            sdp_list_free(services, nullptr);
            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, nullptr);
                sdp_list_free(protos, nullptr);
            }
            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(nullptr);
        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;
        }

        SE_LOG_DEV(NULL, "ObexTransportAgent: OBEX_HandleInput");
        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 nullptr;
    } else {
        return cmdObject;
    }
}

#endif //ENABLE_OBEX
SE_END_CXX
