//
// anyRemote
// a wi-fi or bluetooth remote for your PC.
//
// Copyright (C) 2006-2016 Mikhail Fedotov <anyremote@mail.ru>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 3 of the License, or
// (at your option) any later version.
//
// This program 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
//

#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <errno.h>


#ifdef USE_L2CAP

#ifdef USE_BLUEZ
#include <bluetooth/bluetooth.h>
#include <bluetooth/l2cap.h>
#include <bluetooth/sdp.h>
#include <bluetooth/sdp_lib.h>
#endif

#ifdef USE_BT_FBSD
#include <bluetooth.h>
#include <sdp.h>
#include <err.h>
#endif

#include "common.h"
#include "utils.h"
#include "peer.h"

extern char tmp[MAXMAXLEN];
extern boolean_t stillRun;

typedef struct _L2CapConnection_ {
    int           fileDescriptor;
    int           serverFileDescriptor;
    #ifdef USE_BLUEZ
    sdp_session_t *session;
    sdp_record_t  *record;
    #endif
    #ifdef USE_BT_FBSD
    void      *session = NULL;
    uint32_t      record;
    #endif
} _L2CapConnection;

//
// Support SDP
//

#ifdef USE_BLUEZ

void sdpRegisterL2cap(ConnectInfo* connInfo)
{
    _L2CapConnection* cn = (_L2CapConnection*) connInfo->connectionData;
    if (!cn) return;
    
    uint8_t svc_uuid_int[]   = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xab, 0xcd };
    const char *service_name = "anyRemote/l2cap";
    const char *svc_dsc      = "Bluetooth remote control";
    const char *service_prov = "anyRemote";
    uint8_t l2cap_port       = connInfo->port;

    uuid_t root_uuid, l2cap_uuid, svc_uuid, svc_class_uuid;
    sdp_list_t *l2cap_list = 0,
                *root_list = 0,
                 *proto_list = 0,
                  *access_proto_list = 0,
                   *svc_class_list = 0,
                    *profile_list = 0;
    sdp_data_t *channel = 0;
    sdp_profile_desc_t profile;
    cn->record = sdp_record_alloc();

    // set the general service ID
    sdp_uuid128_create( &svc_uuid, &svc_uuid_int );
    sdp_set_service_id( cn->record, svc_uuid );

    // set the service class
    sdp_uuid16_create(&svc_class_uuid, SERIAL_PORT_SVCLASS_ID);
    svc_class_list = sdp_list_append(0, &svc_class_uuid);
    sdp_set_service_classes(cn->record, svc_class_list);

    // set the Bluetooth profile information
    sdp_uuid16_create(&profile.uuid, SERIAL_PORT_PROFILE_ID);
    profile.version = 0x0100;
    profile_list = sdp_list_append(0, &profile);
    sdp_set_profile_descs(cn->record, profile_list);

    // make the service record publicly browsable
    sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
    root_list = sdp_list_append(0, &root_uuid);
    sdp_set_browse_groups(cn->record, root_list );

    // set l2cap information
    sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID);
    l2cap_list = sdp_list_append( 0, &l2cap_uuid );
    channel = sdp_data_alloc(SDP_UINT8, &l2cap_port);
    sdp_list_append(l2cap_list, channel );
    proto_list = sdp_list_append( 0, l2cap_list );

    access_proto_list = sdp_list_append( 0, proto_list );
    sdp_set_access_protos(cn->record, access_proto_list );

    // set the name, provider, and description
    sdp_set_info_attr(cn->record, service_name, service_prov, svc_dsc);

    // connect to the local SDP server, register the service record,
    // and disconnect
    cn->session = sdp_connect(BDADDR_ANY, BDADDR_LOCAL, SDP_RETRY_IF_BUSY);
    if ( (!(cn->session && cn->record)) || sdp_record_register(cn->session, cn->record, 0) == -1) {
        logger(L_ERR, "can not register SDP service");
    }

    // cleanup
    sdp_data_free( channel );
    sdp_list_free( l2cap_list, 0 );
    sdp_list_free( proto_list, 0 );
    sdp_list_free( root_list, 0 );
    sdp_list_free( access_proto_list, 0 );
    sdp_list_free( svc_class_list, 0 );
    sdp_list_free( profile_list, 0 );
}
#endif

#ifdef USE_BT_FBSD

void sdpRegisterL2cap(ConnectInfo* connInfo)
{
    errx(1, "Not yet supported");
}
#endif

