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; +}  | 
