Start committing Bluetooth HID (Human Interface Device) support.

Note: bthidd(8) is still not complete. Need to commit kernel
support (a-la Linux /dev/input) to feed HID events into kernel.
Also need to write bthidd(8) and bthidd.conf(5) man pages.
This commit is contained in:
Maksim Yevmenkin 2004-04-10 00:18:00 +00:00
parent 079a8a3e69
commit 6490c2ffab
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=128080
17 changed files with 3015 additions and 0 deletions

View file

@ -0,0 +1,15 @@
# $Id: Makefile,v 1.2 2004/02/13 21:44:41 max Exp $
# $FreeBSD$
.PATH: ${.CURDIR}/../bthidd
PROG= bthidcontrol
MAN= bthidcontrol.8
SRCS= bthidcontrol.c hid.c lexer.l parser.y sdp.c
WARNS?= 1
CFLAGS+= -DBTHIDCONTROL=1 -I${.CURDIR}/../bthidd
DPADD= ${LIBBLUETOOTH} ${LIBSDP} ${LIBUSBHID}
LDADD= -lbluetooth -lsdp -lusbhid
.include <bsd.prog.mk>

View file

@ -0,0 +1,99 @@
.\" Copyright (c) 2004 Maksim Yevmenkin <m_evmenkin@yahoo.com>
.\" All rights reserved.
.\"
.\" Redistribution and use in source and binary forms, with or without
.\" modification, are permitted provided that the following conditions
.\" are met:
.\" 1. Redistributions of source code must retain the above copyright
.\" notice, this list of conditions and the following disclaimer.
.\" 2. Redistributions in binary form must reproduce the above copyright
.\" notice, this list of conditions and the following disclaimer in the
.\" documentation and/or other materials provided with the distribution.
.\"
.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
.\" $Id: bthidcontrol.8,v 1.1 2004/02/13 21:44:41 max Exp $
.\" $FreeBSD$
.\"
.Dd February 13, 2004
.Dt BTHIDCONTROL 8
.Os
.Sh NAME
.Nm bthidcontrol
.Nd Bluetooth HID control utility
.Sh SYNOPSIS
.Nm
.Fl h
.Nm
.Op Fl a Ar BD_ADDR
.Op Fl c Ar file
.Op Fl H Ar file
.Ar command
.Sh DESCRIPTION
The
.Nm
utility can be used to query remote Bluetooth HID devices, dump HID descriptors
in human readable form and perform simple manipulations on the Bluetooth HID
daemon configuration files.
.Pp
The
.Nm
utility will print results to the standard output and error messages to the
standard error.
.Pp
The options are as follows:
.Bl -tag -width indent
.It Fl a Ar BD_ADDR
Specify BD_ADDR for the HID device.
Example:
.Fl a Li 00:01:02:03:04:05 .
.It Fl c Ar file
Specify path to the Bluetooth HID daemon configuration file.
The default path is
.Pa /etc/bluetooth/bthidd.conf .
.It Fl H Ar file
Specify path to the Bluetooth HID daemon known HIDs file.
The default path is
.Pa /var/db/bthidd.hids .
.It Fl h
Display usage message and exit.
.It Ar command
One of the supported commands (see below).
Special command
.Cm help
can be used to obtain the list of all supported commands.
To get more information about specific command use
.Cm help Ar command .
.El
.Sh COMMANDS
The currently supported node commands in
.Nm
are:
.Pp
.Bl -tag -offset indent -compact
.It Cm Query
.It Cm Dump
.It Cm Known
.It Cm Forget
.El
.Sh DIAGNOSTICS
.Ex -std
.Sh FILES
.Bl -tag -width ".Pa /etc/bluetooth/bthidd.conf" -compact
.It Pa /etc/bluetooth/bthidd.conf
.It Pa /var/db/bthidd.hids
.El
.Sh SEE ALSO
.Xr bthidd 8
.Sh AUTHORS
.An Maksim Yevmenkin Aq m_evmenkin@yahoo.com

View file

@ -0,0 +1,208 @@
/*
* bthidcontrol.c
*
* Copyright (c) 2004 Maksim Yevmenkin <m_evmenkin@yahoo.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $Id: bthidcontrol.c,v 1.2 2004/02/13 21:44:41 max Exp $
* $FreeBSD$
*/
#include <sys/queue.h>
#include <assert.h>
#include <bluetooth.h>
#include <err.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <usbhid.h>
#include "bthid_config.h"
#include "bthidcontrol.h"
static int do_bthid_command(bdaddr_p bdaddr, int argc, char **argv);
static struct bthid_command * find_bthid_command(char const *command, struct bthid_command *category);
static void print_bthid_command(struct bthid_command *category);
static void usage(void);
int32_t hid_sdp_query(bdaddr_t const *local, bdaddr_t const *remote, int32_t *error);
/*
* bthidcontrol
*/
int
main(int argc, char *argv[])
{
bdaddr_t bdaddr;
int opt;
hid_init(NULL);
memcpy(&bdaddr, NG_HCI_BDADDR_ANY, sizeof(bdaddr));
while ((opt = getopt(argc, argv, "a:c:H:h")) != -1) {
switch (opt) {
case 'a': /* bdaddr */
if (!bt_aton(optarg, &bdaddr)) {
struct hostent *he = NULL;
if ((he = bt_gethostbyname(optarg)) == NULL)
errx(1, "%s: %s", optarg, hstrerror(h_errno));
memcpy(&bdaddr, he->h_addr, sizeof(bdaddr));
}
break;
case 'c': /* config file */
config_file = optarg;
break;
case 'H': /* HIDs file */
hids_file = optarg;
break;
case 'h':
default:
usage();
/* NOT REACHED */
}
}
argc -= optind;
argv += optind;
if (*argv == NULL)
usage();
return (do_bthid_command(&bdaddr, argc, argv));
} /* main */
/* Execute commands */
static int
do_bthid_command(bdaddr_p bdaddr, int argc, char **argv)
{
char *cmd = argv[0];
struct bthid_command *c = NULL;
int e, help;
help = 0;
if (strcasecmp(cmd, "help") == 0) {
argc --;
argv ++;
if (argc <= 0) {
fprintf(stdout, "Supported commands:\n");
print_bthid_command(sdp_commands);
print_bthid_command(hid_commands);
fprintf(stdout, "\nFor more information use " \
"'help command'\n");
return (OK);
}
help = 1;
cmd = argv[0];
}
c = find_bthid_command(cmd, sdp_commands);
if (c == NULL)
c = find_bthid_command(cmd, hid_commands);
if (c == NULL) {
fprintf(stdout, "Unknown command: \"%s\"\n", cmd);
return (ERROR);
}
if (!help)
e = (c->handler)(bdaddr, -- argc, ++ argv);
else
e = USAGE;
switch (e) {
case OK:
case FAILED:
break;
case ERROR:
fprintf(stdout, "Could not execute command \"%s\". %s\n",
cmd, strerror(errno));
break;
case USAGE:
fprintf(stdout, "Usage: %s\n%s\n", c->command, c->description);
break;
default: assert(0); break;
}
return (e);
} /* do_bthid_command */
/* Try to find command in specified category */
static struct bthid_command *
find_bthid_command(char const *command, struct bthid_command *category)
{
struct bthid_command *c = NULL;
for (c = category; c->command != NULL; c++) {
char *c_end = strchr(c->command, ' ');
if (c_end != NULL) {
int len = c_end - c->command;
if (strncasecmp(command, c->command, len) == 0)
return (c);
} else if (strcasecmp(command, c->command) == 0)
return (c);
}
return (NULL);
} /* find_bthid_command */
/* Print commands in specified category */
static void
print_bthid_command(struct bthid_command *category)
{
struct bthid_command *c = NULL;
for (c = category; c->command != NULL; c++)
fprintf(stdout, "\t%s\n", c->command);
} /* print_bthid_command */
/* Usage */
static void
usage(void)
{
fprintf(stderr,
"Usage: bthidcontrol options command\n" \
"Where options are:\n"
" -a bdaddr specify bdaddr\n" \
" -c file specify path to the bthidd config file\n" \
" -H file specify path to the bthidd HIDs file\n" \
" -h display usage and quit\n" \
" command one of the supported commands\n");
exit(255);
} /* usage */

View file

@ -0,0 +1,50 @@
/*
* bthidcontrol.h
*
* Copyright (c) 2004 Maksim Yevmenkin <m_evmenkin@yahoo.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $Id: bthidcontrol.h,v 1.1 2004/02/12 23:25:51 max Exp $
* $FreeBSD$
*/
#ifndef __BTHIDCONTROL_H__
#define __BTHIDCONTROL_H__
#define OK 0 /* everything was OK */
#define ERROR 1 /* could not execute command */
#define FAILED 2 /* error was reported */
#define USAGE 3 /* invalid parameters */
struct bthid_command {
char const *command;
char const *description;
int (*handler)(bdaddr_t *, int, char **);
};
extern struct bthid_command hid_commands[];
extern struct bthid_command sdp_commands[];
#endif /* __BTHIDCONTROL_H__ */

View file

