Logo Search packages:      
Sourcecode: obexd version File versions  Download package

btio.c

/*
 *
 *  BlueZ - Bluetooth protocol stack for Linux
 *
 *  Copyright (C) 2009  Marcel Holtmann <marcel@holtmann.org>
 *  Copyright (C) 2009  Nokia Corporation
 *
 *
 *  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 2 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 <stdarg.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <poll.h>
#include <sys/types.h>
#include <sys/socket.h>

#include <bluetooth/bluetooth.h>
#include <bluetooth/l2cap.h>
#include <bluetooth/rfcomm.h>
#include <bluetooth/sco.h>
#include <bluetooth/hci.h>
#include <bluetooth/hci_lib.h>

#include <glib.h>

#include "btio.h"

#define ERROR_FAILED(gerr, str, err) \
            g_set_error(gerr, BT_IO_ERROR, BT_IO_ERROR_FAILED, \
                        str ": %s (%d)", strerror(err), err)

#define DEFAULT_DEFER_TIMEOUT 30

struct set_opts {
      bdaddr_t src;
      bdaddr_t dst;
      int defer;
      int sec_level;
      uint8_t channel;
      uint16_t psm;
      uint16_t mtu;
      uint16_t imtu;
      uint16_t omtu;
      int master;
};

struct connect {
      BtIOConnect connect;
      gpointer user_data;
      GDestroyNotify destroy;
};

struct accept {
      BtIOConnect connect;
      gpointer user_data;
      GDestroyNotify destroy;
};

struct server {
      BtIOConnect connect;
      BtIOConfirm confirm;
      gpointer user_data;
      GDestroyNotify destroy;
};

static void server_remove(struct server *server)
{
      if (server->destroy)
            server->destroy(server->user_data);
      g_free(server);
}

static void connect_remove(struct connect *conn)
{
      if (conn->destroy)
            conn->destroy(conn->user_data);
      g_free(conn);
}

static void accept_remove(struct accept *accept)
{
      if (accept->destroy)
            accept->destroy(accept->user_data);
      g_free(accept);
}

static gboolean accept_cb(GIOChannel *io, GIOCondition cond,
                                          gpointer user_data)
{
      struct accept *accept = user_data;
      GError *err = NULL;

      /* If the user aborted this accept attempt */
      if (cond & G_IO_NVAL)
            return FALSE;

      if (cond & (G_IO_HUP | G_IO_ERR))
            g_set_error(&err, BT_IO_ERROR, BT_IO_ERROR_DISCONNECTED,
                        "HUP or ERR on socket");

      accept->connect(io, err, accept->user_data);

      g_clear_error(&err);

      return FALSE;
}

static gboolean connect_cb(GIOChannel *io, GIOCondition cond,
                                          gpointer user_data)
{
      struct connect *conn = user_data;
      GError *gerr = NULL;

      /* If the user aborted this connect attempt */
      if (cond & G_IO_NVAL)
            return FALSE;

      if (cond & G_IO_OUT) {
            int err = 0, sock = g_io_channel_unix_get_fd(io);
            socklen_t len = sizeof(err);

            if (getsockopt(sock, SOL_SOCKET, SO_ERROR, &err, &len) < 0)
                  err = errno;

            if (err)
                  g_set_error(&gerr, BT_IO_ERROR,
                              BT_IO_ERROR_CONNECT_FAILED, "%s (%d)",
                              strerror(err), err);
      } else if (cond & (G_IO_HUP | G_IO_ERR))
            g_set_error(&gerr, BT_IO_ERROR, BT_IO_ERROR_CONNECT_FAILED,
                        "HUP or ERR on socket");

      conn->connect(io, gerr, conn->user_data);

      if (gerr)
            g_error_free(gerr);

      return FALSE;
}

static gboolean server_cb(GIOChannel *io, GIOCondition cond,
                                          gpointer user_data)
{
      struct server *server = user_data;
      int srv_sock, cli_sock;
      GIOChannel *cli_io;

      /* If the user closed the server */
      if (cond & G_IO_NVAL)
            return FALSE;

      srv_sock = g_io_channel_unix_get_fd(io);

      cli_sock = accept(srv_sock, NULL, NULL);
      if (cli_sock < 0)
            return TRUE;

      cli_io = g_io_channel_unix_new(cli_sock);

      g_io_channel_set_close_on_unref(cli_io, TRUE);
      g_io_channel_set_flags(cli_io, G_IO_FLAG_NONBLOCK, NULL);

      if (server->confirm)
            server->confirm(cli_io, server->user_data);
      else
            server->connect(cli_io, NULL, server->user_data);

      g_io_channel_unref(cli_io);

      return TRUE;
}

static void server_add(GIOChannel *io, BtIOConnect connect,
                        BtIOConfirm confirm, gpointer user_data,
                        GDestroyNotify destroy)
{
      struct server *server;
      GIOCondition cond;

      server = g_new0(struct server, 1);
      server->connect = connect;
      server->confirm = confirm;
      server->user_data = user_data;
      server->destroy = destroy;

      cond = G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL;
      g_io_add_watch_full(io, G_PRIORITY_DEFAULT, cond, server_cb, server,
                              (GDestroyNotify) server_remove);
}