void sdpDeregisterL2cap(_L2CapConnection* cn)
{
    #ifdef USE_BLUEZ
    if (cn->session != NULL) {
        sdp_record_unregister(cn->session, cn->record);
        cn->session = NULL;
    }
    #endif
    #ifdef USE_BT_FBSD
    if (cn->session != NULL) {
        sdp_unregister_service(cn->session, cn->record);
        sdp_close(cn->session);
        cn->session = NULL;
    }
    #endif
}

//
// Support L2CAP sockets
//

int openL2capPort(ConnectInfo* connInfo)
{
    #ifdef USE_BLUEZ
    struct sockaddr_l2 l2_addr;
    #endif
    #ifdef USE_BT_FBSD

    #endif

    struct sockaddr*   socketaddr = NULL;

    int sz;

    if (connInfo->connectionData && ((_L2CapConnection*) connInfo->connectionData)->serverFileDescriptor > 0) {
        logger(L_ERR, "L2CAP socket was already opened");
        return 1;
    }
    
    if (connInfo->connectionData) {
        free(connInfo->connectionData);
    }
    
    connInfo->connectionData = (_L2CapConnection*) malloc(sizeof(_L2CapConnection));
    _L2CapConnection* cn = (_L2CapConnection*) connInfo->connectionData;
    
    cn->serverFileDescriptor = -1;
    cn->fileDescriptor       = -1;
    cn->session              = NULL;
    cn->record               = NULL;

    if ((cn->serverFileDescriptor = socket(PF_BLUETOOTH, SOCK_SEQPACKET|SOCK_CLOEXEC, BTPROTO_L2CAP)) < 0) {
        logger(L_ERR, "opening BT/L2CAP socket");
        printf("ERROR: opening BT/L2CAP socket\n");
        return -1;
    }

    #ifdef USE_BLUEZ
    memset((void *) &l2_addr, 0, sizeof(l2_addr));
    sz = sizeof(l2_addr);

    // bind socket to the specified port of the first available local bluetooth adapter
    l2_addr.l2_family = AF_BLUETOOTH;
    l2_addr.l2_bdaddr = *BDADDR_ANY;
    l2_addr.l2_psm = htobs(0x1001); //port);

    sdpRegisterL2cap(connInfo);
    sprintf(tmp, "registered L2CAP on port %i", connInfo->port);
    logger(L_INF, tmp);
    socketaddr=(struct sockaddr *)&l2_addr;
    #endif

    #ifdef USE_BT_FBSD

    #endif

    if (bind(cn->serverFileDescriptor, (struct sockaddr *) socketaddr, sz) < 0) {
        logger(L_ERR, "on binding");
        printf("ERROR: on binding %d->%s\n", errno, strerror(errno));
        return -1;
    }
  
    return 1;
}

int l2capOpen(ConnectInfo* connInfo)
{
    DEBUG2("[DS]: L2CAP Server mode. Use port %d", connInfo->port);
    if (openL2capPort(connInfo) < 0) {
        return EXIT_NOK;
    }
    return EXIT_OK;
}

int l2capFD(ConnectInfo* connInfo)
{
    _L2CapConnection* cn = (_L2CapConnection*) connInfo->connectionData;
    return (cn ? cn->fileDescriptor : -1);
}

void l2capClose(ConnectInfo* connInfo, int final)
{
    logger(L_INF, "l2capClose");
    
    _L2CapConnection* cn = (_L2CapConnection*) connInfo->connectionData;
    if (!cn) return;
    
    if (cn->fileDescriptor >= 0) {
        close(cn->fileDescriptor);
        cn->fileDescriptor = -1;
    }
    if (cn->serverFileDescriptor > 0) {
        close(cn->serverFileDescriptor);
        cn->serverFileDescriptor = -1;
    }
    
    if (final) {
        sdpDeregisterL2cap(cn);
    }
    
    free(cn);
    connInfo->connectionData = NULL;
    connInfo->state = PEER_DISCONNECTED;
}

void l2capReset(ConnectInfo* conn)
{
    logger(L_INF, "l2capReset");
    
    _L2CapConnection* cn = (_L2CapConnection*) connInfo->connectionData;
    if (cn) {
        if (cn->fileDescriptor >= 0) {
            close(cn->fileDescriptor);
            cn->fileDescriptor = -1;
        }
        connInfo->state = PEER_WAIT_ACCEPT;
    } else {
        conn->state = PEER_DISCONNECTED;  // should not happens
    }
}