@ -0,0 +1,209 @@
/*
* hid.c
*
* Copyright (c) 2004 Maksim Yevmenkin <m_evmenkin@yahoo.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $Id: hid.c,v 1.3 2004/02/17 22:14:57 max Exp $
* $FreeBSD$
*/
#include <sys/queue.h>
#include <bluetooth.h>
#include <dev/usb/usb.h>
#include <dev/usb/usbhid.h>
#include <stdio.h>
#include <string.h>
#include <usbhid.h>
#include "bthid_config.h"
#include "bthidcontrol.h"
static void hid_dump_descriptor (report_desc_t r);
static void hid_dump_item (char const *label, struct hid_item *h);
static int
hid_dump(bdaddr_t *bdaddr, int argc, char **argv)
{
struct hid_device *hd = NULL;
int e = FAILED;
if (read_config_file() == 0) {
if ((hd = get_hid_device(bdaddr)) != NULL) {
hid_dump_descriptor(hd->desc);
e = OK;
}
clean_config();
}
return (e);
}
static int
hid_forget(bdaddr_t *bdaddr, int argc, char **argv)
{
struct hid_device *hd = NULL;
int e = FAILED;
if (read_config_file() == 0) {
if (read_hids_file() == 0) {
if ((hd = get_hid_device(bdaddr)) != NULL) {
hd->new_device = 1;
if (write_hids_file() == 0)
e = OK;
}
}
clean_config();
}
return (e);
}
static int
hid_known(bdaddr_t *bdaddr, int argc, char **argv)
{
struct hid_device *hd = NULL;
struct hostent *he = NULL;
int e = FAILED;
if (read_config_file() == 0) {
if (read_hids_file() == 0) {
e = OK;
for (hd = get_next_hid_device(hd);
hd != NULL;
hd = get_next_hid_device(hd)) {
if (hd->new_device)
continue;
he = bt_gethostbyaddr((char *) &hd->bdaddr,
sizeof(hd->bdaddr),
AF_BLUETOOTH);
fprintf(stdout,
"%s %s\n", bt_ntoa(&hd->bdaddr, NULL),
(he != NULL && he->h_name != NULL)?
he->h_name : "");
}
}
clean_config();
}
return (e);
}
static void
hid_dump_descriptor(report_desc_t r)
{
struct hid_data *d = NULL;
struct hid_item h;
for (d = hid_start_parse(r, ~0, -1); hid_get_item(d, &h); ) {
switch (h.kind) {
case hid_collection:
fprintf(stdout,
"Collection page=%s usage=%s\n", hid_usage_page(HID_PAGE(h.usage)),
hid_usage_in_page(h.usage));
break;
case hid_endcollection:
fprintf(stdout, "End collection\n");
break;
case hid_input:
hid_dump_item("Input ", &h);
break;
case hid_output:
hid_dump_item("Output ", &h);
break;
case hid_feature:
hid_dump_item("Feature", &h);
break;
}
}
hid_end_parse(d);
}
static void
hid_dump_item(char const *label, struct hid_item *h)
{
fprintf(stdout,
"%s id=%u size=%u count=%u page=%s usage=%s%s%s%s%s%s%s%s%s%s",
label, (uint8_t) h->report_ID, h->report_size, h->report_count,
hid_usage_page(HID_PAGE(h->usage)),
hid_usage_in_page(h->usage),
h->flags & HIO_CONST ? " Const" : "",
h->flags & HIO_VARIABLE ? " Variable" : "",
h->flags & HIO_RELATIVE ? " Relative" : "",
h->flags & HIO_WRAP ? " Wrap" : "",
h->flags & HIO_NONLINEAR ? " NonLinear" : "",
h->flags & HIO_NOPREF ? " NoPref" : "",
h->flags & HIO_NULLSTATE ? " NullState" : "",
h->flags & HIO_VOLATILE ? " Volatile" : "",
h->flags & HIO_BUFBYTES ? " BufBytes" : "");
fprintf(stdout,
", logical range %d..%d",
h->logical_minimum, h->logical_maximum);
if (h->physical_minimum != h->physical_maximum)
fprintf(stdout,
", physical range %d..%d",
h->physical_minimum, h->physical_maximum);
if (h->unit)
fprintf(stdout,
", unit=0x%02x exp=%d", h->unit, h->unit_exponent);
fprintf(stdout, "\n");
}
struct bthid_command hid_commands[] = {
{
"Dump",
"Dump HID descriptor for the specified device in human readable form. The\n" \
"device must have an entry in the Bluetooth HID daemon configuration file.\n",
hid_dump
},
{
"Known",
"List all known to the Bluetooth HID daemon devices.\n",
hid_known
},
{
"Forget",
"Forget (mark as new) specified HID device. This command is useful when it\n" \
"is required to remove device from the known HIDs file. This should be done\n" \
"when reset button was pressed on the device or the battery was changed. The\n"\
"Bluetooth HID daemon should be restarted.\n",
hid_forget
},
{ NULL, NULL, NULL }
};

View file