static void connect_add(GIOChannel *io, BtIOConnect connect,
                        gpointer user_data, GDestroyNotify destroy)
{
      struct connect *conn;
      GIOCondition cond;

      conn = g_new0(struct connect, 1);
      conn->connect = connect;
      conn->user_data = user_data;
      conn->destroy = destroy;

      cond = G_IO_OUT | G_IO_ERR | G_IO_HUP | G_IO_NVAL;
      g_io_add_watch_full(io, G_PRIORITY_DEFAULT, cond, connect_cb, conn,
                              (GDestroyNotify) connect_remove);
}

static void accept_add(GIOChannel *io, BtIOConnect connect, gpointer user_data,
                                          GDestroyNotify destroy)
{
      struct accept *accept;
      GIOCondition cond;

      accept = g_new0(struct accept, 1);
      accept->connect = connect;
      accept->user_data = user_data;
      accept->destroy = destroy;

      cond = G_IO_OUT | G_IO_ERR | G_IO_HUP | G_IO_NVAL;
      g_io_add_watch_full(io, G_PRIORITY_DEFAULT, cond, accept_cb, accept,
                              (GDestroyNotify) accept_remove);
}

static int l2cap_bind(int sock, const bdaddr_t *src, uint16_t psm)
{
      struct sockaddr_l2 addr;

      memset(&addr, 0, sizeof(addr));
      addr.l2_family = AF_BLUETOOTH;
      bacpy(&addr.l2_bdaddr, src);
      addr.l2_psm = htobs(psm);

      return bind(sock, (struct sockaddr *) &addr, sizeof(addr));
}

static int l2cap_connect(int sock, const bdaddr_t *dst, uint16_t psm)
{
      int err;
      struct sockaddr_l2 addr;

      memset(&addr, 0, sizeof(addr));
      addr.l2_family = AF_BLUETOOTH;
      bacpy(&addr.l2_bdaddr, dst);
      addr.l2_psm = htobs(psm);

      err = connect(sock, (struct sockaddr *) &addr, sizeof(addr));
      if (err < 0 && !(errno == EAGAIN || errno == EINPROGRESS))
            return err;

      return 0;
}

static int l2cap_set_master(int sock, int master)
{
      int flags;
      socklen_t len;

      len = sizeof(flags);
      if (getsockopt(sock, SOL_L2CAP, L2CAP_LM, &flags, &len) < 0)
            return -errno;

      if (master) {
            if (flags & L2CAP_LM_MASTER)
                  return 0;
            flags |= L2CAP_LM_MASTER;
      } else {
            if (!(flags & L2CAP_LM_MASTER))
                  return 0;
            flags &= ~L2CAP_LM_MASTER;
      }

      if (setsockopt(sock, SOL_L2CAP, L2CAP_LM, &flags, sizeof(flags)) < 0)
            return -errno;

      return 0;
}

static int rfcomm_set_master(int sock, int master)
{
      int flags;
      socklen_t len;

      len = sizeof(flags);
      if (getsockopt(sock, SOL_RFCOMM, RFCOMM_LM, &flags, &len) < 0)
            return -errno;

      if (master) {
            if (flags & RFCOMM_LM_MASTER)
                  return 0;
            flags |= RFCOMM_LM_MASTER;
      } else {
            if (!(flags & RFCOMM_LM_MASTER))
                  return 0;
            flags &= ~RFCOMM_LM_MASTER;
      }

      if (setsockopt(sock, SOL_RFCOMM, RFCOMM_LM, &flags, sizeof(flags)) < 0)
            return -errno;

      return 0;
}

static int l2cap_set_lm(int sock, int level)
{
      int lm_map[] = {
            0,
            L2CAP_LM_AUTH,
            L2CAP_LM_AUTH | L2CAP_LM_ENCRYPT,
            L2CAP_LM_AUTH | L2CAP_LM_ENCRYPT | L2CAP_LM_SECURE,
      }, opt = lm_map[level];

      if (setsockopt(sock, SOL_L2CAP, L2CAP_LM, &opt, sizeof(opt)) < 0)
            return -errno;

      return 0;
}

static int rfcomm_set_lm(int sock, int level)
{
      int lm_map[] = {
            0,
            RFCOMM_LM_AUTH,
            RFCOMM_LM_AUTH | RFCOMM_LM_ENCRYPT,
            RFCOMM_LM_AUTH | RFCOMM_LM_ENCRYPT | RFCOMM_LM_SECURE,
      }, opt = lm_map[level];

      if (setsockopt(sock, SOL_RFCOMM, RFCOMM_LM, &opt, sizeof(opt)) < 0)
            return -errno;

      return 0;
}