int l2capSetup(ConnectInfo* connInfo)
{
    _L2CapConnection* cn = (_L2CapConnection*) connInfo->connectionData;
    if (!cn) {
        return -1;
    }
    int ret = listen(cn->serverFileDescriptor,0);
    if (ret >= 0) {
        conn->state = PEER_WAIT_ACCEPT;
    }
    return (ret < 0 ? -1 : 1);
}

int l2capAccept(ConnectInfo* connInfo)
{
    logger(L_INF, "[DS]: Server mode/L2CAP: Waiting connection");
    int cnt;
    char buf[1024] = { 0 };

    #ifdef USE_BLUEZ
    struct sockaddr_l2 l2rem_addr;
    socklen_t opt = sizeof(l2rem_addr);
    #endif

    #ifdef USE_BT_FBSD

    #endif
    cnt = 0;
    
    _L2CapConnection* cn = (_L2CapConnection*) connInfo->connectionData;
    if (!cn) return -1;

    while (stillRun) {

        cn->fileDescriptor = accept(cn->serverFileDescriptor, (struct sockaddr *)&l2rem_addr, &opt);

        if (cn->fileDescriptor == -1 && errno == EAGAIN) {

            if (cnt >= 60) {    // Print to log every minute
                logger(L_INF, "l2capAccept: waiting for connection");
                cnt = 0;
            }
            fflush(stdout);

            sleep(1);
            cnt++;

            continue;
        }

        if (cn->fileDescriptor < 0) {
            logger(L_ERR, "on accept");
            printf("ERROR: on accept %d\n", errno);
            return -1;
        }

        ba2str(&l2rem_addr.l2_bdaddr, buf);
    
        if (!isAllowed(buf)) {
            INFO2("l2capAccept: host %s is not in the list of accepted host, close connection", buf);
            write(cn->fileDescriptor,CMD_STR_DISCONNECT,strlen(CMD_STR_DISCONNECT));
        
            close(cn->fileDescriptor);
            cn->fileDescriptor = -1;
            connInfo->state = PEER_DISCONNECTED;
        
            return -1;
        }

        if (getUsePassword()) {
            logger(L_DBG,"[DS]: l2capAccept: Do password verification");

            int ret = EXIT_OK;

            int i=0;
            for ( ; i<3; i++) {
                ret = verifyPassword(cn->fileDescriptor);

                if (ret == EXIT_OK) {    // got it
                   break;
                }
            }

            if (ret != EXIT_OK) {

                if (ret == EXIT_NOK) {
                    write(cn->fileDescriptor,CMD_STR_DISCONNECT,strlen(CMD_STR_DISCONNECT));
                }

                close(cn->fileDescriptor);
                cn->fileDescriptor = -1;
                connInfo->state = PEER_DISCONNECTED;

                return -1;
            }
            logger(L_DBG,"[DS]: l2capAccept: Password verification OK");
        }
    
        sprintf(tmp, "l2capAccept: accepted from %s", buf);
        logger(L_INF, tmp);
    
        connInfo->state = PEER_CONNECTED;
        
        // force to detect cover size. need to do that before (Connect) or syncPeer() handling
        getCoverSize(connInfo->id, cn->fileDescriptor);  

        break;
    }
    return 1;
}

int l2capWrite(ConnectInfo* connInfo, dMessage* msg)
{
    _L2CapConnection* cn = (_L2CapConnection*) connInfo->connectionData;
    if (!cn) {    
        logger(L_DBG,"[DS]: l2capWrite() no connection data");
        return EXIT_NOK;
    }
    const char* command = msg->value; 
    int count = msg->size; 
    
    logger(L_DBG, "l2capWrite");
 
    if (!command || count <= 0) {
        return EXIT_OK;
    }
 
    if (strcmp("End();",command) == 0) {  // used only for WEB/CMXML
        return EXIT_OK;
    }

    // send command
    memset(tmp, 0, MAXMAXLEN);
    strcat(tmp, "l2capWrite ");

    int logSz = (count > 256 ? 255 : count);

    // it is possible to get binary data here
    memcpy(tmp, command, logSz); // Do not dump long commands
    tmp[logSz] = '\0';
    logger(L_DBG, tmp);

    sprintf(tmp, "l2capWrite %d bytes", count);
    logger(L_INF, tmp);

    int n = write(cn->fileDescriptor,command,count);
    if (n < 0) {
        logger(L_ERR, "error writing to L2CAP socket");
        return EXIT_NOK;
    }

    return EXIT_OK;
}

#endif