@ -0,0 +1,432 @@
/*
* sdp.c
*
* Copyright (c) 2004 Maksim Yevmenkin <m_evmenkin@yahoo.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $Id: sdp.c,v 1.3 2004/02/17 22:14:57 max Exp $
* $FreeBSD$
*/
#include <sys/queue.h>
#include <bluetooth.h>
#include <dev/usb/usb.h>
#include <dev/usb/usbhid.h>
#include <errno.h>
#include <sdp.h>
#include <stdio.h>
#include <string.h>
#include <usbhid.h>
#include "bthid_config.h"
#include "bthidcontrol.h"
static int32_t hid_sdp_query (bdaddr_t const *local, struct hid_device *hd, int32_t *error);
static int32_t hid_sdp_parse_protocol_descriptor_list (sdp_attr_p a);
static int32_t hid_sdp_parse_hid_descriptor (sdp_attr_p a);
static int32_t hid_sdp_parse_boolean (sdp_attr_p a);
static uint16_t service = SDP_SERVICE_CLASS_HUMAN_INTERFACE_DEVICE;
static uint32_t attrs[] = {
SDP_ATTR_RANGE( SDP_ATTR_PROTOCOL_DESCRIPTOR_LIST,
SDP_ATTR_PROTOCOL_DESCRIPTOR_LIST),
SDP_ATTR_RANGE (SDP_ATTR_ADDITIONAL_PROTOCOL_DESCRIPTOR_LISTS,
SDP_ATTR_ADDITIONAL_PROTOCOL_DESCRIPTOR_LISTS),
SDP_ATTR_RANGE( 0x0205, /* HIDReconnectInitiate */
0x0206), /* HIDDesctiptorList */
SDP_ATTR_RANGE( 0x020a, /* HIDBatteryPower */
0x020a),
SDP_ATTR_RANGE( 0x020d, /* HIDNormallyConnectable */
0x020d)
};
#define nattrs (sizeof(attrs)/sizeof(attrs[0]))
static sdp_attr_t values[8];
#define nvalues (sizeof(values)/sizeof(values[0]))
static uint8_t buffer[nvalues][512];
/*
* Query remote device
*/
#undef hid_sdp_query_exit
#define hid_sdp_query_exit(e) { \
if (error != NULL) \
*error = (e); \
if (ss != NULL) { \
sdp_close(ss); \
ss = NULL; \
} \
return (((e) == 0)? 0 : -1); \
}
static int32_t
hid_sdp_query(bdaddr_t const *local, struct hid_device *hd, int32_t *error)
{
void *ss = NULL;
uint8_t *hid_descriptor = NULL;
int32_t i, control_psm = -1, interrupt_psm = -1,
reconnect_initiate = -1,
normally_connectable = 0, battery_power = 0,
hid_descriptor_length = -1;
if (local == NULL)
local = NG_HCI_BDADDR_ANY;
if (hd == NULL)
hid_sdp_query_exit(EINVAL);
for (i = 0; i < nvalues; i ++) {
values[i].flags = SDP_ATTR_INVALID;
values[i].attr = 0;
values[i].vlen = sizeof(buffer[i]);
values[i].value = buffer[i];
}
if ((ss = sdp_open(local, &hd->bdaddr)) == NULL)
hid_sdp_query_exit(ENOMEM);
if (sdp_error(ss) != 0)
hid_sdp_query_exit(sdp_error(ss));
if (sdp_search(ss, 1, &service, nattrs, attrs, nvalues, values) != 0)
hid_sdp_query_exit(sdp_error(ss));
sdp_close(ss);
ss = NULL;
for (i = 0; i < nvalues; i ++) {
if (values[i].flags != SDP_ATTR_OK)
continue;
switch (values[i].attr) {
case SDP_ATTR_PROTOCOL_DESCRIPTOR_LIST:
control_psm = hid_sdp_parse_protocol_descriptor_list(&values[i]);
break;
case SDP_ATTR_ADDITIONAL_PROTOCOL_DESCRIPTOR_LISTS:
interrupt_psm = hid_sdp_parse_protocol_descriptor_list(&values[i]);
break;
case 0x0205: /* HIDReconnectInitiate */
reconnect_initiate = hid_sdp_parse_boolean(&values[i]);
break;
case 0x0206: /* HIDDesctiptorList */
if (hid_sdp_parse_hid_descriptor(&values[i]) == 0) {
hid_descriptor = values[i].value;
hid_descriptor_length = values[i].vlen;
}
break;
case 0x020a: /* HIDBatteryPower */
battery_power = hid_sdp_parse_boolean(&values[i]);
break;
case 0x020d: /* HIDNormallyConnectable */
normally_connectable = hid_sdp_parse_boolean(&values[i]);
break;
}
}
if (control_psm == -1 || interrupt_psm == -1 ||
reconnect_initiate == -1 || normally_connectable == -1 ||
hid_descriptor == NULL || hid_descriptor_length == -1)
hid_sdp_query_exit(ENOATTR);
hd->control_psm = control_psm;
hd->interrupt_psm = interrupt_psm;
hd->reconnect_initiate = reconnect_initiate? 1 : 0;
hd->battery_power = battery_power? 1 : 0;
hd->normally_connectable = normally_connectable? 1 : 0;
hd->desc = hid_use_report_desc(hid_descriptor, hid_descriptor_length);
if (hd->desc == NULL)
hid_sdp_query_exit(ENOMEM);
return (0);
}
/*
* seq len 2
* seq len 2
* uuid value 3
* uint16 value 3
* seq len 2
* uuid value 3
*/
static int32_t
hid_sdp_parse_protocol_descriptor_list(sdp_attr_p a)
{
uint8_t *ptr = a->value;
uint8_t *end = a->value + a->vlen;
int32_t type, len, uuid, psm;
if (end - ptr < 15)
return (-1);
if (a->attr == SDP_ATTR_ADDITIONAL_PROTOCOL_DESCRIPTOR_LISTS) {
SDP_GET8(type, ptr);
switch (type) {
case SDP_DATA_SEQ8:
SDP_GET8(len, ptr);
break;
case SDP_DATA_SEQ16:
SDP_GET16(len, ptr);
break;
case SDP_DATA_SEQ32:
SDP_GET32(len, ptr);
break;
default:
return (-1);
}
if (ptr + len > end)
return (-1);
}
SDP_GET8(type, ptr);
switch (type) {
case SDP_DATA_SEQ8:
SDP_GET8(len, ptr);
break;
case SDP_DATA_SEQ16:
SDP_GET16(len, ptr);
break;
case SDP_DATA_SEQ32:
SDP_GET32(len, ptr);
break;
default:
return (-1);
}
if (ptr + len > end)
return (-1);
/* Protocol */
SDP_GET8(type, ptr);
switch (type) {
case SDP_DATA_SEQ8:
SDP_GET8(len, ptr);
break;
case SDP_DATA_SEQ16:
SDP_GET16(len, ptr);
break;
case SDP_DATA_SEQ32:
SDP_GET32(len, ptr);
break;
default:
return (-1);
}
if (ptr + len > end)
return (-1);
/* UUID */
if (ptr + 3 > end)
return (-1);
SDP_GET8(type, ptr);
switch (type) {
case SDP_DATA_UUID16:
SDP_GET16(uuid, ptr);
if (uuid != SDP_UUID_PROTOCOL_L2CAP)
return (-1);
break;
case SDP_DATA_UUID32: /* XXX FIXME can we have 32-bit UUID */
case SDP_DATA_UUID128: /* XXX FIXME can we have 128-bit UUID */
default:
return (-1);
}
/* PSM */
if (ptr + 3 > end)
return (-1);
SDP_GET8(type, ptr);
if (type != SDP_DATA_UINT16)
return (-1);
SDP_GET16(psm, ptr);
return (psm);
}
/*
* seq len 2
* seq len 2
* uint8 value8 2
* str value 3
*/
static int32_t
hid_sdp_parse_hid_descriptor(sdp_attr_p a)
{
uint8_t *ptr = a->value;
uint8_t *end = a->value + a->vlen;
int32_t type, len, descriptor_type;
if (end - ptr < 9)
return (-1);
SDP_GET8(type, ptr);
switch (type) {
case SDP_DATA_SEQ8:
SDP_GET8(len, ptr);
break;
case SDP_DATA_SEQ16:
SDP_GET16(len, ptr);
break;
case SDP_DATA_SEQ32:
SDP_GET32(len, ptr);
break;
default:
return (-1);
}
if (ptr + len > end)
return (-1);
while (ptr < end) {
/* Descriptor */
SDP_GET8(type, ptr);
switch (type) {
case SDP_DATA_SEQ8:
if (ptr + 1 > end)
return (-1);
SDP_GET8(len, ptr);
break;
case SDP_DATA_SEQ16:
if (ptr + 2 > end)
return (-1);
SDP_GET16(len, ptr);
break;
case SDP_DATA_SEQ32:
if (ptr + 4 > end)
return (-1);
SDP_GET32(len, ptr);
break;
default:
return (-1);
}
/* Descripor type */
if (ptr + 1 > end)
return (-1);
SDP_GET8(type, ptr);
if (type != SDP_DATA_UINT8 || ptr + 1 > end)
return (-1);
SDP_GET8(descriptor_type, ptr);
/* Descriptor value */
if (ptr + 1 > end)
return (-1);
SDP_GET8(type, ptr);
switch (type) {
case SDP_DATA_STR8:
if (ptr + 1 > end)
return (-1);
SDP_GET8(len, ptr);
break;
case SDP_DATA_STR16:
if (ptr + 2 > end)
return (-1);
SDP_GET16(len, ptr);
break;
case SDP_DATA_STR32:
if (ptr + 4 > end)
return (-1);
SDP_GET32(len, ptr);
break;
default:
return (-1);
}
if (ptr + len > end)
return (-1);
if (descriptor_type == UDESC_REPORT && len > 0) {
a->value = ptr;
a->vlen = len;
return (0);
}
ptr += len;
}
return (-1);
}
/* bool8 int8 */
static int32_t
hid_sdp_parse_boolean(sdp_attr_p a)
{
if (a->vlen != 2 || a->value[0] != SDP_DATA_BOOL)
return (-1);
return (a->value[1]);
}
/* Perform SDP query */
static int32_t
hid_query(bdaddr_t *bdaddr, int argc, char **argv)
{
struct hid_device hd;
int e;
memcpy(&hd.bdaddr, bdaddr, sizeof(hd.bdaddr));
if (hid_sdp_query(NULL, &hd, &e) < 0) {
fprintf(stderr, "Could not perform SDP query on the " \
"device %s. %s (%d)\n", bt_ntoa(bdaddr, NULL),
strerror(e), e);
return (FAILED);
}
print_hid_device(&hd, stdout);
return (OK);
}
struct bthid_command sdp_commands[] =
{
{
"Query",
"Perform SDP query to the specified device and print HID configuration entry\n"\
"for the device. The configuration entry should be appended to the Bluetooth\n"\
"HID daemon configuration file and the daemon should be restarted.\n",
hid_query
},
{ NULL, NULL, NULL }
};

View file

@ -0,0 +1,14 @@
# $Id: Makefile,v 1.2 2004/02/13 21:46:20 max Exp $
# $FreeBSD$
PROG= bthidd
#MAN= bthidd.8 bthidd.conf.5
NOMAN= 1
SRCS= bthidd.c client.c hid.c lexer.l parser.y server.c session.c
WARNS?= 1
CFLAGS+= -I${.CURDIR}
DPADD= ${LIBBLUETOOTH} ${LIBSDP}
LDADD= -lbluetooth -lusbhid
.include <bsd.prog.mk>

View file

@ -0,0 +1,67 @@
/*
* bthid_config.h
*
* Copyright (c) 2004 Maksim Yevmenkin <m_evmenkin@yahoo.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $Id: bthid_config.h,v 1.3 2004/02/17 22:05:02 max Exp $
* $FreeBSD$
*/
#ifndef _BTHID_CONFIG_H_
#define _BTHID_CONFIG_H_ 1
#define BTHIDD_CONFFILE "/etc/bluetooth/bthidd.conf"
#define BTHIDD_HIDSFILE "/var/db/bthidd.hids"
struct hid_device
{
bdaddr_t bdaddr; /* HID device BDADDR */
uint16_t control_psm; /* control PSM */
uint16_t interrupt_psm; /* interrupt PSM */
unsigned new_device : 1;
unsigned reconnect_initiate : 1;
unsigned battery_power : 1;
unsigned normally_connectable : 1;
unsigned reserved : 12;
report_desc_t desc; /* HID report descriptor */
LIST_ENTRY(hid_device) next; /* link to the next */
};
typedef struct hid_device hid_device_t;
typedef struct hid_device * hid_device_p;
extern char *config_file;
extern char *hids_file;
int read_config_file (void);
void clean_config (void);
hid_device_p get_hid_device (bdaddr_p bdaddr);
hid_device_p get_next_hid_device (hid_device_p d);
void print_hid_device (hid_device_p hid_device, FILE *f);
int read_hids_file (void);
int write_hids_file (void);
#endif /* ndef _BTHID_CONFIG_H_ */

View file