static gboolean set_sec_level(int sock, BtIOType type, int level, GError **err)
{
      struct bt_security sec;
      int ret;

      if (level < BT_SECURITY_LOW || level > BT_SECURITY_HIGH) {
            g_set_error(err, BT_IO_ERROR, BT_IO_ERROR_INVALID_ARGS,
                        "Valid security level range is %d-%d",
                        BT_SECURITY_LOW, BT_SECURITY_HIGH);
            return FALSE;
      }

      memset(&sec, 0, sizeof(sec));
      sec.level = level;

      if (setsockopt(sock, SOL_BLUETOOTH, BT_SECURITY, &sec,
                                          sizeof(sec)) == 0)
            return TRUE;

      if (errno != ENOPROTOOPT) {
            ERROR_FAILED(err, "setsockopt(BT_SECURITY)", errno);
            return FALSE;
      }

      if (type == BT_IO_L2CAP)
            ret = l2cap_set_lm(sock, level);
      else
            ret = rfcomm_set_lm(sock, level);

      if (ret < 0) {
            ERROR_FAILED(err, "setsockopt(LM)", -ret);
            return FALSE;
      }

      return TRUE;
}

static int l2cap_get_lm(int sock, int *sec_level)
{
      int opt;
      socklen_t len;

      len = sizeof(opt);
      if (getsockopt(sock, SOL_L2CAP, L2CAP_LM, &opt, &len) < 0)
            return -errno;

      *sec_level = 0;

      if (opt & L2CAP_LM_AUTH)
            *sec_level = BT_SECURITY_LOW;
      if (opt & L2CAP_LM_ENCRYPT)
            *sec_level = BT_SECURITY_MEDIUM;
      if (opt & L2CAP_LM_SECURE)
            *sec_level = BT_SECURITY_HIGH;

      return 0;
}

static int rfcomm_get_lm(int sock, int *sec_level)
{
      int opt;
      socklen_t len;

      len = sizeof(opt);
      if (getsockopt(sock, SOL_RFCOMM, RFCOMM_LM, &opt, &len) < 0)
            return -errno;

      *sec_level = 0;

      if (opt & RFCOMM_LM_AUTH)
            *sec_level = BT_SECURITY_LOW;
      if (opt & RFCOMM_LM_ENCRYPT)
            *sec_level = BT_SECURITY_MEDIUM;
      if (opt & RFCOMM_LM_SECURE)
            *sec_level = BT_SECURITY_HIGH;

      return 0;
}

static gboolean get_sec_level(int sock, BtIOType type, int *level,
                                                GError **err)
{
      struct bt_security sec;
      socklen_t len;
      int ret;

      memset(&sec, 0, sizeof(sec));
      len = sizeof(sec);
      if (getsockopt(sock, SOL_BLUETOOTH, BT_SECURITY, &sec, &len) == 0) {
            *level = sec.level;
            return TRUE;
      }

      if (errno != ENOPROTOOPT) {
            ERROR_FAILED(err, "getsockopt(BT_SECURITY)", errno);
            return FALSE;
      }

      if (type == BT_IO_L2CAP)
            ret = l2cap_get_lm(sock, level);
      else
            ret = rfcomm_get_lm(sock, level);

      if (ret < 0) {
            ERROR_FAILED(err, "getsockopt(LM)", -ret);
            return FALSE;
      }

      return TRUE;
}

static gboolean l2cap_set(int sock, int sec_level, uint16_t imtu,
                        uint16_t omtu, int master, GError **err)
{
      if (imtu || omtu) {
            struct l2cap_options l2o;
            socklen_t len;

            memset(&l2o, 0, sizeof(l2o));
            len = sizeof(l2o);
            if (getsockopt(sock, SOL_L2CAP, L2CAP_OPTIONS, &l2o,
                                                &len) < 0) {
                  ERROR_FAILED(err, "getsockopt(L2CAP_OPTIONS)", errno);
                  return FALSE;
            }

            if (imtu)
                  l2o.imtu = imtu;
            if (omtu)
                  l2o.omtu = omtu;

            if (setsockopt(sock, SOL_L2CAP, L2CAP_OPTIONS, &l2o,
                                          sizeof(l2o)) < 0) {
                  ERROR_FAILED(err, "setsockopt(L2CAP_OPTIONS)", errno);
                  return FALSE;
            }
      }

      if (master >= 0 && l2cap_set_master(sock, master) < 0) {
            ERROR_FAILED(err, "l2cap_set_master", errno);
            return FALSE;
      }

      if (sec_level && !set_sec_level(sock, BT_IO_L2CAP, sec_level, err))
            return FALSE;

      return TRUE;
}

static int rfcomm_bind(int sock, const bdaddr_t *src, uint8_t channel)
{
      struct sockaddr_rc addr;

      memset(&addr, 0, sizeof(addr));
      addr.rc_family = AF_BLUETOOTH;
      bacpy(&addr.rc_bdaddr, src);
      addr.rc_channel = channel;

      return bind(sock, (struct sockaddr *) &addr, sizeof(addr));
}

