diff options
Diffstat (limited to 'test')
-rw-r--r-- | test/Makefile.am | 44 | ||||
-rwxr-xr-x | test/apitest | 448 | ||||
-rw-r--r-- | test/attest.c | 183 | ||||
-rw-r--r-- | test/auth-agent.c | 351 | ||||
-rw-r--r-- | test/bdaddr.8 | 68 | ||||
-rw-r--r-- | test/bdaddr.c | 460 | ||||
-rw-r--r-- | test/dbusdef.py | 59 | ||||
-rw-r--r-- | test/hciemu.c | 1346 | ||||
-rwxr-xr-x | test/hsmicro | 20 | ||||
-rwxr-xr-x | test/hsplay | 22 | ||||
-rw-r--r-- | test/hstest.c | 308 | ||||
-rw-r--r-- | test/l2test.c | 1104 | ||||
-rw-r--r-- | test/lmptest.c | 175 | ||||
-rw-r--r-- | test/passkey-agent.c | 418 | ||||
-rw-r--r-- | test/rctest.c | 688 | ||||
-rw-r--r-- | test/scotest.c | 434 | ||||
-rw-r--r-- | test/sdptest.c | 146 |
17 files changed, 6274 insertions, 0 deletions
diff --git a/test/Makefile.am b/test/Makefile.am new file mode 100644 index 00000000..16b80e7d --- /dev/null +++ b/test/Makefile.am @@ -0,0 +1,44 @@ + +if TEST +sbin_PROGRAMS = hciemu + +bin_PROGRAMS = l2test rctest + +noinst_PROGRAMS = sdptest scotest attest hstest bdaddr lmptest \ + passkey-agent auth-agent + +hciemu_LDADD = $(top_builddir)/common/libhelper.a \ + @GLIB_LIBS@ @BLUEZ_LIBS@ + +l2test_LDADD = @BLUEZ_LIBS@ + +rctest_LDADD = @BLUEZ_LIBS@ + +sdptest_LDADD = @BLUEZ_LIBS@ + +scotest_LDADD = @BLUEZ_LIBS@ + +attest_LDADD = @BLUEZ_LIBS@ + +hstest_LDADD = @BLUEZ_LIBS@ + +bdaddr_SOURCES = bdaddr.c + +bdaddr_LDADD = @BLUEZ_LIBS@ $(top_builddir)/common/libhelper.a + +lmptest_LDADD = @BLUEZ_LIBS@ + +passkey_agent_LDADD = @DBUS_LIBS@ + +auth_agent_LDADD = @DBUS_LIBS@ + +noinst_MANS = bdaddr.8 + +AM_CFLAGS = @BLUEZ_CFLAGS@ @DBUS_CFLAGS@ @GLIB_CFLAGS@ +endif + +INCLUDES = -I$(top_srcdir)/common + +EXTRA_DIST = apitest hsplay hsmicro bdaddr.8 dbusdef.py + +MAINTAINERCLEANFILES = Makefile.in diff --git a/test/apitest b/test/apitest new file mode 100755 index 00000000..b1c3f103 --- /dev/null +++ b/test/apitest @@ -0,0 +1,448 @@ +#!/usr/bin/env python + +import dbus +import dbus.decorators +import dbus.glib +import gobject +import sys +import getopt +from signal import * + +mgr_cmds = [ "InterfaceVersion", "ListAdapters", "DefaultAdapter" ] +mgr_signals = [ "AdapterAdded", "AdapterRemoved" ] + +dev_cmds = [ "GetAddress", + "GetVersion", + "GetRevision", + "GetManufacturer", + "GetCompany", + "GetMode", + "SetMode", + "GetDiscoverableTimeout", + "SetDiscoverableTimeout", + "IsConnectable", + "IsDiscoverable", + "IsConnected", + "ListConnections", + "GetMajorClass", + "ListAvailableMinorClasses", + "GetMinorClass", + "SetMinorClass", + "GetServiceClasses", + "GetName", + "SetName", + "GetRemoteVersion", + "GetRemoteRevision", + "GetRemoteManufacturer", + "GetRemoteCompany", + "GetRemoteMajorClass", + "GetRemoteMinorClass", + "GetRemoteServiceClasses", + "GetRemoteClass", + "GetRemoteName", + "GetRemoteAlias", + "SetRemoteAlias", + "ClearRemoteAlias", + "LastSeen", + "LastUsed", + "DisconnectRemoteDevice", + "CreateBonding", + "CancelBondingProcess", + "RemoveBonding", + "HasBonding", + "ListBondings", + "GetPinCodeLength", + "GetEncryptionKeySize", + "DiscoverDevices", + "DiscoverDevicesWithoutNameResolving", + "CancelDiscovery", + "ListRemoteDevices", + "ListRecentRemoteDevices" ] +dev_signals = [ "ModeChanged", + "NameChanged", + "MinorClassChanged", + "DiscoveryStarted", + "DiscoveryCompleted", + "RemoteDeviceFound", + "RemoteNameUpdated", + "RemoteNameFailed", + "RemoteAliasChanged" + "RemoteAliasCleared", + "RemoteDeviceConnected", + "RemoteDeviceDisconnectRequested", + "RemoteDeviceDisconnected", + "BondingCreated", + "BondingRemoved" ] + +dev_signals_filter = [ "/org/bluez/hci0", "/org/bluez/hci1", + "/org/bluez/hci2", "/org/bluez/hci3", + "/org/bluez/hci4", "/org/bluez/hci5", + "/org/bluez/hci6", "/org/bluez/hci7" ] + +class Tester: + exit_events = [] + dev_path = None + need_dev = False + listen = False + at_interrupt = None + + def __init__(self, argv): + self.name = argv[0] + + self.parse_args(argv[1:]) + + try: + self.dbus_setup() + except dbus.DBusException, e: + print 'Failed to do D-Bus setup: %s' % e + sys.exit(1) + + def parse_args(self, argv): + try: + opts, args = getopt.getopt(argv, "hli:") + except getopt.GetoptError: + self.usage() + sys.exit(1) + + for o, a in opts: + if o == "-h": + self.usage() + sys.exit() + elif o == "-l": + self.listen = True + elif o == "-i": + if a[0] == '/': + self.dev_path = a + else: + self.dev_path = '/org/bluez/%s' % a + + if not (args or self.listen): + self.usage() + sys.exit(1) + + if args: + self.cmd = args[0] + self.cmd_args = args[1:] + + def dbus_dev_setup(self): + if not self.dev_path: + try: + self.dbus_mgr_setup() + self.dev_path = self.manager.DefaultAdapter() + except dbus.DBusException, e: + print 'Failed to get default device: %s' % e + sys.exit(1) + try: + obj = self.bus.get_object('org.bluez', self.dev_path) + self.device = dbus.Interface(obj, 'org.bluez.Adapter') + except dbus.DBusException, e: + print 'Failed to setup device path: %s' % e + sys.exit(1) + + def dbus_dev_sig_setup(self): + try: + for signal in dev_signals: + for path in dev_signals_filter: + self.bus.add_signal_receiver(self.dev_signal_handler, + signal, 'org.bluez.Adapter', + 'org.bluez', path, + message_keyword='dbus_message') + except dbus.DBusException, e: + print 'Failed to setup signal handler for device path: %s' % e + sys.exit(1) + + def dbus_mgr_sig_setup(self): + try: + for signal in mgr_signals: + self.bus.add_signal_receiver(self.mgr_signal_handler, + signal,'org.bluez.Manager', + 'org.bluez', '/org/bluez') + except dbus.DBusException, e: + print 'Failed to setup signal handler for manager path: %s' % e + sys.exit(1) + + def dbus_mgr_setup(self): + self.manager_obj = self.bus.get_object('org.bluez', '/org/bluez') + self.manager = dbus.Interface(self.manager_obj, 'org.bluez.Manager') + + def dbus_setup(self): + self.bus = dbus.SystemBus() + + def usage(self): + print 'Usage: %s [-i <dev>] [-l] [-h] <cmd> [arg1..]' % self.name + print ' -i <dev> Specify device (e.g. "hci0" or "/org/bluez/hci0")' + print ' -l Listen for events (no command required)' + print ' -h Show this help' + print 'Manager commands:' + for cmd in mgr_cmds: + print '\t%s' % cmd + print 'Adapter commands:' + for cmd in dev_cmds: + print '\t%s' % cmd + + #@dbus.decorators.explicitly_pass_message + def dev_signal_handler(*args, **keywords): + dbus_message = keywords["dbus_message"] + print '%s - %s: ' % (dbus_message.get_member(), dbus_message.get_path()), + for arg in args[1:]: + print '%s ' % arg, + print + + #@dbus.decorators.explicitly_pass_message + def mgr_signal_handler(*args, **keywords): + dbus_message = keywords["dbus_message"] + print '%s: ' % dbus_message.get_member() + for arg in args[1:]: + print '%s ' % arg, + print + + def signal_cb(self, sig, frame): + print 'Caught signal, exiting' + if self.at_interrupt: + self.at_interrupt() + self.main_loop.quit() + + def call_mgr_dbus_func(self): + if self.cmd == 'InterfaceVersion': + try: + print self.manager.InterfaceVersion() + except dbus.DBusException, e: + print 'Sending %s failed: %s' % (self.cmd, e) + if self.cmd == 'ListAdapters': + try: + devices = self.manager.ListAdapters() + except dbus.DBusException, e: + print 'Sending %s failed: %s' % (self.cmd, e) + sys.exit(1) + for device in devices: + print device + elif self.cmd == 'DefaultAdapter': + try: + print self.manager.DefaultAdapter() + except dbus.DBusException, e: + print 'Sending %s failed: %s' % (self.cmd, e) + sys.exit(1) + + def call_dev_dbus_func(self): + try: + if self.cmd == 'GetAddress': + print self.device.GetAddress() + elif self.cmd == 'GetManufacturer': + print self.device.GetManufacturer() + elif self.cmd == 'GetVersion': + print self.device.GetVersion() + elif self.cmd == 'GetRevision': + print self.device.GetRevision() + elif self.cmd == 'GetCompany': + print self.device.GetCompany() + elif self.cmd == 'GetMode': + print self.device.GetMode() + elif self.cmd == 'SetMode': + if len(self.cmd_args) == 1: + self.device.SetMode(self.cmd_args[0]) + else: + print 'Usage: %s -i <dev> SetMode scan_mode' % self.name + elif self.cmd == 'GetDiscoverableTimeout': + print '%u' % (self.device.GetDiscoverableTimeout()) + elif self.cmd == 'SetDiscoverableTimeout': + if len(self.cmd_args) == 1: + self.device.SetDiscoverableTimeout(dbus.UInt32(self.cmd_args[0])) + else: + print 'Usage: %s -i <dev> SetDiscoverableTimeout timeout' % self.name + elif self.cmd == 'IsConnectable': + print self.device.IsConnectable() + elif self.cmd == 'IsDiscoverable': + print self.device.IsDiscoverable() + elif self.cmd == 'IsConnected': + if len(self.cmd_args) == 1: + print self.device.IsConnected(self.cmd_args[0]) + else: + print 'Usage: %s -i <dev> IsConnected address' % self.name + elif self.cmd == 'ListConnections': + print self.device.ListConnections() + elif self.cmd == 'GetMajorClass': + print self.device.GetMajorClass() + elif self.cmd == 'ListAvailableMinorClasses': + print self.device.ListAvailableMinorClasses() + elif self.cmd == 'GetMinorClass': + print self.device.GetMinorClass() + elif self.cmd == 'SetMinorClass': + if len(self.cmd_args) == 1: + self.device.SetMinorClass(self.cmd_args[0]) + else: + print 'Usage: %s -i <dev> SetMinorClass minor' % self.name + elif self.cmd == 'GetServiceClasses': + classes = self.device.GetServiceClasses() + for clas in classes: + print clas, + elif self.cmd == 'GetName': + print self.device.GetName() + elif self.cmd == 'SetName': + if len(self.cmd_args) == 1: + self.device.SetName(self.cmd_args[0]) + else: + print 'Usage: %s -i <dev> SetName newname' % self.name + elif self.cmd == 'GetRemoteName': + if len(self.cmd_args) == 1: + print self.device.GetRemoteName(self.cmd_args[0]) + else: + print 'Usage: %s -i <dev> GetRemoteName address' % self.name + elif self.cmd == 'GetRemoteVersion': + if len(self.cmd_args) == 1: + print self.device.GetRemoteVersion(self.cmd_args[0]) + else: + print 'Usage: %s -i <dev> GetRemoteVersion address' % self.name + elif self.cmd == 'GetRemoteRevision': + if len(self.cmd_args) == 1: + print self.device.GetRemoteRevision(self.cmd_args[0]) + else: + print 'Usage: %s -i <dev> GetRemoteRevision address' % self.name + elif self.cmd == 'GetRemoteManufacturer': + if len(self.cmd_args) == 1: + print self.device.GetRemoteManufacturer(self.cmd_args[0]) + else: + print 'Usage: %s -i <dev> GetRemoteManufacturer address' % self.name + elif self.cmd == 'GetRemoteCompany': + if len(self.cmd_args) == 1: + print self.device.GetRemoteCompany(self.cmd_args[0]) + else: + print 'Usage: %s -i <dev> GetRemoteCompany address' % self.name + elif self.cmd == 'GetRemoteAlias': + if len(self.cmd_args) == 1: + print self.device.GetRemoteAlias(self.cmd_args[0]) + else: + print 'Usage: %s -i <dev> GetRemoteAlias address' % self.name + elif self.cmd == 'GetRemoteMajorClass': + if len(self.cmd_args) == 1: + print self.device.GetRemoteMajorClass(self.cmd_args[0]) + else: + print 'Usage: %s -i <dev> GetRemoteMajorClass address' % self.name + elif self.cmd == 'GetRemoteMinorClass': + if len(self.cmd_args) == 1: + print self.device.GetRemoteMinorClass(self.cmd_args[0]) + else: + print 'Usage: %s -i <dev> GetRemoteMinorClass address' % self.name + elif self.cmd == 'GetRemoteServiceClasses': + if len(self.cmd_args) == 1: + print self.device.GetRemoteServiceClasses(self.cmd_args[0]) + else: + print 'Usage: %s -i <dev> GetRemoteServiceClasses address' % self.name + elif self.cmd == 'SetRemoteAlias': + if len(self.cmd_args) == 2: + self.device.SetRemoteAlias(self.cmd_args[0], self.cmd_args[1]) + else: + print 'Usage: %s -i <dev> SetRemoteAlias address alias' % self.name + elif self.cmd == 'ClearRemoteAlias': + if len(self.cmd_args) == 1: + print self.device.ClearRemoteAlias(self.cmd_args[0]) + else: + print 'Usage: %s -i <dev> ClearRemoteAlias address' % self.name + elif self.cmd == 'LastSeen': + if len(self.cmd_args) == 1: + print self.device.LastSeen(self.cmd_args[0]) + else: + print 'Usage: %s -i <dev> LastSeen address' % self.name + elif self.cmd == 'LastUsed': + if len(self.cmd_args) == 1: + print self.device.LastUsed(self.cmd_args[0]) + else: + print 'Usage: %s -i <dev> LastUsed address' % self.name + elif self.cmd == 'DisconnectRemoteDevice': + if len(self.cmd_args) == 1: + print self.device.LastUsed(self.cmd_args[0]) + else: + print 'Usage: %s -i <dev> DisconnectRemoteDevice address' % self.name + elif self.cmd == 'CreateBonding': + if len(self.cmd_args) == 1: + print self.device.CreateBonding(self.cmd_args[0]) + else: + print 'Usage: %s -i <dev> CreateBonding address' % self.name + elif self.cmd == 'RemoveBonding': + if len(self.cmd_args) == 1: + print self.device.RemoveBonding(self.cmd_args[0]) + else: + print 'Usage: %s -i <dev> RemoveBonding address' % self.name + elif self.cmd == 'CancelBondingProcess': + if len(self.cmd_args) == 1: + print self.device.CancelBondingProcess(self.cmd_args[0]) + else: + print 'Usage: %s -i <dev> CancelBondingProcess address' % self.name + elif self.cmd == 'HasBonding': + if len(self.cmd_args) == 1: + print self.device.HasBonding(self.cmd_args[0]) + else: + print 'Usage: %s -i <dev> HasBonding address' % self.name + elif self.cmd == 'ListBondings': + bondings = self.device.ListBondings() + for bond in bondings: + print bond, + elif self.cmd == 'GetPinCodeLength': + if len(self.cmd_args) == 1: + print self.device.GetPinCodeLength(self.cmd_args[0]) + else: + print 'Usage: %s -i <dev> GetPinCodeLength address' % self.name + elif self.cmd == 'GetEncryptionKeySize': + if len(self.cmd_args) == 1: + print self.device.GetEncryptionKeySize(self.cmd_args[0]) + else: + print 'Usage: %s -i <dev> GetEncryptionKeySize address' % self.name + elif self.cmd == 'DiscoverDevices': + print self.device.DiscoverDevices() + elif self.cmd == 'DiscoverDevicesWithoutNameResolving': + print self.device.DiscoverDevicesWithoutNameResolving() + elif self.cmd == 'ListRemoteDevices': + devices = self.device.ListRemoteDevices() + for device in devices: + print device, + elif self.cmd == 'ListRecentRemoteDevices': + if len(self.cmd_args) == 1: + devices = self.device.ListRecentRemoteDevices(self.cmd_args[0]) + for device in devices: + print device, + else: + print 'Usage: %s -i <dev> ListRecentRemoteDevices date' % self.name + else: + # FIXME: remove at future version + print 'Script Error: Method %s not found. Maybe a mispelled word.' % (self.cmd_args) + except dbus.DBusException, e: + print '%s failed: %s' % (self.cmd, e) + sys.exit(1) + + def run(self): + # Manager methods + if self.listen: + self.dbus_mgr_sig_setup() + self.dbus_dev_sig_setup() + print 'Listening for events...' + + if self.cmd in mgr_cmds: + try: + self.dbus_mgr_setup() + except dbus.DBusException, e: + print 'Failed to setup manager interface: %s' % e + sys.exit(1) + self.call_mgr_dbus_func() + elif self.cmd in dev_cmds: + try: + self.dbus_dev_setup() + except dbus.DBusException, e: + print 'Failed to setup device interface: %s' % e + sys.exit(1) + self.call_dev_dbus_func() + elif not self.listen: + print 'Unknown command: %s' % self.cmd + self.usage() + sys.exit(1) + + if self.listen: + signal(SIGINT, self.signal_cb) + signal(SIGTERM, self.signal_cb) + self.main_loop = gobject.MainLoop() + self.main_loop.run() + +if __name__ == '__main__': + gobject.threads_init() + dbus.glib.init_threads() + + tester = Tester(sys.argv) + tester.run() diff --git a/test/attest.c b/test/attest.c new file mode 100644 index 00000000..531c2157 --- /dev/null +++ b/test/attest.c @@ -0,0 +1,183 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2001-2008 Marcel Holtmann <marcel@holtmann.org> + * + * + * 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 + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <errno.h> +#include <fcntl.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <termios.h> +#include <sys/ioctl.h> +#include <sys/socket.h> + +#include <bluetooth/bluetooth.h> +#include <bluetooth/rfcomm.h> + +static int at_command(int fd, char *cmd, int to) +{ + fd_set rfds; + struct timeval timeout; + char buf[1024]; + int sel, len, i, n; + + len = write(fd, cmd, strlen(cmd)); + + for (i = 0; i < 100; i++) { + + FD_ZERO(&rfds); + FD_SET(fd, &rfds); + + timeout.tv_sec = 0; + timeout.tv_usec = to; + + if ((sel = select(fd + 1, &rfds, NULL, NULL, &timeout)) > 0) { + + if (FD_ISSET(fd, &rfds)) { + memset(buf, 0, sizeof(buf)); + len = read(fd, buf, sizeof(buf)); + for (n = 0; n < len; n++) + printf("%c", buf[n]); + if (strstr(buf, "\r\nOK") != NULL) + break; + if (strstr(buf, "\r\nERROR") != NULL) + break; + if (strstr(buf, "\r\nCONNECT") != NULL) + break; + } + + } + + } + + return 0; +} + +static int open_device(char *device) +{ + struct termios ti; + int fd; + + fd = open(device, O_RDWR | O_NOCTTY | O_NONBLOCK); + if (fd < 0) { + fprintf(stderr, "Can't open serial port: %s (%d)\n", + strerror(errno), errno); + return -1; + } + + tcflush(fd, TCIOFLUSH); + + /* Switch tty to RAW mode */ + cfmakeraw(&ti); + tcsetattr(fd, TCSANOW, &ti); + + return fd; +} + +static int open_socket(bdaddr_t *bdaddr, uint8_t channel) +{ + struct sockaddr_rc addr; + int sk; + + sk = socket(PF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM); + if (sk < 0) { + fprintf(stderr, "Can't create socket: %s (%d)\n", + strerror(errno), errno); + return -1; + } + + memset(&addr, 0, sizeof(addr)); + addr.rc_family = AF_BLUETOOTH; + bacpy(&addr.rc_bdaddr, BDADDR_ANY); + + if (bind(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + fprintf(stderr, "Can't bind socket: %s (%d)\n", + strerror(errno), errno); + close(sk); + return -1; + } + + memset(&addr, 0, sizeof(addr)); + addr.rc_family = AF_BLUETOOTH; + bacpy(&addr.rc_bdaddr, bdaddr); + addr.rc_channel = channel; + + if (connect(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + fprintf(stderr, "Can't connect: %s (%d)\n", + strerror(errno), errno); + close(sk); + return -1; + } + + return sk; +} + +static void usage(void) +{ + printf("Usage:\n\tattest <device> | <bdaddr> [channel]\n"); +} + +int main(int argc, char *argv[]) +{ + int fd; + + bdaddr_t bdaddr; + uint8_t channel; + + switch (argc) { + case 2: + str2ba(argv[1], &bdaddr); + channel = 1; + break; + case 3: + str2ba(argv[1], &bdaddr); + channel = atoi(argv[2]); + break; + default: + usage(); + exit(-1); + } + + if (bacmp(BDADDR_ANY, &bdaddr)) { + printf("Connecting to %s on channel %d\n", argv[1], channel); + fd = open_socket(&bdaddr, channel); + } else { + printf("Opening device %s\n", argv[1]); + fd = open_device(argv[1]); + } + + if (fd < 0) + exit(-2); + + at_command(fd, "ATZ\r\n", 10000); + at_command(fd, "AT+CPBS=\"ME\"\r\n", 10000); + at_command(fd, "AT+CPBR=1,100\r\n", 100000); + + close(fd); + + return 0; +} diff --git a/test/auth-agent.c b/test/auth-agent.c new file mode 100644 index 00000000..2686c3ee --- /dev/null +++ b/test/auth-agent.c @@ -0,0 +1,351 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2008 Marcel Holtmann <marcel@holtmann.org> + * + * + * 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 + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <errno.h> +#include <unistd.h> +#include <stdlib.h> +#include <signal.h> +#include <getopt.h> +#include <string.h> + +#include <dbus/dbus.h> + +#define INTERFACE "org.bluez.Security" + +static volatile sig_atomic_t __io_canceled = 0; +static volatile sig_atomic_t __io_terminated = 0; + +static void sig_term(int sig) +{ + __io_canceled = 1; +} + +static DBusHandlerResult agent_filter(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + const char *name, *old, *new; + + if (!dbus_message_is_signal(msg, DBUS_INTERFACE_DBUS, "NameOwnerChanged")) + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_STRING, &name, DBUS_TYPE_STRING, &old, + DBUS_TYPE_STRING, &new, DBUS_TYPE_INVALID)) { + fprintf(stderr, "Invalid arguments for NameOwnerChanged signal"); + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + + if (!strcmp(name, "org.bluez") && *new == '\0') { + fprintf(stderr, "Authorization service has been terminated\n"); + __io_terminated = 1; + } + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +static DBusHandlerResult authorize_message(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + DBusMessage *reply; + const char *adapter, *address, *service, *string; + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_STRING, &adapter, DBUS_TYPE_STRING, &address, + DBUS_TYPE_STRING, &service, DBUS_TYPE_STRING, &string, + DBUS_TYPE_INVALID)) { + fprintf(stderr, "Invalid arguments for passkey Confirm method"); + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + + printf("Authorization request for device %s\n", address); + + reply = dbus_message_new_method_return(msg); + if (!reply) { + fprintf(stderr, "Can't create reply message\n"); + return DBUS_HANDLER_RESULT_NEED_MEMORY; + } + + dbus_connection_send(conn, reply, NULL); + + dbus_connection_flush(conn); + + dbus_message_unref(reply); + + return DBUS_HANDLER_RESULT_HANDLED; +} + +static DBusHandlerResult cancel_message(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + DBusMessage *reply; + const char *adapter, *address, *service, *string; + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_STRING, &adapter, DBUS_TYPE_STRING, &address, + DBUS_TYPE_STRING, &service, DBUS_TYPE_STRING, &string, + DBUS_TYPE_INVALID)) { + fprintf(stderr, "Invalid arguments for passkey Confirm method"); + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + + printf("Request canceled for device %s\n", address); + + reply = dbus_message_new_method_return(msg); + if (!reply) { + fprintf(stderr, "Can't create reply message\n"); + return DBUS_HANDLER_RESULT_NEED_MEMORY; + } + + dbus_connection_send(conn, reply, NULL); + + dbus_connection_flush(conn); + + dbus_message_unref(reply); + + return DBUS_HANDLER_RESULT_HANDLED; +} + +static DBusHandlerResult release_message(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + DBusMessage *reply; + + if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_INVALID)) { + fprintf(stderr, "Invalid arguments for Release method"); + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + + if (!__io_canceled) + fprintf(stderr, "Authorization agent has been released\n"); + + __io_terminated = 1; + + reply = dbus_message_new_method_return(msg); + if (!reply) { + fprintf(stderr, "Can't create reply message\n"); + return DBUS_HANDLER_RESULT_NEED_MEMORY; + } + + dbus_connection_send(conn, reply, NULL); + + dbus_connection_flush(conn); + + dbus_message_unref(reply); + + return DBUS_HANDLER_RESULT_HANDLED; +} + +static DBusHandlerResult auth_message(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + if (dbus_message_is_method_call(msg, "org.bluez.AuthorizationAgent", "Authorize")) + return authorize_message(conn, msg, data); + + if (dbus_message_is_method_call(msg, "org.bluez.AuthorizationAgent", "Cancel")) + return cancel_message(conn, msg, data); + + if (dbus_message_is_method_call(msg, "org.bluez.AuthorizationAgent", "Release")) + return release_message(conn, msg, data); + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +static const DBusObjectPathVTable auth_table = { + .message_function = auth_message, +}; + +static int register_auth(DBusConnection *conn, const char *auth_path) +{ + DBusMessage *msg, *reply; + DBusError err; + + if (!dbus_connection_register_object_path(conn, auth_path, + &auth_table, NULL)) { + fprintf(stderr, "Can't register object path for agent\n"); + return -1; + } + + msg = dbus_message_new_method_call("org.bluez", "/org/bluez", + INTERFACE, "RegisterDefaultAuthorizationAgent"); + if (!msg) { + fprintf(stderr, "Can't allocate new method call\n"); + return -1; + } + + dbus_message_append_args(msg, DBUS_TYPE_STRING, &auth_path, + DBUS_TYPE_INVALID); + + dbus_error_init(&err); + + reply = dbus_connection_send_with_reply_and_block(conn, msg, -1, &err); + + dbus_message_unref(msg); + + if (!reply) { + fprintf(stderr, "Can't register authorization agent\n"); + if (dbus_error_is_set(&err)) { + fprintf(stderr, "%s\n", err.message); + dbus_error_free(&err); + } + return -1; + } + + dbus_message_unref(reply); + + dbus_connection_flush(conn); + + return 0; +} + +static int unregister_auth(DBusConnection *conn, const char *auth_path) +{ + DBusMessage *msg, *reply; + DBusError err; + + msg = dbus_message_new_method_call("org.bluez", "/org/bluez", + INTERFACE, "UnregisterDefaultAuthorizationAgent"); + if (!msg) { + fprintf(stderr, "Can't allocate new method call\n"); + dbus_connection_unref(conn); + exit(1); + } + + dbus_message_append_args(msg, DBUS_TYPE_STRING, &auth_path, + DBUS_TYPE_INVALID); + + dbus_error_init(&err); + + reply = dbus_connection_send_with_reply_and_block(conn, msg, -1, &err); + + dbus_message_unref(msg); + + if (!reply) { + fprintf(stderr, "Can't unregister authorization agent\n"); + if (dbus_error_is_set(&err)) { + fprintf(stderr, "%s\n", err.message); + dbus_error_free(&err); + } + return -1; + } + + dbus_message_unref(reply); + + dbus_connection_flush(conn); + + dbus_connection_unregister_object_path(conn, auth_path); + + return 0; +} + +static void usage(void) +{ + printf("Bluetooth authorization agent ver %s\n\n", VERSION); + + printf("Usage:\n" + "\tauth-agent [--path auth-path]\n" + "\n"); +} + +static struct option main_options[] = { + { "path", 1, 0, 'p' }, + { "help", 0, 0, 'h' }, + { 0, 0, 0, 0 } +}; + +int main(int argc, char *argv[]) +{ + struct sigaction sa; + DBusConnection *conn; + char match_string[128], default_path[128], *auth_path = NULL; + int opt; + + snprintf(default_path, sizeof(default_path), + "/org/bluez/auth_agent_%d", getpid()); + + while ((opt = getopt_long(argc, argv, "+p:h", main_options, NULL)) != EOF) { + switch(opt) { + case 'p': + if (optarg[0] != '/') { + fprintf(stderr, "Invalid path\n"); + exit(1); + } + auth_path = strdup(optarg); + break; + case 'h': + usage(); + exit(0); + default: + exit(1); + } + } + + argc -= optind; + argv += optind; + optind = 0; + + if (!auth_path) + auth_path = strdup(default_path); + + conn = dbus_bus_get(DBUS_BUS_SYSTEM, NULL); + if (!conn) { + fprintf(stderr, "Can't get on system bus"); + exit(1); + } + + if (register_auth(conn, auth_path) < 0) { + dbus_connection_unref(conn); + exit(1); + } + + if (!dbus_connection_add_filter(conn, agent_filter, NULL, NULL)) + fprintf(stderr, "Can't add signal filter"); + + snprintf(match_string, sizeof(match_string), + "interface=%s,member=NameOwnerChanged,arg0=%s", + DBUS_INTERFACE_DBUS, "org.bluez"); + + dbus_bus_add_match(conn, match_string, NULL); + + memset(&sa, 0, sizeof(sa)); + sa.sa_flags = SA_NOCLDSTOP; + sa.sa_handler = sig_term; + sigaction(SIGTERM, &sa, NULL); + sigaction(SIGINT, &sa, NULL); + + while (!__io_canceled && !__io_terminated) { + if (dbus_connection_read_write_dispatch(conn, 500) != TRUE) + break; + } + + if (!__io_terminated) + unregister_auth(conn, auth_path); + + dbus_connection_unref(conn); + + return 0; +} diff --git a/test/bdaddr.8 b/test/bdaddr.8 new file mode 100644 index 00000000..78d47945 --- /dev/null +++ b/test/bdaddr.8 @@ -0,0 +1,68 @@ +.TH BDADDR 8 "Sep 27 2005" BlueZ "Linux System Administration" +.SH NAME +bdaddr \- Utility for changing the Bluetooth device address +.SH SYNOPSIS +.B bdaddr +.br +.B bdaddr -h +.br +.B bdaddr [-i <dev>] [-r] [-t] [new bdaddr] + +.SH DESCRIPTION +.LP +.B +bdaddr +is used to query or set the local Bluetooth device address (BD_ADDR). If run +with no arguments, +.B +bdaddr +prints the chip manufacturer's name, and the current BD_ADDR. If the IEEE OUI +index file "oui.txt" is installed on the system, the BD_ADDR owner will be +displayed. If the optional [new bdaddr] argument is given, the device will be +reprogrammed with that address. This can either be permanent or temporary, as +specified by the -t flag. In both cases, the device must be reset before the +new address will become active. This can be done with a 'soft' reset by +specifying the -r flag, or a 'hard' reset by removing and replugging the +device. A 'hard' reset will cause the address to revert to the current +non-volatile value. +.PP +.B +bdaddr +uses manufacturer specific commands to set the address, and is therefore +device specific. For this reason, not all devices are supported, and not all +options are supported on all devices. +Current supported manufacturers are: +.B Ericsson, Cambridge Silicon Radio (CSR), Texas Instruments (TI), Zeevo +and +.B ST Microelectronics (ST) + +.SH OPTIONS +.TP +.BI -h +Gives a list of possible commands. +.TP +.BI -i\ <dev> +Specify a particular device to operate on. If not specified, default is the +first available device. +.TP +.BI -r +Reset device and make new BD_ADDR active. +.B +CSR +devices only. +.TP +.BI -t +Temporary change. Do not write to non-volatile memory. +.B +CSR +devices only. +.SH FILES +.TP +.I +/usr/share/misc/oui.txt +IEEE Organizationally Unique Identifier master file. +Manually update from: http://standards.ieee.org/regauth/oui/oui.txt +.SH AUTHORS +Written by Marcel Holtmann <marcel@holtmann.org>, +man page by Adam Laurie <adam@algroup.co.uk> +.PP diff --git a/test/bdaddr.c b/test/bdaddr.c new file mode 100644 index 00000000..7c0df31d --- /dev/null +++ b/test/bdaddr.c @@ -0,0 +1,460 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2008 Marcel Holtmann <marcel@holtmann.org> + * + * + * 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 + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <errno.h> +#include <stdlib.h> +#include <getopt.h> +#include <sys/ioctl.h> +#include <sys/socket.h> + +#include <bluetooth/bluetooth.h> +#include <bluetooth/hci.h> +#include <bluetooth/hci_lib.h> + +#include "oui.h" + +static int transient = 0; + +static int generic_reset_device(int dd) +{ + bdaddr_t bdaddr; + int err; + + err = hci_send_cmd(dd, 0x03, 0x0003, 0, NULL); + if (err < 0) + return err; + + return hci_read_bd_addr(dd, &bdaddr, 10000); +} + +#define OCF_ERICSSON_WRITE_BD_ADDR 0x000d +typedef struct { + bdaddr_t bdaddr; +} __attribute__ ((packed)) ericsson_write_bd_addr_cp; +#define ERICSSON_WRITE_BD_ADDR_CP_SIZE 6 + +static int ericsson_write_bd_addr(int dd, bdaddr_t *bdaddr) +{ + struct hci_request rq; + ericsson_write_bd_addr_cp cp; + + memset(&cp, 0, sizeof(cp)); + bacpy(&cp.bdaddr, bdaddr); + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_VENDOR_CMD; + rq.ocf = OCF_ERICSSON_WRITE_BD_ADDR; + rq.cparam = &cp; + rq.clen = ERICSSON_WRITE_BD_ADDR_CP_SIZE; + rq.rparam = NULL; + rq.rlen = 0; + + if (hci_send_req(dd, &rq, 1000) < 0) + return -1; + + return 0; +} + +#define OCF_ERICSSON_STORE_IN_FLASH 0x0022 +typedef struct { + uint8_t user_id; + uint8_t flash_length; + uint8_t flash_data[253]; +} __attribute__ ((packed)) ericsson_store_in_flash_cp; +#define ERICSSON_STORE_IN_FLASH_CP_SIZE 255 + +static int ericsson_store_in_flash(int dd, uint8_t user_id, uint8_t flash_length, uint8_t *flash_data) +{ + struct hci_request rq; + ericsson_store_in_flash_cp cp; + + memset(&cp, 0, sizeof(cp)); + cp.user_id = user_id; + cp.flash_length = flash_length; + if (flash_length > 0) + memcpy(cp.flash_data, flash_data, flash_length); + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_VENDOR_CMD; + rq.ocf = OCF_ERICSSON_STORE_IN_FLASH; + rq.cparam = &cp; + rq.clen = ERICSSON_STORE_IN_FLASH_CP_SIZE; + rq.rparam = NULL; + rq.rlen = 0; + + if (hci_send_req(dd, &rq, 1000) < 0) + return -1; + + return 0; +} + +static int csr_write_bd_addr(int dd, bdaddr_t *bdaddr) +{ + unsigned char cmd[] = { 0x02, 0x00, 0x0c, 0x00, 0x11, 0x47, 0x03, 0x70, + 0x00, 0x00, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + + unsigned char cp[254], rp[254]; + struct hci_request rq; + + if (transient) + cmd[14] = 0x08; + + cmd[16] = bdaddr->b[2]; + cmd[17] = 0x00; + cmd[18] = bdaddr->b[0]; + cmd[19] = bdaddr->b[1]; + cmd[20] = bdaddr->b[3]; + cmd[21] = 0x00; + cmd[22] = bdaddr->b[4]; + cmd[23] = bdaddr->b[5]; + + memset(&cp, 0, sizeof(cp)); + cp[0] = 0xc2; + memcpy(cp + 1, cmd, sizeof(cmd)); + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_VENDOR_CMD; + rq.ocf = 0x00; + rq.event = EVT_VENDOR; + rq.cparam = cp; + rq.clen = sizeof(cmd) + 1; + rq.rparam = rp; + rq.rlen = sizeof(rp); + + if (hci_send_req(dd, &rq, 2000) < 0) + return -1; + + if (rp[0] != 0xc2) { + errno = EIO; + return -1; + } + + if ((rp[9] + (rp[10] << 8)) != 0) { + errno = ENXIO; + return -1; + } + + return 0; +} + +static int csr_reset_device(int dd) +{ + unsigned char cmd[] = { 0x02, 0x00, 0x09, 0x00, + 0x00, 0x00, 0x01, 0x40, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + + unsigned char cp[254], rp[254]; + struct hci_request rq; + + if (transient) + cmd[6] = 0x02; + + memset(&cp, 0, sizeof(cp)); + cp[0] = 0xc2; + memcpy(cp + 1, cmd, sizeof(cmd)); + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_VENDOR_CMD; + rq.ocf = 0x00; + rq.event = EVT_VENDOR; + rq.cparam = cp; + rq.clen = sizeof(cmd) + 1; + rq.rparam = rp; + rq.rlen = sizeof(rp); + + if (hci_send_req(dd, &rq, 2000) < 0) + return -1; + + return 0; +} + +#define OCF_TI_WRITE_BD_ADDR 0x0006 +typedef struct { + bdaddr_t bdaddr; +} __attribute__ ((packed)) ti_write_bd_addr_cp; +#define TI_WRITE_BD_ADDR_CP_SIZE 6 + +static int ti_write_bd_addr(int dd, bdaddr_t *bdaddr) +{ + struct hci_request rq; + ti_write_bd_addr_cp cp; + + memset(&cp, 0, sizeof(cp)); + bacpy(&cp.bdaddr, bdaddr); + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_VENDOR_CMD; + rq.ocf = OCF_TI_WRITE_BD_ADDR; + rq.cparam = &cp; + rq.clen = TI_WRITE_BD_ADDR_CP_SIZE; + rq.rparam = NULL; + rq.rlen = 0; + + if (hci_send_req(dd, &rq, 1000) < 0) + return -1; + + return 0; +} + +#define OCF_BCM_WRITE_BD_ADDR 0x0001 +typedef struct { + bdaddr_t bdaddr; +} __attribute__ ((packed)) bcm_write_bd_addr_cp; +#define BCM_WRITE_BD_ADDR_CP_SIZE 6 + +static int bcm_write_bd_addr(int dd, bdaddr_t *bdaddr) +{ + struct hci_request rq; + bcm_write_bd_addr_cp cp; + + memset(&cp, 0, sizeof(cp)); + bacpy(&cp.bdaddr, bdaddr); + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_VENDOR_CMD; + rq.ocf = OCF_BCM_WRITE_BD_ADDR; + rq.cparam = &cp; + rq.clen = BCM_WRITE_BD_ADDR_CP_SIZE; + rq.rparam = NULL; + rq.rlen = 0; + + if (hci_send_req(dd, &rq, 1000) < 0) + return -1; + + return 0; +} + +#define OCF_ZEEVO_WRITE_BD_ADDR 0x0001 +typedef struct { + bdaddr_t bdaddr; +} __attribute__ ((packed)) zeevo_write_bd_addr_cp; +#define ZEEVO_WRITE_BD_ADDR_CP_SIZE 6 + +static int zeevo_write_bd_addr(int dd, bdaddr_t *bdaddr) +{ + struct hci_request rq; + zeevo_write_bd_addr_cp cp; + + memset(&cp, 0, sizeof(cp)); + bacpy(&cp.bdaddr, bdaddr); + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_VENDOR_CMD; + rq.ocf = OCF_ZEEVO_WRITE_BD_ADDR; + rq.cparam = &cp; + rq.clen = ZEEVO_WRITE_BD_ADDR_CP_SIZE; + rq.rparam = NULL; + rq.rlen = 0; + + if (hci_send_req(dd, &rq, 1000) < 0) + return -1; + + return 0; +} + +static int st_write_bd_addr(int dd, bdaddr_t *bdaddr) +{ + return ericsson_store_in_flash(dd, 0xfe, 6, (uint8_t *) bdaddr); +} + +static struct { + uint16_t compid; + int (*write_bd_addr)(int dd, bdaddr_t *bdaddr); + int (*reset_device)(int dd); +} vendor[] = { + { 0, ericsson_write_bd_addr, NULL }, + { 10, csr_write_bd_addr, csr_reset_device }, + { 13, ti_write_bd_addr, NULL }, + { 15, bcm_write_bd_addr, generic_reset_device }, + { 18, zeevo_write_bd_addr, NULL }, + { 48, st_write_bd_addr, generic_reset_device }, + { 57, ericsson_write_bd_addr, generic_reset_device }, + { 65535, NULL, NULL }, +}; + +static void usage(void) +{ + printf("bdaddr - Utility for changing the Bluetooth device address\n\n"); + printf("Usage:\n" + "\tbdaddr [-i <dev>] [-r] [-t] [new bdaddr]\n"); +} + +static struct option main_options[] = { + { "device", 1, 0, 'i' }, + { "reset", 0, 0, 'r' }, + { "transient", 0, 0, 't' }, + { "help", 0, 0, 'h' }, + { 0, 0, 0, 0 } +}; + +int main(int argc, char *argv[]) +{ + struct hci_dev_info di; + struct hci_version ver; + bdaddr_t bdaddr; + char addr[18], oui[9], *comp; + int i, dd, opt, dev = 0, reset = 0; + + bacpy(&bdaddr, BDADDR_ANY); + + while ((opt=getopt_long(argc, argv, "+i:rth", main_options, NULL)) != -1) { + switch (opt) { + case 'i': + dev = hci_devid(optarg); + if (dev < 0) { + perror("Invalid device"); + exit(1); + } + break; + + case 'r': + reset = 1; + break; + + case 't': + transient = 1; + break; + + case 'h': + default: + usage(); + exit(0); + } + } + + argc -= optind; + argv += optind; + optind = 0; + + dd = hci_open_dev(dev); + if (dd < 0) { + fprintf(stderr, "Can't open device hci%d: %s (%d)\n", + dev, strerror(errno), errno); + exit(1); + } + + if (hci_devinfo(dev, &di) < 0) { + fprintf(stderr, "Can't get device info for hci%d: %s (%d)\n", + dev, strerror(errno), errno); + hci_close_dev(dd); + exit(1); + } + + if (hci_read_local_version(dd, &ver, 1000) < 0) { + fprintf(stderr, "Can't read version info for hci%d: %s (%d)\n", + dev, strerror(errno), errno); + hci_close_dev(dd); + exit(1); + } + + if (!bacmp(&di.bdaddr, BDADDR_ANY)) { + if (hci_read_bd_addr(dd, &bdaddr, 1000) < 0) { + fprintf(stderr, "Can't read address for hci%d: %s (%d)\n", + dev, strerror(errno), errno); + hci_close_dev(dd); + exit(1); + } + } else + bacpy(&bdaddr, &di.bdaddr); + + printf("Manufacturer: %s (%d)\n", + bt_compidtostr(ver.manufacturer), ver.manufacturer); + + ba2oui(&bdaddr, oui); + comp = ouitocomp(oui); + + ba2str(&bdaddr, addr); + printf("Device address: %s", addr); + + if (comp) { + printf(" (%s)\n", comp); + free(comp); + } else + printf("\n"); + + if (argc < 1) { + hci_close_dev(dd); + exit(0); + } + + str2ba(argv[0], &bdaddr); + if (!bacmp(&bdaddr, BDADDR_ANY)) { + hci_close_dev(dd); + exit(0); + } + + for (i = 0; vendor[i].compid != 65535; i++) + if (ver.manufacturer == vendor[i].compid) { + ba2oui(&bdaddr, oui); + comp = ouitocomp(oui); + + ba2str(&bdaddr, addr); + printf("New BD address: %s", addr); + + if (comp) { + printf(" (%s)\n\n", comp); + free(comp); + } else + printf("\n\n"); + + + if (vendor[i].write_bd_addr(dd, &bdaddr) < 0) { + fprintf(stderr, "Can't write new address\n"); + hci_close_dev(dd); + exit(1); + } + + printf("Address changed - "); + + if (reset && vendor[i].reset_device) { + if (vendor[i].reset_device(dd) < 0) { + printf("Reset device manually\n"); + } else { + ioctl(dd, HCIDEVRESET, dev); + printf("Device reset successully\n"); + } + } else { + printf("Reset device now\n"); + } + + //ioctl(dd, HCIDEVRESET, dev); + //ioctl(dd, HCIDEVDOWN, dev); + //ioctl(dd, HCIDEVUP, dev); + + hci_close_dev(dd); + exit(0); + } + + hci_close_dev(dd); + + printf("\n"); + fprintf(stderr, "Unsupported manufacturer\n"); + + exit(1); +} diff --git a/test/dbusdef.py b/test/dbusdef.py new file mode 100644 index 00000000..ca6debf4 --- /dev/null +++ b/test/dbusdef.py @@ -0,0 +1,59 @@ +import dbus + +bus = dbus.SystemBus() + + +dummy = dbus.Interface(bus.get_object('org.bluez', '/org/bluez'), 'org.freedesktop.DBus.Introspectable') + +#print dummy.Introspect() + + +manager = dbus.Interface(bus.get_object('org.bluez', '/org/bluez'), 'org.bluez.Manager') + +database = dbus.Interface(bus.get_object('org.bluez', '/org/bluez'), 'org.bluez.Database') + + +try: + adapter = dbus.Interface(bus.get_object('org.bluez', manager.DefaultAdapter()), 'org.bluez.Adapter') + + test = dbus.Interface(bus.get_object('org.bluez', manager.DefaultAdapter()), 'org.bluez.Test') + + rfcomm = dbus.Interface(bus.get_object('org.bluez', manager.DefaultAdapter()), 'org.bluez.RFCOMM') +except: + adapter = "" + + test = "" + + rfcomm = "" + + +def create_service(identifier): + try: + path = manager.FindService(identifier) + except: + path = "" + + if (path != ""): + return dbus.Interface(bus.get_object('org.bluez', path), 'org.bluez.Service') + +echo = create_service("echo") + +transfer = create_service("transfer") + +network = create_service("network") + +input = create_service("input") + +audio = create_service("audio") + +headset = create_service("headset") + + +def connect_service(identifier): + try: + conn = manager.ActivateService(identifier) + except: + conn = "" + + if (conn != ""): + return dbus.Interface(bus.get_object(conn, "/org/bluez/" + identifier), 'org.bluez.' + identifier + '.Manager') diff --git a/test/hciemu.c b/test/hciemu.c new file mode 100644 index 00000000..bebcd9c8 --- /dev/null +++ b/test/hciemu.c @@ -0,0 +1,1346 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2000-2002 Maxim Krasnyansky <maxk@qualcomm.com> + * Copyright (C) 2003-2008 Marcel Holtmann <marcel@holtmann.org> + * + * + * 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 + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <errno.h> +#include <ctype.h> +#include <fcntl.h> +#include <unistd.h> +#include <stdlib.h> +#include <stdint.h> +#include <string.h> +#include <signal.h> +#include <getopt.h> +#include <syslog.h> +#include <sys/time.h> +#include <sys/stat.h> +#include <sys/poll.h> +#include <sys/ioctl.h> +#include <sys/socket.h> +#include <sys/resource.h> + +#include <bluetooth/bluetooth.h> +#include <bluetooth/hci.h> +#include <bluetooth/hci_lib.h> + +#include <netdb.h> + +#include <glib.h> + +#if __BYTE_ORDER == __LITTLE_ENDIAN +static inline uint64_t ntoh64(uint64_t n) +{ + uint64_t h; + uint64_t tmp = ntohl(n & 0x00000000ffffffff); + h = ntohl(n >> 32); + h |= tmp << 32; + return h; +} +#elif __BYTE_ORDER == __BIG_ENDIAN +#define ntoh64(x) (x) +#else +#error "Unknown byte order" +#endif +#define hton64(x) ntoh64(x) + +#define GHCI_DEV "/dev/ghci" + +#define VHCI_DEV "/dev/vhci" +#define VHCI_UDEV "/dev/hci_vhci" + +#define VHCI_MAX_CONN 12 + +#define VHCI_ACL_MTU 192 +#define VHCI_ACL_MAX_PKT 8 + +struct vhci_device { + uint8_t features[8]; + uint8_t name[248]; + uint8_t dev_class[3]; + uint8_t inq_mode; + uint8_t eir_fec; + uint8_t eir_data[240]; + uint16_t acl_cnt; + bdaddr_t bdaddr; + int fd; + int dd; + GIOChannel *scan; +}; + +struct vhci_conn { + bdaddr_t dest; + uint16_t handle; + GIOChannel *chan; +}; + +struct vhci_link_info { + bdaddr_t bdaddr; + uint8_t dev_class[3]; + uint8_t link_type; + uint8_t role; +} __attribute__ ((packed)); + +static struct vhci_device vdev; +static struct vhci_conn *vconn[VHCI_MAX_CONN]; + +struct btsnoop_hdr { + uint8_t id[8]; /* Identification Pattern */ + uint32_t version; /* Version Number = 1 */ + uint32_t type; /* Datalink Type */ +} __attribute__ ((packed)); +#define BTSNOOP_HDR_SIZE (sizeof(struct btsnoop_hdr)) + +struct btsnoop_pkt { + uint32_t size; /* Original Length */ + uint32_t len; /* Included Length */ + uint32_t flags; /* Packet Flags */ + uint32_t drops; /* Cumulative Drops */ + uint64_t ts; /* Timestamp microseconds */ + uint8_t data[0]; /* Packet Data */ +} __attribute__ ((packed)); +#define BTSNOOP_PKT_SIZE (sizeof(struct btsnoop_pkt)) + +static uint8_t btsnoop_id[] = { 0x62, 0x74, 0x73, 0x6e, 0x6f, 0x6f, 0x70, 0x00 }; + +static GMainLoop *event_loop; + +static volatile sig_atomic_t __io_canceled; + +static inline void io_init(void) +{ + __io_canceled = 0; +} + +static inline void io_cancel(void) +{ + __io_canceled = 1; +} + +static void sig_term(int sig) +{ + io_cancel(); + g_main_loop_quit(event_loop); +} + +static gboolean io_acl_data(GIOChannel *chan, GIOCondition cond, gpointer data); +static gboolean io_conn_ind(GIOChannel *chan, GIOCondition cond, gpointer data); +static gboolean io_hci_data(GIOChannel *chan, GIOCondition cond, gpointer data); + +static inline int read_n(int fd, void *buf, int len) +{ + register int w, t = 0; + + while (!__io_canceled && len > 0) { + if ((w = read(fd, buf, len)) < 0 ){ + if( errno == EINTR || errno == EAGAIN ) + continue; + return -1; + } + if (!w) + return 0; + len -= w; buf += w; t += w; + } + return t; +} + +/* Write exactly len bytes (Signal safe)*/ +static inline int write_n(int fd, void *buf, int len) +{ + register int w, t = 0; + + while (!__io_canceled && len > 0) { + if ((w = write(fd, buf, len)) < 0 ){ + if( errno == EINTR || errno == EAGAIN ) + continue; + return -1; + } + if (!w) + return 0; + len -= w; buf += w; t += w; + } + return t; +} + +static int create_snoop(char *file) +{ + struct btsnoop_hdr hdr; + int fd, len; + + fd = open(file, O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + if (fd < 0) + return fd; + + memcpy(hdr.id, btsnoop_id, sizeof(btsnoop_id)); + hdr.version = htonl(1); + hdr.type = htonl(1002); + + len = write(fd, &hdr, BTSNOOP_HDR_SIZE); + if (len < 0) { + close(fd); + return -EIO; + } + + if (len != BTSNOOP_HDR_SIZE) { + close(fd); + return -1; + } + + return fd; +} + +static int write_snoop(int fd, int type, int incoming, unsigned char *buf, int len) +{ + struct btsnoop_pkt pkt; + struct timeval tv; + uint32_t size = len; + uint64_t ts; + int err; + + if (fd < 0) + return -1; + + memset(&tv, 0, sizeof(tv)); + gettimeofday(&tv, NULL); + ts = (tv.tv_sec - 946684800ll) * 1000000ll + tv.tv_usec; + + pkt.size = htonl(size); + pkt.len = pkt.size; + pkt.flags = ntohl(incoming & 0x01); + pkt.drops = htonl(0); + pkt.ts = hton64(ts + 0x00E03AB44A676000ll); + + if (type == HCI_COMMAND_PKT || type == HCI_EVENT_PKT) + pkt.flags |= ntohl(0x02); + + err = write(fd, &pkt, BTSNOOP_PKT_SIZE); + err = write(fd, buf, size); + + return 0; +} + +static struct vhci_conn *conn_get_by_bdaddr(bdaddr_t *ba) +{ + register int i; + + for (i = 0; i < VHCI_MAX_CONN; i++) + if (!bacmp(&vconn[i]->dest, ba)) + return vconn[i]; + + return NULL; +} + +static void command_status(uint16_t ogf, uint16_t ocf, uint8_t status) +{ + uint8_t buf[HCI_MAX_FRAME_SIZE], *ptr = buf; + evt_cmd_status *cs; + hci_event_hdr *he; + + /* Packet type */ + *ptr++ = HCI_EVENT_PKT; + + /* Event header */ + he = (void *) ptr; ptr += HCI_EVENT_HDR_SIZE; + + he->evt = EVT_CMD_STATUS; + he->plen = EVT_CMD_STATUS_SIZE; + + cs = (void *) ptr; ptr += EVT_CMD_STATUS_SIZE; + + cs->status = status; + cs->ncmd = 1; + cs->opcode = htobs(cmd_opcode_pack(ogf, ocf)); + + write_snoop(vdev.dd, HCI_EVENT_PKT, 1, buf, ptr - buf); + + if (write(vdev.fd, buf, ptr - buf) < 0) + syslog(LOG_ERR, "Can't send event: %s(%d)", + strerror(errno), errno); +} + +static void command_complete(uint16_t ogf, uint16_t ocf, int plen, void *data) +{ + uint8_t buf[HCI_MAX_FRAME_SIZE], *ptr = buf; + evt_cmd_complete *cc; + hci_event_hdr *he; + + /* Packet type */ + *ptr++ = HCI_EVENT_PKT; + + /* Event header */ + he = (void *) ptr; ptr += HCI_EVENT_HDR_SIZE; + + he->evt = EVT_CMD_COMPLETE; + he->plen = EVT_CMD_COMPLETE_SIZE + plen; + + cc = (void *) ptr; ptr += EVT_CMD_COMPLETE_SIZE; + + cc->ncmd = 1; + cc->opcode = htobs(cmd_opcode_pack(ogf, ocf)); + + if (plen) { + memcpy(ptr, data, plen); + ptr += plen; + } + + write_snoop(vdev.dd, HCI_EVENT_PKT, 1, buf, ptr - buf); + + if (write(vdev.fd, buf, ptr - buf) < 0) + syslog(LOG_ERR, "Can't send event: %s(%d)", + strerror(errno), errno); +} + +static void connect_request(struct vhci_conn *conn) +{ + uint8_t buf[HCI_MAX_FRAME_SIZE], *ptr = buf; + evt_conn_request *cr; + hci_event_hdr *he; + + /* Packet type */ + *ptr++ = HCI_EVENT_PKT; + + /* Event header */ + he = (void *) ptr; ptr += HCI_EVENT_HDR_SIZE; + + he->evt = EVT_CONN_REQUEST; + he->plen = EVT_CONN_REQUEST_SIZE; + + cr = (void *) ptr; ptr += EVT_CONN_REQUEST_SIZE; + + bacpy(&cr->bdaddr, &conn->dest); + memset(&cr->dev_class, 0, sizeof(cr->dev_class)); + cr->link_type = ACL_LINK; + + write_snoop(vdev.dd, HCI_EVENT_PKT, 1, buf, ptr - buf); + + if (write(vdev.fd, buf, ptr - buf) < 0) + syslog(LOG_ERR, "Can't send event: %s (%d)", + strerror(errno), errno); +} + +static void connect_complete(struct vhci_conn *conn) +{ + uint8_t buf[HCI_MAX_FRAME_SIZE], *ptr = buf; + evt_conn_complete *cc; + hci_event_hdr *he; + + /* Packet type */ + *ptr++ = HCI_EVENT_PKT; + + /* Event header */ + he = (void *) ptr; ptr += HCI_EVENT_HDR_SIZE; + + he->evt = EVT_CONN_COMPLETE; + he->plen = EVT_CONN_COMPLETE_SIZE; + + cc = (void *) ptr; ptr += EVT_CONN_COMPLETE_SIZE; + + bacpy(&cc->bdaddr, &conn->dest); + cc->status = 0x00; + cc->handle = htobs(conn->handle); + cc->link_type = ACL_LINK; + cc->encr_mode = 0x00; + + write_snoop(vdev.dd, HCI_EVENT_PKT, 1, buf, ptr - buf); + + if (write(vdev.fd, buf, ptr - buf) < 0) + syslog(LOG_ERR, "Can't send event: %s (%d)", + strerror(errno), errno); +} + +static void disconn_complete(struct vhci_conn *conn) +{ + uint8_t buf[HCI_MAX_FRAME_SIZE], *ptr = buf; + evt_disconn_complete *dc; + hci_event_hdr *he; + + /* Packet type */ + *ptr++ = HCI_EVENT_PKT; + + /* Event header */ + he = (void *) ptr; ptr += HCI_EVENT_HDR_SIZE; + + he->evt = EVT_DISCONN_COMPLETE; + he->plen = EVT_DISCONN_COMPLETE_SIZE; + + dc = (void *) ptr; ptr += EVT_DISCONN_COMPLETE_SIZE; + + dc->status = 0x00; + dc->handle = htobs(conn->handle); + dc->reason = 0x00; + + write_snoop(vdev.dd, HCI_EVENT_PKT, 1, buf, ptr - buf); + + if (write(vdev.fd, buf, ptr - buf) < 0) + syslog(LOG_ERR, "Can't send event: %s (%d)", + strerror(errno), errno); + + vdev.acl_cnt = 0; +} + +static void num_completed_pkts(struct vhci_conn *conn) +{ + uint8_t buf[HCI_MAX_FRAME_SIZE], *ptr = buf; + evt_num_comp_pkts *np; + hci_event_hdr *he; + + /* Packet type */ + *ptr++ = HCI_EVENT_PKT; + + /* Event header */ + he = (void *) ptr; ptr += HCI_EVENT_HDR_SIZE; + + he->evt = EVT_NUM_COMP_PKTS; + he->plen = EVT_NUM_COMP_PKTS_SIZE; + + np = (void *) ptr; ptr += EVT_NUM_COMP_PKTS_SIZE; + np->num_hndl = 1; + + *((uint16_t *) ptr) = htobs(conn->handle); ptr += 2; + *((uint16_t *) ptr) = htobs(vdev.acl_cnt); ptr += 2; + + write_snoop(vdev.dd, HCI_EVENT_PKT, 1, buf, ptr - buf); + + if (write(vdev.fd, buf, ptr - buf) < 0) + syslog(LOG_ERR, "Can't send event: %s (%d)", + strerror(errno), errno); +} + +static int scan_enable(uint8_t *data) +{ + struct sockaddr_in sa; + GIOChannel *sk_io; + bdaddr_t ba; + int sk, opt; + + if (!(*data & SCAN_PAGE)) { + if (vdev.scan) { + g_io_channel_close(vdev.scan); + vdev.scan = NULL; + } + return 0; + } + + if (vdev.scan) + return 0; + + if ((sk = socket(AF_INET, SOCK_STREAM, 0)) < 0) { + syslog(LOG_ERR, "Can't create socket: %s (%d)", + strerror(errno), errno); + return 1; + } + + opt = 1; + setsockopt(sk, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); + + baswap(&ba, &vdev.bdaddr); + sa.sin_family = AF_INET; + sa.sin_addr.s_addr = *(uint32_t *) &ba; + sa.sin_port = *(uint16_t *) &ba.b[4]; + if (bind(sk, (struct sockaddr *) &sa, sizeof(sa))) { + syslog(LOG_ERR, "Can't bind socket: %s (%d)", + strerror(errno), errno); + goto failed; + } + + if (listen(sk, 10)) { + syslog(LOG_ERR, "Can't listen on socket: %s (%d)", + strerror(errno), errno); + goto failed; + } + + sk_io = g_io_channel_unix_new(sk); + g_io_add_watch(sk_io, G_IO_IN | G_IO_NVAL, io_conn_ind, NULL); + vdev.scan = sk_io; + return 0; + +failed: + close(sk); + return 1; +} + +static void accept_connection(uint8_t *data) +{ + accept_conn_req_cp *cp = (void *) data; + struct vhci_conn *conn; + + if (!(conn = conn_get_by_bdaddr(&cp->bdaddr))) + return; + + connect_complete(conn); + + g_io_add_watch(conn->chan, G_IO_IN | G_IO_NVAL | G_IO_HUP, + io_acl_data, (gpointer) conn); +} + +static void close_connection(struct vhci_conn *conn) +{ + syslog(LOG_INFO, "Closing connection %s handle %d", + batostr(&conn->dest), conn->handle); + + g_io_channel_close(conn->chan); + g_io_channel_unref(conn->chan); + + vconn[conn->handle - 1] = NULL; + disconn_complete(conn); + free(conn); +} + +static void disconnect(uint8_t *data) +{ + disconnect_cp *cp = (void *) data; + struct vhci_conn *conn; + uint16_t handle; + + handle = btohs(cp->handle); + + if (handle - 1 > VHCI_MAX_CONN) + return; + + if (!(conn = vconn[handle-1])) + return; + + close_connection(conn); +} + +static void create_connection(uint8_t *data) +{ + create_conn_cp *cp = (void *) data; + struct vhci_link_info info; + struct vhci_conn *conn; + struct sockaddr_in sa; + int h, sk, opt; + bdaddr_t ba; + + for (h = 0; h < VHCI_MAX_CONN; h++) + if (!vconn[h]) + goto do_connect; + + syslog(LOG_ERR, "Too many connections"); + return; + +do_connect: + if ((sk = socket(AF_INET, SOCK_STREAM, 0)) < 0) { + syslog(LOG_ERR, "Can't create socket: %s (%d)", + strerror(errno), errno); + return; + } + + opt = 1; + setsockopt(sk, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); + + baswap(&ba, &vdev.bdaddr); + sa.sin_family = AF_INET; + sa.sin_addr.s_addr = INADDR_ANY; // *(uint32_t *) &ba; + sa.sin_port = 0; // *(uint16_t *) &ba.b[4]; + if (bind(sk, (struct sockaddr *) &sa, sizeof(sa))) { + syslog(LOG_ERR, "Can't bind socket: %s (%d)", + strerror(errno), errno); + close(sk); + return; + } + + baswap(&ba, &cp->bdaddr); + sa.sin_family = AF_INET; + sa.sin_addr.s_addr = *(uint32_t *) &ba; + sa.sin_port = *(uint16_t *) &ba.b[4]; + if (connect(sk, (struct sockaddr *) &sa, sizeof(sa)) < 0) { + syslog(LOG_ERR, "Can't connect: %s (%d)", + strerror(errno), errno); + close(sk); + return; + } + + /* Send info */ + memset(&info, 0, sizeof(info)); + bacpy(&info.bdaddr, &vdev.bdaddr); + info.link_type = ACL_LINK; + info.role = 1; + write_n(sk, (void *) &info, sizeof(info)); + + if (!(conn = malloc(sizeof(*conn)))) { + syslog(LOG_ERR, "Can't alloc new connection: %s (%d)", + strerror(errno), errno); + close(sk); + return; + } + + memcpy((uint8_t *) &ba, (uint8_t *) &sa.sin_addr, 4); + memcpy((uint8_t *) &ba.b[4], (uint8_t *) &sa.sin_port, 2); + baswap(&conn->dest, &ba); + + vconn[h] = conn; + conn->handle = h + 1; + conn->chan = g_io_channel_unix_new(sk); + + connect_complete(conn); + g_io_add_watch(conn->chan, G_IO_IN | G_IO_NVAL | G_IO_HUP, + io_acl_data, (gpointer) conn); + return; +} + +static void inline hci_link_control(uint16_t ocf, int plen, uint8_t *data) +{ + uint8_t status; + + const uint16_t ogf = OGF_LINK_CTL; + + switch (ocf) { + case OCF_CREATE_CONN: + command_status(ogf, ocf, 0x00); + create_connection(data); + break; + + case OCF_ACCEPT_CONN_REQ: + command_status(ogf, ocf, 0x00); + accept_connection(data); + break; + + case OCF_DISCONNECT: + command_status(ogf, ocf, 0x00); + disconnect(data); + break; + + default: + status = 0x01; + command_complete(ogf, ocf, 1, &status); + break; + } +} + +static void inline hci_link_policy(uint16_t ocf, int plen, uint8_t *data) +{ + uint8_t status; + + const uint16_t ogf = OGF_INFO_PARAM; + + switch (ocf) { + default: + status = 0x01; + command_complete(ogf, ocf, 1, &status); + break; + } +} + +static void inline hci_host_control(uint16_t ocf, int plen, uint8_t *data) +{ + read_local_name_rp ln; + read_class_of_dev_rp cd; + read_inquiry_mode_rp im; + read_ext_inquiry_response_rp ir; + uint8_t status; + + const uint16_t ogf = OGF_HOST_CTL; + + switch (ocf) { + case OCF_RESET: + status = 0x00; + command_complete(ogf, ocf, 1, &status); + break; + + case OCF_SET_EVENT_FLT: + status = 0x00; + command_complete(ogf, ocf, 1, &status); + break; + + case OCF_CHANGE_LOCAL_NAME: + status = 0x00; + memcpy(vdev.name, data, sizeof(vdev.name)); + command_complete(ogf, ocf, 1, &status); + break; + + case OCF_READ_LOCAL_NAME: + ln.status = 0x00; + memcpy(ln.name, vdev.name, sizeof(ln.name)); + command_complete(ogf, ocf, sizeof(ln), &ln); + break; + + case OCF_WRITE_CONN_ACCEPT_TIMEOUT: + case OCF_WRITE_PAGE_TIMEOUT: + status = 0x00; + command_complete(ogf, ocf, 1, &status); + break; + + case OCF_WRITE_SCAN_ENABLE: + status = scan_enable(data); + command_complete(ogf, ocf, 1, &status); + break; + + case OCF_WRITE_AUTH_ENABLE: + status = 0x00; + command_complete(ogf, ocf, 1, &status); + break; + + case OCF_WRITE_ENCRYPT_MODE: + status = 0x00; + command_complete(ogf, ocf, 1, &status); + break; + + case OCF_READ_CLASS_OF_DEV: + cd.status = 0x00; + memcpy(cd.dev_class, vdev.dev_class, 3); + command_complete(ogf, ocf, sizeof(cd), &cd); + break; + + case OCF_WRITE_CLASS_OF_DEV: + status = 0x00; + memcpy(vdev.dev_class, data, 3); + command_complete(ogf, ocf, 1, &status); + break; + + case OCF_READ_INQUIRY_MODE: + im.status = 0x00; + im.mode = vdev.inq_mode; + command_complete(ogf, ocf, sizeof(im), &im); + break; + + case OCF_WRITE_INQUIRY_MODE: + status = 0x00; + vdev.inq_mode = data[0]; + command_complete(ogf, ocf, 1, &status); + break; + + case OCF_READ_EXT_INQUIRY_RESPONSE: + ir.status = 0x00; + ir.fec = vdev.eir_fec; + memcpy(ir.data, vdev.eir_data, 240); + command_complete(ogf, ocf, sizeof(ir), &ir); + break; + + case OCF_WRITE_EXT_INQUIRY_RESPONSE: + status = 0x00; + vdev.eir_fec = data[0]; + memcpy(vdev.eir_data, data + 1, 240); + command_complete(ogf, ocf, 1, &status); + break; + + default: + status = 0x01; + command_complete(ogf, ocf, 1, &status); + break; + } +} + +static void inline hci_info_param(uint16_t ocf, int plen, uint8_t *data) +{ + read_local_version_rp lv; + read_local_features_rp lf; + read_local_ext_features_rp ef; + read_buffer_size_rp bs; + read_bd_addr_rp ba; + uint8_t status; + + const uint16_t ogf = OGF_INFO_PARAM; + + switch (ocf) { + case OCF_READ_LOCAL_VERSION: + lv.status = 0x00; + lv.hci_ver = 0x03; + lv.hci_rev = htobs(0x0000); + lv.lmp_ver = 0x03; + lv.manufacturer = htobs(29); + lv.lmp_subver = htobs(0x0000); + command_complete(ogf, ocf, sizeof(lv), &lv); + break; + + case OCF_READ_LOCAL_FEATURES: + lf.status = 0x00; + memcpy(lf.features, vdev.features, 8); + command_complete(ogf, ocf, sizeof(lf), &lf); + break; + + case OCF_READ_LOCAL_EXT_FEATURES: + ef.status = 0x00; + if (*data == 0) { + ef.page_num = 0; + ef.max_page_num = 0; + memcpy(ef.features, vdev.features, 8); + } else { + ef.page_num = *data; + ef.max_page_num = 0; + memset(ef.features, 0, 8); + } + command_complete(ogf, ocf, sizeof(ef), &ef); + break; + + case OCF_READ_BUFFER_SIZE: + bs.status = 0x00; + bs.acl_mtu = htobs(VHCI_ACL_MTU); + bs.sco_mtu = 0; + bs.acl_max_pkt = htobs(VHCI_ACL_MAX_PKT); + bs.sco_max_pkt = htobs(0); + command_complete(ogf, ocf, sizeof(bs), &bs); + break; + + case OCF_READ_BD_ADDR: + ba.status = 0x00; + bacpy(&ba.bdaddr, &vdev.bdaddr); + command_complete(ogf, ocf, sizeof(ba), &ba); + break; + + default: + status = 0x01; + command_complete(ogf, ocf, 1, &status); + break; + } +} + +static void hci_command(uint8_t *data) +{ + hci_command_hdr *ch; + uint8_t *ptr = data; + uint16_t ogf, ocf; + + ch = (hci_command_hdr *) ptr; + ptr += HCI_COMMAND_HDR_SIZE; + + ch->opcode = btohs(ch->opcode); + ogf = cmd_opcode_ogf(ch->opcode); + ocf = cmd_opcode_ocf(ch->opcode); + + switch (ogf) { + case OGF_LINK_CTL: + hci_link_control(ocf, ch->plen, ptr); + break; + + case OGF_LINK_POLICY: + hci_link_policy(ocf, ch->plen, ptr); + break; + + case OGF_HOST_CTL: + hci_host_control(ocf, ch->plen, ptr); + break; + + case OGF_INFO_PARAM: + hci_info_param(ocf, ch->plen, ptr); + break; + } +} + +static void hci_acl_data(uint8_t *data) +{ + hci_acl_hdr *ah = (void *) data; + struct vhci_conn *conn; + uint16_t handle; + int fd; + + handle = acl_handle(btohs(ah->handle)); + + if (handle > VHCI_MAX_CONN || !(conn = vconn[handle - 1])) { + syslog(LOG_ERR, "Bad connection handle %d", handle); + return; + } + + fd = g_io_channel_unix_get_fd(conn->chan); + if (write_n(fd, data, btohs(ah->dlen) + HCI_ACL_HDR_SIZE) < 0) { + close_connection(conn); + return; + } + + if (++vdev.acl_cnt > VHCI_ACL_MAX_PKT - 1) { + /* Send num of complete packets event */ + num_completed_pkts(conn); + vdev.acl_cnt = 0; + } +} + +static gboolean io_acl_data(GIOChannel *chan, GIOCondition cond, gpointer data) +{ + struct vhci_conn *conn = (struct vhci_conn *) data; + unsigned char buf[HCI_MAX_FRAME_SIZE], *ptr; + hci_acl_hdr *ah; + uint16_t flags; + int fd, err, len; + + if (cond & G_IO_NVAL) { + g_io_channel_unref(chan); + return FALSE; + } + + if (cond & G_IO_HUP) { + close_connection(conn); + return FALSE; + } + + fd = g_io_channel_unix_get_fd(chan); + + ptr = buf + 1; + if (read_n(fd, ptr, HCI_ACL_HDR_SIZE) <= 0) { + close_connection(conn); + return FALSE; + } + + ah = (void *) ptr; + ptr += HCI_ACL_HDR_SIZE; + + len = btohs(ah->dlen); + if (read_n(fd, ptr, len) <= 0) { + close_connection(conn); + return FALSE; + } + + buf[0] = HCI_ACLDATA_PKT; + + flags = acl_flags(btohs(ah->handle)); + ah->handle = htobs(acl_handle_pack(conn->handle, flags)); + len += HCI_ACL_HDR_SIZE + 1; + + write_snoop(vdev.dd, HCI_ACLDATA_PKT, 1, buf, len); + + err = write(vdev.fd, buf, len); + + return TRUE; +} + +static gboolean io_conn_ind(GIOChannel *chan, GIOCondition cond, gpointer data) +{ + struct vhci_link_info info; + struct vhci_conn *conn; + struct sockaddr_in sa; + socklen_t len; + int sk, nsk, h; + + if (cond & G_IO_NVAL) + return FALSE; + + sk = g_io_channel_unix_get_fd(chan); + + len = sizeof(sa); + if ((nsk = accept(sk, (struct sockaddr *) &sa, &len)) < 0) + return TRUE; + + if (read_n(nsk, &info, sizeof(info)) < 0) { + syslog(LOG_ERR, "Can't read link info"); + return TRUE; + } + + if (!(conn = malloc(sizeof(*conn)))) { + syslog(LOG_ERR, "Can't alloc new connection"); + close(nsk); + return TRUE; + } + + bacpy(&conn->dest, &info.bdaddr); + + for (h = 0; h < VHCI_MAX_CONN; h++) + if (!vconn[h]) + goto accepted; + + syslog(LOG_ERR, "Too many connections"); + free(conn); + close(nsk); + return TRUE; + +accepted: + vconn[h] = conn; + conn->handle = h + 1; + conn->chan = g_io_channel_unix_new(nsk); + connect_request(conn); + + return TRUE; +} + +static gboolean io_hci_data(GIOChannel *chan, GIOCondition cond, gpointer data) +{ + unsigned char buf[HCI_MAX_FRAME_SIZE], *ptr; + int type; + gsize len; + GIOError err; + + ptr = buf; + + if ((err = g_io_channel_read(chan, (gchar *) buf, sizeof(buf), &len))) { + if (err == G_IO_ERROR_AGAIN) + return TRUE; + + syslog(LOG_ERR, "Read failed: %s (%d)", strerror(errno), errno); + g_io_channel_unref(chan); + g_main_loop_quit(event_loop); + return FALSE; + } + + type = *ptr++; + + write_snoop(vdev.dd, type, 0, buf, len); + + switch (type) { + case HCI_COMMAND_PKT: + hci_command(ptr); + break; + + case HCI_ACLDATA_PKT: + hci_acl_data(ptr); + break; + + default: + syslog(LOG_ERR, "Unknown packet type 0x%2.2x", type); + break; + } + + return TRUE; +} + +static int getbdaddrbyname(char *str, bdaddr_t *ba) +{ + int i, n, len; + + len = strlen(str); + + /* Check address format */ + for (i = 0, n = 0; i < len; i++) + if (str[i] == ':') + n++; + + if (n == 5) { + /* BD address */ + baswap(ba, strtoba(str)); + return 0; + } + + if (n == 1) { + /* IP address + port */ + struct hostent *hent; + bdaddr_t b; + char *ptr; + + ptr = strchr(str, ':'); + *ptr++ = 0; + + if (!(hent = gethostbyname(str))) { + fprintf(stderr, "Can't resolve %s\n", str); + return -2; + } + + memcpy(&b, hent->h_addr, 4); + *(uint16_t *) (&b.b[4]) = htons(atoi(ptr)); + baswap(ba, &b); + + return 0; + } + + fprintf(stderr, "Invalid address format\n"); + + return -1; +} + +static void rewrite_bdaddr(unsigned char *buf, int len, bdaddr_t *bdaddr) +{ + hci_event_hdr *eh; + unsigned char *ptr = buf; + int type; + + if (!bdaddr) + return; + + if (!bacmp(bdaddr, BDADDR_ANY)) + return; + + type = *ptr++; + + switch (type) { + case HCI_EVENT_PKT: + eh = (hci_event_hdr *) ptr; + ptr += HCI_EVENT_HDR_SIZE; + + if (eh->evt == EVT_CMD_COMPLETE) { + evt_cmd_complete *cc = (void *) ptr; + + ptr += EVT_CMD_COMPLETE_SIZE; + + if (cc->opcode == htobs(cmd_opcode_pack(OGF_INFO_PARAM, + OCF_READ_BD_ADDR))) { + bacpy((bdaddr_t *) (ptr + 1), bdaddr); + } + } + break; + } +} + +static int run_proxy(int fd, int dev, bdaddr_t *bdaddr) +{ + unsigned char buf[HCI_MAX_FRAME_SIZE + 1]; + struct hci_dev_info di; + struct hci_filter flt; + struct pollfd p[2]; + int dd, err, len, need_raw; + + dd = hci_open_dev(dev); + if (dd < 0) { + syslog(LOG_ERR, "Can't open device hci%d: %s (%d)", + dev, strerror(errno), errno); + return 1; + } + + if (hci_devinfo(dev, &di) < 0) { + syslog(LOG_ERR, "Can't get device info for hci%d: %s (%d)", + dev, strerror(errno), errno); + hci_close_dev(dd); + return 1; + } + + need_raw = !hci_test_bit(HCI_RAW, &di.flags); + + hci_filter_clear(&flt); + hci_filter_all_ptypes(&flt); + hci_filter_all_events(&flt); + + if (setsockopt(dd, SOL_HCI, HCI_FILTER, &flt, sizeof(flt)) < 0) { + syslog(LOG_ERR, "Can't set filter for hci%d: %s (%d)", + dev, strerror(errno), errno); + hci_close_dev(dd); + return 1; + } + + if (need_raw) { + if (ioctl(dd, HCISETRAW, 1) < 0) { + syslog(LOG_ERR, "Can't set raw mode on hci%d: %s (%d)", + dev, strerror(errno), errno); + hci_close_dev(dd); + return 1; + } + } + + p[0].fd = fd; + p[0].events = POLLIN; + p[1].fd = dd; + p[1].events = POLLIN; + + while (!__io_canceled) { + p[0].revents = 0; + p[1].revents = 0; + err = poll(p, 2, 500); + if (err < 0) + break; + if (!err) + continue; + + if (p[0].revents & POLLIN) { + len = read(fd, buf, sizeof(buf)); + if (len > 0) { + rewrite_bdaddr(buf, len, bdaddr); + err = write(dd, buf, len); + } + } + + if (p[1].revents & POLLIN) { + len = read(dd, buf, sizeof(buf)); + if (len > 0) { + rewrite_bdaddr(buf, len, bdaddr); + err = write(fd, buf, len); + } + } + } + + if (need_raw) { + if (ioctl(dd, HCISETRAW, 0) < 0) + syslog(LOG_ERR, "Can't clear raw mode on hci%d: %s (%d)", + dev, strerror(errno), errno); + } + + hci_close_dev(dd); + + syslog(LOG_INFO, "Exit"); + + return 0; +} + +static void usage(void) +{ + printf("hciemu - HCI emulator ver %s\n", VERSION); + printf("Usage: \n"); + printf("\thciemu [-n] local_address\n"); +} + +static struct option main_options[] = { + { "device", 1, 0, 'd' }, + { "bdaddr", 1, 0, 'b' }, + { "snoop", 1, 0, 's' }, + { "nodetach", 0, 0, 'n' }, + { "help", 0, 0, 'h' }, + { 0 } +}; + +int main(int argc, char *argv[]) +{ + struct sigaction sa; + GIOChannel *dev_io; + char *device = NULL, *snoop = NULL; + bdaddr_t bdaddr; + int fd, dd, opt, detach = 1, dev = -1; + + bacpy(&bdaddr, BDADDR_ANY); + + while ((opt=getopt_long(argc, argv, "d:b:s:nh", main_options, NULL)) != EOF) { + switch(opt) { + case 'd': + device = strdup(optarg); + break; + + case 'b': + str2ba(optarg, &bdaddr); + break; + + case 's': + snoop = strdup(optarg); + break; + + case 'n': + detach = 0; + break; + + case 'h': + default: + usage(); + exit(0); + } + } + + argc -= optind; + argv += optind; + optind = 0; + + if (argc < 1) { + usage(); + exit(1); + } + + if (strlen(argv[0]) > 3 && !strncasecmp(argv[0], "hci", 3)) { + dev = hci_devid(argv[0]); + if (dev < 0) { + perror("Invalid device"); + exit(1); + } + } else { + if (getbdaddrbyname(argv[0], &vdev.bdaddr) < 0) + exit(1); + } + + if (detach) { + if (daemon(0, 0)) { + perror("Can't start daemon"); + exit(1); + } + } + + /* Start logging to syslog and stderr */ + openlog("hciemu", LOG_PID | LOG_NDELAY | LOG_PERROR, LOG_DAEMON); + syslog(LOG_INFO, "HCI emulation daemon ver %s started", VERSION); + + memset(&sa, 0, sizeof(sa)); + sa.sa_flags = SA_NOCLDSTOP; + sa.sa_handler = SIG_IGN; + sigaction(SIGCHLD, &sa, NULL); + sigaction(SIGPIPE, &sa, NULL); + + sa.sa_handler = sig_term; + sigaction(SIGTERM, &sa, NULL); + sigaction(SIGINT, &sa, NULL); + + io_init(); + + if (!device && dev >= 0) + device = strdup(GHCI_DEV); + + /* Open and create virtual HCI device */ + if (device) { + fd = open(device, O_RDWR); + if (fd < 0) { + syslog(LOG_ERR, "Can't open device %s: %s (%d)", + device, strerror(errno), errno); + free(device); + exit(1); + } + free(device); + } else { + fd = open(VHCI_DEV, O_RDWR); + if (fd < 0) { + fd = open(VHCI_UDEV, O_RDWR); + if (fd < 0) { + syslog(LOG_ERR, "Can't open device %s: %s (%d)", + VHCI_DEV, strerror(errno), errno); + exit(1); + } + } + } + + /* Create snoop file */ + if (snoop) { + dd = create_snoop(snoop); + if (dd < 0) + syslog(LOG_ERR, "Can't create snoop file %s: %s (%d)", + snoop, strerror(errno), errno); + free(snoop); + } else + dd = -1; + + /* Create event loop */ + event_loop = g_main_loop_new(NULL, FALSE); + + if (dev >= 0) + return run_proxy(fd, dev, &bdaddr); + + /* Device settings */ + vdev.features[0] = 0xff; + vdev.features[1] = 0xff; + vdev.features[2] = 0x8f; + vdev.features[3] = 0xfe; + vdev.features[4] = 0x9b; + vdev.features[5] = 0xf9; + vdev.features[6] = 0x01; + vdev.features[7] = 0x80; + + memset(vdev.name, 0, sizeof(vdev.name)); + strncpy((char *) vdev.name, "BlueZ (Virtual HCI)", sizeof(vdev.name)); + + vdev.dev_class[0] = 0x00; + vdev.dev_class[1] = 0x00; + vdev.dev_class[2] = 0x00; + + vdev.inq_mode = 0x00; + vdev.eir_fec = 0x00; + memset(vdev.eir_data, 0, sizeof(vdev.eir_data)); + + vdev.fd = fd; + vdev.dd = dd; + + dev_io = g_io_channel_unix_new(fd); + g_io_add_watch(dev_io, G_IO_IN, io_hci_data, NULL); + + setpriority(PRIO_PROCESS, 0, -19); + + /* Start event processor */ + g_main_loop_run(event_loop); + + close(fd); + + if (dd >= 0) + close(dd); + + syslog(LOG_INFO, "Exit"); + + return 0; +} diff --git a/test/hsmicro b/test/hsmicro new file mode 100755 index 00000000..c254226b --- /dev/null +++ b/test/hsmicro @@ -0,0 +1,20 @@ +#!/bin/sh + +SOX=`which sox` +HSTEST=`which hstest` + +if [ -z "$HSTEST" ] +then + HSTEST="./hstest" +fi + +if [ -z "$1" ] +then + echo -e "Usage:\n\thsmicro <bdaddr> [channel]" + exit +fi + +BDADDR=$1 +CHANNEL=$2 + +$HSTEST record - $BDADDR $CHANNEL | $SOX -t raw -r 8000 -c 1 -s -w - -t ossdsp -r 44100 -c 2 -s -w /dev/dsp polyphase vol 5.0 2> /dev/null diff --git a/test/hsplay b/test/hsplay new file mode 100755 index 00000000..8cecbffa --- /dev/null +++ b/test/hsplay @@ -0,0 +1,22 @@ +#!/bin/sh + +MPG123=`which mpg123` +SOX=`which sox` +HSTEST=`which hstest` + +if [ -z "$HSTEST" ] +then + HSTEST="./hstest" +fi + +if [ -z "$1" ] || [ -z "$2" ] +then + echo -e "Usage:\n\thsplay <file> <bdaddr> [channel]" + exit +fi + +FILE=$1 +BDADDR=$2 +CHANNEL=$3 + +$MPG123 -q -s "$FILE" | $SOX -t raw -r 44100 -c 2 -s -w - -t raw -r 8000 -c 1 -s -w - | $HSTEST play - $BDADDR $CHANNEL diff --git a/test/hstest.c b/test/hstest.c new file mode 100644 index 00000000..158bf204 --- /dev/null +++ b/test/hstest.c @@ -0,0 +1,308 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2002-2008 Marcel Holtmann <marcel@holtmann.org> + * + * + * 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 + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <errno.h> +#include <fcntl.h> +#include <unistd.h> +#include <stdlib.h> +#include <signal.h> +#include <termios.h> +#include <sys/wait.h> +#include <sys/time.h> +#include <sys/ioctl.h> +#include <sys/socket.h> + +#include <bluetooth/bluetooth.h> +#include <bluetooth/hci.h> +#include <bluetooth/hci_lib.h> +#include <bluetooth/sco.h> +#include <bluetooth/rfcomm.h> + +static volatile int terminate = 0; + +static void sig_term(int sig) { + terminate = 1; +} + +static int rfcomm_connect(bdaddr_t *src, bdaddr_t *dst, uint8_t channel) +{ + struct sockaddr_rc addr; + int s; + + if ((s = socket(PF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM)) < 0) { + return -1; + } + + memset(&addr, 0, sizeof(addr)); + addr.rc_family = AF_BLUETOOTH; + bacpy(&addr.rc_bdaddr, src); + addr.rc_channel = 0; + if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + close(s); + return -1; + } + + memset(&addr, 0, sizeof(addr)); + addr.rc_family = AF_BLUETOOTH; + bacpy(&addr.rc_bdaddr, dst); + addr.rc_channel = channel; + if (connect(s, (struct sockaddr *)&addr, sizeof(addr)) < 0 ){ + close(s); + return -1; + } + + return s; +} + +static int sco_connect(bdaddr_t *src, bdaddr_t *dst, uint16_t *handle, uint16_t *mtu) +{ + struct sockaddr_sco addr; + struct sco_conninfo conn; + struct sco_options opts; + socklen_t size; + int s; + + if ((s = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_SCO)) < 0) { + return -1; + } + + memset(&addr, 0, sizeof(addr)); + addr.sco_family = AF_BLUETOOTH; + bacpy(&addr.sco_bdaddr, src); + + if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + close(s); + return -1; + } + + memset(&addr, 0, sizeof(addr)); + addr.sco_family = AF_BLUETOOTH; + bacpy(&addr.sco_bdaddr, dst); + + if (connect(s, (struct sockaddr *)&addr, sizeof(addr)) < 0 ){ + close(s); + return -1; + } + + memset(&conn, 0, sizeof(conn)); + size = sizeof(conn); + + if (getsockopt(s, SOL_SCO, SCO_CONNINFO, &conn, &size) < 0) { + close(s); + return -1; + } + + memset(&opts, 0, sizeof(opts)); + size = sizeof(opts); + + if (getsockopt(s, SOL_SCO, SCO_OPTIONS, &opts, &size) < 0) { + close(s); + return -1; + } + + if (handle) + *handle = conn.hci_handle; + + if (mtu) + *mtu = opts.mtu; + + return s; +} + +static void usage(void) +{ + printf("Usage:\n" + "\thstest play <file> <bdaddr> [channel]\n" + "\thstest record <file> <bdaddr> [channel]\n"); +} + +#define PLAY 1 +#define RECORD 2 + +int main(int argc, char *argv[]) +{ + struct sigaction sa; + + fd_set rfds; + struct timeval timeout; + unsigned char buf[2048], *p; + int maxfd, sel, rlen, wlen; + + bdaddr_t local; + bdaddr_t bdaddr; + uint8_t channel; + + char *filename; + mode_t filemode; + int err, mode = 0; + int dd, rd, sd, fd; + uint16_t sco_handle, sco_mtu, vs; + + switch (argc) { + case 4: + str2ba(argv[3], &bdaddr); + channel = 6; + break; + case 5: + str2ba(argv[3], &bdaddr); + channel = atoi(argv[4]); + break; + default: + usage(); + exit(-1); + } + + if (strncmp(argv[1], "play", 4) == 0) { + mode = PLAY; + filemode = O_RDONLY; + } else if (strncmp(argv[1], "rec", 3) == 0) { + mode = RECORD; + filemode = O_WRONLY | O_CREAT | O_TRUNC; + } else { + usage(); + exit(-1); + } + + filename = argv[2]; + + hci_devba(0, &local); + dd = hci_open_dev(0); + hci_read_voice_setting(dd, &vs, 1000); + vs = htobs(vs); + fprintf(stderr, "Voice setting: 0x%04x\n", vs); + close(dd); + if (vs != 0x0060) { + fprintf(stderr, "The voice setting must be 0x0060\n"); + return -1; + } + + if (strcmp(filename, "-") == 0) { + switch (mode) { + case PLAY: + fd = 0; + break; + case RECORD: + fd = 1; + break; + default: + return -1; + } + } else { + if ((fd = open(filename, filemode)) < 0) { + perror("Can't open input/output file"); + return -1; + } + } + + memset(&sa, 0, sizeof(sa)); + sa.sa_flags = SA_NOCLDSTOP; + sa.sa_handler = sig_term; + sigaction(SIGTERM, &sa, NULL); + sigaction(SIGINT, &sa, NULL); + + sa.sa_handler = SIG_IGN; + sigaction(SIGCHLD, &sa, NULL); + sigaction(SIGPIPE, &sa, NULL); + + if ((rd = rfcomm_connect(&local, &bdaddr, channel)) < 0) { + perror("Can't connect RFCOMM channel"); + return -1; + } + + fprintf(stderr, "RFCOMM channel connected\n"); + + if ((sd = sco_connect(&local, &bdaddr, &sco_handle, &sco_mtu)) < 0) { + perror("Can't connect SCO audio channel"); + close(rd); + return -1; + } + + fprintf(stderr, "SCO audio channel connected (handle %d, mtu %d)\n", sco_handle, sco_mtu); + + if (mode == RECORD) + err = write(rd, "RING\r\n", 6); + + maxfd = (rd > sd) ? rd : sd; + + while (!terminate) { + + FD_ZERO(&rfds); + FD_SET(rd, &rfds); + FD_SET(sd, &rfds); + + timeout.tv_sec = 0; + timeout.tv_usec = 10000; + + if ((sel = select(maxfd + 1, &rfds, NULL, NULL, &timeout)) > 0) { + + if (FD_ISSET(rd, &rfds)) { + memset(buf, 0, sizeof(buf)); + rlen = read(rd, buf, sizeof(buf)); + if (rlen > 0) { + fprintf(stderr, "%s\n", buf); + wlen = write(rd, "OK\r\n", 4); + } + } + + if (FD_ISSET(sd, &rfds)) { + memset(buf, 0, sizeof(buf)); + rlen = read(sd, buf, sizeof(buf)); + if (rlen > 0) + switch (mode) { + case PLAY: + rlen = read(fd, buf, rlen); + + wlen = 0; + p = buf; + while (rlen > sco_mtu) { + wlen += write(sd, p, sco_mtu); + rlen -= sco_mtu; + p += sco_mtu; + } + wlen += write(sd, p, rlen); + break; + case RECORD: + wlen = write(fd, buf, rlen); + break; + default: + break; + } + } + + } + + } + + close(sd); + sleep(5); + close(rd); + + close(fd); + + return 0; +} diff --git a/test/l2test.c b/test/l2test.c new file mode 100644 index 00000000..bc57d6b6 --- /dev/null +++ b/test/l2test.c @@ -0,0 +1,1104 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2000-2001 Qualcomm Incorporated + * Copyright (C) 2002-2003 Maxim Krasnyansky <maxk@qualcomm.com> + * Copyright (C) 2002-2008 Marcel Holtmann <marcel@holtmann.org> + * + * + * 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 + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <errno.h> +#include <ctype.h> +#include <fcntl.h> +#include <unistd.h> +#include <stdlib.h> +#include <getopt.h> +#include <syslog.h> +#include <signal.h> +#include <sys/time.h> +#include <sys/poll.h> +#include <sys/ioctl.h> +#include <sys/socket.h> + +#include <bluetooth/bluetooth.h> +#include <bluetooth/hci.h> +#include <bluetooth/hci_lib.h> +#include <bluetooth/l2cap.h> + +#define NIBBLE_TO_ASCII(c) ((c) < 0x0a ? (c) + 0x30 : (c) + 0x57) + +/* Test modes */ +enum { + SEND, + RECV, + RECONNECT, + MULTY, + DUMP, + CONNECT, + CRECV, + LSEND, + SENDDUMP, + LSENDDUMP, + INFOREQ +}; + +static unsigned char *buf; + +/* Default mtu */ +static int imtu = 672; +static int omtu = 0; + +/* Default data size */ +static long data_size = -1; + +/* Default addr and psm */ +static bdaddr_t bdaddr; +static unsigned short psm = 10; + +/* Default number of frames to send (-1 = infinite) */ +static int num_frames = -1; + +/* Default number of consecutive frames before the delay */ +static int count = 1; + +/* Default delay after sending count number of frames */ +static unsigned long delay = 0; + +static char *filename = NULL; + +static int rfcmode = 0; +static int master = 0; +static int auth = 0; +static int encrypt = 0; +static int secure = 0; +static int socktype = SOCK_SEQPACKET; +static int linger = 0; +static int reliable = 0; +static int timestamp = 0; + +static float tv2fl(struct timeval tv) +{ + return (float)tv.tv_sec + (float)(tv.tv_usec/1000000.0); +} + +static char *ltoh(unsigned long c, char* s) +{ + int c1; + + c1 = (c >> 28) & 0x0f; + *(s++) = NIBBLE_TO_ASCII (c1); + c1 = (c >> 24) & 0x0f; + *(s++) = NIBBLE_TO_ASCII (c1); + c1 = (c >> 20) & 0x0f; + *(s++) = NIBBLE_TO_ASCII (c1); + c1 = (c >> 16) & 0x0f; + *(s++) = NIBBLE_TO_ASCII (c1); + c1 = (c >> 12) & 0x0f; + *(s++) = NIBBLE_TO_ASCII (c1); + c1 = (c >> 8) & 0x0f; + *(s++) = NIBBLE_TO_ASCII (c1); + c1 = (c >> 4) & 0x0f; + *(s++) = NIBBLE_TO_ASCII (c1); + c1 = c & 0x0f; + *(s++) = NIBBLE_TO_ASCII (c1); + *s = 0; + return s; +} + +static char *ctoh(char c, char* s) +{ + char c1; + + c1 = (c >> 4) & 0x0f; + *(s++) = NIBBLE_TO_ASCII (c1); + c1 = c & 0x0f; + *(s++) = NIBBLE_TO_ASCII (c1); + *s = 0; + return s; +} + +static void hexdump(unsigned char *s, unsigned long l) +{ + char bfr[80]; + char *pb; + unsigned long i, n = 0; + + if (l == 0) + return; + + while (n < l) { + pb = bfr; + pb = ltoh (n, pb); + *(pb++) = ':'; + *(pb++) = ' '; + for (i = 0; i < 16; i++) { + if (n + i >= l) { + *(pb++) = ' '; + *(pb++) = ' '; + } else + pb = ctoh (*(s + i), pb); + *(pb++) = ' '; + } + *(pb++) = ' '; + for (i = 0; i < 16; i++) { + if (n + i >= l) + break; + else + *(pb++) = (isprint (*(s + i)) ? *(s + i) : '.'); + } + *pb = 0; + n += 16; + s += 16; + puts(bfr); + } +} + +static int do_connect(char *svr) +{ + struct sockaddr_l2 addr; + struct l2cap_options opts; + struct l2cap_conninfo conn; + socklen_t optlen; + int sk, opt; + + /* Create socket */ + sk = socket(PF_BLUETOOTH, socktype, BTPROTO_L2CAP); + if (sk < 0) { + syslog(LOG_ERR, "Can't create socket: %s (%d)", + strerror(errno), errno); + return -1; + } + + /* Bind to local address */ + memset(&addr, 0, sizeof(addr)); + addr.l2_family = AF_BLUETOOTH; + bacpy(&addr.l2_bdaddr, &bdaddr); + + if (bind(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + syslog(LOG_ERR, "Can't bind socket: %s (%d)", + strerror(errno), errno); + goto error; + } + + /* Get default options */ + memset(&opts, 0, sizeof(opts)); + optlen = sizeof(opts); + + if (getsockopt(sk, SOL_L2CAP, L2CAP_OPTIONS, &opts, &optlen) < 0) { + syslog(LOG_ERR, "Can't get default L2CAP options: %s (%d)", + strerror(errno), errno); + goto error; + } + + /* Set new options */ + opts.omtu = omtu; + opts.imtu = imtu; + if (rfcmode > 0) + opts.mode = rfcmode; + + if (setsockopt(sk, SOL_L2CAP, L2CAP_OPTIONS, &opts, sizeof(opts)) < 0) { + syslog(LOG_ERR, "Can't set L2CAP options: %s (%d)", + strerror(errno), errno); + goto error; + } + +#if 0 + /* Enable SO_TIMESTAMP */ + if (timestamp) { + int t = 1; + + if (setsockopt(sk, SOL_SOCKET, SO_TIMESTAMP, &t, sizeof(t)) < 0) { + syslog(LOG_ERR, "Can't enable SO_TIMESTAMP: %s (%d)", + strerror(errno), errno); + goto error; + } + } +#endif + + /* Enable SO_LINGER */ + if (linger) { + struct linger l = { .l_onoff = 1, .l_linger = linger }; + + if (setsockopt(sk, SOL_SOCKET, SO_LINGER, &l, sizeof(l)) < 0) { + syslog(LOG_ERR, "Can't enable SO_LINGER: %s (%d)", + strerror(errno), errno); + goto error; + } + } + + /* Set link mode */ + opt = 0; + if (reliable) + opt |= L2CAP_LM_RELIABLE; + if (master) + opt |= L2CAP_LM_MASTER; + if (auth) + opt |= L2CAP_LM_AUTH; + if (encrypt) + opt |= L2CAP_LM_ENCRYPT; + if (secure) + opt |= L2CAP_LM_SECURE; + + if (setsockopt(sk, SOL_L2CAP, L2CAP_LM, &opt, sizeof(opt)) < 0) { + syslog(LOG_ERR, "Can't set L2CAP link mode: %s (%d)", + strerror(errno), errno); + goto error; + } + + /* Connect to remote device */ + memset(&addr, 0, sizeof(addr)); + addr.l2_family = AF_BLUETOOTH; + str2ba(svr, &addr.l2_bdaddr); + addr.l2_psm = htobs(psm); + + if (connect(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0 ) { + syslog(LOG_ERR, "Can't connect: %s (%d)", + strerror(errno), errno); + goto error; + } + + /* Get current options */ + memset(&opts, 0, sizeof(opts)); + optlen = sizeof(opts); + + if (getsockopt(sk, SOL_L2CAP, L2CAP_OPTIONS, &opts, &optlen) < 0) { + syslog(LOG_ERR, "Can't get L2CAP options: %s (%d)", + strerror(errno), errno); + goto error; + } + + /* Get connection information */ + memset(&conn, 0, sizeof(conn)); + optlen = sizeof(conn); + + if (getsockopt(sk, SOL_L2CAP, L2CAP_CONNINFO, &conn, &optlen) < 0) { + syslog(LOG_ERR, "Can't get L2CAP connection information: %s (%d)", + strerror(errno), errno); + goto error; + } + + syslog(LOG_INFO, "Connected [imtu %d, omtu %d, flush_to %d, " + "mode %d, handle %d, class 0x%02x%02x%02x]", + opts.imtu, opts.omtu, opts.flush_to, opts.mode, conn.hci_handle, + conn.dev_class[2], conn.dev_class[1], conn.dev_class[0]); + + if (data_size > opts.omtu) + data_size = opts.omtu; + + return sk; + +error: + close(sk); + return -1; +} + +static void do_listen(void (*handler)(int sk)) +{ + struct sockaddr_l2 addr; + struct l2cap_options opts; + struct l2cap_conninfo conn; + socklen_t optlen; + int sk, nsk, opt; + char ba[18]; + + /* Create socket */ + sk = socket(PF_BLUETOOTH, socktype, BTPROTO_L2CAP); + if (sk < 0) { + syslog(LOG_ERR, "Can't create socket: %s (%d)", + strerror(errno), errno); + exit(1); + } + + /* Bind to local address */ + addr.l2_family = AF_BLUETOOTH; + bacpy(&addr.l2_bdaddr, &bdaddr); + addr.l2_psm = htobs(psm); + + if (bind(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + syslog(LOG_ERR, "Can't bind socket: %s (%d)", + strerror(errno), errno); + goto error; + } + + /* Set link mode */ + opt = 0; + if (reliable) + opt |= L2CAP_LM_RELIABLE; + if (master) + opt |= L2CAP_LM_MASTER; + if (auth) + opt |= L2CAP_LM_AUTH; + if (encrypt) + opt |= L2CAP_LM_ENCRYPT; + if (secure) + opt |= L2CAP_LM_SECURE; + + if (opt && setsockopt(sk, SOL_L2CAP, L2CAP_LM, &opt, sizeof(opt)) < 0) { + syslog(LOG_ERR, "Can't set L2CAP link mode: %s (%d)", + strerror(errno), errno); + goto error; + } + + /* Get default options */ + memset(&opts, 0, sizeof(opts)); + optlen = sizeof(opts); + + if (getsockopt(sk, SOL_L2CAP, L2CAP_OPTIONS, &opts, &optlen) < 0) { + syslog(LOG_ERR, "Can't get default L2CAP options: %s (%d)", + strerror(errno), errno); + goto error; + } + + /* Set new options */ + opts.omtu = omtu; + opts.imtu = imtu; + if (rfcmode > 0) + opts.mode = rfcmode; + + if (setsockopt(sk, SOL_L2CAP, L2CAP_OPTIONS, &opts, sizeof(opts)) < 0) { + syslog(LOG_ERR, "Can't set L2CAP options: %s (%d)", + strerror(errno), errno); + goto error; + } + + if (socktype == SOCK_DGRAM) { + handler(sk); + return; + } + + /* Listen for connections */ + if (listen(sk, 10)) { + syslog(LOG_ERR, "Can not listen on the socket: %s (%d)", + strerror(errno), errno); + goto error; + } + + /* Check for socket address */ + memset(&addr, 0, sizeof(addr)); + optlen = sizeof(addr); + + if (getsockname(sk, (struct sockaddr *) &addr, &optlen) < 0) { + syslog(LOG_ERR, "Can't get socket name: %s (%d)", + strerror(errno), errno); + goto error; + } + + psm = btohs(addr.l2_psm); + + syslog(LOG_INFO, "Waiting for connection on psm %d ...", psm); + + while(1) { + memset(&addr, 0, sizeof(addr)); + optlen = sizeof(addr); + + nsk = accept(sk, (struct sockaddr *) &addr, &optlen); + if (nsk < 0) { + syslog(LOG_ERR, "Accept failed: %s (%d)", + strerror(errno), errno); + goto error; + } + if (fork()) { + /* Parent */ + close(nsk); + continue; + } + /* Child */ + close(sk); + + /* Get current options */ + memset(&opts, 0, sizeof(opts)); + optlen = sizeof(opts); + + if (getsockopt(nsk, SOL_L2CAP, L2CAP_OPTIONS, &opts, &optlen) < 0) { + syslog(LOG_ERR, "Can't get L2CAP options: %s (%d)", + strerror(errno), errno); + close(nsk); + goto error; + } + + /* Get connection information */ + memset(&conn, 0, sizeof(conn)); + optlen = sizeof(conn); + + if (getsockopt(nsk, SOL_L2CAP, L2CAP_CONNINFO, &conn, &optlen) < 0) { + syslog(LOG_ERR, "Can't get L2CAP connection information: %s (%d)", + strerror(errno), errno); + close(nsk); + goto error; + } + + ba2str(&addr.l2_bdaddr, ba); + syslog(LOG_INFO, "Connect from %s [imtu %d, omtu %d, flush_to %d, " + "mode %d, handle %d, class 0x%02x%02x%02x]", + ba, opts.imtu, opts.omtu, opts.flush_to, opts.mode, conn.hci_handle, + conn.dev_class[2], conn.dev_class[1], conn.dev_class[0]); + +#if 0 + /* Enable SO_TIMESTAMP */ + if (timestamp) { + int t = 1; + + if (setsockopt(nsk, SOL_SOCKET, SO_TIMESTAMP, &t, sizeof(t)) < 0) { + syslog(LOG_ERR, "Can't enable SO_TIMESTAMP: %s (%d)", + strerror(errno), errno); + goto error; + } + } +#endif + + /* Enable SO_LINGER */ + if (linger) { + struct linger l = { .l_onoff = 1, .l_linger = linger }; + + if (setsockopt(nsk, SOL_SOCKET, SO_LINGER, &l, sizeof(l)) < 0) { + syslog(LOG_ERR, "Can't enable SO_LINGER: %s (%d)", + strerror(errno), errno); + close(nsk); + goto error; + } + } + + handler(nsk); + + syslog(LOG_INFO, "Disconnect: %m"); + exit(0); + } + + return; + +error: + close(sk); + exit(1); +} + +static void dump_mode(int sk) +{ + socklen_t optlen; + int opt, len; + + syslog(LOG_INFO, "Receiving ..."); + while (1) { + fd_set rset; + + FD_ZERO(&rset); + FD_SET(sk, &rset); + + if (select(sk + 1, &rset, NULL, NULL, NULL) < 0) + return; + + if (!FD_ISSET(sk, &rset)) + continue; + + len = read(sk, buf, data_size); + if (len <= 0) { + if (len < 0) { + if (reliable && (errno == ECOMM)) { + syslog(LOG_INFO, "L2CAP Error ECOMM - clearing error and continuing."); + optlen = sizeof(opt); + if (getsockopt(sk, SOL_SOCKET, SO_ERROR, &opt, &optlen) < 0) { + syslog(LOG_ERR, "Couldn't getsockopt(SO_ERROR): %s (%d)", + strerror(errno), errno); + return; + } + continue; + } else { + syslog(LOG_ERR, "Read error: %s(%d)", + strerror(errno), errno); + } + } + return; + } + + syslog(LOG_INFO, "Recevied %d bytes", len); + hexdump(buf, len); + } +} + +static void recv_mode(int sk) +{ + struct timeval tv_beg, tv_end, tv_diff; + struct pollfd p; + char ts[30]; + long total; + uint32_t seq; + socklen_t optlen; + int opt; + + syslog(LOG_INFO, "Receiving ..."); + + memset(ts, 0, sizeof(ts)); + + p.fd = sk; + p.events = POLLIN | POLLERR | POLLHUP; + + seq = 0; + while (1) { + gettimeofday(&tv_beg, NULL); + total = 0; + while (total < data_size) { + uint32_t sq; + uint16_t l; + int i, len; + + p.revents = 0; + if (poll(&p, 1, -1) <= 0) + return; + + if (p.revents & (POLLERR | POLLHUP)) + return; + + len = recv(sk, buf, data_size, 0); + if (len < 0) { + if (reliable && (errno == ECOMM)) { + syslog(LOG_INFO, "L2CAP Error ECOMM - clearing error and continuing.\n"); + optlen = sizeof(opt); + if (getsockopt(sk, SOL_SOCKET, SO_ERROR, &opt, &optlen) < 0) { + syslog(LOG_ERR, "Couldn't getsockopt(SO_ERROR): %s (%d)", + strerror(errno), errno); + return; + } + continue; + } else { + syslog(LOG_ERR, "Read failed: %s (%d)", + strerror(errno), errno); + } + } + + if (len < 6) + break; + + if (timestamp) { + struct timeval tv; + + if (ioctl(sk, SIOCGSTAMP, &tv) < 0) { + timestamp = 0; + memset(ts, 0, sizeof(ts)); + } else { + sprintf(ts, "[%ld.%ld] ", + tv.tv_sec, tv.tv_usec); + } + } + + /* Check sequence */ + sq = btohl(*(uint32_t *) buf); + if (seq != sq) { + syslog(LOG_INFO, "seq missmatch: %d -> %d", seq, sq); + seq = sq; + } + seq++; + + /* Check length */ + l = btohs(*(uint16_t *) (buf + 4)); + if (len != l) { + syslog(LOG_INFO, "size missmatch: %d -> %d", len, l); + continue; + } + + /* Verify data */ + for (i = 6; i < len; i++) { + if (buf[i] != 0x7f) + syslog(LOG_INFO, "data missmatch: byte %d 0x%2.2x", i, buf[i]); + } + + total += len; + } + gettimeofday(&tv_end, NULL); + + timersub(&tv_end, &tv_beg, &tv_diff); + + syslog(LOG_INFO,"%s%ld bytes in %.2f sec, %.2f kB/s", ts, total, + tv2fl(tv_diff), (float)(total / tv2fl(tv_diff) ) / 1024.0); + } +} + +static void do_send(int sk) +{ + uint32_t seq; + int i, fd, len; + + syslog(LOG_INFO, "Sending ..."); + + if (filename) { + fd = open(filename, O_RDONLY); + if (fd < 0) { + syslog(LOG_ERR, "Open failed: %s (%d)", + strerror(errno), errno); + exit(1); + } + len = read(fd, buf, data_size); + send(sk, buf, len, 0); + return; + } else { + for (i = 6; i < data_size; i++) + buf[i] = 0x7f; + } + + seq = 0; + while ((num_frames == -1) || (num_frames-- > 0)) { + *(uint32_t *) buf = htobl(seq); + *(uint16_t *) (buf + 4) = htobs(data_size); + seq++; + + len = send(sk, buf, data_size, 0); + if (len < 0 || len != data_size) { + syslog(LOG_ERR, "Send failed: %s (%d)", + strerror(errno), errno); + exit(1); + } + + if (num_frames && delay && count && !(seq % count)) + usleep(delay); + } +} + +static void send_mode(int sk) +{ + do_send(sk); + + syslog(LOG_INFO, "Closing channel ..."); + if (shutdown(sk, SHUT_RDWR) < 0) + syslog(LOG_INFO, "Close failed: %m"); + else + syslog(LOG_INFO, "Done"); +} + +static void senddump_mode(int sk) +{ + do_send(sk); + + dump_mode(sk); +} + +static void reconnect_mode(char *svr) +{ + while (1) { + int sk = do_connect(svr); + close(sk); + } +} + +static void connect_mode(char *svr) +{ + struct pollfd p; + int sk; + + if ((sk = do_connect(svr)) < 0) + exit(1); + + p.fd = sk; + p.events = POLLERR | POLLHUP; + + while (1) { + p.revents = 0; + if (poll(&p, 1, 500)) + break; + } + + syslog(LOG_INFO, "Disconnected"); + + close(sk); +} + +static void multi_connect_mode(int argc, char *argv[]) +{ + int i, n, sk; + + while (1) { + for (n = 0; n < argc; n++) { + for (i = 0; i < count; i++) { + if (fork()) + continue; + + /* Child */ + sk = do_connect(argv[n]); + usleep(500); + close(sk); + exit(0); + } + } + sleep(4); + } +} + +static void info_request(char *svr) +{ + unsigned char buf[48]; + l2cap_cmd_hdr *cmd = (l2cap_cmd_hdr *) buf; + l2cap_info_req *req = (l2cap_info_req *) (buf + L2CAP_CMD_HDR_SIZE); + l2cap_info_rsp *rsp = (l2cap_info_rsp *) (buf + L2CAP_CMD_HDR_SIZE); + uint16_t mtu; + uint32_t mask; + struct sockaddr_l2 addr; + int sk, err; + + sk = socket(PF_BLUETOOTH, SOCK_RAW, BTPROTO_L2CAP); + if (sk < 0) { + perror("Can't create socket"); + return; + } + + memset(&addr, 0, sizeof(addr)); + addr.l2_family = AF_BLUETOOTH; + bacpy(&addr.l2_bdaddr, &bdaddr); + + if (bind(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + perror("Can't bind socket"); + goto failed; + } + + memset(&addr, 0, sizeof(addr)); + addr.l2_family = AF_BLUETOOTH; + str2ba(svr, &addr.l2_bdaddr); + + if (connect(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0 ) { + perror("Can't connect socket"); + goto failed; + } + + memset(buf, 0, sizeof(buf)); + cmd->code = L2CAP_INFO_REQ; + cmd->ident = 42; + cmd->len = htobs(2); + req->type = htobs(0x0001); + + if (send(sk, buf, L2CAP_CMD_HDR_SIZE + L2CAP_INFO_REQ_SIZE, 0) < 0) { + perror("Can't send info request"); + goto failed; + } + + err = recv(sk, buf, L2CAP_CMD_HDR_SIZE + L2CAP_INFO_RSP_SIZE + 2, 0); + if (err < 0) { + perror("Can't receive info response"); + goto failed; + } + + switch (btohs(rsp->result)) { + case 0x0000: + mtu = btohs(bt_get_unaligned((uint16_t *) rsp->data)); + printf("Connectionless MTU size is %d\n", mtu); + break; + case 0x0001: + printf("Connectionless MTU is not supported\n"); + break; + } + + memset(buf, 0, sizeof(buf)); + cmd->code = L2CAP_INFO_REQ; + cmd->ident = 42; + cmd->len = htobs(2); + req->type = htobs(0x0002); + + if (send(sk, buf, L2CAP_CMD_HDR_SIZE + L2CAP_INFO_REQ_SIZE, 0) < 0) { + perror("Can't send info request"); + goto failed; + } + + err = recv(sk, buf, L2CAP_CMD_HDR_SIZE + L2CAP_INFO_RSP_SIZE + 4, 0); + if (err < 0) { + perror("Can't receive info response"); + goto failed; + } + + switch (btohs(rsp->result)) { + case 0x0000: + mask = btohl(bt_get_unaligned((uint32_t *) rsp->data)); + printf("Extended feature mask is 0x%04x\n", mask); + if (mask & 0x01) + printf(" Flow control mode\n"); + if (mask & 0x02) + printf(" Retransmission mode\n"); + if (mask & 0x04) + printf(" Bi-directional QoS\n"); + break; + case 0x0001: + printf("Extended feature mask is not supported\n"); + break; + } + +failed: + close(sk); +} + +static void usage(void) +{ + printf("l2test - L2CAP testing\n" + "Usage:\n"); + printf("\tl2test <mode> [options] [bdaddr]\n"); + printf("Modes:\n" + "\t-r listen and receive\n" + "\t-w listen and send\n" + "\t-d listen and dump incoming data\n" + "\t-x listen, then send, then dump incoming data\n" + "\t-s connect and send\n" + "\t-u connect and receive\n" + "\t-n connect and be silent\n" + "\t-y connect, then send, then dump incoming data\n" + "\t-c connect, disconnect, connect, ...\n" + "\t-m multiple connects\n" + "\t-z information request\n"); + + printf("Options:\n" + "\t[-b bytes] [-i device] [-P psm]\n" + "\t[-I imtu] [-O omtu]\n" + "\t[-L seconds] enable SO_LINGER\n" + "\t[-B filename] use data packets from file\n" + "\t[-N num] send num frames (default = infinite)\n" + "\t[-C num] send num frames before delay (default = 1)\n" + "\t[-D milliseconds] delay after sending num frames (default = 0)\n" + "\t[-X mode] select retransmission/flow-control mode\n" + "\t[-R] reliable mode\n" + "\t[-G] use connectionless channel (datagram)\n" + "\t[-A] request authentication\n" + "\t[-E] request encryption\n" + "\t[-S] secure connection\n" + "\t[-M] become master\n" + "\t[-T] enable timestamps\n"); +} + +int main(int argc, char *argv[]) +{ + struct sigaction sa; + int opt, sk, mode = RECV, need_addr = 0; + + bacpy(&bdaddr, BDADDR_ANY); + + while ((opt=getopt(argc,argv,"rdscuwmnxyzb:i:P:I:O:B:N:L:C:D:X:RGAESMT")) != EOF) { + switch(opt) { + case 'r': + mode = RECV; + break; + + case 's': + mode = SEND; + need_addr = 1; + break; + + case 'w': + mode = LSEND; + break; + + case 'u': + mode = CRECV; + need_addr = 1; + break; + + case 'd': + mode = DUMP; + break; + + case 'c': + mode = RECONNECT; + need_addr = 1; + break; + + case 'n': + mode = CONNECT; + need_addr = 1; + break; + + case 'm': + mode = MULTY; + need_addr = 1; + break; + + case 'x': + mode = LSENDDUMP; + break; + + case 'y': + mode = SENDDUMP; + break; + + case 'z': + mode = INFOREQ; + need_addr = 1; + break; + + case 'b': + data_size = atoi(optarg); + break; + + case 'i': + if (!strncasecmp(optarg, "hci", 3)) + hci_devba(atoi(optarg + 3), &bdaddr); + else + str2ba(optarg, &bdaddr); + break; + + case 'P': + psm = atoi(optarg); + break; + + case 'I': + imtu = atoi(optarg); + break; + + case 'O': + omtu = atoi(optarg); + break; + + case 'L': + linger = atoi(optarg); + break; + + case 'B': + filename = strdup(optarg); + break; + + case 'N': + num_frames = atoi(optarg); + break; + + case 'C': + count = atoi(optarg); + break; + + case 'D': + delay = atoi(optarg) * 1000; + break; + + case 'X': + rfcmode = atoi(optarg); + break; + + case 'R': + reliable = 1; + break; + + case 'M': + master = 1; + break; + + case 'A': + auth = 1; + break; + + case 'E': + encrypt = 1; + break; + + case 'S': + secure = 1; + break; + + case 'G': + socktype = SOCK_DGRAM; + break; + + case 'T': + timestamp = 1; + break; + + default: + usage(); + exit(1); + } + } + + if (need_addr && !(argc - optind)) { + usage(); + exit(1); + } + + if (data_size < 0) { + data_size = 48; + if (imtu > data_size) + data_size = imtu; + if (omtu > data_size) + data_size = omtu; + } + + if (!(buf = malloc(data_size))) { + perror("Can't allocate data buffer"); + exit(1); + } + + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = SIG_IGN; + sa.sa_flags = SA_NOCLDSTOP; + sigaction(SIGCHLD, &sa, NULL); + + openlog("l2test", LOG_PERROR | LOG_PID, LOG_LOCAL0); + + switch (mode) { + case RECV: + do_listen(recv_mode); + break; + + case CRECV: + sk = do_connect(argv[optind]); + if (sk < 0) + exit(1); + recv_mode(sk); + break; + + case DUMP: + do_listen(dump_mode); + break; + + case SEND: + sk = do_connect(argv[optind]); + if (sk < 0) + exit(1); + send_mode(sk); + break; + + case LSEND: + do_listen(send_mode); + break; + + case RECONNECT: + reconnect_mode(argv[optind]); + break; + + case MULTY: + multi_connect_mode(argc - optind, argv + optind); + break; + + case CONNECT: + connect_mode(argv[optind]); + break; + + case SENDDUMP: + sk = do_connect(argv[optind]); + if (sk < 0) + exit(1); + senddump_mode(sk); + break; + + case LSENDDUMP: + do_listen(senddump_mode); + break; + + case INFOREQ: + info_request(argv[optind]); + exit(0); + } + + syslog(LOG_INFO, "Exit"); + + closelog(); + + return 0; +} diff --git a/test/lmptest.c b/test/lmptest.c new file mode 100644 index 00000000..276ca071 --- /dev/null +++ b/test/lmptest.c @@ -0,0 +1,175 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2005-2008 Marcel Holtmann <marcel@holtmann.org> + * + * + * 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 + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <errno.h> +#include <stdlib.h> +#include <getopt.h> +#include <sys/ioctl.h> +#include <sys/socket.h> + +#include <bluetooth/bluetooth.h> +#include <bluetooth/hci.h> +#include <bluetooth/hci_lib.h> + +#if 0 +#define OCF_ERICSSON_SEND_LMP 0x0021 +typedef struct { + uint16_t handle; + uint8_t length; + uint8_t data[17]; +} __attribute__ ((packed)) ericsson_send_lmp_cp; +#define ERICSSON_SEND_LMP_CP_SIZE 20 + +static int ericsson_send_lmp(int dd, uint16_t handle, uint8_t length, uint8_t *data) +{ + struct hci_request rq; + ericsson_send_lmp_cp cp; + + memset(&cp, 0, sizeof(cp)); + cp.handle = htobs(handle); + cp.length = length; + memcpy(cp.data, data, length); + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_VENDOR_CMD; + rq.ocf = OCF_ERICSSON_SEND_LMP; + rq.cparam = &cp; + rq.clen = ERICSSON_SEND_LMP_CP_SIZE; + rq.rparam = NULL; + rq.rlen = 0; + + if (hci_send_req(dd, &rq, 1000) < 0) + return -1; + + return 0; +} +#endif + +#define OCF_ERICSSON_WRITE_EVENTS 0x0043 +typedef struct { + uint8_t mask; + uint8_t opcode; + uint8_t opcode_ext; +} __attribute__ ((packed)) ericsson_write_events_cp; +#define ERICSSON_WRITE_EVENTS_CP_SIZE 3 + +static int ericsson_write_events(int dd, uint8_t mask) +{ + struct hci_request rq; + ericsson_write_events_cp cp; + + memset(&cp, 0, sizeof(cp)); + cp.mask = mask; + cp.opcode = 0x00; + cp.opcode_ext = 0x00; + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_VENDOR_CMD; + rq.ocf = OCF_ERICSSON_WRITE_EVENTS; + rq.cparam = &cp; + rq.clen = ERICSSON_WRITE_EVENTS_CP_SIZE; + rq.rparam = NULL; + rq.rlen = 0; + + if (hci_send_req(dd, &rq, 1000) < 0) + return -1; + + return 0; +} + +static void usage(void) +{ + printf("lmptest - Utility for testing special LMP functions\n\n"); + printf("Usage:\n" + "\tlmptest [-i <dev>]\n"); +} + +static struct option main_options[] = { + { "device", 1, 0, 'i' }, + { "help", 0, 0, 'h' }, + { 0, 0, 0, 0 } +}; + +int main(int argc, char *argv[]) +{ + struct hci_version ver; + int dd, opt, dev = 0; + + while ((opt=getopt_long(argc, argv, "+i:h", main_options, NULL)) != -1) { + switch (opt) { + case 'i': + dev = hci_devid(optarg); + if (dev < 0) { + perror("Invalid device"); + exit(1); + } + break; + + case 'h': + default: + usage(); + exit(0); + } + } + + argc -= optind; + argv += optind; + optind = 0; + + dd = hci_open_dev(dev); + if (dd < 0) { + fprintf(stderr, "Can't open device hci%d: %s (%d)\n", + dev, strerror(errno), errno); + exit(1); + } + + if (hci_read_local_version(dd, &ver, 1000) < 0) { + fprintf(stderr, "Can't read version for hci%d: %s (%d)\n", + dev, strerror(errno), errno); + hci_close_dev(dd); + exit(1); + } + + if (ver.manufacturer != 37 && ver.manufacturer != 48) { + fprintf(stderr, "Can't find supported device hci%d: %s (%d)\n", + dev, strerror(ENOSYS), ENOSYS); + hci_close_dev(dd); + exit(1); + } + + if (ericsson_write_events(dd, 0x03) < 0) { + fprintf(stderr, "Can't activate events for hci%d: %s (%d)\n", + dev, strerror(errno), errno); + hci_close_dev(dd); + exit(1); + } + + hci_close_dev(dd); + + return 0; +} diff --git a/test/passkey-agent.c b/test/passkey-agent.c new file mode 100644 index 00000000..8ac91d27 --- /dev/null +++ b/test/passkey-agent.c @@ -0,0 +1,418 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2008 Marcel Holtmann <marcel@holtmann.org> + * + * + * 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 + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <errno.h> +#include <unistd.h> +#include <stdlib.h> +#include <signal.h> +#include <getopt.h> +#include <string.h> + +#include <dbus/dbus.h> + +#define INTERFACE "org.bluez.Security" + +static char *passkey = NULL; +static char *address = NULL; + +static int do_reject = 0; + +static volatile sig_atomic_t __io_canceled = 0; +static volatile sig_atomic_t __io_terminated = 0; + +static void sig_term(int sig) +{ + __io_canceled = 1; +} + +static DBusHandlerResult agent_filter(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + const char *name, *old, *new; + + if (!dbus_message_is_signal(msg, DBUS_INTERFACE_DBUS, "NameOwnerChanged")) + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_STRING, &name, DBUS_TYPE_STRING, &old, + DBUS_TYPE_STRING, &new, DBUS_TYPE_INVALID)) { + fprintf(stderr, "Invalid arguments for NameOwnerChanged signal"); + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + + if (!strcmp(name, "org.bluez") && *new == '\0') { + fprintf(stderr, "Passkey service has been terminated\n"); + __io_terminated = 1; + } + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +static DBusHandlerResult request_message(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + DBusMessage *reply; + const char *path, *address; + dbus_bool_t numeric; + + if (!passkey) + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_STRING, &path, DBUS_TYPE_STRING, &address, + DBUS_TYPE_BOOLEAN, &numeric, DBUS_TYPE_INVALID)) { + fprintf(stderr, "Invalid arguments for passkey Request method"); + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + + if (do_reject) { + reply = dbus_message_new_error(msg, + "org.bluez.Error.Rejected", ""); + goto send; + } + + reply = dbus_message_new_method_return(msg); + if (!reply) { + fprintf(stderr, "Can't create reply message\n"); + return DBUS_HANDLER_RESULT_NEED_MEMORY; + } + + printf("Passkey request for device %s\n", address); + + dbus_message_append_args(reply, DBUS_TYPE_STRING, &passkey, + DBUS_TYPE_INVALID); + +send: + dbus_connection_send(conn, reply, NULL); + + dbus_connection_flush(conn); + + dbus_message_unref(reply); + + return DBUS_HANDLER_RESULT_HANDLED; +} + +static DBusHandlerResult cancel_message(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + DBusMessage *reply; + const char *path, *address; + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_STRING, &path, DBUS_TYPE_STRING, &address, + DBUS_TYPE_INVALID)) { + fprintf(stderr, "Invalid arguments for passkey Confirm method"); + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + + printf("Request canceled for device %s\n", address); + + reply = dbus_message_new_method_return(msg); + if (!reply) { + fprintf(stderr, "Can't create reply message\n"); + return DBUS_HANDLER_RESULT_NEED_MEMORY; + } + + dbus_connection_send(conn, reply, NULL); + + dbus_connection_flush(conn); + + dbus_message_unref(reply); + + return DBUS_HANDLER_RESULT_HANDLED; +} + +static DBusHandlerResult release_message(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + DBusMessage *reply; + + if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_INVALID)) { + fprintf(stderr, "Invalid arguments for passkey Release method"); + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + + if (!__io_canceled) + fprintf(stderr, "Passkey service has been released\n"); + + __io_terminated = 1; + + reply = dbus_message_new_method_return(msg); + if (!reply) { + fprintf(stderr, "Can't create reply message\n"); + return DBUS_HANDLER_RESULT_NEED_MEMORY; + } + + dbus_connection_send(conn, reply, NULL); + + dbus_connection_flush(conn); + + dbus_message_unref(reply); + + return DBUS_HANDLER_RESULT_HANDLED; +} + +static DBusHandlerResult agent_message(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + if (dbus_message_is_method_call(msg, "org.bluez.PasskeyAgent", "Request")) + return request_message(conn, msg, data); + + if (dbus_message_is_method_call(msg, "org.bluez.PasskeyAgent", "Cancel")) + return cancel_message(conn, msg, data); + + if (dbus_message_is_method_call(msg, "org.bluez.PasskeyAgent", "Release")) + return release_message(conn, msg, data); + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +static const DBusObjectPathVTable agent_table = { + .message_function = agent_message, +}; + +static int register_agent(DBusConnection *conn, const char *agent_path, + const char *remote_address, int use_default) +{ + DBusMessage *msg, *reply; + DBusError err; + const char *path, *method, *address = remote_address; + + if (!dbus_connection_register_object_path(conn, agent_path, + &agent_table, NULL)) { + fprintf(stderr, "Can't register object path for agent\n"); + return -1; + } + + if (use_default) { + path = "/org/bluez"; + method = "RegisterDefaultPasskeyAgent"; + } else { + path = "/org/bluez/hci0"; + method = "RegisterPasskeyAgent"; + } + + msg = dbus_message_new_method_call("org.bluez", path, INTERFACE, method); + if (!msg) { + fprintf(stderr, "Can't allocate new method call\n"); + return -1; + } + + if (use_default) + dbus_message_append_args(msg, DBUS_TYPE_STRING, &agent_path, + DBUS_TYPE_INVALID); + else + dbus_message_append_args(msg, DBUS_TYPE_STRING, &agent_path, + DBUS_TYPE_STRING, &address, DBUS_TYPE_INVALID); + + dbus_error_init(&err); + + reply = dbus_connection_send_with_reply_and_block(conn, msg, -1, &err); + + dbus_message_unref(msg); + + if (!reply) { + fprintf(stderr, "Can't register passkey agent\n"); + if (dbus_error_is_set(&err)) { + fprintf(stderr, "%s\n", err.message); + dbus_error_free(&err); + } + return -1; + } + + dbus_message_unref(reply); + + dbus_connection_flush(conn); + + return 0; +} + +static int unregister_agent(DBusConnection *conn, const char *agent_path, + const char *remote_address, int use_default) +{ + DBusMessage *msg, *reply; + DBusError err; + const char *path, *method, *address = remote_address; + + if (use_default) { + path = "/org/bluez"; + method = "UnregisterDefaultPasskeyAgent"; + } else { + path = "/org/bluez/hci0"; + method = "UnregisterPasskeyAgent"; + } + + msg = dbus_message_new_method_call("org.bluez", path, INTERFACE, method); + if (!msg) { + fprintf(stderr, "Can't allocate new method call\n"); + dbus_connection_unref(conn); + exit(1); + } + + if (use_default) + dbus_message_append_args(msg, DBUS_TYPE_STRING, &agent_path, + DBUS_TYPE_INVALID); + else + dbus_message_append_args(msg, DBUS_TYPE_STRING, &agent_path, + DBUS_TYPE_STRING, &address, DBUS_TYPE_INVALID); + + dbus_error_init(&err); + + reply = dbus_connection_send_with_reply_and_block(conn, msg, -1, &err); + + dbus_message_unref(msg); + + if (!reply) { + fprintf(stderr, "Can't unregister passkey agent\n"); + if (dbus_error_is_set(&err)) { + fprintf(stderr, "%s\n", err.message); + dbus_error_free(&err); + } + return -1; + } + + dbus_message_unref(reply); + + dbus_connection_flush(conn); + + dbus_connection_unregister_object_path(conn, agent_path); + + return 0; +} + +static void usage(void) +{ + printf("Bluetooth passkey agent ver %s\n\n", VERSION); + + printf("Usage:\n" + "\tpasskey-agent [--default] [--path agent-path] <passkey> [address]\n" + "\n"); +} + +static struct option main_options[] = { + { "default", 0, 0, 'd' }, + { "reject", 0, 0, 'r' }, + { "path", 1, 0, 'p' }, + { "help", 0, 0, 'h' }, + { 0, 0, 0, 0 } +}; + +int main(int argc, char *argv[]) +{ + struct sigaction sa; + DBusConnection *conn; + char match_string[128], default_path[128], *agent_path = NULL; + int opt, use_default = 0; + + snprintf(default_path, sizeof(default_path), + "/org/bluez/passkey_agent_%d", getpid()); + + while ((opt = getopt_long(argc, argv, "+dp:h", main_options, NULL)) != EOF) { + switch(opt) { + case 'd': + use_default = 1; + break; + case 'r': + do_reject = 1; + break; + case 'p': + if (optarg[0] != '/') { + fprintf(stderr, "Invalid path\n"); + exit(1); + } + agent_path = strdup(optarg); + break; + case 'h': + usage(); + exit(0); + default: + exit(1); + } + } + + argc -= optind; + argv += optind; + optind = 0; + + if (argc < 1) { + usage(); + exit(1); + } + + passkey = strdup(argv[0]); + address = (argc > 1) ? strdup(argv[1]) : NULL; + + if (!use_default && !address) { + usage(); + exit(1); + } + + if (!agent_path) + agent_path = strdup(default_path); + + conn = dbus_bus_get(DBUS_BUS_SYSTEM, NULL); + if (!conn) { + fprintf(stderr, "Can't get on system bus"); + exit(1); + } + + if (register_agent(conn, agent_path, address, use_default) < 0) { + dbus_connection_unref(conn); + exit(1); + } + + if (!dbus_connection_add_filter(conn, agent_filter, NULL, NULL)) + fprintf(stderr, "Can't add signal filter"); + + snprintf(match_string, sizeof(match_string), + "interface=%s,member=NameOwnerChanged,arg0=%s", + DBUS_INTERFACE_DBUS, "org.bluez"); + + dbus_bus_add_match(conn, match_string, NULL); + + memset(&sa, 0, sizeof(sa)); + sa.sa_flags = SA_NOCLDSTOP; + sa.sa_handler = sig_term; + sigaction(SIGTERM, &sa, NULL); + sigaction(SIGINT, &sa, NULL); + + while (!__io_canceled && !__io_terminated) { + if (dbus_connection_read_write_dispatch(conn, 500) != TRUE) + break; + } + + if (!__io_terminated) + unregister_agent(conn, agent_path, address, use_default); + + if (passkey) + free(passkey); + + dbus_connection_unref(conn); + + return 0; +} diff --git a/test/rctest.c b/test/rctest.c new file mode 100644 index 00000000..08620d4e --- /dev/null +++ b/test/rctest.c @@ -0,0 +1,688 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2002-2003 Maxim Krasnyansky <maxk@qualcomm.com> + * Copyright (C) 2002-2008 Marcel Holtmann <marcel@holtmann.org> + * + * + * 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 + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <errno.h> +#include <ctype.h> +#include <fcntl.h> +#include <unistd.h> +#include <stdlib.h> +#include <getopt.h> +#include <syslog.h> +#include <signal.h> +#include <sys/time.h> +#include <sys/ioctl.h> +#include <sys/socket.h> + +#include <bluetooth/bluetooth.h> +#include <bluetooth/hci.h> +#include <bluetooth/hci_lib.h> +#include <bluetooth/rfcomm.h> + +/* Test modes */ +enum { + SEND, + RECV, + RECONNECT, + MULTY, + DUMP, + CONNECT, + CRECV, + LSEND +}; + +static unsigned char *buf; + +/* Default data size */ +static long data_size = 127; +static long num_frames = -1; + +/* Default number of consecutive frames before the delay */ +static int count = 1; + +/* Default delay after sending count number of frames */ +static unsigned long delay = 0; + +/* Default addr and channel */ +static bdaddr_t bdaddr; +static uint8_t channel = 10; + +static char *filename = NULL; + +static int master = 0; +static int auth = 0; +static int encrypt = 0; +static int secure = 0; +static int socktype = SOCK_STREAM; +static int linger = 0; +static int timestamp = 0; + +static float tv2fl(struct timeval tv) +{ + return (float)tv.tv_sec + (float)(tv.tv_usec/1000000.0); +} + +static int do_connect(char *svr) +{ + struct sockaddr_rc addr; + struct rfcomm_conninfo conn; + socklen_t optlen; + int sk, opt; + + /* Create socket */ + sk = socket(PF_BLUETOOTH, socktype, BTPROTO_RFCOMM); + if (sk < 0) { + syslog(LOG_ERR, "Can't create socket: %s (%d)", + strerror(errno), errno); + return -1; + } + + /* Bind to local address */ + memset(&addr, 0, sizeof(addr)); + addr.rc_family = AF_BLUETOOTH; + bacpy(&addr.rc_bdaddr, &bdaddr); + + if (bind(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + syslog(LOG_ERR, "Can't bind socket: %s (%d)", + strerror(errno), errno); + goto error; + } + +#if 0 + /* Enable SO_TIMESTAMP */ + if (timestamp) { + int t = 1; + + if (setsockopt(sk, SOL_SOCKET, SO_TIMESTAMP, &t, sizeof(t)) < 0) { + syslog(LOG_ERR, "Can't enable SO_TIMESTAMP: %s (%d)", + strerror(errno), errno); + goto error; + } + } +#endif + + /* Enable SO_LINGER */ + if (linger) { + struct linger l = { .l_onoff = 1, .l_linger = linger }; + + if (setsockopt(sk, SOL_SOCKET, SO_LINGER, &l, sizeof(l)) < 0) { + syslog(LOG_ERR, "Can't enable SO_LINGER: %s (%d)", + strerror(errno), errno); + goto error; + } + } + + /* Set link mode */ + opt = 0; + if (master) + opt |= RFCOMM_LM_MASTER; + if (auth) + opt |= RFCOMM_LM_AUTH; + if (encrypt) + opt |= RFCOMM_LM_ENCRYPT; + if (secure) + opt |= RFCOMM_LM_SECURE; + + if (opt && setsockopt(sk, SOL_RFCOMM, RFCOMM_LM, &opt, sizeof(opt)) < 0) { + syslog(LOG_ERR, "Can't set RFCOMM link mode: %s (%d)", + strerror(errno), errno); + goto error; + } + + /* Connect to remote device */ + memset(&addr, 0, sizeof(addr)); + addr.rc_family = AF_BLUETOOTH; + str2ba(svr, &addr.rc_bdaddr); + addr.rc_channel = channel; + + if (connect(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + syslog(LOG_ERR, "Can't connect: %s (%d)", + strerror(errno), errno); + goto error; + } + + /* Get connection information */ + memset(&conn, 0, sizeof(conn)); + optlen = sizeof(conn); + + if (getsockopt(sk, SOL_RFCOMM, RFCOMM_CONNINFO, &conn, &optlen) < 0) { + syslog(LOG_ERR, "Can't get RFCOMM connection information: %s (%d)", + strerror(errno), errno); + //goto error; + } + + syslog(LOG_INFO, "Connected [handle %d, class 0x%02x%02x%02x]", + conn.hci_handle, + conn.dev_class[2], conn.dev_class[1], conn.dev_class[0]); + + return sk; + +error: + close(sk); + return -1; +} + +static void do_listen(void (*handler)(int sk)) +{ + struct sockaddr_rc addr; + struct rfcomm_conninfo conn; + socklen_t optlen; + int sk, nsk, opt; + char ba[18]; + + /* Create socket */ + sk = socket(PF_BLUETOOTH, socktype, BTPROTO_RFCOMM); + if (sk < 0) { + syslog(LOG_ERR, "Can't create socket: %s (%d)", + strerror(errno), errno); + exit(1); + } + + /* Bind to local address */ + addr.rc_family = AF_BLUETOOTH; + bacpy(&addr.rc_bdaddr, &bdaddr); + addr.rc_channel = channel; + + if (bind(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + syslog(LOG_ERR, "Can't bind socket: %s (%d)", + strerror(errno), errno); + goto error; + } + + /* Set link mode */ + opt = 0; + if (master) + opt |= RFCOMM_LM_MASTER; + if (auth) + opt |= RFCOMM_LM_AUTH; + if (encrypt) + opt |= RFCOMM_LM_ENCRYPT; + if (secure) + opt |= RFCOMM_LM_SECURE; + + if (opt && setsockopt(sk, SOL_RFCOMM, RFCOMM_LM, &opt, sizeof(opt)) < 0) { + syslog(LOG_ERR, "Can't set RFCOMM link mode: %s (%d)", + strerror(errno), errno); + goto error; + } + + /* Listen for connections */ + if (listen(sk, 10)) { + syslog(LOG_ERR,"Can not listen on the socket: %s (%d)", + strerror(errno), errno); + goto error; + } + + /* Check for socket address */ + memset(&addr, 0, sizeof(addr)); + optlen = sizeof(addr); + + if (getsockname(sk, (struct sockaddr *) &addr, &optlen) < 0) { + syslog(LOG_ERR, "Can't get socket name: %s (%d)", + strerror(errno), errno); + goto error; + } + + channel = addr.rc_channel; + + syslog(LOG_INFO, "Waiting for connection on channel %d ...", channel); + + while(1) { + memset(&addr, 0, sizeof(addr)); + optlen = sizeof(addr); + + nsk = accept(sk, (struct sockaddr *) &addr, &optlen); + if (nsk < 0) { + syslog(LOG_ERR,"Accept failed: %s (%d)", + strerror(errno), errno); + goto error; + } + if (fork()) { + /* Parent */ + close(nsk); + continue; + } + /* Child */ + close(sk); + + /* Get connection information */ + memset(&conn, 0, sizeof(conn)); + optlen = sizeof(conn); + + if (getsockopt(nsk, SOL_RFCOMM, RFCOMM_CONNINFO, &conn, &optlen) < 0) { + syslog(LOG_ERR, "Can't get RFCOMM connection information: %s (%d)", + strerror(errno), errno); + //close(nsk); + //goto error; + } + + ba2str(&addr.rc_bdaddr, ba); + syslog(LOG_INFO, "Connect from %s [handle %d, class 0x%02x%02x%02x]", + ba, conn.hci_handle, + conn.dev_class[2], conn.dev_class[1], conn.dev_class[0]); + +#if 0 + /* Enable SO_TIMESTAMP */ + if (timestamp) { + int t = 1; + + if (setsockopt(nsk, SOL_SOCKET, SO_TIMESTAMP, &t, sizeof(t)) < 0) { + syslog(LOG_ERR, "Can't enable SO_TIMESTAMP: %s (%d)", + strerror(errno), errno); + goto error; + } + } +#endif + + /* Enable SO_LINGER */ + if (linger) { + struct linger l = { .l_onoff = 1, .l_linger = linger }; + + if (setsockopt(nsk, SOL_SOCKET, SO_LINGER, &l, sizeof(l)) < 0) { + syslog(LOG_ERR, "Can't enable SO_LINGER: %s (%d)", + strerror(errno), errno); + close(nsk); + goto error; + } + } + + handler(nsk); + + syslog(LOG_INFO, "Disconnect: %m"); + exit(0); + } + + return; + +error: + close(sk); + exit(1); +} + +static void dump_mode(int sk) +{ + int len; + + syslog(LOG_INFO, "Receiving ..."); + while ((len = read(sk, buf, data_size)) > 0) + syslog(LOG_INFO, "Recevied %d bytes", len); +} + +static void recv_mode(int sk) +{ + struct timeval tv_beg, tv_end, tv_diff; + char ts[30]; + long total; + uint32_t seq; + + syslog(LOG_INFO, "Receiving ..."); + + memset(ts, 0, sizeof(ts)); + + seq = 0; + while (1) { + gettimeofday(&tv_beg,NULL); + total = 0; + while (total < data_size) { + //uint32_t sq; + //uint16_t l; + int r; + + if ((r = recv(sk, buf, data_size, 0)) <= 0) { + if (r < 0) + syslog(LOG_ERR, "Read failed: %s (%d)", + strerror(errno), errno); + return; + } + + if (timestamp) { + struct timeval tv; + + if (ioctl(sk, SIOCGSTAMP, &tv) < 0) { + timestamp = 0; + memset(ts, 0, sizeof(ts)); + } else { + sprintf(ts, "[%ld.%ld] ", + tv.tv_sec, tv.tv_usec); + } + } + +#if 0 + /* Check sequence */ + sq = btohl(*(uint32_t *) buf); + if (seq != sq) { + syslog(LOG_INFO, "seq missmatch: %d -> %d", seq, sq); + seq = sq; + } + seq++; + + /* Check length */ + l = btohs(*(uint16_t *) (buf + 4)); + if (r != l) { + syslog(LOG_INFO, "size missmatch: %d -> %d", r, l); + continue; + } + + /* Verify data */ + for (i = 6; i < r; i++) { + if (buf[i] != 0x7f) + syslog(LOG_INFO, "data missmatch: byte %d 0x%2.2x", i, buf[i]); + } +#endif + total += r; + } + gettimeofday(&tv_end,NULL); + + timersub(&tv_end,&tv_beg,&tv_diff); + + syslog(LOG_INFO,"%s%ld bytes in %.2f sec, %.2f kB/s", ts, total, + tv2fl(tv_diff), (float)(total / tv2fl(tv_diff) ) / 1024.0); + } +} + +static void do_send(int sk) +{ + uint32_t seq; + int i, fd, len; + + syslog(LOG_INFO,"Sending ..."); + + if (filename) { + fd = open(filename, O_RDONLY); + if (fd < 0) { + syslog(LOG_ERR, "Open failed: %s (%d)", + strerror(errno), errno); + exit(1); + } + len = read(fd, buf, data_size); + send(sk, buf, len, 0); + return; + } else { + for (i = 6; i < data_size; i++) + buf[i] = 0x7f; + } + + seq = 0; + while ((num_frames == -1) || (num_frames-- > 0)) { + *(uint32_t *) buf = htobl(seq); + *(uint16_t *) (buf + 4) = htobs(data_size); + seq++; + + if (send(sk, buf, data_size, 0) <= 0) { + syslog(LOG_ERR, "Send failed: %s (%d)", + strerror(errno), errno); + exit(1); + } + + if (num_frames && delay && count && !(seq % count)) + usleep(delay); + } +} + +static void send_mode(int sk) +{ + do_send(sk); + + syslog(LOG_INFO, "Closing channel ..."); + if (shutdown(sk, SHUT_RDWR) < 0) + syslog(LOG_INFO, "Close failed: %m"); + else + syslog(LOG_INFO, "Done"); +} + +static void reconnect_mode(char *svr) +{ + while(1) { + int sk = do_connect(svr); + close(sk); + } +} + +static void multi_connect_mode(int argc, char *argv[]) +{ + int i, n, sk; + + while (1) { + for (n = 0; n < argc; n++) { + for (i = 0; i < count; i++) { + if (fork()) + continue; + + /* Child */ + sk = do_connect(argv[n]); + usleep(500); + close(sk); + exit(0); + } + } + sleep(4); + } +} + +static void usage(void) +{ + printf("rctest - RFCOMM testing\n" + "Usage:\n"); + printf("\trctest <mode> [options] [bdaddr]\n"); + printf("Modes:\n" + "\t-r listen and receive\n" + "\t-w listen and send\n" + "\t-d listen and dump incoming data\n" + "\t-s connect and send\n" + "\t-u connect and receive\n" + "\t-n connect and be silent\n" + "\t-c connect, disconnect, connect, ...\n" + "\t-m multiple connects\n"); + + printf("Options:\n" + "\t[-b bytes] [-i device] [-P channel]\n" + "\t[-L seconds] enabled SO_LINGER option\n" + "\t[-B filename] use data packets from file\n" + "\t[-N num] number of frames to send\n" + "\t[-C num] send num frames before delay (default = 1)\n" + "\t[-D milliseconds] delay after sending num frames (default = 0)\n" + "\t[-A] request authentication\n" + "\t[-E] request encryption\n" + "\t[-S] secure connection\n" + "\t[-M] become master\n" + "\t[-T] enable timestamps\n"); +} + +int main(int argc, char *argv[]) +{ + struct sigaction sa; + int opt, sk, mode = RECV, need_addr = 0; + + bacpy(&bdaddr, BDADDR_ANY); + + while ((opt=getopt(argc,argv,"rdscuwmnb:i:P:B:N:MAESL:C:D:T")) != EOF) { + switch (opt) { + case 'r': + mode = RECV; + break; + + case 's': + mode = SEND; + need_addr = 1; + break; + + case 'w': + mode = LSEND; + break; + + case 'u': + mode = CRECV; + need_addr = 1; + break; + + case 'd': + mode = DUMP; + break; + + case 'c': + mode = RECONNECT; + need_addr = 1; + break; + + case 'n': + mode = CONNECT; + need_addr = 1; + break; + + case 'm': + mode = MULTY; + need_addr = 1; + break; + + case 'b': + data_size = atoi(optarg); + break; + + case 'i': + if (!strncasecmp(optarg, "hci", 3)) + hci_devba(atoi(optarg + 3), &bdaddr); + else + str2ba(optarg, &bdaddr); + break; + + case 'P': + channel = atoi(optarg); + break; + + case 'M': + master = 1; + break; + + case 'A': + auth = 1; + break; + + case 'E': + encrypt = 1; + break; + + case 'S': + secure = 1; + break; + + case 'L': + linger = atoi(optarg); + break; + + case 'B': + filename = strdup(optarg); + break; + + case 'N': + num_frames = atoi(optarg); + break; + + case 'C': + count = atoi(optarg); + break; + + case 'D': + delay = atoi(optarg) * 1000; + break; + + case 'T': + timestamp = 1; + break; + + default: + usage(); + exit(1); + } + } + + if (need_addr && !(argc - optind)) { + usage(); + exit(1); + } + + if (!(buf = malloc(data_size))) { + perror("Can't allocate data buffer"); + exit(1); + } + + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = SIG_IGN; + sa.sa_flags = SA_NOCLDSTOP; + sigaction(SIGCHLD, &sa, NULL); + + openlog("rctest", LOG_PERROR | LOG_PID, LOG_LOCAL0); + + switch (mode) { + case RECV: + do_listen(recv_mode); + break; + + case CRECV: + sk = do_connect(argv[optind]); + if (sk < 0) + exit(1); + recv_mode(sk); + break; + + case DUMP: + do_listen(dump_mode); + break; + + case SEND: + sk = do_connect(argv[optind]); + if (sk < 0) + exit(1); + send_mode(sk); + break; + + case LSEND: + do_listen(send_mode); + break; + + case RECONNECT: + reconnect_mode(argv[optind]); + break; + + case MULTY: + multi_connect_mode(argc - optind, argv + optind); + break; + + case CONNECT: + sk = do_connect(argv[optind]); + if (sk < 0) + exit(1); + dump_mode(sk); + break; + } + + syslog(LOG_INFO, "Exit"); + + closelog(); + + return 0; +} diff --git a/test/scotest.c b/test/scotest.c new file mode 100644 index 00000000..4c5a310c --- /dev/null +++ b/test/scotest.c @@ -0,0 +1,434 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2002-2003 Maxim Krasnyansky <maxk@qualcomm.com> + * Copyright (C) 2002-2008 Marcel Holtmann <marcel@holtmann.org> + * + * + * 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 + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <errno.h> +#include <ctype.h> +#include <unistd.h> +#include <stdlib.h> +#include <getopt.h> +#include <syslog.h> +#include <signal.h> +#include <sys/time.h> +#include <sys/socket.h> + +#include <bluetooth/bluetooth.h> +#include <bluetooth/sco.h> + +/* Test modes */ +enum { + SEND, + RECV, + RECONNECT, + MULTY, + DUMP, + CONNECT +}; + +static unsigned char *buf; + +/* Default data size */ +static long data_size = 672; + +static bdaddr_t bdaddr; + +static float tv2fl(struct timeval tv) +{ + return (float)tv.tv_sec + (float)(tv.tv_usec/1000000.0); +} + +static int do_connect(char *svr) +{ + struct sockaddr_sco addr; + struct sco_conninfo conn; + socklen_t optlen; + int sk; + + /* Create socket */ + sk = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_SCO); + if (sk < 0) { + syslog(LOG_ERR, "Can't create socket: %s (%d)", + strerror(errno), errno); + return -1; + } + + /* Bind to local address */ + memset(&addr, 0, sizeof(addr)); + addr.sco_family = AF_BLUETOOTH; + bacpy(&addr.sco_bdaddr, &bdaddr); + + if (bind(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + syslog(LOG_ERR, "Can't bind socket: %s (%d)", + strerror(errno), errno); + goto error; + } + + /* Connect to remote device */ + memset(&addr, 0, sizeof(addr)); + addr.sco_family = AF_BLUETOOTH; + str2ba(svr, &addr.sco_bdaddr); + + if (connect(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + syslog(LOG_ERR, "Can't connect: %s (%d)", + strerror(errno), errno); + goto error; + } + + /* Get connection information */ + memset(&conn, 0, sizeof(conn)); + optlen = sizeof(conn); + + if (getsockopt(sk, SOL_SCO, SCO_CONNINFO, &conn, &optlen) < 0) { + syslog(LOG_ERR, "Can't get SCO connection information: %s (%d)", + strerror(errno), errno); + goto error; + } + + syslog(LOG_INFO, "Connected [handle %d, class 0x%02x%02x%02x]", + conn.hci_handle, + conn.dev_class[2], conn.dev_class[1], conn.dev_class[0]); + + return sk; + +error: + close(sk); + return -1; +} + +static void do_listen(void (*handler)(int sk)) +{ + struct sockaddr_sco addr; + struct sco_conninfo conn; + socklen_t optlen; + int sk, nsk; + char ba[18]; + + /* Create socket */ + sk = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_SCO); + if (sk < 0) { + syslog(LOG_ERR, "Can't create socket: %s (%d)", + strerror(errno), errno); + exit(1); + } + + /* Bind to local address */ + memset(&addr, 0, sizeof(addr)); + addr.sco_family = AF_BLUETOOTH; + bacpy(&addr.sco_bdaddr, &bdaddr); + + if (bind(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + syslog(LOG_ERR, "Can't bind socket: %s (%d)", + strerror(errno), errno); + goto error; + } + + /* Listen for connections */ + if (listen(sk, 10)) { + syslog(LOG_ERR,"Can not listen on the socket: %s (%d)", + strerror(errno), errno); + goto error; + } + + syslog(LOG_INFO,"Waiting for connection ..."); + + while (1) { + memset(&addr, 0, sizeof(addr)); + optlen = sizeof(addr); + + nsk = accept(sk, (struct sockaddr *) &addr, &optlen); + if (nsk < 0) { + syslog(LOG_ERR,"Accept failed: %s (%d)", + strerror(errno), errno); + goto error; + } + if (fork()) { + /* Parent */ + close(nsk); + continue; + } + /* Child */ + close(sk); + + /* Get connection information */ + memset(&conn, 0, sizeof(conn)); + optlen = sizeof(conn); + + if (getsockopt(nsk, SOL_SCO, SCO_CONNINFO, &conn, &optlen) < 0) { + syslog(LOG_ERR, "Can't get SCO connection information: %s (%d)", + strerror(errno), errno); + close(nsk); + goto error; + } + + ba2str(&addr.sco_bdaddr, ba); + syslog(LOG_INFO, "Connect from %s [handle %d, class 0x%02x%02x%02x]", + ba, conn.hci_handle, + conn.dev_class[2], conn.dev_class[1], conn.dev_class[0]); + + handler(nsk); + + syslog(LOG_INFO, "Disconnect"); + exit(0); + } + + return; + +error: + close(sk); + exit(1); +} + +static void dump_mode(int sk) +{ + int len; + + syslog(LOG_INFO,"Receiving ..."); + while ((len = read(sk, buf, data_size)) > 0) + syslog(LOG_INFO, "Recevied %d bytes", len); +} + +static void recv_mode(int sk) +{ + struct timeval tv_beg,tv_end,tv_diff; + long total; + uint32_t seq; + + syslog(LOG_INFO, "Receiving ..."); + + seq = 0; + while (1) { + gettimeofday(&tv_beg, NULL); + total = 0; + while (total < data_size) { + int r; + if ((r = recv(sk, buf, data_size, 0)) <= 0) { + if (r < 0) + syslog(LOG_ERR, "Read failed: %s (%d)", + strerror(errno), errno); + return; + } + total += r; + } + gettimeofday(&tv_end, NULL); + + timersub(&tv_end, &tv_beg, &tv_diff); + + syslog(LOG_INFO,"%ld bytes in %.2fm speed %.2f kb", total, + tv2fl(tv_diff) / 60.0, + (float)( total / tv2fl(tv_diff) ) / 1024.0 ); + } +} + +static void send_mode(char *svr) +{ + struct sco_options so; + socklen_t len; + uint32_t seq; + int i, sk; + + if ((sk = do_connect(svr)) < 0) { + syslog(LOG_ERR, "Can't connect to the server: %s (%d)", + strerror(errno), errno); + exit(1); + } + + len = sizeof(so); + if (getsockopt(sk, SOL_SCO, SCO_OPTIONS, &so, &len) < 0) { + syslog(LOG_ERR, "Can't get SCO options: %s (%d)", + strerror(errno), errno); + exit(1); + } + + syslog(LOG_INFO,"Sending ..."); + + for (i = 6; i < so.mtu; i++) + buf[i] = 0x7f; + + seq = 0; + while (1) { + *(uint32_t *) buf = htobl(seq); + *(uint16_t *) (buf + 4) = htobs(data_size); + seq++; + + if (send(sk, buf, so.mtu, 0) <= 0) { + syslog(LOG_ERR, "Send failed: %s (%d)", + strerror(errno), errno); + exit(1); + } + + usleep(1); + } +} + +static void reconnect_mode(char *svr) +{ + while (1) { + int sk; + + if ((sk = do_connect(svr)) < 0) { + syslog(LOG_ERR, "Can't connect to the server: %s (%d)", + strerror(errno), errno); + exit(1); + } + + close(sk); + + sleep(5); + } +} + +static void multy_connect_mode(char *svr) +{ + while (1) { + int i, sk; + + for (i = 0; i < 10; i++){ + if (fork()) + continue; + + /* Child */ + sk = do_connect(svr); + if (sk < 0) { + syslog(LOG_ERR, "Can't connect to the server: %s (%d)", + strerror(errno), errno); + } + close(sk); + exit(0); + } + + sleep(19); + } +} + +static void usage(void) +{ + printf("scotest - SCO testing\n" + "Usage:\n"); + printf("\tscotest <mode> [-b bytes] [bd_addr]\n"); + printf("Modes:\n" + "\t-d dump (server)\n" + "\t-c reconnect (client)\n" + "\t-m multiple connects (client)\n" + "\t-r receive (server)\n" + "\t-s connect and send (client)\n" + "\t-n connect and be silent (client)\n"); +} + +int main(int argc ,char *argv[]) +{ + struct sigaction sa; + int opt, sk, mode = RECV; + + while ((opt=getopt(argc,argv,"rdscmnb:")) != EOF) { + switch(opt) { + case 'r': + mode = RECV; + break; + + case 's': + mode = SEND; + break; + + case 'd': + mode = DUMP; + break; + + case 'c': + mode = RECONNECT; + break; + + case 'm': + mode = MULTY; + break; + + case 'n': + mode = CONNECT; + break; + + case 'b': + data_size = atoi(optarg); + break; + + default: + usage(); + exit(1); + } + } + + if (!(argc - optind) && (mode != RECV && mode != DUMP)) { + usage(); + exit(1); + } + + if (!(buf = malloc(data_size))) { + perror("Can't allocate data buffer"); + exit(1); + } + + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = SIG_IGN; + sa.sa_flags = SA_NOCLDSTOP; + sigaction(SIGCHLD, &sa, NULL); + + openlog("scotest", LOG_PERROR | LOG_PID, LOG_LOCAL0); + + switch( mode ){ + case RECV: + do_listen(recv_mode); + break; + + case DUMP: + do_listen(dump_mode); + break; + + case SEND: + send_mode(argv[optind]); + break; + + case RECONNECT: + reconnect_mode(argv[optind]); + break; + + case MULTY: + multy_connect_mode(argv[optind]); + break; + + case CONNECT: + sk = do_connect(argv[optind]); + if (sk < 0) + exit(1); + dump_mode(sk); + break; + } + + syslog(LOG_INFO, "Exit"); + + closelog(); + + return 0; +} diff --git a/test/sdptest.c b/test/sdptest.c new file mode 100644 index 00000000..d2213bfa --- /dev/null +++ b/test/sdptest.c @@ -0,0 +1,146 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2005-2008 Marcel Holtmann <marcel@holtmann.org> + * + * + * 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 + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <errno.h> +#include <stdlib.h> +#include <getopt.h> +#include <signal.h> +#include <sys/socket.h> + +#include <bluetooth/bluetooth.h> +#include <bluetooth/hci.h> +#include <bluetooth/hci_lib.h> +#include <bluetooth/sdp.h> +#include <bluetooth/sdp_lib.h> + +static volatile sig_atomic_t __io_finished = 0; + +static void callback(uint8_t type, uint16_t status, + uint8_t *rsp, size_t size, void *udata) +{ + int i; + + for (i = 0; i < size; i++) { + printf("%02x ", rsp[i]); + if ((i + 1) % 8 == 0) + printf(" "); + if ((i + 1) % 16 == 0) + printf("\n"); + } + printf("\n"); + + __io_finished = 1; +} + +static void cmd_search(bdaddr_t *src, bdaddr_t *dst) +{ + sdp_session_t *session; + sdp_list_t *search, *attrids; + uint32_t range = 0x0000ffff; + uuid_t uuid; + + session = sdp_connect(src, dst, 0); + if (!session) { + perror("Can't connect to SDP service"); + exit(1); + } + + sdp_set_notify(session, callback, NULL); + + sdp_uuid16_create(&uuid, PUBLIC_BROWSE_GROUP); + + search = sdp_list_append(NULL, &uuid); + + attrids = sdp_list_append(NULL, &range); + + //sdp_service_search_attr_async(session, search, + // SDP_ATTR_REQ_RANGE, attrids); + + sdp_service_search_async(session, search, 0xffff); + + sdp_list_free(attrids, NULL); + + sdp_list_free(search, NULL); + + while (!__io_finished) + sdp_process(session); + + sdp_close(session); +} + +static void usage(void) +{ + printf("sdptest - Utility for SDP testing\n\n"); + printf("Usage:\n" + "\tsdptest [-i <dev>] <bdaddr>\n"); +} + +static struct option main_options[] = { + { "device", 1, 0, 'i' }, + { "help", 0, 0, 'h' }, + { 0, 0, 0, 0 } +}; + +int main(int argc, char *argv[]) +{ + bdaddr_t src, dst; + int opt; + + bacpy(&src, BDADDR_ANY); + + while ((opt=getopt_long(argc, argv, "+i:h", main_options, NULL)) != -1) { + switch (opt) { + case 'i': + if (!strncasecmp(optarg, "hci", 3)) + hci_devba(atoi(optarg + 3), &src); + else + str2ba(optarg, &dst); + break; + + case 'h': + default: + usage(); + exit(0); + } + } + + argc -= optind; + argv += optind; + optind = 0; + + if (argc < 1) { + usage(); + exit(1); + } + + str2ba(argv[0], &dst); + + cmd_search(&src, &dst); + + return 0; +} |