@ -0,0 +1,256 @@
/*
* bthidd.c
*
* Copyright (c) 2004 Maksim Yevmenkin <m_evmenkin@yahoo.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $Id: bthidd.c,v 1.4 2004/02/26 21:48:44 max Exp $
* $FreeBSD$
*/
#include <sys/time.h>
#include <sys/queue.h>
#include <assert.h>
#include <bluetooth.h>
#include <err.h>
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>
#include <usbhid.h>
#include "bthidd.h"
#include "bthid_config.h"
static int write_pid_file (char const *file);
static int remove_pid_file (char const *file);
static int elapsed (int tval);
static void sighandler (int s);
static void usage (void);
/*
* bthidd
*/
static int done = 0; /* are we done? */
int
main(int argc, char *argv[])
{
struct bthid_server srv;
struct sigaction sa;
char const *pid_file = BTHIDD_PIDFILE;
int opt, detach, tval;
memcpy(&srv.bdaddr, NG_HCI_BDADDR_ANY, sizeof(srv.bdaddr));
detach = 1;
tval = 10; /* sec */
while ((opt = getopt(argc, argv, "a:c:dH:hp:t:")) != -1) {
switch (opt) {
case 'a': /* BDADDR */
if (!bt_aton(optarg, &srv.bdaddr)) {
struct hostent *he = NULL;
if ((he = bt_gethostbyname(optarg)) == NULL)
errx(1, "%s: %s", optarg, hstrerror(h_errno));
memcpy(&srv.bdaddr, he->h_addr, sizeof(srv.bdaddr));
}
break;
case 'c': /* config file */
config_file = optarg;
break;
case 'd': /* do not detach */
detach = 0;
break;
case 'H': /* hids file */
hids_file = optarg;
break;
case 'p': /* pid file */
pid_file = optarg;
break;
case 't': { /* rescan interval */
char *ep = NULL;
tval = strtol(optarg, &ep, 10);
if (*ep != '\0' || tval <= 0)
usage();
} break;
case 'h':
default:
usage();
/* NOT REACHED */
}
}
openlog(BTHIDD_IDENT, LOG_PID|LOG_PERROR|LOG_NDELAY, LOG_USER);
/* Become daemon if required */
if (detach && daemon(0, 0) < 0) {
syslog(LOG_CRIT, "Could not become daemon. %s (%d)",
strerror(errno), errno);
exit(1);
}
/* Install signal handler */
memset(&sa, 0, sizeof(sa));
sa.sa_handler = sighandler;
if (sigaction(SIGTERM, &sa, NULL) < 0 ||
sigaction(SIGHUP, &sa, NULL) < 0 ||
sigaction(SIGINT, &sa, NULL) < 0) {
syslog(LOG_CRIT, "Could not install signal handlers. %s (%d)",
strerror(errno), errno);
exit(1);
}
sa.sa_handler = SIG_IGN;
if (sigaction(SIGPIPE, &sa, NULL) < 0) {
syslog(LOG_CRIT, "Could not install signal handlers. %s (%d)",
strerror(errno), errno);
exit(1);
}
if (read_config_file() < 0 || read_hids_file() < 0 ||
server_init(&srv) < 0 || write_pid_file(pid_file) < 0)
exit(1);
for (done = 0; !done; ) {
if (elapsed(tval))
client_rescan(&srv);
if (server_do(&srv) < 0)
break;
}
server_shutdown(&srv);
remove_pid_file(pid_file);
clean_config();
closelog();
return (0);
}
/*
* Write pid file
*/
static int
write_pid_file(char const *file)
{
FILE *pid = NULL;
assert(file != NULL);
if ((pid = fopen(file, "w")) == NULL) {
syslog(LOG_ERR, "Could not open file %s. %s (%d)",
file, strerror(errno), errno);
return (-1);
}
fprintf(pid, "%d", getpid());
fclose(pid);
return (0);
}
/*
* Remote pid file
*/
static int
remove_pid_file(char const *file)
{
assert(file != NULL);
if (unlink(file) < 0) {
syslog(LOG_ERR, "Could not unlink file %s. %s (%d)",
file, strerror(errno), errno);
return (-1);
}
return (0);
}
/*
* Returns true if desired time interval has elapsed
*/
static int
elapsed(int tval)
{
static struct timeval last = { 0, };
struct timeval now;
gettimeofday(&now, NULL);
if (now.tv_sec - last.tv_sec >= tval) {
last = now;
return (1);
}
return (0);
}
/*
* Signal handler
*/
static void
sighandler(int s)
{
syslog(LOG_NOTICE, "Got signal %d, total number of signals %d",
s, ++ done);
}
/*
* Display usage and exit
*/
static void
usage(void)
{
fprintf(stderr,
"Usage: %s [options]\n" \
"Where options are:\n" \
" -a bdaddr specify BDADDR to listen on (default ANY)\n" \
" -c file specify config file name\n" \
" -d run in foreground\n" \
" -H file specify known HIDs file name\n" \
" -h display this message\n" \
" -p file specify PID file name\n" \
" -t tval client rescan interval (sec)\n" \
"", BTHIDD_IDENT);
exit(255);
}

View file

@ -0,0 +1,72 @@
# $FreeBSD$
device {
bdaddr 00:50:f2:e5:68:84;
control_psm 0x11;
interrupt_psm 0x13;
reconnect_initiate true;
normally_connectable false;
hid_descriptor {
0x05 0x01 0x09 0x02 0xa1 0x01 0x85 0x02
0x09 0x01 0xa1 0x00 0x05 0x09 0x19 0x01
0x29 0x05 0x15 0x00 0x25 0x01 0x75 0x01
0x95 0x05 0x81 0x02 0x75 0x03 0x95 0x01
0x81 0x01 0x05 0x01 0x09 0x30 0x09 0x31
0x09 0x38 0x15 0x81 0x25 0x7f 0x75 0x08
0x95 0x03 0x81 0x06 0xc0 0xc0 0x05 0x0c
0x09 0x01 0xa1 0x01 0x85 0x03 0x05 0x01
0x09 0x02 0xa1 0x02 0x06 0x00 0xff 0x15
0x00 0x25 0x03 0x95 0x01 0x75 0x02 0x0a
0x01 0xfe 0x81 0x02 0x75 0x06 0x81 0x01
0xc0 0xc0
};
}
device {
bdaddr 00:50:f2:e3:fb:e1;
control_psm 0x11;
interrupt_psm 0x13;
reconnect_initiate true;
normally_connectable false;
hid_descriptor {
0x05 0x01 0x09 0x06 0xa1 0x01 0x85 0x01
0x05 0x08 0x19 0x01 0x29 0x03 0x15 0x00
0x25 0x01 0x75 0x01 0x95 0x03 0x91 0x02
0x09 0x4b 0x95 0x01 0x91 0x02 0x95 0x04
0x91 0x01 0x05 0x07 0x19 0xe0 0x29 0xe7
0x95 0x08 0x81 0x02 0x75 0x08 0x95 0x01
0x81 0x01 0x19 0x00 0x29 0x91 0x26 0xff
0x00 0x95 0x06 0x81 0x00 0xc0 0x05 0x0c
0x09 0x01 0xa1 0x01 0x85 0x02 0x05 0x0c
0x15 0x00 0x25 0x01 0x75 0x01 0x95 0x1c
0x09 0xe2 0x09 0xb7 0x09 0xcd 0x09 0xea
0x09 0xe9 0x09 0xb6 0x09 0xb5 0x0a 0x83
0x01 0x0a 0x1a 0x02 0x0a 0x79 0x02 0x0a
0xab 0x01 0x0a 0x08 0x02 0x0a 0x02 0x02
0x0a 0x03 0x02 0x0a 0x07 0x02 0x0a 0x01
0x02 0x0a 0x92 0x01 0x0a 0x9c 0x01 0x09
0x95 0x0a 0x23 0x02 0x0a 0x89 0x02 0x0a
0x8b 0x02 0x0a 0x8c 0x02 0x0a 0x8a 0x01
0x0a 0x99 0x01 0x0a 0xa7 0x01 0x0a 0xb6
0x01 0x0a 0xb7 0x01 0x81 0x02 0x75 0x01
0x95 0x04 0x81 0x01 0x06 0x00 0xff 0x0a
0x02 0xff 0x26 0xff 0x00 0x95 0x01 0x75
0x08 0x81 0x02 0xc0 0x05 0x01 0x09 0x80
0xa1 0x01 0x85 0x03 0x19 0x81 0x29 0x83
0x25 0x01 0x95 0x03 0x75 0x01 0x81 0x02
0x95 0x05 0x81 0x01 0xc0 0x05 0x0c 0x09
0x01 0xa1 0x01 0x85 0x04 0x05 0x01 0x09
0x06 0xa1 0x02 0x06 0x00 0xff 0x15 0x00
0x25 0x03 0x95 0x01 0x75 0x02 0x0a 0x01
0xfe 0x81 0x02 0x75 0x06 0x81 0x01 0xc0
0xc0 0x05 0x0c 0x09 0x01 0xa1 0x01 0x85
0x05 0x05 0x01 0x09 0x06 0xa1 0x02 0x06
0x00 0xff 0x25 0x01 0x75 0x01 0x95 0x02
0x0a 0x03 0xfe 0x0a 0x04 0xfe 0x81 0x02
0x95 0x06 0x81 0x01 0xc0 0xc0 0x05 0x0c
0x09 0x01 0xa1 0x01 0x85 0xff 0x05 0x06
0x95 0x01 0x75 0x02 0x19 0x24 0x29 0x26
0x81 0x02 0x75 0x06 0x81 0x01 0xc0
};
}

View file