static int rfcomm_connect(int sock, const bdaddr_t *dst, uint8_t channel)
{
      int err;
      struct sockaddr_rc addr;

      memset(&addr, 0, sizeof(addr));
      addr.rc_family = AF_BLUETOOTH;
      bacpy(&addr.rc_bdaddr, dst);
      addr.rc_channel = channel;

      err = connect(sock, (struct sockaddr *) &addr, sizeof(addr));
      if (err < 0 && !(errno == EAGAIN || errno == EINPROGRESS))
            return err;

      return 0;
}

static gboolean rfcomm_set(int sock, int sec_level, int master, GError **err)
{
      if (sec_level && !set_sec_level(sock, BT_IO_RFCOMM, sec_level, err))
            return FALSE;

      if (master >= 0 && rfcomm_set_master(sock, master) < 0) {
            ERROR_FAILED(err, "rfcomm_set_master", errno);
            return FALSE;
      }

      return TRUE;
}

static int sco_bind(int sock, const bdaddr_t *src)
{
      struct sockaddr_sco addr;

      memset(&addr, 0, sizeof(addr));
      addr.sco_family = AF_BLUETOOTH;
      bacpy(&addr.sco_bdaddr, src);

      return bind(sock, (struct sockaddr *) &addr, sizeof(addr));
}

static int sco_connect(int sock, const bdaddr_t *dst)
{
      struct sockaddr_sco addr;
      int err;

      memset(&addr, 0, sizeof(addr));
      addr.sco_family = AF_BLUETOOTH;
      bacpy(&addr.sco_bdaddr, dst);

      err = connect(sock, (struct sockaddr *) &addr, sizeof(addr));
      if (err < 0 && !(errno == EAGAIN || errno == EINPROGRESS))
            return err;

      return 0;
}

static gboolean sco_set(int sock, uint16_t mtu, GError **err)
{
      struct sco_options sco_opt;
      socklen_t len;

      if (!mtu)
            return TRUE;

      len = sizeof(sco_opt);
      memset(&sco_opt, 0, len);
      if (getsockopt(sock, SOL_SCO, SCO_OPTIONS, &sco_opt, &len) < 0) {
            ERROR_FAILED(err, "getsockopt(SCO_OPTIONS)", errno);
            return FALSE;
      }

      sco_opt.mtu = mtu;
      if (setsockopt(sock, SOL_SCO, SCO_OPTIONS, &sco_opt,
                                    sizeof(sco_opt)) < 0) {
            ERROR_FAILED(err, "setsockopt(SCO_OPTIONS)", errno);
            return FALSE;
      }

      return TRUE;
}

static gboolean parse_set_opts(struct set_opts *opts, GError **err,
                                    BtIOOption opt1, va_list args)
{
      BtIOOption opt = opt1;
      const char *str;

      memset(opts, 0, sizeof(*opts));

      /* Set defaults */
      opts->defer = DEFAULT_DEFER_TIMEOUT;
      opts->master = -1;

      while (opt != BT_IO_OPT_INVALID) {
            switch (opt) {
            case BT_IO_OPT_SOURCE:
                  str = va_arg(args, const char *);
                  if (strncasecmp(str, "hci", 3) == 0)
                        hci_devba(atoi(str + 3), &opts->src);
                  else
                        str2ba(str, &opts->src);
                  break;
            case BT_IO_OPT_SOURCE_BDADDR:
                  bacpy(&opts->src, va_arg(args, const bdaddr_t *));
                  break;
            case BT_IO_OPT_DEST:
                  str2ba(va_arg(args, const char *), &opts->dst);
                  break;
            case BT_IO_OPT_DEST_BDADDR:
                  bacpy(&opts->dst, va_arg(args, const bdaddr_t *));
                  break;
            case BT_IO_OPT_DEFER_TIMEOUT:
                  opts->defer = va_arg(args, int);
                  break;
            case BT_IO_OPT_SEC_LEVEL:
                  opts->sec_level = va_arg(args, int);
                  break;
            case BT_IO_OPT_CHANNEL:
                  opts->channel = va_arg(args, int);
                  break;
            case BT_IO_OPT_PSM:
                  opts->psm = va_arg(args, int);
                  break;
            case BT_IO_OPT_MTU:
                  opts->mtu = va_arg(args, int);
                  opts->imtu = opts->mtu;
                  opts->omtu = opts->mtu;
                  break;
            case BT_IO_OPT_OMTU:
                  opts->omtu = va_arg(args, int);
                  if (!opts->mtu)
                        opts->mtu = opts->omtu;
                  break;
            case BT_IO_OPT_IMTU:
                  opts->imtu = va_arg(args, int);
                  if (!opts->mtu)
                        opts->mtu = opts->imtu;
                  break;
            case BT_IO_OPT_MASTER:
                  opts->master = va_arg(args, gboolean);
                  break;
            default:
                  g_set_error(err, BT_IO_ERROR, BT_IO_ERROR_INVALID_ARGS,
                              "Unknown option %d", opt);
                  return FALSE;
            }

            opt = va_arg(args, int);
      }

      return TRUE;
}

static gboolean get_peers(int sock, struct sockaddr *src, struct sockaddr *dst,
                        socklen_t len, GError **err)
{
      socklen_t olen;

      memset(src, 0, len);
      olen = len;
      if (getsockname(sock, src, &olen) < 0) {
            ERROR_FAILED(err, "getsockname", errno);
            return FALSE;
      }

      memset(dst, 0, len);
      olen = len;
      if (getpeername(sock, dst, &olen) < 0) {
            ERROR_FAILED(err, "getpeername", errno);
            return FALSE;
      }

      return TRUE;
}

static int l2cap_get_info(int sock, uint16_t *handle, uint8_t *dev_class)
{
      struct l2cap_conninfo info;
      socklen_t len;

      len = sizeof(info);
      if (getsockopt(sock, SOL_L2CAP, L2CAP_CONNINFO, &info, &len) < 0)
            return -errno;

      if (handle)
            *handle = info.hci_handle;

      if (dev_class)
            memcpy(dev_class, info.dev_class, 3);

      return 0;
}

static gboolean l2cap_get(int sock, GError **err, BtIOOption opt1,
                                                va_list args)
{
      BtIOOption opt = opt1;
      struct sockaddr_l2 src, dst;
      struct l2cap_options l2o;
      int flags;
      uint8_t dev_class[3];
      uint16_t handle;
      socklen_t len;

      len = sizeof(l2o);
      memset(&l2o, 0, len);
      if (getsockopt(sock, SOL_L2CAP, L2CAP_OPTIONS, &l2o, &len) < 0) {
            ERROR_FAILED(err, "getsockopt(L2CAP_OPTIONS)", errno);
            return FALSE;
      }

      if (!get_peers(sock, (struct sockaddr *) &src,
                        (struct sockaddr *) &dst, sizeof(src), err))
            return FALSE;

      while (opt != BT_IO_OPT_INVALID) {
            switch (opt) {
            case BT_IO_OPT_SOURCE:
                  ba2str(&src.l2_bdaddr, va_arg(args, char *));
                  break;
            case BT_IO_OPT_SOURCE_BDADDR:
                  bacpy(va_arg(args, bdaddr_t *), &src.l2_bdaddr);
                  break;
            case BT_IO_OPT_DEST:
                  ba2str(&dst.l2_bdaddr, va_arg(args, char *));
                  break;
            case BT_IO_OPT_DEST_BDADDR:
                  bacpy(va_arg(args, bdaddr_t *), &dst.l2_bdaddr);
                  break;
            case BT_IO_OPT_DEFER_TIMEOUT:
                  len = sizeof(int);
                  if (getsockopt(sock, SOL_BLUETOOTH, BT_DEFER_SETUP,
                              va_arg(args, int *), &len) < 0) {
                        ERROR_FAILED(err, "getsockopt(DEFER_SETUP)",
                                                      errno);
                        return FALSE;
                  }
                  break;
            case BT_IO_OPT_SEC_LEVEL:
                  if (!get_sec_level(sock, BT_IO_L2CAP,
                                    va_arg(args, int *), err))
                        return FALSE;
                  break;
            case BT_IO_OPT_PSM:
                  *(va_arg(args, uint16_t *)) = src.l2_psm ?
                                    src.l2_psm : dst.l2_psm;
                  break;
            case BT_IO_OPT_OMTU:
                  *(va_arg(args, uint16_t *)) = l2o.omtu;
                  break;
            case BT_IO_OPT_IMTU:
                  *(va_arg(args, uint16_t *)) = l2o.imtu;
                  break;
            case BT_IO_OPT_MASTER:
                  len = sizeof(flags);
                  if (getsockopt(sock, SOL_L2CAP, L2CAP_LM, &flags,
                                                &len) < 0) {
                        ERROR_FAILED(err, "getsockopt(L2CAP_LM)",
                                                      errno);
                        return FALSE;
                  }
                  *(va_arg(args, gboolean *)) =
                        (flags & L2CAP_LM_MASTER) ? TRUE : FALSE;
                  break;
            case BT_IO_OPT_HANDLE:
                  if (l2cap_get_info(sock, &handle, dev_class) < 0) {
                        ERROR_FAILED(err, "L2CAP_CONNINFO", errno);
                        return FALSE;
                  }
                  *(va_arg(args, uint16_t *)) = handle;
                  break;
            case BT_IO_OPT_CLASS:
                  if (l2cap_get_info(sock, &handle, dev_class) < 0) {
                        ERROR_FAILED(err, "L2CAP_CONNINFO", errno);
                        return FALSE;
                  }
                  memcpy(va_arg(args, uint8_t *), dev_class, 3);
                  break;
            default:
                  g_set_error(err, BT_IO_ERROR, BT_IO_ERROR_INVALID_ARGS,
                              "Unknown option %d", opt);
                  return FALSE;
            }

            opt = va_arg(args, int);
      }

      return TRUE;
}