@ -0,0 +1,88 @@
/*
* bthidd.h
*
* Copyright (c) 2004 Maksim Yevmenkin <m_evmenkin@yahoo.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $Id: bthidd.h,v 1.4 2004/02/26 21:44:20 max Exp $
* $FreeBSD$
*/
#ifndef _BTHIDD_H_
#define _BTHIDD_H_ 1
#define BTHIDD_IDENT "bthidd"
#define BTHIDD_PIDFILE "/var/run/" BTHIDD_IDENT ".pid"
struct bthid_session;
struct bthid_server
{
bdaddr_t bdaddr; /* local bdaddr */
int cons; /* /dev/consolectl */
int ctrl; /* control channel (listen) */
int intr; /* interrupt channel (listen) */
int maxfd; /* max fd in sets */
fd_set rfdset; /* read descriptor set */
fd_set wfdset; /* write descriptor set */
LIST_HEAD(, bthid_session) sessions;
};
typedef struct bthid_server bthid_server_t;
typedef struct bthid_server * bthid_server_p;
struct bthid_session
{
bthid_server_p srv; /* pointer back to server */
int ctrl; /* control channel */
int intr; /* interrupt channel */
bdaddr_t bdaddr; /* remote bdaddr */
short state; /* session state */
#define CLOSED 0
#define W4CTRL 1
#define W4INTR 2
#define OPEN 3
LIST_ENTRY(bthid_session) next; /* link to next */
};
typedef struct bthid_session bthid_session_t;
typedef struct bthid_session * bthid_session_p;
int server_init (bthid_server_p srv);
void server_shutdown (bthid_server_p srv);
int server_do (bthid_server_p srv);
int client_rescan (bthid_server_p srv);
int client_connect (bthid_server_p srv, int fd);
bthid_session_p session_open (bthid_server_p srv, bdaddr_p bdaddr);
bthid_session_p session_by_bdaddr(bthid_server_p srv, bdaddr_p bdaddr);
bthid_session_p session_by_fd (bthid_server_p srv, int fd);
void session_close (bthid_session_p s);
int hid_control (bthid_session_p s, char *data, int len);
int hid_interrupt (bthid_session_p s, char *data, int len);
#endif /* ndef _BTHIDD_H_ */

View file

@ -0,0 +1,244 @@
/*
* client.c
*
* Copyright (c) 2004 Maksim Yevmenkin <m_evmenkin@yahoo.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $Id: client.c,v 1.6 2004/02/26 21:57:55 max Exp $
* $FreeBSD$
*/
#include <sys/queue.h>
#include <assert.h>
#include <bluetooth.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>
#include <usbhid.h>
#include "bthidd.h"
#include "bthid_config.h"
static int client_socket(bdaddr_p bdaddr, int psm);
/*
* Get next config entry and create outbound connection (if required)
*
* XXX Do only one device at a time. At least one of my devices (3COM
* Bluetooth PCCARD) rejects Create_Connection command if another
* Create_Connection command is still pending. Weird...
*/
static int connect_in_progress = 0;
int
client_rescan(bthid_server_p srv)
{
static hid_device_p d = NULL;
bthid_session_p s = NULL;
assert(srv != NULL);
if (connect_in_progress)
return (0); /* another connect is still pending */
d = get_next_hid_device(d);
if (d == NULL)
return (0); /* XXX should not happen? empty config? */
if ((s = session_by_bdaddr(srv, &d->bdaddr)) != NULL)
return (0); /* session already active */
if (!d->new_device) {
if (d->reconnect_initiate)
return (0); /* device will initiate reconnect */
}
syslog(LOG_NOTICE, "Opening outbound session for %s " \
"(new_device=%d, reconnect_initiate=%d)",
bt_ntoa(&d->bdaddr, NULL), d->new_device, d->reconnect_initiate);
if ((s = session_open(srv, &d->bdaddr)) == NULL) {
syslog(LOG_CRIT, "Could not open outbound session for %s. " \
"Not enough memory", bt_ntoa(&d->bdaddr, NULL));
return (-1);
}
/* Open control channel */
s->ctrl = client_socket(&s->bdaddr, d->control_psm);
if (s->ctrl < 0) {
syslog(LOG_ERR, "Could not open control channel to %s. %s (%d)",
bt_ntoa(&s->bdaddr, NULL), strerror(errno), errno);
session_close(s);
return (-1);
}
s->state = W4CTRL;
FD_SET(s->ctrl, &srv->wfdset);
if (s->ctrl > srv->maxfd)
srv->maxfd = s->ctrl;
connect_in_progress = 1;
return (0);
}
/*
* Process connect on the socket
*/
int
client_connect(bthid_server_p srv, int fd)
{
bthid_session_p s = NULL;
hid_device_p d = NULL;
int error, len;
assert(srv != NULL);
assert(fd >= 0);
s = session_by_fd(srv, fd);
assert(s != NULL);
d = get_hid_device(&s->bdaddr);
assert(d != NULL);
error = 0;
len = sizeof(error);
if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &len) < 0) {
syslog(LOG_ERR, "Could not get socket error for %s. %s (%d)",
bt_ntoa(&s->bdaddr, NULL), strerror(errno), errno);
session_close(s);
connect_in_progress = 0;
return (-1);
}
if (error != 0) {
syslog(LOG_ERR, "Could not connect to %s. %s (%d)",
bt_ntoa(&s->bdaddr, NULL), strerror(error), error);
session_close(s);
connect_in_progress = 0;
return (0);
}
switch (s->state) {
case W4CTRL: /* Control channel is open */
assert(s->ctrl == fd);
assert(s->intr == -1);
/* Open interrupt channel */
s->intr = client_socket(&s->bdaddr, d->interrupt_psm);
if (s->intr < 0) {
syslog(LOG_ERR, "Could not open interrupt channel " \
"to %s. %s (%d)", bt_ntoa(&s->bdaddr, NULL),
strerror(errno), errno);
session_close(s);
connect_in_progress = 0;
return (-1);
}
s->state = W4INTR;
FD_SET(s->intr, &srv->wfdset);
if (s->intr > srv->maxfd)
srv->maxfd = s->intr;
d->new_device = 0; /* reset new device flag */
write_hids_file();
break;
case W4INTR: /* Interrupt channel is open */
assert(s->ctrl != -1);
assert(s->intr == fd);
s->state = OPEN;
connect_in_progress = 0;
break;
default:
assert(0);
break;
}
/* Move fd to from the write fd set into read fd set */
FD_CLR(fd, &srv->wfdset);
FD_SET(fd, &srv->rfdset);
return (0);
}
/*
* Create bound non-blocking socket and initiate connect
*/
static int
client_socket(bdaddr_p bdaddr, int psm)
{
struct sockaddr_l2cap l2addr;
int s, m;
s = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BLUETOOTH_PROTO_L2CAP);
if (s < 0)
return (-1);
m = fcntl(s, F_GETFL);
if (m < 0) {
close(s);
return (-1);
}
if (fcntl(s, F_SETFL, (m|O_NONBLOCK)) < 0) {
close(s);
return (-1);
}
l2addr.l2cap_len = sizeof(l2addr);
l2addr.l2cap_family = AF_BLUETOOTH;
memcpy(&l2addr.l2cap_bdaddr, NG_HCI_BDADDR_ANY, sizeof(l2addr.l2cap_bdaddr));
l2addr.l2cap_psm = 0;
if (bind(s, (struct sockaddr *) &l2addr, sizeof(l2addr)) < 0) {
close(s);
return (-1);
}
memcpy(&l2addr.l2cap_bdaddr, bdaddr, sizeof(l2addr.l2cap_bdaddr));
l2addr.l2cap_psm = psm;
if (connect(s, (struct sockaddr *) &l2addr, sizeof(l2addr)) < 0 &&
errno != EINPROGRESS) {
close(s);
return (-1);
}
return (s);
}

View file