static int rfcomm_get_info(int sock, uint16_t *handle, uint8_t *dev_class)
{
      struct rfcomm_conninfo info;
      socklen_t len;

      len = sizeof(info);
      if (getsockopt(sock, SOL_RFCOMM, RFCOMM_CONNINFO, &info, &len) < 0)
            return -errno;

      if (handle)
            *handle = info.hci_handle;

      if (dev_class)
            memcpy(dev_class, info.dev_class, 3);

      return 0;
}

static gboolean rfcomm_get(int sock, GError **err, BtIOOption opt1,
                                                va_list args)
{
      BtIOOption opt = opt1;
      struct sockaddr_rc src, dst;
      int flags;
      socklen_t len;
      uint8_t dev_class[3];
      uint16_t handle;

      if (!get_peers(sock, (struct sockaddr *) &src,
                        (struct sockaddr *) &dst, sizeof(src), err))
            return FALSE;

      while (opt != BT_IO_OPT_INVALID) {
            switch (opt) {
            case BT_IO_OPT_SOURCE:
                  ba2str(&src.rc_bdaddr, va_arg(args, char *));
                  break;
            case BT_IO_OPT_SOURCE_BDADDR:
                  bacpy(va_arg(args, bdaddr_t *), &src.rc_bdaddr);
                  break;
            case BT_IO_OPT_DEST:
                  ba2str(&dst.rc_bdaddr, va_arg(args, char *));
                  break;
            case BT_IO_OPT_DEST_BDADDR:
                  bacpy(va_arg(args, bdaddr_t *), &dst.rc_bdaddr);
                  break;
            case BT_IO_OPT_DEFER_TIMEOUT:
                  len = sizeof(int);
                  if (getsockopt(sock, SOL_BLUETOOTH, BT_DEFER_SETUP,
                              va_arg(args, int *), &len) < 0) {
                        ERROR_FAILED(err, "getsockopt(DEFER_SETUP)",
                                                      errno);
                        return FALSE;
                  }
                  break;
            case BT_IO_OPT_SEC_LEVEL:
                  if (!get_sec_level(sock, BT_IO_RFCOMM,
                                    va_arg(args, int *), err))
                        return FALSE;
                  break;
            case BT_IO_OPT_CHANNEL:
                  *(va_arg(args, uint8_t *)) = src.rc_channel ?
                              src.rc_channel : dst.rc_channel;
                  break;
            case BT_IO_OPT_MASTER:
                  len = sizeof(flags);
                  if (getsockopt(sock, SOL_RFCOMM, RFCOMM_LM, &flags,
                                                &len) < 0) {
                        ERROR_FAILED(err, "getsockopt(RFCOMM_LM)",
                                                      errno);
                        return FALSE;
                  }
                  *(va_arg(args, gboolean *)) =
                        (flags & RFCOMM_LM_MASTER) ? TRUE : FALSE;
                  break;
            case BT_IO_OPT_HANDLE:
                  if (rfcomm_get_info(sock, &handle, dev_class) < 0) {
                        ERROR_FAILED(err, "RFCOMM_CONNINFO", errno);
                        return FALSE;
                  }
                  *(va_arg(args, uint16_t *)) = handle;
                  break;
            case BT_IO_OPT_CLASS:
                  if (rfcomm_get_info(sock, &handle, dev_class) < 0) {
                        ERROR_FAILED(err, "RFCOMM_CONNINFO", errno);
                        return FALSE;
                  }
                  memcpy(va_arg(args, uint8_t *), dev_class, 3);
                  break;
            default:
                  g_set_error(err, BT_IO_ERROR, BT_IO_ERROR_INVALID_ARGS,
                              "Unknown option %d", opt);
                  return FALSE;
            }

            opt = va_arg(args, int);
      }

      return TRUE;
}

static int sco_get_info(int sock, uint16_t *handle, uint8_t *dev_class)
{
      struct sco_conninfo info;
      socklen_t len;

      len = sizeof(info);
      if (getsockopt(sock, SOL_SCO, SCO_CONNINFO, &info, &len) < 0)
            return -errno;

      if (handle)
            *handle = info.hci_handle;

      if (dev_class)
            memcpy(dev_class, info.dev_class, 3);

      return 0;
}

static gboolean sco_get(int sock, GError **err, BtIOOption opt1, va_list args)
{
      BtIOOption opt = opt1;
      struct sockaddr_sco src, dst;
      struct sco_options sco_opt;
      socklen_t len;
      uint8_t dev_class[3];
      uint16_t handle;

      len = sizeof(sco_opt);
      memset(&sco_opt, 0, len);
      if (getsockopt(sock, SOL_SCO, SCO_OPTIONS, &sco_opt, &len) < 0) {
            ERROR_FAILED(err, "getsockopt(SCO_OPTIONS)", errno);
            return FALSE;
      }

      if (!get_peers(sock, (struct sockaddr *) &src,
                        (struct sockaddr *) &dst, sizeof(src), err))
            return FALSE;

      while (opt != BT_IO_OPT_INVALID) {
            switch (opt) {
            case BT_IO_OPT_SOURCE:
                  ba2str(&src.sco_bdaddr, va_arg(args, char *));
                  break;
            case BT_IO_OPT_SOURCE_BDADDR:
                  bacpy(va_arg(args, bdaddr_t *), &src.sco_bdaddr);
                  break;
            case BT_IO_OPT_DEST:
                  ba2str(&dst.sco_bdaddr, va_arg(args, char *));
                  break;
            case BT_IO_OPT_DEST_BDADDR:
                  bacpy(va_arg(args, bdaddr_t *), &dst.sco_bdaddr);
                  break;
            case BT_IO_OPT_MTU:
            case BT_IO_OPT_IMTU:
            case BT_IO_OPT_OMTU:
                  *(va_arg(args, uint16_t *)) = sco_opt.mtu;
                  break;
            case BT_IO_OPT_HANDLE:
                  if (sco_get_info(sock, &handle, dev_class) < 0) {
                        ERROR_FAILED(err, "RFCOMM_CONNINFO", errno);
                        return FALSE;
                  }
                  *(va_arg(args, uint16_t *)) = handle;
                  break;
            case BT_IO_OPT_CLASS:
                  if (sco_get_info(sock, &handle, dev_class) < 0) {
                        ERROR_FAILED(err, "RFCOMM_CONNINFO", errno);
                        return FALSE;
                  }
                  memcpy(va_arg(args, uint8_t *), dev_class, 3);
                  break;
            default:
                  g_set_error(err, BT_IO_ERROR, BT_IO_ERROR_INVALID_ARGS,
                              "Unknown option %d", opt);
                  return FALSE;
            }

            opt = va_arg(args, int);
      }

      return TRUE;
}

static gboolean get_valist(GIOChannel *io, BtIOType type, GError **err,
                                    BtIOOption opt1, va_list args)
{
      int sock;

      sock = g_io_channel_unix_get_fd(io);

      switch (type) {
      case BT_IO_L2RAW:
      case BT_IO_L2CAP:
            return l2cap_get(sock, err, opt1, args);
      case BT_IO_RFCOMM:
            return rfcomm_get(sock, err, opt1, args);
      case BT_IO_SCO:
            return sco_get(sock, err, opt1, args);
      }

      g_set_error(err, BT_IO_ERROR, BT_IO_ERROR_INVALID_ARGS,
                  "Unknown BtIO type %d", type);
      return FALSE;
}

gboolean bt_io_accept(GIOChannel *io, BtIOConnect connect, gpointer user_data,
                              GDestroyNotify destroy, GError **err)
{
      int sock;
      char c;
      struct pollfd pfd;

      sock = g_io_channel_unix_get_fd(io);

      memset(&pfd, 0, sizeof(pfd));
      pfd.fd = sock;
      pfd.events = POLLOUT;

      if (poll(&pfd, 1, 0) < 0) {
            ERROR_FAILED(err, "poll", errno);
            return FALSE;
      }

      if (!(pfd.revents & POLLOUT)) {
            int ret;
            ret = read(sock, &c, 1);
      }

      accept_add(io, connect, user_data, destroy);

      return TRUE;
}

gboolean bt_io_set(GIOChannel *io, BtIOType type, GError **err,
                                          BtIOOption opt1, ...)
{
      va_list args;
      gboolean ret;
      struct set_opts opts;
      int sock;

      va_start(args, opt1);
      ret = parse_set_opts(&opts, err, opt1, args);
      va_end(args);

      if (!ret)
            return ret;

      sock = g_io_channel_unix_get_fd(io);

      switch (type) {
      case BT_IO_L2RAW:
      case BT_IO_L2CAP:
            return l2cap_set(sock, opts.sec_level, opts.imtu, opts.omtu,
                                          opts.master, err);
      case BT_IO_RFCOMM:
            return rfcomm_set(sock, opts.sec_level, opts.master, err);
      case BT_IO_SCO:
            return sco_set(sock, opts.mtu, err);
      }

      g_set_error(err, BT_IO_ERROR, BT_IO_ERROR_INVALID_ARGS,
                  "Unknown BtIO type %d", type);
      return FALSE;
}

gboolean bt_io_get(GIOChannel *io, BtIOType type, GError **err,
                                          BtIOOption opt1, ...)
{
      va_list args;
      gboolean ret;

      va_start(args, opt1);
      ret = get_valist(io, type, err, opt1, args);
      va_end(args);

      return ret;
}

static GIOChannel *create_io(BtIOType type, gboolean server,
                              struct set_opts *opts, GError **err)
{
      int sock;
      GIOChannel *io;