@ -0,0 +1,262 @@
/*
* hid.c
*
* Copyright (c) 2004 Maksim Yevmenkin <m_evmenkin@yahoo.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $Id: hid.c,v 1.3 2004/02/26 21:47:35 max Exp $
* $FreeBSD$
*/
#include <sys/consio.h>
#include <sys/mouse.h>
#include <sys/queue.h>
#include <assert.h>
#include <bluetooth.h>
#include <errno.h>
#include <dev/usb/usb.h>
#include <dev/usb/usbhid.h>
#include <stdio.h>
#include <string.h>
#include <syslog.h>
#include <usbhid.h>
#include "bthidd.h"
#include "bthid_config.h"
#undef min
#define min(x, y) (((x) < (y))? (x) : (y))
/*
* Process data from control channel
*/
int
hid_control(bthid_session_p s, char *data, int len)
{
assert(s != NULL);
assert(data != NULL);
assert(len > 0);
switch (data[0] >> 4) {
case 0: /* Handshake (response to command) */
if (data[0] & 0xf)
syslog(LOG_ERR, "Got handshake message with error " \
"response 0x%x from %s",
data[0], bt_ntoa(&s->bdaddr, NULL));
break;
case 1: /* HID Control */
switch (data[0] & 0xf) {
case 0: /* NOP */
break;
case 1: /* Hard reset */
case 2: /* Soft reset */
syslog(LOG_WARNING, "Device %s requested %s reset",
bt_ntoa(&s->bdaddr, NULL),
((data[0] & 0xf) == 1)? "hard" : "soft");
break;
case 3: /* Suspend */
syslog(LOG_NOTICE, "Device %s requested Suspend",
bt_ntoa(&s->bdaddr, NULL));
break;
case 4: /* Exit suspend */
syslog(LOG_NOTICE, "Device %s requested Exit Suspend",
bt_ntoa(&s->bdaddr, NULL));
break;
case 5: /* Virtual cable unplug */
syslog(LOG_NOTICE, "Device %s unplugged virtual cable",
bt_ntoa(&s->bdaddr, NULL));
session_close(s);
break;
default:
syslog(LOG_WARNING, "Device %s sent unknown " \
"HID_Control message 0x%x",
bt_ntoa(&s->bdaddr, NULL), data[0]);
break;
}
break;
default:
syslog(LOG_WARNING, "Got unexpected message 0x%x on Control " \
"channel from %s", data[0], bt_ntoa(&s->bdaddr, NULL));
break;
}
return (0);
}
/*
* Process data from the interrupt channel
*/
int
hid_interrupt(bthid_session_p s, char *data, int len)
{
hid_device_p hid_device = NULL;
hid_data_t d;
hid_item_t h;
int report_id, usage, page, val,
mouse_x, mouse_y, mouse_z, mouse_butt,
nkeys, keys[32]; /* XXX how big keys[] should be? */
assert(s != NULL);
assert(data != NULL);
if (len < 3) {
syslog(LOG_ERR, "Got short message (%d bytes) on Interrupt " \
"channel from %s", len, bt_ntoa(&s->bdaddr, NULL));
return (-1);
}
if ((unsigned char) data[0] != 0xa1) {
syslog(LOG_ERR, "Got unexpected message 0x%x on " \
"Interrupt channel from %s",
data[0], bt_ntoa(&s->bdaddr, NULL));
return (-1);
}
report_id = data[1];
data += 2;
len -= 2;
hid_device = get_hid_device(&s->bdaddr);
assert(hid_device != NULL);
mouse_x = mouse_y = mouse_z = mouse_butt = nkeys = 0;
for (d = hid_start_parse(hid_device->desc, 1 << hid_input, -1);
hid_get_item(d, &h) > 0; ) {
if ((h.flags & HIO_CONST) || (h.report_ID != report_id))
continue;
page = HID_PAGE(h.usage);
usage = HID_USAGE(h.usage);
val = hid_get_data(data, &h);
switch (page) {
case HUP_GENERIC_DESKTOP:
switch (usage) {
case HUG_X:
mouse_x = val;
break;
case HUG_Y:
mouse_y = val;
break;
case HUG_WHEEL:
mouse_z = -val;
break;
case HUG_SYSTEM_SLEEP:
if (val)
syslog(LOG_NOTICE, "Sleep button pressed");
break;
}
break;
case HUP_KEYBOARD:
if (h.flags & HIO_VARIABLE) {
if (val && nkeys < sizeof(keys))
keys[nkeys ++] = usage;
} else {
if (val && nkeys < sizeof(keys))
keys[nkeys ++] = val;
data ++;
len --;
len = min(len, h.report_size);
while (len > 0) {
val = hid_get_data(data, &h);
if (val && nkeys < sizeof(keys))
keys[nkeys ++] = val;
data ++;
len --;
}
}
break;
case HUP_BUTTON:
mouse_butt |= (val << (usage - 1));
break;
case HUP_MICROSOFT:
switch (usage) {
case 0xfe01:
if (!hid_device->battery_power)
break;
switch (val) {
case 1:
syslog(LOG_INFO, "Battery is OK on %s",
bt_ntoa(&s->bdaddr, NULL));
break;
case 2:
syslog(LOG_NOTICE, "Low battery on %s",
bt_ntoa(&s->bdaddr, NULL));
break;
case 3:
syslog(LOG_WARNING, "Very low battery "\
"on %s",
bt_ntoa(&s->bdaddr, NULL));
break;
}
break;
}
break;
}
}
hid_end_parse(d);
/*
* XXX FIXME Feed mouse and keyboard events into kernel
* The code block below works, but it is not
* good enough
*/
if (mouse_x != 0 || mouse_y != 0 || mouse_z != 0 || mouse_butt != 0) {
struct mouse_info mi;
mi.operation = MOUSE_ACTION;
mi.u.data.x = mouse_x;
mi.u.data.y = mouse_y;
mi.u.data.z = mouse_z;
mi.u.data.buttons = mouse_butt;
if (ioctl(s->srv->cons, CONS_MOUSECTL, &mi) < 0)
syslog(LOG_ERR, "Could not process mouse events from " \
"%s. %s (%d)", bt_ntoa(&s->bdaddr, NULL),
strerror(errno), errno);
}
return (0);
}

View file

@ -0,0 +1,100 @@
%{
/*
* lexer.l
*
* Copyright (c) 2004 Maksim Yevmenkin <m_evmenkin@yahoo.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $Id: lexer.l,v 1.2 2004/02/13 21:46:20 max Exp $
* $FreeBSD$
*/
#include <bluetooth.h>
#include <stdlib.h>
#include "parser.h"
%}
%option yylineno noyywrap nounput
delim [ \t\n]
ws {delim}+
empty {delim}*
comment \#.*
hexdigit [0-9a-fA-F]
hexbyte {hexdigit}{hexdigit}?
device_word device
bdaddr_word bdaddr
control_psm_word control_psm
interrupt_psm_word interrupt_psm
reconnect_initiate_word reconnect_initiate
battery_power_word battery_power
normally_connectable_word normally_connectable
hid_descriptor_word hid_descriptor
true_word true
false_word false
bdaddrstring {hexbyte}:{hexbyte}:{hexbyte}:{hexbyte}:{hexbyte}:{hexbyte}
hexbytestring 0x{hexbyte}
%%
\; return (';');
\: return (':');
\{ return ('{');
\} return ('}');
{ws} ;
{empty} ;
{comment} ;
{device_word} return (T_DEVICE);
{bdaddr_word} return (T_BDADDR);
{control_psm_word} return (T_CONTROL_PSM);
{interrupt_psm_word} return (T_INTERRUPT_PSM);
{reconnect_initiate_word} return (T_RECONNECT_INITIATE);
{battery_power_word} return (T_BATTERY_POWER);
{normally_connectable_word} return (T_NORMALLY_CONNECTABLE);
{hid_descriptor_word} return (T_HID_DESCRIPTOR);
{true_word} return (T_TRUE);
{false_word} return (T_FALSE);
{bdaddrstring} {
return (bt_aton(yytext, &yylval.bdaddr)?
T_BDADDRSTRING : T_ERROR);
}
{hexbytestring} {
char *ep = NULL;
yylval.num = strtoul(yytext, &ep, 16);
return (*ep == '\0'? T_HEXBYTE : T_ERROR);
}
. return (T_ERROR);
%%

View file

@ -0,0 +1,447 @@
%{
/*
* parser.y
*
* Copyright (c) 2004 Maksim Yevmenkin <m_evmenkin@yahoo.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $Id: parser.y,v 1.3 2004/02/13 21:46:21 max Exp $
* $FreeBSD$
*/
#include <sys/queue.h>
#include <bluetooth.h>
#include <errno.h>
#include <libusbhid.h>
#include <limits.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#ifndef BTHIDCONTROL
#include <stdarg.h>
#include <syslog.h>
#define SYSLOG syslog
#define LOGCRIT LOG_CRIT
#define LOGERR LOG_ERR
#define LOGWARNING LOG_WARNING
#define EOL
#else
#define SYSLOG fprintf
#define LOGCRIT stderr
#define LOGERR stderr
#define LOGWARNING stderr
#define EOL "\n"
#endif /* ndef BTHIDCONTROL */
#include "bthid_config.h"
int yyparse (void);
int yylex (void);
static int check_hid_device(hid_device_p hid_device);
static void free_hid_device (hid_device_p hid_device);
extern int yylineno;
char *config_file = BTHIDD_CONFFILE;
char *hids_file = BTHIDD_HIDSFILE;
static char buffer[1024];
static int hid_descriptor_size;
static hid_device_t *hid_device = NULL;
static LIST_HEAD(, hid_device) hid_devices;
%}
%union {
bdaddr_t bdaddr;
int num;
}
%token <bdaddr> T_BDADDRSTRING
%token <num> T_HEXBYTE
%token T_DEVICE T_BDADDR T_CONTROL_PSM T_INTERRUPT_PSM T_RECONNECT_INITIATE
%token T_BATTERY_POWER T_NORMALLY_CONNECTABLE T_HID_DESCRIPTOR
%token T_TRUE T_FALSE T_ERROR
%%
config: line
| config line
;
line: T_DEVICE
{
hid_device = (hid_device_t *) calloc(1, sizeof(*hid_device));
if (hid_device == NULL) {
SYSLOG(LOGCRIT, "Could not allocate new " \
"config entry" EOL);
YYABORT;
}
hid_device->new_device = 1;
}
'{' options '}'
{
if (check_hid_device(hid_device))
LIST_INSERT_HEAD(&hid_devices,hid_device,next);
else
free_hid_device(hid_device);
hid_device = NULL;
}
;
options: option ';'
| options option ';'
;
option: bdaddr
| control_psm
| interrupt_psm
| reconnect_initiate
| battery_power
| normally_connectable
| hid_descriptor
| parser_error
;
bdaddr: T_BDADDR T_BDADDRSTRING
{
memcpy(&hid_device->bdaddr, &$2, sizeof(hid_device->bdaddr));
}
;
control_psm: T_CONTROL_PSM T_HEXBYTE
{
hid_device->control_psm = $2;
}
;
interrupt_psm: T_INTERRUPT_PSM T_HEXBYTE
{
hid_device->interrupt_psm = $2;
}
;
reconnect_initiate: T_RECONNECT_INITIATE T_TRUE
{
hid_device->reconnect_initiate = 1;
}
| T_RECONNECT_INITIATE T_FALSE
{
hid_device->reconnect_initiate = 0;
}
;
battery_power: T_BATTERY_POWER T_TRUE
{
hid_device->battery_power = 1;
}
| T_BATTERY_POWER T_FALSE
{
hid_device->battery_power = 0;
}
;
normally_connectable: T_NORMALLY_CONNECTABLE T_TRUE
{
hid_device->normally_connectable = 1;
}
| T_NORMALLY_CONNECTABLE T_FALSE
{
hid_device->normally_connectable = 0;
}
;
hid_descriptor: T_HID_DESCRIPTOR
{
hid_descriptor_size = 0;
}
'{' hid_descriptor_bytes '}'
{
if (hid_device->desc != NULL)
hid_dispose_report_desc(hid_device->desc);
hid_device->desc = hid_use_report_desc(buffer, hid_descriptor_size);
if (hid_device->desc == NULL) {
SYSLOG(LOGCRIT, "Could not use HID descriptor" EOL);
YYABORT;
}
}
;
hid_descriptor_bytes: hid_descriptor_byte
| hid_descriptor_bytes hid_descriptor_byte
;
hid_descriptor_byte: T_HEXBYTE
{
if (hid_descriptor_size >= sizeof(buffer)) {
SYSLOG(LOGCRIT, "HID descriptor is too big" EOL);
YYABORT;
}
buffer[hid_descriptor_size ++] = $1;
}
;
parser_error: T_ERROR
{
YYABORT;
}
%%
/* Display parser error message */
void
yyerror(char const *message)
{
SYSLOG(LOGERR, "%s in line %d" EOL, message, yylineno);
}
/* Re-read config file */
int
read_config_file(void)
{
extern FILE *yyin;
int e;
if (config_file == NULL) {
SYSLOG(LOGERR, "Unknown config file name!" EOL);
return (-1);
}
if ((yyin = fopen(config_file, "r")) == NULL) {
SYSLOG(LOGERR, "Could not open config file '%s'. %s (%d)" EOL,
config_file, strerror(errno), errno);
return (-1);
}
clean_config();
if (yyparse() < 0) {
SYSLOG(LOGERR, "Could not parse config file '%s'" EOL,
config_file);
e = -1;
} else
e = 0;
fclose(yyin);
yyin = NULL;
return (e);
}
/* Clean config */
void
clean_config(void)
{
while (!LIST_EMPTY(&hid_devices)) {
hid_device_p hid_device = LIST_FIRST(&hid_devices);
LIST_REMOVE(hid_device, next);
free_hid_device(hid_device);
}
}
/* Lookup config entry */
hid_device_p
get_hid_device(bdaddr_p bdaddr)
{
hid_device_p hid_device;
LIST_FOREACH(hid_device, &hid_devices, next)
if (memcmp(&hid_device->bdaddr, bdaddr, sizeof(bdaddr_t)) == 0)
break;
return (hid_device);
}
/* Get next config entry */
hid_device_p
get_next_hid_device(hid_device_p d)
{
return ((d == NULL)? LIST_FIRST(&hid_devices) : LIST_NEXT(d, next));
}
/* Print config entry */
void
print_hid_device(hid_device_p hid_device, FILE *f)
{
/* XXX FIXME hack! */
struct report_desc {
unsigned int size;
unsigned char data[1];
};
/* XXX FIXME hack! */
struct report_desc *desc = (struct report_desc *) hid_device->desc;
int i;
fprintf(f,
"device {\n" \
" bdaddr %s;\n" \
" control_psm 0x%x;\n" \
" interrupt_psm 0x%d;\n" \
" reconnect_initiate %s;\n" \
" battery_power %s;\n" \
" normally_connectable %s;\n" \
" hid_descriptor {",
bt_ntoa(&hid_device->bdaddr, NULL),
hid_device->control_psm, hid_device->interrupt_psm,
hid_device->reconnect_initiate? "true" : "false",
hid_device->battery_power? "true" : "false",
hid_device->normally_connectable? "true" : "false");
for (i = 0; i < desc->size; i ++) {
if ((i % 8) == 0)
fprintf(stdout, "\n ");
fprintf(f, "0x%2.2x ", desc->data[i]);
}
fprintf(stdout,
"\n" \
" };\n" \
"}\n");
}
/* Check config entry */
static int
check_hid_device(hid_device_p hid_device)
{
if (get_hid_device(&hid_device->bdaddr) != NULL) {
SYSLOG(LOGERR, "Ignoring duplicated entry for bdaddr %s" EOL,
bt_ntoa(&hid_device->bdaddr, NULL));
return (0);
}
if (hid_device->control_psm == 0) {
SYSLOG(LOGERR, "Ignoring entry with invalid control PSM" EOL);
return (0);
}
if (hid_device->interrupt_psm == 0) {
SYSLOG(LOGERR, "Ignoring entry with invalid interrupt PSM" EOL);
return (0);
}
if (hid_device->desc == NULL) {
SYSLOG(LOGERR, "Ignoring entry without HID descriptor" EOL);
return (0);
}
return (1);
}
/* Free config entry */
static void
free_hid_device(hid_device_p hid_device)
{
if (hid_device->desc != NULL)
hid_dispose_report_desc(hid_device->desc);
memset(hid_device, 0, sizeof(*hid_device));
free(hid_device);
}
/* Re-read hids file */
int
read_hids_file(void)
{
FILE *f = NULL;
hid_device_t *hid_device = NULL;
char *line = NULL;
bdaddr_t bdaddr;
int lineno;
if (hids_file == NULL) {
SYSLOG(LOGERR, "Unknown HIDs file name!" EOL);
return (-1);
}
if ((f = fopen(hids_file, "r")) == NULL) {
if (errno == ENOENT)
return (0);
SYSLOG(LOGERR, "Could not open HIDs file '%s'. %s (%d)" EOL,
hids_file, strerror(errno), errno);
return (-1);
}
for (lineno = 1; fgets(buffer, sizeof(buffer), f) != NULL; lineno ++) {
if ((line = strtok(buffer, "\r\n\t ")) == NULL)
continue; /* ignore empty lines */
if (!bt_aton(line, &bdaddr)) {
SYSLOG(LOGWARNING, "Ignoring unparseable BD_ADDR in " \
"%s:%d" EOL, hids_file, lineno);
continue;
}
if ((hid_device = get_hid_device(&bdaddr)) != NULL)
hid_device->new_device = 0;
}
fclose(f);
return (0);
}
/* Write hids file */
int
write_hids_file(void)
{
char path[PATH_MAX];
FILE *f = NULL;
hid_device_t *hid_device = NULL;
if (hids_file == NULL) {
SYSLOG(LOGERR, "Unknown HIDs file name!" EOL);
return (-1);
}
snprintf(path, sizeof(path), "%s.new", hids_file);
if ((f = fopen(path, "w")) == NULL) {
SYSLOG(LOGERR, "Could not open HIDs file '%s'. %s (%d)" EOL,
path, strerror(errno), errno);
return (-1);
}
LIST_FOREACH(hid_device, &hid_devices, next)
if (!hid_device->new_device)
fprintf(f, "%s\n", bt_ntoa(&hid_device->bdaddr, NULL));
fclose(f);
if (rename(path, hids_file) < 0) {
SYSLOG(LOGERR, "Could not rename new HIDs file '%s' to '%s'. " \
"%s (%d)" EOL, path, hids_file, strerror(errno), errno);
unlink(path);
return (-1);
}
return (0);
}

View file