      switch (type) {
      case BT_IO_L2RAW:
            sock = socket(PF_BLUETOOTH, SOCK_RAW, BTPROTO_L2CAP);
            if (sock < 0) {
                  ERROR_FAILED(err, "socket(RAW, L2CAP)", errno);
                  return NULL;
            }
            if (l2cap_bind(sock, &opts->src, server ? opts->psm : 0) < 0) {
                  ERROR_FAILED(err, "l2cap_bind", errno);
                  return NULL;
            }
            if (!l2cap_set(sock, opts->sec_level, 0, 0, -1, err))
                  return NULL;
            break;
      case BT_IO_L2CAP:
            sock = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP);
            if (sock < 0) {
                  ERROR_FAILED(err, "socket(SEQPACKET, L2CAP)", errno);
                  return NULL;
            }
            if (l2cap_bind(sock, &opts->src, server ? opts->psm : 0) < 0) {
                  ERROR_FAILED(err, "l2cap_bind", errno);
                  return NULL;
            }
            if (!l2cap_set(sock, opts->sec_level, opts->imtu, opts->omtu,
                                          opts->master, err))
                  return NULL;
            break;
      case BT_IO_RFCOMM:
            sock = socket(PF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM);
            if (sock < 0) {
                  ERROR_FAILED(err, "socket(STREAM, RFCOMM)", errno);
                  return NULL;
            }
            if (rfcomm_bind(sock, &opts->src,
                              server ? opts->channel : 0) < 0) {
                  ERROR_FAILED(err, "rfcomm_bind", errno);
                  return NULL;
            }
            if (!rfcomm_set(sock, opts->sec_level, opts->master, err))
                  return NULL;
            break;
      case BT_IO_SCO:
            sock = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_SCO);
            if (sock < 0) {
                  ERROR_FAILED(err, "socket(SEQPACKET, SCO)", errno);
                  return NULL;
            }
            if (sco_bind(sock, &opts->src) < 0) {
                  ERROR_FAILED(err, "sco_bind", errno);
                  return NULL;
            }
            if (!sco_set(sock, opts->mtu, err))
                  return NULL;
            break;
      default:
            g_set_error(err, BT_IO_ERROR, BT_IO_ERROR_INVALID_ARGS,
                        "Unknown BtIO type %d", type);
            return NULL;
      }

      io = g_io_channel_unix_new(sock);

      g_io_channel_set_close_on_unref(io, TRUE);
      g_io_channel_set_flags(io, G_IO_FLAG_NONBLOCK, NULL);

      return io;
}

GIOChannel *bt_io_connect(BtIOType type, BtIOConnect connect,
                        gpointer user_data, GDestroyNotify destroy,
                        GError **gerr, BtIOOption opt1, ...)
{
      GIOChannel *io;
      va_list args;
      struct set_opts opts;
      int err, sock;
      gboolean ret;

      va_start(args, opt1);
      ret = parse_set_opts(&opts, gerr, opt1, args);
      va_end(args);

      if (ret == FALSE)
            return NULL;

      io = create_io(type, FALSE, &opts, gerr);
      if (io == NULL)
            return NULL;

      sock = g_io_channel_unix_get_fd(io);

      switch (type) {
      case BT_IO_L2RAW:
            err = l2cap_connect(sock, &opts.dst, 0);
            break;
      case BT_IO_L2CAP:
            err = l2cap_connect(sock, &opts.dst, opts.psm);
            break;
      case BT_IO_RFCOMM:
            err = rfcomm_connect(sock, &opts.dst, opts.channel);
            break;
      case BT_IO_SCO:
            err = sco_connect(sock, &opts.dst);
            break;
      default:
            g_set_error(gerr, BT_IO_ERROR, BT_IO_ERROR_INVALID_ARGS,
                                    "Unknown BtIO type %d", type);
            return NULL;
      }

      if (err < 0) {
            g_set_error(gerr, BT_IO_ERROR, BT_IO_ERROR_CONNECT_FAILED,
                        "connect: %s (%d)", strerror(-err), -err);
            g_io_channel_unref(io);
            return NULL;
      }

      connect_add(io, connect, user_data, destroy);

      return io;
}

GIOChannel *bt_io_listen(BtIOType type, BtIOConnect connect,
                        BtIOConfirm confirm, gpointer user_data,
                        GDestroyNotify destroy, GError **err,
                        BtIOOption opt1, ...)
{
      GIOChannel *io;
      va_list args;
      struct set_opts opts;
      int sock;
      gboolean ret;

      if (type == BT_IO_L2RAW) {
            g_set_error(err, BT_IO_ERROR, BT_IO_ERROR_INVALID_ARGS,
                        "Server L2CAP RAW sockets not supported");
            return NULL;
      }

      va_start(args, opt1);
      ret = parse_set_opts(&opts, err, opt1, args);
      va_end(args);

      if (ret == FALSE)
            return NULL;

      io = create_io(type, TRUE, &opts, err);
      if (io == NULL)
            return NULL;

      sock = g_io_channel_unix_get_fd(io);

      if (confirm)
            setsockopt(sock, SOL_BLUETOOTH, BT_DEFER_SETUP, &opts.defer,
                                          sizeof(opts.defer));

      if (listen(sock, 5) < 0) {
            ERROR_FAILED(err, "listen", errno);
            g_io_channel_unref(io);
            return NULL;
      }

      server_add(io, connect, confirm, user_data, destroy);

      return io;
}

GQuark bt_io_error_quark(void)
{
      return g_quark_from_static_string("bt-io-error-quark");
}


Generated by  Doxygen 1.6.0   Back to index