@ -0,0 +1,316 @@
/*
* server.c
*
* Copyright (c) 2004 Maksim Yevmenkin <m_evmenkin@yahoo.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $Id: server.c,v 1.5 2004/02/26 21:43:36 max Exp $
* $FreeBSD$
*/
#include <sys/queue.h>
#include <assert.h>
#include <bluetooth.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>
#include <usbhid.h>
#include "bthidd.h"
#include "bthid_config.h"
#undef max
#define max(x, y) (((x) > (y))? (x) : (y))
static int server_accept (bthid_server_p srv, int fd);
static int server_process(bthid_server_p srv, int fd);
/*
* Initialize server
*/
int
server_init(bthid_server_p srv)
{
struct sockaddr_l2cap l2addr;
assert(srv != NULL);
srv->ctrl = srv->intr = -1;
FD_ZERO(&srv->rfdset);
FD_ZERO(&srv->wfdset);
LIST_INIT(&srv->sessions);
/* Open /dev/consolectl */
srv->cons = open("/dev/consolectl", O_RDWR);
if (srv->cons < 0) {
syslog(LOG_ERR, "Could not open /dev/consolectl. %s (%d)",
strerror(errno), errno);
return (-1);
}
/* Create control socket */
srv->ctrl = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BLUETOOTH_PROTO_L2CAP);
if (srv->ctrl < 0) {
syslog(LOG_ERR, "Could not create control L2CAP socket. " \
"%s (%d)", strerror(errno), errno);
close(srv->cons);
return (-1);
}
l2addr.l2cap_len = sizeof(l2addr);
l2addr.l2cap_family = AF_BLUETOOTH;
memcpy(&l2addr.l2cap_bdaddr, &srv->bdaddr, sizeof(l2addr.l2cap_bdaddr));
l2addr.l2cap_psm = 0x11;
if (bind(srv->ctrl, (struct sockaddr *) &l2addr, sizeof(l2addr)) < 0) {
syslog(LOG_ERR, "Could not bind control L2CAP socket. " \
"%s (%d)", strerror(errno), errno);
close(srv->cons);
return (-1);
}
if (listen(srv->ctrl, 10) < 0) {
syslog(LOG_ERR, "Could not listen on control L2CAP socket. " \
"%s (%d)", strerror(errno), errno);
close(srv->cons);
return (-1);
}
/* Create intrrupt socket */
srv->intr = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BLUETOOTH_PROTO_L2CAP);
if (srv->intr < 0) {
syslog(LOG_ERR, "Could not create interrupt L2CAP socket. " \
"%s (%d)", strerror(errno), errno);
close(srv->ctrl);
close(srv->cons);
return (-1);
}
l2addr.l2cap_psm = 0x13;
if (bind(srv->intr, (struct sockaddr *) &l2addr, sizeof(l2addr)) < 0) {
syslog(LOG_ERR, "Could not bind interrupt L2CAP socket. " \
"%s (%d)", strerror(errno), errno);
close(srv->ctrl);
close(srv->cons);
return (-1);
}
if (listen(srv->intr, 10) < 0) {
syslog(LOG_ERR, "Could not listen on interrupt L2CAP socket. "\
"%s (%d)", strerror(errno), errno);
close(srv->ctrl);
close(srv->cons);
return (-1);
}
FD_SET(srv->ctrl, &srv->rfdset);
FD_SET(srv->intr, &srv->rfdset);
srv->maxfd = max(srv->ctrl, srv->intr);
return (0);
}
/*
* Shutdown server
*/
void
server_shutdown(bthid_server_p srv)
{
assert(srv != NULL);
close(srv->cons);
close(srv->ctrl);
close(srv->intr);
while (!LIST_EMPTY(&srv->sessions))
session_close(LIST_FIRST(&srv->sessions));
memset(srv, 0, sizeof(*srv));
}
/*
* Do one server iteration
*/
int
server_do(bthid_server_p srv)
{
struct timeval tv;
fd_set rfdset, wfdset;
int n, fd;
assert(srv != NULL);
tv.tv_sec = 1;
tv.tv_usec = 0;
/* Copy cached version of the fd sets and call select */
memcpy(&rfdset, &srv->rfdset, sizeof(rfdset));
memcpy(&wfdset, &srv->wfdset, sizeof(wfdset));
n = select(srv->maxfd + 1, &rfdset, &wfdset, NULL, &tv);
if (n < 0) {
if (errno == EINTR)
return (0);
syslog(LOG_ERR, "Could not select(%d, %p, %p). %s (%d)",
srv->maxfd + 1, &rfdset, &wfdset, strerror(errno), errno);
return (-1);
}
/* Process descriptors (if any) */
for (fd = 0; fd < srv->maxfd + 1 && n > 0; fd ++) {
if (FD_ISSET(fd, &rfdset)) {
n --;
if (fd == srv->ctrl || fd == srv->intr)
server_accept(srv, fd);
else
server_process(srv, fd);
} else if (FD_ISSET(fd, &wfdset)) {
n --;
client_connect(srv, fd);
}
}
return (0);
}
/*
* Accept new connection
*/
static int
server_accept(bthid_server_p srv, int fd)
{
bthid_session_p s = NULL;
hid_device_p d = NULL;
struct sockaddr_l2cap l2addr;
int len, new_fd;
len = sizeof(l2addr);
if ((new_fd = accept(fd, (struct sockaddr *) &l2addr, &len)) < 0) {
syslog(LOG_ERR, "Could not accept %s connection. %s (%d)",
(fd == srv->ctrl)? "control" : "interrupt",
strerror(errno), errno);
return (-1);
}
/* Check if we have session for the device */
if ((s = session_by_bdaddr(srv, &l2addr.l2cap_bdaddr)) == NULL) {
/* Is device configured? */
if ((d = get_hid_device(&l2addr.l2cap_bdaddr)) == NULL) {
syslog(LOG_ERR, "Rejecting %s connection from %s. " \
"Device not configured",
(fd == srv->ctrl)? "control" : "interrupt",
bt_ntoa(&l2addr.l2cap_bdaddr, NULL));
close(new_fd);
return (-1);
}
d->new_device = 0; /* reset new device flag */
write_hids_file();
/* Create new inbound session */
if ((s = session_open(srv, &l2addr.l2cap_bdaddr)) == NULL) {
syslog(LOG_CRIT, "Could not open inbound session " \
"for %s. Not enough memory",
bt_ntoa(&l2addr.l2cap_bdaddr, NULL));
close(new_fd);
return (-1);
}
}
/* Update descriptors */
if (fd == srv->ctrl) {
assert(s->ctrl == -1);
s->ctrl = new_fd;
s->state = (s->intr == -1)? W4INTR : OPEN;
} else {
assert(s->intr == -1);
s->intr = new_fd;
s->state = (s->ctrl == -1)? W4CTRL : OPEN;
}
FD_SET(new_fd, &srv->rfdset);
if (new_fd > srv->maxfd)
srv->maxfd = new_fd;
syslog(LOG_NOTICE, "Accepted %s connection from %s",
(fd == srv->ctrl)? "control" : "interrupt",
bt_ntoa(&l2addr.l2cap_bdaddr, NULL));
return (0);
}
/*
* Process data on the connection
*/
static int
server_process(bthid_server_p srv, int fd)
{
bthid_session_p s = session_by_fd(srv, fd);
char data[1024];
int len;
assert(s != NULL);
do {
len = read(fd, data, sizeof(data));
} while (len < 0 && errno == EINTR);
if (len < 0) {
syslog(LOG_ERR, "Could not read data from %s (%s). %s (%d)",
bt_ntoa(&s->bdaddr, NULL),
(fd == s->ctrl)? "control" : "interrupt",
strerror(errno), errno);
session_close(s);
return (0);
}
if (len == 0) {
syslog(LOG_NOTICE, "Remote device %s has closed %s connection",
bt_ntoa(&s->bdaddr, NULL),
(fd == s->ctrl)? "control" : "interrupt");
session_close(s);
return (0);
}
if (fd == s->ctrl)
hid_control(s, data, len);
else
hid_interrupt(s, data, len);
return (0);
}

View file

@ -0,0 +1,136 @@
/*
* session.c
*
* Copyright (c) 2004 Maksim Yevmenkin <m_evmenkin@yahoo.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $Id: session.c,v 1.1 2004/02/12 22:46:59 max Exp $
* $FreeBSD$
*/
#include <sys/queue.h>
#include <assert.h>
#include <bluetooth.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "bthidd.h"
/*
* Create new session
*/
bthid_session_p
session_open(bthid_server_p srv, bdaddr_p bdaddr)
{
bthid_session_p s = NULL;
assert(srv != NULL);
assert(bdaddr != NULL);
if ((s = (bthid_session_p) malloc(sizeof(*s))) != NULL) {
s->srv = srv;
memcpy(&s->bdaddr, bdaddr, sizeof(s->bdaddr));
s->ctrl = s->intr = -1;
s->state = CLOSED;
LIST_INSERT_HEAD(&srv->sessions, s, next);
}
return (s);
}
/*
* Lookup session by bdaddr
*/
bthid_session_p
session_by_bdaddr(bthid_server_p srv, bdaddr_p bdaddr)
{
bthid_session_p s = NULL;
assert(srv != NULL);
assert(bdaddr != NULL);
LIST_FOREACH(s, &srv->sessions, next)
if (memcmp(&s->bdaddr, bdaddr, sizeof(s->bdaddr)) == 0)
break;
return (s);
}
/*
* Lookup session by fd
*/
bthid_session_p
session_by_fd(bthid_server_p srv, int fd)
{
bthid_session_p s = NULL;
assert(srv != NULL);
assert(fd >= 0);
LIST_FOREACH(s, &srv->sessions, next)
if (s->ctrl == fd || s->intr == fd)
break;
return (s);
}
/*
* Close session
*/
void
session_close(bthid_session_p s)
{
assert(s != NULL);
assert(s->srv != NULL);
LIST_REMOVE(s, next);
if (s->intr != -1) {
FD_CLR(s->intr, &s->srv->rfdset);
FD_CLR(s->intr, &s->srv->wfdset);
close(s->intr);
if (s->srv->maxfd == s->intr)
s->srv->maxfd --;
}
if (s->ctrl != -1) {
FD_CLR(s->ctrl, &s->srv->rfdset);
FD_CLR(s->ctrl, &s->srv->wfdset);
close(s->ctrl);
if (s->srv->maxfd == s->ctrl)
s->srv->maxfd --;
}
memset(s, 0, sizeof(*s));
free(s);
}