From emax at freebsd.org Mon Apr 6 13:58:45 2009 From: emax at freebsd.org (Maksim Yevmenkin) Date: Mon Apr 6 13:58:51 2009 Subject: libhci update In-Reply-To: <49D92E26.2030508@incunabulum.net> References: <49D92E26.2030508@incunabulum.net> Message-ID: Hi Bruce, > So I decided to just go ahead and reimplement Markus Brueffer's libhci. > I've attached a tarball of the Hg repository. thanks! > It would be great to have feedback from you about this. right, a few things. - dev_id is kinda gross, imo. Iain and i discussed this and agreed that devname is the way to go. mapping between dev_id and devname can be (has to be) done and i have no objection to this, however, all the native api probably should use devname and not dev_id; - all the hci_xxx functions (with possible exception of inquiry) should probably be left out of the library. there is really not enough consumers for them; - i not sure i like, hci_xxx names; Iain and i have been discussing this before and agreed that, bt_XXX and bt_devXXXX are better names; i'm attaching the diffs that was sitting in my queue (still need to update man pages). please take a look at it and let me know what you think. > The API is now compatible with the current BlueZ SVN code. One of the > trade-offs involved was choosing an identifier which would fit in an 'int' > type, as BlueZ identifies its devices using uint16_t. > > As a compromise, I wrote code which emulates BlueZ device enumeration > using the Netgraph node ID (32 bits wide). Unfortunately this introduces > a dependency on -lnetgraph, unless the Bluetooth stack is itself taught to > produce such an identifier (and supply it via an ioctl()). i actually went this route before :) but i opted for much simpler scheme: #define hci_devid2type(id) (((id) >> 12) & 0x0f) #define hci_devid2unit(id) ((id) & 0x0fff) #define hci_mkdevid(type, unit) ((((type) & 0x0f) << 12) | (unit & 0x0fff)) struct hci_type2prefix { int type; char const *prefix; }; static struct hci_type2prefix const type2prefix[] = { { .type = HCI_VIRTUAL, .prefix = NULL, }, { .type = HCI_USB, .prefix = "ubt", }, { .type = HCI_PCCARD, .prefix = "btccc", }, { .type = HCI_UART, .prefix = "h4", }, { .type = HCI_RS232, .prefix = NULL, }, { .type = HCI_PCI, .prefix = NULL, }, { .type = HCI_SDIO, .prefix = NULL, }, { .type = -1, .prefix = NULL, }, /* should be last */ }; static int hci_name2devid(char const *name) { struct hci_type2prefix const *t; int plen, unit; char *ep; for (t = &type2prefix[0]; t->type != -1; t ++) { if (t->prefix == NULL) continue; plen = strlen(t->prefix); if (strncmp(name, t->prefix, plen) != 0) continue; unit = strtoul(name + plen, &ep, 10); if (*ep != '\0' && strcmp(ep, "hci") != 0 && strcmp(ep, "l2cap") != 0) { errno = ENODEV; return (-1); } return (hci_mkdevid(t->type, unit)); } errno = ENODEV; return (-1); } static char * hci_devid2name(int dev_id, char *name, int namelen) { struct hci_type2prefix const *t; int type, unit; if (dev_id >= 0) { type = hci_devid2type(dev_id); unit = hci_devid2unit(dev_id); for (t = &type2prefix[0]; t->type != -1; t ++) { if (t->type == type && t->prefix != NULL) { snprintf(name, namelen, "%s%uhci", t->prefix, unit); return (name); } } } errno = EINVAL; return (NULL); } this code is left out for now. not sure if i like it :) > I noticed during the port that periodic inquiry doesn't actually buy us > anything > at all. With a CSR BlueCore4-ROM dongle, the microcontroller will not > raise any other events *at all* at the HCI layer during inquiry. > > Mind you, I haven't tried any of the Bluetooth HCI 2.0/EDR commands, > many of these are not yet implemented in the FreeBSD stack, am I right? well, most of 2.0 commands do not need any implementation. just need to add defines and typedefs. some events might need handling, i.e. inquiry with rssi. but generally it should just work. > I also had a crack at porting NetBSD's 'btconfig'. I haven't included it in > this drop, however, it does depend on the libhci functions I have written. how about just fixing hcicontrol(8) do to that you want? is this too much work? > I see that NetBSD's libsdp does support 128 bit UUIDs. Whilst this is > highly desirable, it is also a prerequisite for any of the high level > language > wrappers (e.g. PyBlueZ, BlueCove) which make use of SDP. ok, i will take a look at the diffs to see what is different. > Anyway, let me know what you think. I am eager to get the SDP issues > knocked on the head ASAP. right, that would likely be complete replacement of libsdp and sdpd. thanks, max -------------- next part -------------- Index: hci.c =================================================================== --- hci.c (revision 190594) +++ hci.c (working copy) @@ -30,15 +30,421 @@ * $FreeBSD$ */ +#include #include #include #include #include #include +static int bt_devany_cb(int s, struct bt_devinfo const *di, void *xdevname); static char * bt_dev2node (char const *devname, char *nodename, int nnlen); int +bt_devopen(char const *devname) +{ + struct sockaddr_hci ha; + bdaddr_t ba; + int s; + + if (devname == NULL) { + errno = EINVAL; + return (-1); + } + + memset(&ha, 0, sizeof(ha)); + ha.hci_len = sizeof(ha); + ha.hci_family = AF_BLUETOOTH; + + if (bt_aton(devname, &ba)) { + if (!bt_devname(ha.hci_node, &ba)) + return (-1); + } else if (bt_dev2node(devname, ha.hci_node, + sizeof(ha.hci_node)) == NULL) { + errno = ENXIO; + return (-1); + } + + s = socket(PF_BLUETOOTH, SOCK_RAW, BLUETOOTH_PROTO_HCI); + if (s < 0) + return (-1); + + if (bind(s, (struct sockaddr *) &ha, sizeof(ha)) < 0 || + connect(s, (struct sockaddr *) &ha, sizeof(ha)) < 0) { + close(s); + return (-1); + } + + return (s); +} + +int +bt_devclose(int s) +{ + return (close(s)); +} + +int +bt_devsend(int s, uint16_t ogf, uint16_t ocf, int plen, void *param) +{ + ng_hci_cmd_pkt_t h; + struct iovec iv[2]; + int ivn; + + if (plen < 0 || (plen > 0 && param == NULL)) { + errno = EINVAL; + return (-1); + } + + iv[0].iov_base = &h; + iv[0].iov_len = sizeof(h); + ivn = 1; + + h.type = NG_HCI_CMD_PKT; + h.opcode = htole16(NG_HCI_OPCODE(ogf, ocf)); + if (plen > 0) { + h.length = plen; + + iv[1].iov_base = param; + iv[1].iov_len = plen; + ivn = 2; + } else + h.length = 0; + + while (writev(s, iv, ivn) < 0) { + if (errno == EAGAIN || errno == EINTR) + continue; + + return (-1); + } + + return (0); +} + +int +bt_devrecv(int s, uint8_t *buf, int size, time_t to) +{ + fd_set rfd; + struct timeval tv; + int n; + + if (buf == NULL || size <= 0 || to < 0) { + errno = EINVAL; + return (-1); + } + + FD_ZERO(&rfd); + FD_SET(s, &rfd); + + tv.tv_sec = to; + tv.tv_usec = 0; + + while ((n = select(s + 1, &rfd, NULL, NULL, &tv)) < 0) { + if (errno == EAGAIN || errno == EINTR) + continue; + + return (-1); + } + + if (n == 0) { + errno = ETIMEDOUT; + return (-1); + } + + assert(FD_ISSET(s, &rfd)); + + while ((n = read(s, buf, size)) < 0) { + if (errno == EAGAIN || errno == EINTR) + continue; + + return (-1); + } + + return (n); +} + +int +bt_devreq(int s, struct bt_devreq *r, time_t to) +{ + uint8_t buf[320]; /* more than enough */ + ng_hci_event_pkt_t *e = (ng_hci_event_pkt_t *) buf; + ng_hci_command_compl_ep *cc = (ng_hci_command_compl_ep *)(e+1); + ng_hci_command_status_ep *cs = (ng_hci_command_status_ep*)(e+1); + uint16_t opcode; + time_t t_end; + int n; + + if (s < 0 || r == NULL || to < 0) { + errno = EINVAL; + return (-1); + } + + if (r->rlen < 0 || (r->rlen > 0 && r->rparam == NULL)) { + errno = EINVAL; + return (-1); + } + + n = bt_devsend(s, r->ogf, r->ocf, r->clen, r->cparam); + if (n < 0) + return (-1); + + opcode = htole16(NG_HCI_OPCODE(r->ogf, r->ocf)); + + t_end = time(NULL) + to; + + do { + to = t_end - time(NULL); + if (to < 0) + to = 0; + + n = bt_devrecv(s, buf, sizeof(buf), to); + if (n < 0) + return (-1); + + if (n < sizeof(*e)) { + errno = EMSGSIZE; + return (-1); + } + + if (e->type != NG_HCI_EVENT_PKT) { + errno = EIO; + return (-1); + } + + n -= sizeof(*e); + + switch (e->event) { + case NG_HCI_EVENT_COMMAND_COMPL: + if (cc->opcode == opcode) { + n -= sizeof(*cc); + + if (r->rlen >= n) { + r->rlen = n; + memcpy(r->rparam, cc + 1, r->rlen); + } + + return (0); + } + break; + + case NG_HCI_EVENT_COMMAND_STATUS: + if (cs->opcode == opcode) { + if (r->event != NG_HCI_EVENT_COMMAND_STATUS) { + if (cs->status != 0) { + errno = EIO; + return (-1); + } + } else { + if (r->rlen >= n) { + r->rlen = n; + memcpy(r->rparam, cs, r->rlen); + } + + return (0); + } + } + break; + + default: + if (e->event == r->event) { + if (r->rlen >= n) { + r->rlen = n; + memcpy(r->rparam, e + 1, r->rlen); + } + + return (0); + } + break; + } + } while (to > 0); + + errno = ETIMEDOUT; + + return (-1); +} + +int +bt_devfilter(int s, struct bt_devfilter const *new, struct bt_devfilter *old) +{ + struct ng_btsocket_hci_raw_filter f; + socklen_t len; + int bit; + + if (new == NULL && old == NULL) { + errno = EINVAL; + return (-1); + } + + if (old != NULL) { + len = sizeof(f); + if (getsockopt(s, SOL_HCI_RAW, SO_HCI_RAW_FILTER, &f, &len) < 0) + return (-1); + + memset(old, 0, sizeof(*old)); + + for (bit = 0; bit < NG_HCI_EVENT_PKT; bit ++) + if (bit_test(f.packet_mask, bit)) + old->packet_mask |= (1 << bit); + + for (bit = 0; bit < NG_HCI_EVENT_MASK_SIZE * 8; bit ++) + if (bit_test(f.event_mask, bit)) + old->event_mask |= (1 << bit); + } + + if (new != NULL) { + memset(&f, 0, sizeof(f)); + + for (bit = 0; bit < NG_HCI_EVENT_PKT; bit ++) + if (new->packet_mask & (1 << bit)) + bit_set(f.packet_mask, bit); + + for (bit = 0; bit < (NG_HCI_EVENT_MASK_SIZE * 8); bit ++) + if (new->event_mask & (1 << bit)) + bit_set(f.event_mask, bit); + + len = sizeof(f); + if (setsockopt(s, SOL_HCI_RAW, SO_HCI_RAW_FILTER, &f, len) < 0) + return (-1); + } + + return (0); +} + +int +bt_devinquiry(char const *devname, int length, int num_rsp, + uint8_t const *lap, struct bt_devinquiry **ii) +{ + uint8_t buf[320]; + char _devname[HCI_DEVNAME_SIZE]; + struct bt_devfilter f; + ng_hci_inquiry_cp *cp = (ng_hci_inquiry_cp *) buf; + ng_hci_event_pkt_t *e = (ng_hci_event_pkt_t *) buf; + ng_hci_inquiry_result_ep *ep = (ng_hci_inquiry_result_ep *)(e+1); + ng_hci_inquiry_response *ir; + struct bt_devinquiry *i; + int s, n; + time_t to; + + if (ii == NULL) { + errno = EINVAL; + return (-1); + } + + if (devname == NULL) { + memset(_devname, 0, sizeof(_devname)); + devname = _devname; + + n = bt_devenum(bt_devany_cb, _devname); + if (n <= 0) { + if (n == 0) + *ii = NULL; + + return (n); + } + } + + s = bt_devopen(devname); + if (s < 0) + return (-1); + + if (bt_devfilter(s, NULL, &f) < 0) { + bt_devclose(s); + return (-1); + } + + f.event_mask |= (1 << (NG_HCI_EVENT_INQUIRY_COMPL - 1)); + f.event_mask |= (1 << (NG_HCI_EVENT_INQUIRY_RESULT - 1)); + + if (bt_devfilter(s, &f, NULL) < 0) { + bt_devclose(s); + return (-1); + } + + if (lap == NULL) { + cp->lap[0] = 0x33; + cp->lap[1] = 0x8b; + cp->lap[2] = 0x9e; + } else { + cp->lap[0] = lap[0]; + cp->lap[1] = lap[1]; + cp->lap[2] = lap[2]; + } + + if (length <= 0 || length > 255) + length = 4; /* 5.12 seconds */ + cp->inquiry_length = (uint8_t) length; + + to = (time_t)((double) length * 1.28) + 1; + + if (num_rsp <= 0 || num_rsp > 255) + num_rsp = 8; + cp->num_responses = (uint8_t) num_rsp; + + i = *ii = calloc(num_rsp, sizeof(struct bt_devinquiry)); + if (i == NULL) { + bt_devclose(s); + errno = ENOMEM; + return (-1); + } + + if (bt_devsend(s, NG_HCI_OGF_LINK_CONTROL, NG_HCI_OCF_INQUIRY, + sizeof(*cp), cp) < 0) { + free(i); + bt_devclose(s); + return (-1); + } + +wait_for_more: + + n = bt_devrecv(s, buf, sizeof(buf), to); + if (n < 0) { + free(i); + bt_devclose(s); + return (-1); + } + + if (n < sizeof(ng_hci_event_pkt_t)) { + free(i); + bt_devclose(s); + errno = EIO; + return (-1); + } + + switch (e->event) { + case NG_HCI_EVENT_INQUIRY_COMPL: + break; + + case NG_HCI_EVENT_INQUIRY_RESULT: + ir = (ng_hci_inquiry_response *)(ep + 1); + +#undef MIN +#define MIN(a, b) (((a) < (b))? (a) : (b)) + + for (n = 0; n < MIN(ep->num_responses, num_rsp); n ++) { + bdaddr_copy(&i->bdaddr, &ir->bdaddr); + i->pscan_rep_mode = ir->page_scan_rep_mode; + i->pscan_period_mode = ir->page_scan_period_mode; + i->pscan_mode = ir->page_scan_mode; + memcpy(i->dev_class, ir->uclass, sizeof(i->dev_class)); + i->clock_offset = le16toh(ir->clock_offset); + + ir ++; + i ++; + num_rsp --; + } + /* FALLTHROUGH */ + + default: + goto wait_for_more; + /* NOT REACHED */ + } + + bt_devclose(s); + + return (i - *ii); +} + +int bt_devinfo(struct bt_devinfo *di) { union { @@ -53,6 +459,7 @@ struct ng_btsocket_hci_raw_node_debug r8; } rp; struct sockaddr_hci ha; + socklen_t halen; int s, rval; if (di == NULL) { @@ -60,27 +467,14 @@ return (-1); } - memset(&ha, 0, sizeof(ha)); - ha.hci_len = sizeof(ha); - ha.hci_family = AF_BLUETOOTH; - - if (bt_aton(di->devname, &rp.r1.bdaddr)) { - if (!bt_devname(ha.hci_node, &rp.r1.bdaddr)) - return (-1); - } else if (bt_dev2node(di->devname, ha.hci_node, - sizeof(ha.hci_node)) == NULL) { - errno = ENXIO; - return (-1); - } - - s = socket(PF_BLUETOOTH, SOCK_RAW, BLUETOOTH_PROTO_HCI); + s = bt_devopen(di->devname); if (s < 0) return (-1); rval = -1; - if (bind(s, (struct sockaddr *) &ha, sizeof(ha)) < 0 || - connect(s, (struct sockaddr *) &ha, sizeof(ha)) < 0) + halen = sizeof(ha); + if (getsockname(s, (struct sockaddr *) &ha, &halen) < 0) goto bad; strlcpy(di->devname, ha.hci_node, sizeof(di->devname)); @@ -138,7 +532,7 @@ rval = 0; bad: - close(s); + bt_devclose(s); return (rval); } @@ -205,6 +599,13 @@ return (count); } +static int +bt_devany_cb(int s, struct bt_devinfo const *di, void *xdevname) +{ + strlcpy((char *) xdevname, di->devname, HCI_DEVNAME_SIZE); + return (1); +} + static char * bt_dev2node(char const *devname, char *nodename, int nnlen) { Index: bluetooth.h =================================================================== --- bluetooth.h (revision 190594) +++ bluetooth.h (working copy) @@ -39,6 +39,7 @@ #include #include #include +#include #include #include #include @@ -46,6 +47,7 @@ #include #include #include +#include __BEGIN_DECLS @@ -129,11 +131,46 @@ uint8_t _padding[20]; /* leave space for future additions */ }; +struct bt_devreq +{ + uint16_t ogf; + uint16_t ocf; + int event; + void *cparam; + int clen; + void *rparam; + int rlen; +}; + +struct bt_devfilter { + uint64_t event_mask; + uint8_t packet_mask; +}; + +struct bt_devinquiry { + bdaddr_t bdaddr; + uint8_t pscan_rep_mode; + uint8_t pscan_period_mode; + uint8_t pscan_mode; + uint8_t dev_class[3]; + uint16_t clock_offset; +}; + typedef int (bt_devenum_cb_t)(int, struct bt_devinfo const *, void *); +int bt_devopen (char const *devname); +int bt_devclose(int s); +int bt_devsend (int s, uint16_t ogf, uint16_t ocf, int plen, void *param); +int bt_devrecv (int s, uint8_t *buf, int size, time_t to); +int bt_devreq (int s, struct bt_devreq *r, time_t to); +int bt_devfilter(int s, struct bt_devfilter const *new, + struct bt_devfilter *old); +int bt_devinquiry(char const *devname, int length, int num_rsp, + uint8_t const *lap, struct bt_devinquiry **ii); int bt_devinfo (struct bt_devinfo *di); int bt_devenum (bt_devenum_cb_t *cb, void *arg); + /* * bdaddr utility functions (from NetBSD) */ From bms at incunabulum.net Wed Apr 8 17:44:01 2009 From: bms at incunabulum.net (Bruce Simpson) Date: Wed Apr 8 17:44:08 2009 Subject: libhci update In-Reply-To: References: <49D92E26.2030508@incunabulum.net> Message-ID: <49DD40E2.5030403@incunabulum.net> Maksim Yevmenkin wrote: > > right, a few things. > > - dev_id is kinda gross, imo. Iain and i discussed this and agreed > that devname is the way to go. mapping between dev_id and devname can > be (has to be) done and i have no objection to this, however, all the > native api probably should use devname and not dev_id; > I agree with you here, but that doesn't change the fact that we are potentially being Betamax'd by BlueZ, even if not intentionally ;-) FYI if you are not familiar with the story of Betamax: it was the technological superior of the VHS video cassette standard, bot of course VHS got the dominant market share, and therefore won out in the end. > - all the hci_xxx functions (with possible exception of inquiry) > should probably be left out of the library. there is really not enough > consumers for them; > I disagree. I did a fairly in-depth code audit of PyBlueZ, LightBlue, and BlueCove. All of them use BlueZ hci_* APIs for a number of things, mostly to do with querying properties of the local interfaces. This might not be the case for Bluetooth daemons in the base system, and much of what is in BlueZ, is used by its daemons. Given that I've read a lot of BlueZ code now, I can't entirely claim that my implementation of the API is truly clean-room. > - i not sure i like, hci_xxx names; Iain and i have been discussing > this before and agreed that, bt_XXX and bt_devXXXX are better names; > Well, the thing is, folk have already gone off and started building high-level language APIs on top of the BlueZ C language APIs. I understand from the 'code purity' point of view why one would be keen to adopt this kind of compartmentalization in terms of naming conventions. The problem is that the horse has already left the cart. There have been books published on how to use Bluetooth from Java and other higher level languages than C. It seems unreasonable, in my view, to expect folk developing applications in a commercial model, to have to adapt their code for BSD targets beyond say 2 or 3 ifdef's. I realize this might be an inelegant approach, but it's based on observation of harsh commercial realities. > i'm attaching the diffs that was sitting in my queue (still need to > update man pages). please take a look at it and let me know what you > think. > Looks very similar in places to some of the code Markus wrote which I worked with. We certainly need an easy to use API which doesn't concern high level language users with too many of the specifics of low level I/O event handling. >> The API is now compatible with the current BlueZ SVN code. One of the >> trade-offs involved was choosing an identifier which would fit in an 'int' >> type, as BlueZ identifies its devices using uint16_t. >> >> As a compromise, I wrote code which emulates BlueZ device enumeration >> using the Netgraph node ID (32 bits wide). Unfortunately this introduces >> a dependency on -lnetgraph, unless the Bluetooth stack is itself taught to >> produce such an identifier (and supply it via an ioctl()). >> > > i actually went this route before :) but i opted for much simpler scheme: > > Thanks for this. I would far rather not introduce a runtime or link-time dependency on -lnetgraph if I can possibly avoid it. I'll digest further and try to see if this can be incorporated. > ... >> raise any other events *at all* at the HCI layer during inquiry. >> >> Mind you, I haven't tried any of the Bluetooth HCI 2.0/EDR commands, >> many of these are not yet implemented in the FreeBSD stack, am I right? >> > > well, most of 2.0 commands do not need any implementation. just need > to add defines and typedefs. some events might need handling, i.e. > inquiry with rssi. but generally it should just work. > I agree with you up to the point where 'it should just work'. A lot of what goes on with Bluetooth is actually going on behind closed doors. There are individuals (whom I won't name here to defend them from any possible litigation) who have found it necessary to disassemble what's actually going on with the 16-bit microcontrollers found in most Bluetooth devices. There have also been moves on the part of the Bluetooth SIG to make it harder to find information about Bluetooth's actual operation itself, as I am also sure you are aware. Nevertheless, that's largely orthogonal to the issues of implementing Bluetooth 2.0/EDR compatibility. What is clear is that the BlueZ API has moved with the times, and ours hasn't. My hope is that by actually using Bluetooth for interesting applications, that will help to drive interest forward. One observation I make from the current state of the BlueZ SVN code is that their primary focus is on desktop integration. If they were really serious, this work would already have been completed. As things stand, BlueZ as a platform is missing a few things which would make it easier to build Bluetooth server applications. ... Back to periodic inquiry: I am very surprised that given Bluetooth's value as a highly localized network, that the implementation of periodic inquiry, a feature pretty much essential to building endpoint discovery in a highly dynamic network environment, would tie up the whole microcontroller for the duration of the inquiry. I looked at the Bluetooth specs and I can see that the inquiry sequence doesn't hog all of the radio spectrum in use, but the implementation on the CSR dongles won't raise any other events whilst the inquiry is in progress. It's a bit of a cop out, to be sure, and we can certainly work around this by using a dedicated dongle to perform neighbor discovery. But why didn't they think about this in the spec? That begs the question. > >> I also had a crack at porting NetBSD's 'btconfig'. I haven't included it in >> this drop, however, it does depend on the libhci functions I have written. >> > > how about just fixing hcicontrol(8) do to that you want? is this too much work? > Whilst hccontrol(8) is an excellent and very complete tool, it is perhaps too complete for its own good. It captures a fairly complete set of the HCI commands normally used to manipulate Bluetooth host controller devices. However, it isn't terribly user friendly. My motivation in porting NetBSD's btconfig was more to do with the fact that it captures common use cases much like Linux BlueZ's hcitool -- i.e. folk familiar with UNIX in general become familiar with ifconfig(8), and btconfig has very similar semantics. Whilst in the FreeBSD case this isn't terribly important -- the devd.conf integration is thorough and works at boot-time just fine for building appliances -- it's not great for operational use i.e. debugging or manual config where it's needed. I would agree with the view that ifconfig(8) has become something of a kitchen sink, and whilst I wouldn't initially attempt to integrate Bluetooth functionality with that tool, I wouldn't rule it out, either. > >> I see that NetBSD's libsdp does support 128 bit UUIDs. Whilst this is >> highly desirable, it is also a prerequisite for any of the high level >> language >> wrappers (e.g. PyBlueZ, BlueCove) which make use of SDP. >> > > ok, i will take a look at the diffs to see what is different. > Yeah, talk about two arrows going in opposite directions :-) I found when hacking with BlueCove on Windows, I had to tell it *explicity* to use 16-bit UUIDs so that most phones could see the services it was advertising. The SDP story is a mess, to be sure, and could do with better APIs everywhere. What developers tend to do, in my experience, is to avoid dealing with platform specifics -- *unless they absolutely have to* -- and focus on delivering the solution with APIs that they can readily grasp. Sadly, the SDP APIs on all platforms are sorely missing this casual ease-of-use. >> Anyway, let me know what you think. I am eager to get the SDP issues >> knocked on the head ASAP. >> > > right, that would likely be complete replacement of libsdp and sdpd. > If that's what it amounts to, I'll get my hands dirty. It's just essential for what my partner and I are trying to do, that we have high-level language bindings for Bluetooth features essential to building a server application, working on FreeBSD. I am willing to help out in any way I can to drive this forward. thanks and regards, BMS From chuckop at gmail.com Wed Apr 8 23:27:17 2009 From: chuckop at gmail.com (Charles Oppermann) Date: Wed Apr 8 23:28:32 2009 Subject: libhci update In-Reply-To: <49DD40E2.5030403@incunabulum.net> References: <49D92E26.2030508@incunabulum.net> <49DD40E2.5030403@incunabulum.net> Message-ID: <200904082307.01454.chuckop@gmail.com> On Wednesday 08 April 2009 5:27:14 pm Bruce Simpson wrote: > I agree with you here, but that doesn't change the fact that we are > potentially being Betamax'd by BlueZ, even if not intentionally ;-) > > FYI if you are not familiar with the story of Betamax: it was the > technological superior of the VHS video cassette standard, bot of > course VHS got the dominant market share, and therefore won > out in the end. Common misconception. The Betamax was only marginally of better quality, and any differences were not discernable to consumers. People bought VHS-format VCR's because the first publically available recorders could record up to go 2 hours, vs. Sony's 1 hour maximum for Betamax. When the major use of VCR's was to record movies off of cable for repeated viewings, the recording time was a major factor. Every time Sony came up with longer recording times, JVC and RCA would stay ahead. Sony tried very hard to position Betamax as the higher quality alternative, but Sony's 2-hour long tapes made the same compromises as VHS did. SuperBeta tapes were higher quality than VHS, but the format war was over by the time they were available along with recorders. From plunky at rya-online.net Thu Apr 9 01:01:44 2009 From: plunky at rya-online.net (Iain Hibbert) Date: Thu Apr 9 01:01:52 2009 Subject: libhci update In-Reply-To: <49DD40E2.5030403@incunabulum.net> References: <49D92E26.2030508@incunabulum.net> <49DD40E2.5030403@incunabulum.net> Message-ID: <1239264003.862926.638.nullmailer@galant.ukfsn.org> On Thu, 9 Apr 2009, Bruce Simpson wrote: > I disagree. I did a fairly in-depth code audit of PyBlueZ, LightBlue, and > BlueCove. All of them use BlueZ hci_* APIs for a number of things, mostly > to do with querying properties of the local interfaces. I looked briefly at BlueCove the other day and it seems to use a module to interface with the BlueZ/Linux API but it also has Windows and Mac modules amongst others. If it needs a FreeBSD or NetBSD module then that doesn't seem so difficult? I looked at LightBlue but I don't remember anything about it, was it perchance really incomplete and very BlueZ/Linux based? > The problem is that the horse has already left the cart. That happened many years ago when Microsoft was the leader in the marketplace. If you want to stay with the horse, use Windows. > There have been books published on how to use Bluetooth from Java and > other higher level languages than C. It seems unreasonable, in my view, > to expect folk developing applications in a commercial model, to have to > adapt their code for BSD targets beyond say 2 or 3 ifdef's. so write a module that interfaces (for example) the Java (BlueCove?) API to the FreeBSD OS layer. Its not that different from the BlueZ/Linux API and you can probably just do some copy and paste work. Thats how the GPL world works. Yes, you will not be able to integrate that work directly into FreeBSD but then I doubt a Java interface is ever going to be accepted into base anyway. Donate it to BlueCove. > I realize this might be an inelegant approach, but it's based on > observation of harsh commercial realities. If its commercial, get those companies to contribute some BSD code. > We certainly need an easy to use API which doesn't concern high level > language users with too many of the specifics of low level I/O event > handling. as you say, the Java API is one such. > Thanks for this. I would far rather not introduce a runtime or link-time > dependency on -lnetgraph if I can possibly avoid it. I'll digest further > and try to see if this can be incorporated. But I thought, on FreeBSD the whole bluetooth stack is netgraph based..? My stance on the compatibility issue is that there are some things in the BlueZ/Linux C API (the major thing being 'devid' to address the radio) that are tied to the actual OS support and are unsupportable unless you provide exactly the same API in the OS. But, OS support is way too low level for an application to deal (with as you say), and a higher level API is needed that does not contain such specifics. The BlueZ guys are, I think, working on a dbus API that will be used by GNOME and KDE and hopefully it won't be tied to the Linux OS API so closely, so that we can write dbus modules and have applications just work on our OS. I have not been providing any input or review of that API though, it would be good if somebody would step up and point out where the API is tied too closely to the Linux OS interface and get them to make it a bit more generic. > I looked at the Bluetooth specs and I can see that the inquiry sequence > doesn't hog all of the radio spectrum in use, but the implementation on > the CSR dongles won't raise any other events whilst the inquiry is in > progress. Is this purely a CSR problem? My laptop has a Broadcom chip in and I notice that it can make multiple connections concurrently in that on bootup, it connects to both my mouse and keyboard by itself sometimes - the CSR dongle I used previously would connect to the keyboard fine but always fail the second connect with "Command Disallowed". So much so that I thought perhaps about serialising connection attempts in the kernel. I've never looked at periodic inquiry though.. > > right, that would likely be complete replacement of libsdp and sdpd. > > If that's what it amounts to, I'll get my hands dirty. It's just > essential for what my partner and I are trying to do, that we have > high-level language bindings for Bluetooth features essential to > building a server application, working on FreeBSD. I have written at least a set of SDP primitives that I'm intending to import to NetBSD 'soon' (I have only one computer and am concentrating on 5.0 release first because running different OS versions is messy) I think the latest archive was at http://www.netbsd.org/~plunky/sdp-20090227.tar.gz iain From maksim.yevmenkin at gmail.com Thu Apr 9 10:13:25 2009 From: maksim.yevmenkin at gmail.com (Maksim Yevmenkin) Date: Thu Apr 9 10:13:32 2009 Subject: libhci update In-Reply-To: <1239264003.862926.638.nullmailer@galant.ukfsn.org> References: <49D92E26.2030508@incunabulum.net> <49DD40E2.5030403@incunabulum.net> <1239264003.862926.638.nullmailer@galant.ukfsn.org> Message-ID: On Thu, Apr 9, 2009 at 12:00 AM, Iain Hibbert wrote: > On Thu, 9 Apr 2009, Bruce Simpson wrote: > >> I disagree. I did a fairly in-depth code audit of PyBlueZ, LightBlue, and >> BlueCove. All of them use BlueZ hci_* APIs for a number of things, mostly >> to do with querying properties of the local interfaces. > > I looked briefly at BlueCove the other day and it seems to use a module to > interface with the BlueZ/Linux API but it also has Windows and Mac modules > amongst others. If it needs a FreeBSD or NetBSD module then that doesn't > seem so difficult? right, that is something i kinda wondering too. of course, i have no idea how hard it would be to plug new bluetooth module into bluecove. perhaps cost of adding the new bluetooth module more than implementing something that looks like bluez? >> The problem is that the horse has already left the cart. > > That happened many years ago when Microsoft was the leader in the > marketplace. If you want to stay with the horse, use Windows. the real question is: do we really want to follow the horse? :) personally, i think that we should keep an eye on bluez etc. to see where its going, but blindly follow it, is not something we want to do, imo, having said that, we have to recognize the fact that there is lots of code that is bluez specific. so, how about we separate flies from meatballs, and, meet halfway: 1) we put bsd-style bluetooth api and make sure it is shared with as many bsd's (and possibly other os's) as possible. i personally would like to continue to work with Iain and get his input. this api is going into the base system and will be bsd-licensed. obviously, we will keep an eye on bluez while designing and implementing bsd bluetooth api. 2) to ensure compatibility with bluez we create a separate library and put it into the ports/ collection. it can even use original libhci headers and re-use original libhci code if needed. missing/different parts will have to be re-implemented in terms of bsd bluetooth api. this way it would probably be easier to play catch up game with bluez and it will be less of pain for folks who use bsd bluetooth api. basically, if you choose bluez api be prepared to change your code every time bluez folks change something. >> There have been books published on how to use Bluetooth from Java and >> other higher level languages than C. It seems unreasonable, in my view, >> to expect folk developing applications in a commercial model, to have to >> adapt their code for BSD targets beyond say 2 or 3 ifdef's. > > so write a module that interfaces (for example) the Java (BlueCove?) API > to the FreeBSD OS layer. Its not that different from the BlueZ/Linux API > and you can probably just do some copy and paste work. Thats how the GPL > world works. Yes, you will not be able to integrate that work directly > into FreeBSD but then I doubt a Java interface is ever going to be > accepted into base anyway. Donate it to BlueCove. well, it depends on how hard it is to add such module. i can certainly see and understand yours and Bruce's point. >> I realize this might be an inelegant approach, but it's based on >> observation of harsh commercial realities. > > If its commercial, get those companies to contribute some BSD code. oh, well, sometimes it is not as easy as it sounds. >> Thanks for this. I would far rather not introduce a runtime or link-time >> dependency on -lnetgraph if I can possibly avoid it. I'll digest further >> and try to see if this can be incorporated. > > But I thought, on FreeBSD the whole bluetooth stack is netgraph based..? yes, but in userspace you almost never need to use anything netgraph related. almost everything can be done through sockets. > My stance on the compatibility issue is that there are some things in the > BlueZ/Linux C API (the major thing being 'devid' to address the radio) > that are tied to the actual OS support and are unsupportable unless you > provide exactly the same API in the OS. But, OS support is way too low > level for an application to deal (with as you say), and a higher level API > is needed that does not contain such specifics. mostly agreed. its not really that bad with devid. we could invent some "static" mapping between devname and devid. Bruce used id's from netgraph, i used (dev_type|unit_no) for mapping, and, i'm sure, you can find something as simple as this. it really does not matter that much as long as the application that uses devid's is not making any assumptions about them (for example does not hardwire devid 0 - or whatever - anywhere to talk to the first available bluetooth device). > The BlueZ guys are, I think, working on a dbus API that will be used by > GNOME and KDE and hopefully it won't be tied to the Linux OS API so > closely, so that we can write dbus modules and have applications just work > on our OS. I have not been providing any input or review of that API > though, it would be good if somebody would step up and point out where the > API is tied too closely to the Linux OS interface and get them to make it > a bit more generic. right, and that is a very good point. that is something that i have to deal with every day at $realjob. things in linux world change very rapidly. from commercial point of view it is very annoying. its not that uncommon that entire subsystems are being thrown away and re-written from scratch without too much consideration. that is why i think having a separate libhci/ port is making more sense here. >> I looked at the Bluetooth specs and I can see that the inquiry sequence >> doesn't hog all of the radio spectrum in use, but the implementation on >> the CSR dongles won't raise any other events whilst the inquiry is in >> progress. > > Is this purely a CSR problem? My laptop has a Broadcom chip in and I > notice that it can make multiple connections concurrently in that on > bootup, it connects to both my mouse and keyboard by itself sometimes - > the CSR dongle I used previously would connect to the keyboard fine but > always fail the second connect with "Command Disallowed". So much so that > I thought perhaps about serialising connection attempts in the kernel. that's right, some dongles would not do 2 or more create_connection commands at the same time. i do not think specification actually mandates this, so it is probably vendor/chip/firmware specific. as far as periodic inquiry goes, its probably not the rf spectrum hogging. its probably related to the way hardware and/or link manager is implemented, i.e. from specification A unit that wants to discover other Bluetooth units enters an inquiry substate. In this substate, it continuously transmits the inquiry message (which is the ID packet, see Section 4.4.1.1 on page 55) at different hop frequencies. The inquiry hop sequence is always derived from the LAP of the GIAC. Thus, even when DIACs are used, the applied hopping sequence is generated from the GIAC LAP. the key thing is that device has to continuously transmits the inquiry message at different hop frequencies. at the same time the same device may participate in another connection (which may require hopping as well). > I've never looked at periodic inquiry though.. me too. but now i'm interested :) thanks max From bms at incunabulum.net Thu Apr 9 11:40:55 2009 From: bms at incunabulum.net (Bruce Simpson) Date: Thu Apr 9 11:41:02 2009 Subject: libhci update In-Reply-To: <1239264003.862926.638.nullmailer@galant.ukfsn.org> References: <49D92E26.2030508@incunabulum.net> <49DD40E2.5030403@incunabulum.net> <1239264003.862926.638.nullmailer@galant.ukfsn.org> Message-ID: <49DE4134.9060604@incunabulum.net> Hi guys, I really hate to be long-winded with this... but it seems there are a number of thorny issues in the Bluetooth area which we need to carefully consider and act upon going forward. Iain Hibbert wrote: > I looked briefly at BlueCove the other day and it seems to use a module to > interface with the BlueZ/Linux API but it also has Windows and Mac modules > amongst others. If it needs a FreeBSD or NetBSD module then that doesn't > seem so difficult? > I had literally spent two evenings before staring at PyBluez/LightBlue/BlueCove code on my ThinkPad, whilst lying on my mother's sofa, and after hacking MLDv2 into FreeBSD-CURRENT's IPv6 stack. When I hacked on libhci I was on a train from Glasgow to London last Thursday evening, and let me tell you, I was staring at KScope a lot! Of course it's also to do with the fact that BlueZ libhci is an API which is being used by a number of apps out there, so I had more example material to work from, having waded into this knowing NOTHING about the existing APIs. If you look at the differences between netbt, FreeBSD bluetooth, and BlueZ, it's ifdef city. Nothing is all that different under the hood in terms of meaning, or role within the stack, but every possible thing is named differently. In terms of porting/compatibility, my focus has been on developing shims for the BlueZ API, given its relative wide adoption in the open source software available for Bluetooth, rather than hacking BSD native support code, as this just seemed like the quicker path towards my goal: Python support for building Bluetooth server apps, on a FreeBSD based system. I also wanted to minimize the diff sets required for interoperability. In my experience, the smaller patches are, the more likely third parties are to take them on upstream. > I looked at LightBlue but I don't remember anything about it, was it > perchance really incomplete and very BlueZ/Linux based? > Yes, LightBlue is actually quite incomplete, although it is intended to be dog simple. Some technical detail follows: LightBlue relies on PyBlueZ as a prerequisite, and uses a handful of libhci functions, as well as requiring OpenOBEX. It is also GPLv3'd which is off-putting to say the least for commercial use. Trouble is, PyBlueZ is actually very tied to some BlueZ specifics, i.e. the HCI interface in particular. Any time we need to reach down to baseband, which you end up doing to implement neighbor discovery, you need to use HCI. SDP alone just doesn't cut it; paging every device in the immediate vicinity is quite an expensive operation. It is the only Python native binding I know of which ties OBEX on top of Bluetooth in a similar way to that of Java JSR-82's 'SessionNotifier' interface -- which is what we really need for building Bluetooth server apps. We found Python's HTTPServer very useful indeed, and having something like an OBEXServer would be just right for us. However, LightBlue has the limitation that it doesn't have any support for multiple devices/enumeration, unless you go directly to PyBlueZ, and doesn't support a event driven interface for Bluetooth server apps -- its APIs are strictly synchronous function calls. Same for its OBEXClient class, however at least that can have some methods overridden, but still isn't as flexible as we would like it to be. To be fair, it isn't a lot of code, and implementing a new Python API similar to LightBlue would not take too much time, provided the foundations are in place. The real fly in the soup is the fact that the BlueZ bindings I've seen, are all tied to BlueZ's use of a 16 bit wide identifier for each local Bluetooth device in device enumeration. These end up leaking into the APIs. That's just unavoidable, as it is still reasonable that there is a means of referring to devices without opening them. In an ideal world we'd be able to use a string identifier, but that is not what's happened NOW. JSR-82 is less tied up in this way, but it still assumes that there is an integer device ID unique to each local device which will fit in a Java JNI jint type. ... To summarize: I'm just concerned that in focusing too much on building a clean / BSD native API, we risk sidelining ourselves. There has been a lot of Bluetooth activity in the Linux community, and by not having at least some degree of source level compatibility, we risk not being able to leverage and make use of that work which we could otherwise benefit from for small change cost. There is no 'standard' for high level Bluetooth APIs that I am aware of, other than JSR-82, and wider adoption of a platform tends to wind up with it being 'de-facto' standard. Whether we like it or not, it's a harsh reality of software work. Hence my comparison to Betamax/VHS in my original reply. I myself, I just get sick and tired of having to HACK HACK HACK every time someone writes a neato little app for BlueZ. This situation is endemic simply because Linux has a popularity which the BSDs don't, as such it can potentially attract a greater number of folk getting into software dev for the first time, or on a hobbyist basis. I realize that this might be an unpopular view in the BSD camp, but that is how it looks from where I'm standing. [I've been living on the edge with this stuff for months, and I really just want to make progress so I can focus on more fun things in life than fscking computers!!] > so write a module that interfaces (for example) the Java (BlueCove?) API > to the FreeBSD OS layer. Its not that different from the BlueZ/Linux API > and you can probably just do some copy and paste work. Thats how the GPL > world works. Yes, you will not be able to integrate that work directly > into FreeBSD but then I doubt a Java interface is ever going to be > accepted into base anyway. Donate it to BlueCove. > Well, that's exactly my point, I am not for one moment suggesting we fold JSR-82 into any base system... but time is critical and we need to work with what's already there, unless someone comes up with a compelling alternative NOW. > If its commercial, get those companies to contribute some BSD code. > That's a reasonable statement, but for the fact that... ...The companies either don't exist or are potential competitors who aren't using BSD or Linux -- or, WE are the potential companies! My partner and I are already working on this, so it is very much a matter of beg borrow or steal. There seems no sense, to my mind, in reinventing wheels... There are competing interests in this space, but they've had the CapEx to go off and implement their own platforms. As to the business end of it, whether that's a tax write-off for investment purposes I know and care not, it's just what any new player would be up against in the Bluetooth space. > >> Thanks for this. I would far rather not introduce a runtime or link-time >> dependency on -lnetgraph if I can possibly avoid it. I'll digest further >> and try to see if this can be incorporated. >> > > But I thought, on FreeBSD the whole bluetooth stack is netgraph based..? > It is, but generally the libbluetooth / libsdp libraries 'get away' with not touching Netgraph directly, as the Netgraph ng_btsocket node exposes a number of socket options and ioctls which require no knowledge/linkage to Netgraph in order to use. I just ended up going to Netgraph to perform BlueZ style device enumeration, which needs an integer handle (a bit like an ifnet ifindex in the network stack), rather than a textual name. The code which Maksim posted would help us to side step this in any prospective libhci compatibility library. > My stance on the compatibility issue is that there are some things in the > BlueZ/Linux C API (the major thing being 'devid' to address the radio) > that are tied to the actual OS support and are unsupportable unless you > provide exactly the same API in the OS. But, OS support is way too low > level for an application to deal (with as you say), and a higher level API > is needed that does not contain such specifics. > Sure, but I don't have free time to come up with such a higher level API, and as such, I need to work with what's already out there. Of course, if anyone else is willing to volunteer to work on this, they are more than welcome to do so, but the problem with assembling a product / strategy based on that, is, we need to know how much it's going to cost, and how much time it's going to take, i.e. when will it be ready. Hence my interest in leveraging what is already out there *NOW*. It is in no way a technical or political endorsement of any particular approach, camp, product, or philosophy, it is a purely pragmatic approach to the reality of working with limited resources and time. :-) As I say, I'm not trying to tread on anyone's toes, or otherwise rule out good and technically valid solutions. We just needed this thing months ago and it's not there. > The BlueZ guys are, I think, working on a dbus API that will be used by > GNOME and KDE and hopefully it won't be tied to the Linux OS API so > closely, so that we can write dbus modules and have applications just work > on our OS. I have not been providing any input or review of that API > though, it would be good if somebody would step up and point out where the > API is tied too closely to the Linux OS interface and get them to make it > a bit more generic. > Yes they are, however, as I've hinted at before, they seem focused on this and little else. Back in January my colleague Alexei (Cc'd)and I did some work with the latest BlueZ bluetoothd frm SVN. We were astonished and dismayed to find that operations critical to the receiving of files had completely changed, i.e. the 'inquiry scan' setting, and the documentation had not been updated. Whilst we had discovered and found the problem, and documented a workaround, we had a demo suffer from a technical hitch because of this issue. Of course, it is fair to argue that we get what we deserve for trying to work with the bleeding edge of development. However, this wasn't just a few minor changes, the entire configuration mechanism had been rewritten, with no documentation other than the code. I could care less about Linux or BSD specifics at this point. If folk want to create new APIs, please do so -- I ain't stopping you! And if I can help out, I will. I'm certainly receptive to everyone's ideas here given their technical merit, but it should be borne in mind that we have a very specific goal. > >> I looked at the Bluetooth specs and I can see that the inquiry sequence >> doesn't hog all of the radio spectrum in use, but the implementation on >> the CSR dongles won't raise any other events whilst the inquiry is in >> progress. >> > > Is this purely a CSR problem? My laptop has a Broadcom chip in and I > notice that it can make multiple connections concurrently in that on > bootup, it connects to both my mouse and keyboard by itself sometimes - > the CSR dongle I used previously would connect to the keyboard fine but > always fail the second connect with "Command Disallowed". So much so that > I thought perhaps about serialising connection attempts in the kernel. > I doubt that the issue with inquiry tying up the controller is limited to CSR dongles, but it's one area where we either need to engage the vendor directly and ask them what the story is, or conduct experiments to map out the behaviour in the time domain amongst different vendors and models (very time consuming). If you look at the specs, inquiry keeps the controller quite busy. There are several different inquiry variants, and they all involve frequency hopping, and the transmission of the inquiry sequence. It's mostly baseband stuff and doesn't involve e.g. L2CAP layer. The inquiry sub-state, as described in the spec, doesn't preclude other HCI events during the inquiry, however it does recommend that ACL transports be parked, as it only reserves slots for SCO. I haven't delved deep enough to see if FreeBSD Bluetooth is doing ACL park when periodic inquiry is active, I wager it doesn't, and what we're actually seeing is the client device attempting to reconnect after the inquiry sub-state is left, after seeing no reply. > I've never looked at periodic inquiry though.. > Periodic inquiry is basically the same as regular inquiry, with the exception that the periodic timer is hosted by the microcontroller itself. When the timer fires, it will drop just about everything else that it's doing, mind you I've seen with OBEX sessions running at the same time, it will either pick them up once inquiry has finished, or finish the OBEX session (RFCOMM) before starting inquiry. Of course given that it will not generate any event upcall at HCI layer for any other baseband protocol event during the inquiry, it's of very limited usefulness for real apps. The only application I've seen which uses it is the Bluetooth scanner/sniffer/brute forcer 'Fine Tooth Comb' from shmoo.com. > I have written at least a set of SDP primitives that I'm intending to > import to NetBSD 'soon' (I have only one computer and am concentrating on > 5.0 release first because running different OS versions is messy) > > I think the latest archive was at > http://www.netbsd.org/~plunky/sdp-20090227.tar.gz > Thanks for this, I'll be sure to take a look once I can download it. I don't seem to be able to reach www.netbsd.org at the moment, either from my home ISP connection, or from freefall.freebsd.org. :-( I am very grateful for your input and feedback, and hope that we can get this ball rolling faster. cheers, BMS From bms at incunabulum.net Thu Apr 9 12:36:21 2009 From: bms at incunabulum.net (Bruce Simpson) Date: Thu Apr 9 12:36:28 2009 Subject: libhci update In-Reply-To: References: <49D92E26.2030508@incunabulum.net> <49DD40E2.5030403@incunabulum.net> <1239264003.862926.638.nullmailer@galant.ukfsn.org> Message-ID: <49DE4E2F.2000805@incunabulum.net> Maksim Yevmenkin wrote: > On Thu, Apr 9, 2009 at 12:00 AM, Iain Hibbert wrote: > >> I looked briefly at BlueCove the other day and it seems to use a module to >> interface with the BlueZ/Linux API but it also has Windows and Mac modules >> amongst others. If it needs a FreeBSD or NetBSD module then that doesn't >> seem so difficult? >> > > right, that is something i kinda wondering too. of course, i have no > idea how hard it would be to plug new bluetooth module into bluecove. > perhaps cost of adding the new bluetooth module more than implementing > something that looks like bluez? > That's exactly the question I set about reading code in order to answer. The short version is, yes, in my opinion it's easier to just crib the existing APIs rather than rewrite the OS support code for all of these higher layer Bluetooth APIs completely from scratch. Dealing with JNI code can be a bit messy. I don't do Java on a daily basis so I'm less inclined to get my hands dirty with ant build.xml files. Other folks' mileage may vary. > > having said that, we have to recognize the fact that there is lots of > code that is bluez specific. so, how about we separate flies from > meatballs, and, meet halfway: > A bit of background: The whole reason we migrated to BSD in the first place was simply because it offered a faster route to building an appliance-like product than customizing Linux, unless we purchased an expensive license for a Linux derived embedded distribution. There are alternatives in the Linux space to this too, but even they have a high degree of change cost associated with them. We're talking about the difference between 1.5 man- days and 10 man-days. From a business point of view, that's a no brainer. If you look at the libhci drop I provided, you'll see I've tried to keep the BlueZ and NetBT compatibility shims separate from the core API. It is ifdef city. Iain: If you want to see this code give me a shout and I'll throw it up somewhere. I would hope to do the same for libsdp. > 1) we put bsd-style bluetooth api and make sure it is shared with as > many bsd's (and possibly other os's) as possible. i personally would > like to continue to work with Iain and get his input. this api is > going into the base system and will be bsd-licensed. obviously, we > will keep an eye on bluez while designing and implementing bsd > bluetooth api. > The thing is, if we go down this road, it would be wise to name the BSD libraries, structs and functions completely differently from their Linux counterparts, due to exactly the situation you describe so well below. As I say, I am happy to play along and adapt what I'm doing to the base system work that you guys want to do, however, the crucial part is that we are able to develop something without having to go directly to C/C++ every time. Doing this kind of work to the standard required, and on time, is difficult, and not all folk working as developers in this space have the requisite skills, nor the time to battle-test such code. Also, hardcore C/C++ comms software developers are not folk you can just pick up on a recruitment milk round. ;-) Accessibility is paramount. Projects like PyBlueZ already go far towards achieving this goal, which is why they are important. It's much easier to prototype, test, and design in Python, than it is in C. The development cycle is much shorter. > 2) to ensure compatibility with bluez we create a separate library and > put it into the ports/ collection. it can even use original libhci > headers and re-use original libhci code if needed. missing/different > parts will have to be re-implemented in terms of bsd bluetooth api. > this way it would probably be easier to play catch up game with bluez > and it will be less of pain for folks who use bsd bluetooth api. > basically, if you choose bluez api be prepared to change your code > every time bluez folks change something. > > Yes. BlueCove actually already has to do this for some SDP specifics -- it will do a dlopen() of libhci, look for a given function name, and it will use different SDP calls if a certain function isn't present. Some Linux-related projects have had something of a poor track record as regards change control and versioning of APIs. Yes, they do push further and faster, but we are then left with a legacy of kludge, much like GLIBC, which just gets in the way when trying to deliver product. I agree that it's reasonable to push a compatibility layer into ports. If you look at what Luigi Rizzo did with Linux USB webcam drivers, you'll see this too -- it saves code churn on the FreeBSD SVN repository. And separate change control for this kind of project is preferable for all sorts of reasons. >> But I thought, on FreeBSD the whole bluetooth stack is netgraph based..? >> > > yes, but in userspace you almost never need to use anything netgraph > related. almost everything can be done through sockets. > Provided the ioctls are there :-) I have not needed to touch Netgraph specifics apart from this one thorny problem of getting a unique integer ID for each interface. Of course, persistence of the identifier between reboots / changes is a whole other matter -- and it affects e.g. Ethernet interfaces too. The way BlueZ has chosen to deal with this problem, in the 4.x/SVN train, is to keep bluetoothd state under /var/lib/bluetooth/ma:ca:dd:re:ss/*. But it still means that the dev_id has to be looked up at runtime, and thence the device opened. To be fair, this is no different e.g. from a standards-aware IP multicast application having to be aware of each individual link configured in the system, although a lot of grotty code in open-source land still isn't doing this (even though it's been in the RFCs for WAYYY over 5 years -- Come on guys, it's even in the textbooks... and the more TCP and Torrent saturated the 'Net gets, the more multicast matters!!) I would love to hear everyone's thoughts on how to deal with this issue. > >> My stance on the compatibility issue is that there are some things in the >> BlueZ/Linux C API (the major thing being 'devid' to address the radio) >> that are tied to the actual OS support and are unsupportable unless you >> provide exactly the same API in the OS. But, OS support is way too low >> level for an application to deal (with as you say), and a higher level API >> is needed that does not contain such specifics. >> > > mostly agreed. its not really that bad with devid. we could invent > some "static" mapping between devname and devid. Bruce used id's from > netgraph, i used (dev_type|unit_no) for mapping, and, i'm sure, you > can find something as simple as this. it really does not matter that > much as long as the application that uses devid's is not making any > assumptions about them (for example does not hardwire devid 0 - or > whatever - anywhere to talk to the first available bluetooth device). > > That seems reasonable. I should point out that a lot of the BlueZ consumer code I've looked at is using 'hci_get_route(NULL)' to get a dev_id for the first available dongle. It's actually a deceptively named API because it does not perform a 'route lookup' as such, it doesn't look at neighbour caches, LMP connection handles, etc. It just looks for the first dongle which doesn't correspond to the argument provided. ... re DBus: > right, and that is a very good point. that is something that i have to > deal with every day at $realjob. things in linux world change very > rapidly. from commercial point of view it is very annoying. its not > that uncommon that entire subsystems are being thrown away and > re-written from scratch without too much consideration. that is why i > think having a separate libhci/ port is making more sense here. > That is exactly the barrier which Alexei and I ran head first into back in January, when we seriously started looking at this space. Again, the chaotic approach will let one push forward thick and fast, but it doesn't always yield the best long term result. As I say, I haven't had time to look at the DBus integration to figure out in which respects it differs, or is Linux/BlueZ specific. The BlueZ project team focus at the moment seems to be on DBus. What is clear is that the KDE bluetooth subsystem had to be completely rewritten for the new DBus semantics. ... > that's right, some dongles would not do 2 or more create_connection > commands at the same time. i do not think specification actually > mandates this, so it is probably vendor/chip/firmware specific. > It is most likely a case of 'you get the Bluetooth LMP stack you pay for'. ;-) If you look at the list of Bluetooth SIG members, most of them aren't in a pure software play, but have invested in doing the baseband and microcontroller work from the ground up. CSR and Broadcom are manufacturing mass-market Bluetooth devices. I'm sure they supply specialized product line to the mobile phone industry, but in the main, Bluetooth chipsets intended for use with PCs are probably more limited in what they can do than what is actually possible with what's inside the plastic. This is another reason why Iain's suggestion that we appeal to such companies to contribute BSD code may fall down -- the chances are, if they are looking to dominate the space, they have probably already invested in proprietary OS solutions for building platforms. Although I certainly can't speak for Iain's relationship with Itronix, I am most interested and curious about their reasons for diving into the Bluetooth space, and why NetBSD of all platforms ;-) ...given who their main customer base is. Also, there has been a movement in terms of strategy by technology companies to adopt GPLv2 as an alternative, not because they believe it gives them some open source credibility, although that is part of the smokescreen sometimes -- more because the terms of the GPLv2 force potential competitors to give you their code, if they derive their work from yours. That isn't cut and dried fact, it's just based on observation of who is doing what and why. > as far as periodic inquiry goes, its probably not the rf spectrum > hogging. its probably related to the way hardware and/or link manager > is implemented, i.e. from specification > ... > > the key thing is that device has to continuously transmits the inquiry > message at different hop frequencies. at the same time the same device > may participate in another connection (which may require hopping as > well). > Yup, please see my other recent reply to Iain where I noted down the bit in the specs about it being *suggests* that ACLs are held/parked/sniffed, however, the spec does not make this mandatory -- except for eSCO frames, which have reserved time slots at the Bluetooth baseband layer. At the moment, FreeBSD's stack doesn't do SCO, to my knowledge, nor do my colleague or I make use of SCO yet. it is something we'd want to play with in the not so distant future, though. cheers, BMS From bms at incunabulum.net Thu Apr 9 12:40:55 2009 From: bms at incunabulum.net (Bruce Simpson) Date: Thu Apr 9 12:41:01 2009 Subject: Speeding up device discovery: paper In-Reply-To: References: <49D92E26.2030508@incunabulum.net> <49DD40E2.5030403@incunabulum.net> <1239264003.862926.638.nullmailer@galant.ukfsn.org> Message-ID: <49DE4F44.8070707@incunabulum.net> Hi all, I found this paper an interesting skim, even if only for the numbers and timings: http://faculty.cs.byu.edu/~knutson/publications/IrDA_Assisted_BT_Discovery.pdf It is very much a rehash of the old broadcast vs point-to-point dichotomy. I don't think IrDA is the answer for most deployments, but it's an interesting piece of research as it sheds light on how the discovery mechanisms operate (without having to read the entire specification), and how these might be sped up or worked around. I keep wishing Bluetooth had passive scanning like 802.11 does. cheers... BMS From maksim.yevmenkin at gmail.com Thu Apr 9 13:37:04 2009 From: maksim.yevmenkin at gmail.com (Maksim Yevmenkin) Date: Thu Apr 9 13:37:12 2009 Subject: libhci update In-Reply-To: <49DE4E2F.2000805@incunabulum.net> References: <49D92E26.2030508@incunabulum.net> <49DD40E2.5030403@incunabulum.net> <1239264003.862926.638.nullmailer@galant.ukfsn.org> <49DE4E2F.2000805@incunabulum.net> Message-ID: On Thu, Apr 9, 2009 at 11:36 AM, Bruce Simpson wrote: > Maksim Yevmenkin wrote: [...] >> having said that, we have to recognize the fact that there is lots of >> code that is bluez specific. so, how about we separate flies from >> meatballs, and, meet halfway: >> > > A bit of background: > > The whole reason we migrated to BSD in the first place was simply because it > offered a faster route to building an appliance-like product than > customizing Linux, > unless we purchased an expensive license for a Linux derived embedded > distribution. > > There are alternatives in the Linux space to this too, but even they have a > high degree > of change cost associated with them. > > We're talking about the difference between 1.5 man- days and 10 man-days. > From > a business point of view, that's a no brainer. > > If you look at the libhci drop I provided, you'll see I've tried to keep the > BlueZ and > NetBT compatibility shims separate from the core API. It is ifdef city. > > Iain: If you want to see this code give me a shout and I'll throw it up > somewhere. yes, i looked at it. i saw what are you talking about. believe me, i know *exactly* what are you talking about here. that is why i suggested to put your existing code to ports/. this way you get to use it right now and avoid re-hacking things all the time. hell, if we were talking only about freebsd here, i would probably not object to put it into the base right now. however, i really, really want to keep compatibility and consistency here. at least with other bsd's. i'm still kicking myself for putting all those ng_ prefixes everywhere in userland code and not choosing location for headers wisely (it was long time ago and i was not a committer back then, so everything was developed outside of the main tree). i wish someone would point it to me back then. anyway, the last thing i want to do is to introduce even more differences. most likely (and correct me if i wrong here) Iain would never accept something like libhci into base. at least not in its current form. > I would hope to do the same for libsdp. well, here is where things might get a bit tricky because of sdpd(8). depending on what you want to do, you might need to bring both libsdp and sdpd (or whatever it is called these days) from bluez. >> 1) we put bsd-style bluetooth api and make sure it is shared with as >> many bsd's (and possibly other os's) as possible. i personally would >> like to continue to work with Iain and get his input. this api is >> going into the base system and will be bsd-licensed. obviously, we >> will keep an eye on bluez while designing and implementing bsd >> bluetooth api. > > The thing is, if we go down this road, it would be wise to name the BSD > libraries, structs and functions completely differently from their Linux > counterparts, > due to exactly the situation you describe so well below. yes, unfortunately that is true, but that is where libhci compat port comes to the rescue. yes, it would not be pretty. yes, most functions will just copy data and silly stuff like that. but it least it will be separate and will be maintainable (or so i hope). > As I say, I am happy to play along and adapt what I'm doing to the base > system work > that you guys want to do, however, the crucial part is that we are able to > develop > something without having to go directly to C/C++ every time. Doing this kind > of work > to the standard required, and on time, is difficult, and not all folk > working as developers > in this space have the requisite skills, nor the time to battle-test such > code. i understand and can relate to what you are saying. again, we do not keep things like python, gmake, autoconf, etc. etc. in base. we do keep them in ports/. so, imo, it makes sense to keep libhci compat layer in ports/ too. you get to use bluez libhci and we get to keep our base tree clean. its a win-win to me :) >> 2) to ensure compatibility with bluez we create a separate library and >> put it into the ports/ collection. it can even use original libhci >> headers and re-use original libhci code if needed. missing/different >> parts will have to be re-implemented in terms of bsd bluetooth api. >> this way it would probably be easier to play catch up game with bluez >> and it will be less of pain for folks who use bsd bluetooth api. >> basically, if you choose bluez api be prepared to change your code >> every time bluez folks change something. > > Yes. BlueCove actually already has to do this for some SDP specifics -- it > will do a dlopen() of libhci, look for a given function name, and it will > use > different SDP calls if a certain function isn't present. why even bother with that? just install compat library and have all the symbols available, no? > I agree that it's reasonable to push a compatibility layer into ports. If > you look > at what Luigi Rizzo did with Linux USB webcam drivers, you'll see this too > -- > it saves code churn on the FreeBSD SVN repository. And separate change > control for this kind of project is preferable for all sorts of reasons. great! i will try to clean up my patches and send them out one more time. i'd like to get Iain's comments before putting them into the base. mfc can be done quickly as well (if needed). >>> But I thought, on FreeBSD the whole bluetooth stack is netgraph based..? >> >> yes, but in userspace you almost never need to use anything netgraph >> related. almost everything can be done through sockets. > > Provided the ioctls are there :-) I have not needed to touch Netgraph > specifics apart > from this one thorny problem of getting a unique integer ID for each > interface. let me know what is missing and i will add it :) > Of course, persistence of the identifier between reboots / changes is a > whole other > matter -- and it affects e.g. Ethernet interfaces too. that is the thing. bd_addr is the only "unique" (it can't be easily changed, but it still can be done) thing about bluetooth device. but in order to get it, you need to address the device somehow. devname is better, but still does not solve the problem as you pointed out it can change. > The way BlueZ has chosen to deal with this problem, in the 4.x/SVN train, is > to > keep bluetoothd state under /var/lib/bluetooth/ma:ca:dd:re:ss/*. But it > still means > that the dev_id has to be looked up at runtime, and thence the device > opened. > > To be fair, this is no different e.g. from a standards-aware IP multicast > application > having to be aware of each individual link configured in the system, > although a lot > of grotty code in open-source land still isn't doing this (even though it's > been in the > RFCs for WAYYY over 5 years -- Come on guys, it's even in the textbooks... > and > the more TCP and Torrent saturated the 'Net gets, the more multicast > matters!!) > > I would love to hear everyone's thoughts on how to deal with this issue. why do you care so much about devid? i assume whatever it is you are building, it will have multiple radios, right? are you planning to setup different radios in different way? >>> My stance on the compatibility issue is that there are some things in the >>> BlueZ/Linux C API (the major thing being 'devid' to address the radio) >>> that are tied to the actual OS support and are unsupportable unless you >>> provide exactly the same API in the OS. But, OS support is way too low >>> level for an application to deal (with as you say), and a higher level >>> API >>> is needed that does not contain such specifics. >> >> mostly agreed. its not really that bad with devid. we could invent >> some "static" mapping between devname and devid. Bruce used id's from >> netgraph, i used (dev_type|unit_no) for mapping, and, i'm sure, you >> can find something as simple as this. it really does not matter that >> much as long as the application that uses devid's is not making any >> assumptions about them (for example does not hardwire devid 0 - or >> whatever - anywhere to talk to the first available bluetooth device). > > That seems reasonable. > > I should point out that a lot of the BlueZ consumer code I've looked at is > using 'hci_get_route(NULL)' to get a dev_id for the first available dongle. > > It's actually a deceptively named API because it does not perform a 'route > lookup' > as such, it doesn't look at neighbour caches, LMP connection handles, etc. > > It just looks for the first dongle which doesn't correspond to the argument > provided. yep, the whole devid vs. devname difference only matters when you have more than 1 radio connected to the system. 99% of the people have only 1 radio, so it does not matter that much. [...] >> that's right, some dongles would not do 2 or more create_connection >> commands at the same time. i do not think specification actually >> mandates this, so it is probably vendor/chip/firmware specific. > > It is most likely a case of 'you get the Bluetooth LMP stack you pay for'. > ;-) > > If you look at the list of Bluetooth SIG members, most of them aren't in a > pure software play, > but have invested in doing the baseband and microcontroller work from the > ground up. > > CSR and Broadcom are manufacturing mass-market Bluetooth devices. I'm sure > they > supply specialized product line to the mobile phone industry, but in the > main, Bluetooth > chipsets intended for use with PCs are probably more limited in what they > can do than > what is actually possible with what's inside the plastic. i actually have a hands on experience with csr bluecore chips. i did some work with csr bluecore chips and csr bluelab sdk. i'm not sure if you know, but bluecore chips are actually bluetooth system-on-chip. that is you can run custom application on the chip itself. before bluecore5 the application was running inside the virtual machine running on bluecore. starting with bluecore5 you actually have a choice to run application natively or run it inside vm. firmware for bluecore chips comes in few flavors: low end goes up to hci layer only. that is what you will find in almost all consumer bluetooth dongles that use csr bluecore. in fact, almost all of them are based on bluecore-rom reference designs that are available from csr. high end goes up to rfcomm and hid layers. then you also have bluecoreX-mm (multimedia) chips that have dsp on board. those are typically used in mid/high end bluetooth headsets. the whole headset profile is actually running on bluecore chip. no external microcontroller is required. some dongles have dual personality - typical for hid dongles, i.e. you can have it to act as usb hub with keyboard and mouse (hid) or you can boot it into hci-only mode and it will look like regular bluetooth device. [...] > At the moment, FreeBSD's stack doesn't do SCO, to my knowledge, nor do my > colleague or I make use of SCO yet. > it is something we'd want to play with in the not so distant future, though. there are some bits and pieces in various degree of working'ness :) thanks, max From mi+thun at aldan.algebra.com Thu Apr 9 14:11:30 2009 From: mi+thun at aldan.algebra.com (Mikhail T.) Date: Thu Apr 9 14:11:36 2009 Subject: per-user directories for obexapp-server Message-ID: <49DE5ECD.8020202@aldan.algebra.com> Hello! I was able to set up the Bluetooth-server on my main machine, but all of the pushed files (Object Pushed) end up in the same root-only directory... Is there, perhaps, a way to specify a different directory for each device-ID (bdaddr)? This way my wife and myself could have our own locations (under home-directories somewhere) for pictures and phonebook backups... Please, advise. Thanks! Yours, -mi P.S. Maybe, the hcsecd.conf's syntax can be extended to (optionally) provide the username associated with each listed device? From bms at incunabulum.net Thu Apr 9 14:48:54 2009 From: bms at incunabulum.net (Bruce Simpson) Date: Thu Apr 9 14:49:02 2009 Subject: libhci update In-Reply-To: References: <49D92E26.2030508@incunabulum.net> <49DD40E2.5030403@incunabulum.net> <1239264003.862926.638.nullmailer@galant.ukfsn.org> <49DE4E2F.2000805@incunabulum.net> Message-ID: <49DE6D42.6000004@incunabulum.net> Maksim Yevmenkin wrote: > believe me, i know *exactly* what are you talking about here. that is > why i suggested to put your existing code to ports/. this way you get > to use it right now and avoid re-hacking things all the time. hell, if > we were talking only about freebsd here, i would probably not object > to put it into the base right now. however, i really, really want to > keep compatibility and consistency here. at least with other bsd's. > i'm still kicking myself for putting all those ng_ prefixes everywhere > in userland code and not choosing location for headers wisely (it was > long time ago and i was not a committer back then, so everything was > developed outside of the main tree). i wish someone would point it to > me back then. > well said :-) It takes balls to admit our mistakes :-) but we all make them, it's called "learning"... :-) > anyway, the last thing i want to do is to introduce even more > differences. most likely (and correct me if i wrong here) Iain would > never accept something like libhci into base. at least not in its > current form. > Yup, base system addition isn't really my intent :-) > >> I would hope to do the same for libsdp. >> > > well, here is where things might get a bit tricky because of sdpd(8). > depending on what you want to do, you might need to bring both libsdp > and sdpd (or whatever it is called these days) from bluez. > Yup, that's pretty scary because there are significant differences. If you look at all the APIs, they all end up building on-the-wire-records to advertise Bluetooth services via SDP -- be that L2CAP PSM's, RFCOMM channels, or anything else. it is irksome that the SDP APIs between OSes differ so radically. So I'd make a radical suggestion here: can we change the existing BSD-space applications and daemons to use a different name e.g. libbtsdp for the base system? That would be a big help for BlueZ compatibility... yes, it sucks, but it's a hackish fix to the namespace collision between these two radically different sets of libraries. > i understand and can relate to what you are saying. again, we do not > keep things like python, gmake, autoconf, etc. etc. in base. we do > keep them in ports/. so, imo, it makes sense to keep libhci compat > layer in ports/ too. you get to use bluez libhci and we get to keep > our base tree clean. its a win-win to me :) > Yup. Shipping in ports means we ship more quickly. We've had to overcome some hurdles with 7.2-RELEASE, to be sure, but most of what we needed in the NOW, has been dealt with, and it's in FreeBSD 7.2 which will be shipping any minute now. I loathe autotools with a passion, having had to hack with it extensively for a living. [re BlueCove dlopen() lameness] > why even bother with that? just install compat library and have all > the symbols available, no? > Now that you mention it, if libhci gets folded off into ports (hey, let's call it comms/libhci), it gets much easier... > >> I agree that it's reasonable to push a compatibility layer into ports. If >> you look >> at what Luigi Rizzo did with Linux USB webcam drivers, you'll see this too >> -- >> it saves code churn on the FreeBSD SVN repository. And separate change >> control for this kind of project is preferable for all sorts of reasons. >> > > great! > > i will try to clean up my patches and send them out one more time. i'd > like to get Iain's comments before putting them into the base. mfc can > be done quickly as well (if needed). > > > OK. That would be great. I can look at such diffs too if need be. :-) I don't believe an MFC will be too difficult as long as we're in the 7.2 slush. We are tracking SVN stable/7 here, so even if it doesn't make it into 7.2-RELEASE, all is not lost for what my partner and I are trying to do. [ioctls] > let me know what is missing and i will add it :) > What's the easiest way to get the unit number? ... I think your non-Netgraph based solution is simpler and more elegant, although it was nice to have the exercise of getting my hands dirtier with Netgraph :-) > > that is the thing. bd_addr is the only "unique" (it can't be easily > changed, but it still can be done) thing about bluetooth device. but > in order to get it, you need to address the device somehow. devname is > better, but still does not solve the problem as you pointed out it can > change. > It would be nice to have a 'device name' registry or be able to renumber/rename the Bluetooth dongles in a manner similar to that of what folk end up doing for FreeBSD using ifconfig(8). BTW: I should point out that FreeBSD doesn't actually do this for network interfaces at the moment in the base system in any automated way. The udev mechanisms in Linux distributions do have some provision for this. You can tell it to tie down instances to specific PCI fields, usually vendor/chip. It does bite ifnets in particular because FreeBSD's NEWBUS code generally probes PCI buses in the opposite order from Linux -- so dual-booted systems end up with a different device tree, as the unit numbers are assigned in inverse order. The way people tend to work around this in practice is to construct some sort of device registry of their own, on top of what's already in base. I know previous clients of mine have been doing this, without naming names, and I know pfSense does something like this. > why do you care so much about devid? i assume whatever it is you are > building, it will have multiple radios, right? are you planning to > setup different radios in different way? > Because... (bad English ;o)) yes we do want to use multiple radios -- inquiry is an expensive operation -- and also, BlueCove/JSR-82, PyBlueZ and the other high level language stuff currently wants to use dev_id as the unique endpoint identifier. :-( They do provide APIs to lookup dev_id from the MAC address, but if that is used as-is, any port would still have to maintain the kludge. It is irritating, but that is what has unfolded. ... > yep, the whole devid vs. devname difference only matters when you have > more than 1 radio connected to the system. 99% of the people have only > 1 radio, so it does not matter that much. > Absolutely. Only when you actually want to do something that is a bit more complicated than uploading your home-made pornography to your laptop, does having more than 1 Bluetooth radio get important. ;-D > [...] > > > > some dongles have dual personality - typical for hid dongles, i.e. you > can have it to act as usb hub with keyboard and mouse (hid) or you can > boot it into hci-only mode and it will look like regular bluetooth > device. > I saw this when using dfutool under Linux to flash a generic BlueCore4-EXT dongle with the generic CSR firmware. It turned out this image was extracted from a MacBook integrated Bluetooth dongle, so it presented itself as a USB HID device. From maksim.yevmenkin at gmail.com Thu Apr 9 15:37:02 2009 From: maksim.yevmenkin at gmail.com (Maksim Yevmenkin) Date: Thu Apr 9 15:37:09 2009 Subject: libhci update In-Reply-To: <49DE6D42.6000004@incunabulum.net> References: <49D92E26.2030508@incunabulum.net> <49DD40E2.5030403@incunabulum.net> <1239264003.862926.638.nullmailer@galant.ukfsn.org> <49DE4E2F.2000805@incunabulum.net> <49DE6D42.6000004@incunabulum.net> Message-ID: On Thu, Apr 9, 2009 at 1:48 PM, Bruce Simpson wrote: [...] >>> I would hope to do the same for libsdp. >> >> well, here is where things might get a bit tricky because of sdpd(8). >> depending on what you want to do, you might need to bring both libsdp >> and sdpd (or whatever it is called these days) from bluez. > > Yup, that's pretty scary because there are significant differences. > > If you look at all the APIs, they all end up building on-the-wire-records to > advertise Bluetooth > services via SDP -- be that L2CAP PSM's, RFCOMM channels, or anything else. oh, no :) please do not open this can of worms :) Iain knows its a very touchy subject with me :) i dislike *intensely* the fact that sdp records have to be built in on-the-wire format. i just dont get why any application has to know about uuid's, sequences and other low level sdp stuff just to register the damn service. that is why i went an extreme route (a bit too extreme perhaps :) and introduced "pre-cooked" sdp records that are constructed on the fly. application only transfers minimum information required to register the service. the downside, of course, is that its not very flexible. in fact, its pretty damn rigid. which means that if you want to introduce new profile, you have to change sdp parts as well. Iain and i beat this horse to death, imo. we think there is really no good way around this and we can only improve api to be more user friendly and require less typing, but the records, unfortunately, would have to be in on-the-wire format (or something pretty damn close to it). > it is irksome that the SDP APIs between OSes differ so radically. > > So I'd make a radical suggestion here: can we change the existing BSD-space > applications > and daemons to use a different name e.g. libbtsdp for the base system? yes, we can. i was actually thinking to merge add the sdp stuff into libbluetooth. and while i'm at it, move bluetooth.h and sdp.h into include/bluetooth/ where it belongs. i just want to get hci stuff out the way first. > That would be a big help for BlueZ compatibility... yes, it sucks, but it's > a hackish fix to the > namespace collision between these two radically different sets of libraries. if we fix our sdp first to be more like bluez (i.e. use on-the-wire format) then we can add compat bluez sdp library that would simply translate bluez call to bsd calls. same route as with libhci. [....] >> i will try to clean up my patches and send them out one more time. i'd >> like to get Iain's comments before putting them into the base. mfc can >> be done quickly as well (if needed). > > OK. That would be great. I can look at such diffs too if need be. :-) > I don't believe an MFC will be too difficult as long as we're in the 7.2 > slush. thanks! i have attached the latest diff. its pretty much the same as previous one, except i added documentation. > [ioctls] >> >> let me know what is missing and i will add it :) > > What's the easiest way to get the unit number? > ... hci nodes do not really have unit numbers. they have names. hci nodes are named as "device+unit+hci". device + unit comes from driver for a particular device. in 99% of the cases it will be "ubtX", i.e. usb dongle, but we also support h4 devices (currently broken due to new tty) and btccc - 3com pccard. so just enumerate all the radios (i.e. list all the hci nodes - there is an ioctl for that) and unit will be the number before "hci" part of the name. however, that will only work if if have devices of the same type in the system (i.e. all the radios are bluetooth dongles). otherwise, if, say, you have 3com pccard and bluetooth dongle, then you will have btccc0hci and ubt0hci nodes both having 0 unit number. >> that is the thing. bd_addr is the only "unique" (it can't be easily >> changed, but it still can be done) thing about bluetooth device. but >> in order to get it, you need to address the device somehow. devname is >> better, but still does not solve the problem as you pointed out it can >> change. > > It would be nice to have a 'device name' registry or be able to > renumber/rename the Bluetooth > dongles in a manner similar to that of what folk end up doing for FreeBSD > using ifconfig(8). i'm not quite follow why is that needed? [....] >> why do you care so much about devid? i assume whatever it is you are >> building, it will have multiple radios, right? are you planning to >> setup different radios in different way? > > Because... (bad English ;o)) > yes we do want to use multiple radios -- inquiry is an expensive operation > -- and also, > BlueCove/JSR-82, PyBlueZ and the other high level language stuff currently > wants to use > dev_id as the unique endpoint identifier. :-( > > They do provide APIs to lookup dev_id from the MAC address, but if that is > used as-is, > any port would still have to maintain the kludge. It is irritating, but that > is what has unfolded. just a stupid idea - "hash" devname into devid? where "hash" does not necessarily means use some real hash function :) could be partitioning function (as in my previously posted code). how wide is devid, you mentioned that it could be as narrow as unit16 and as wide as int? is that correct? thanks, max -------------- next part -------------- Index: hci.c =================================================================== --- hci.c (revision 190870) +++ hci.c (working copy) @@ -30,15 +30,421 @@ * $FreeBSD$ */ +#include #include #include #include #include #include +static int bt_devany_cb(int s, struct bt_devinfo const *di, void *xdevname); static char * bt_dev2node (char const *devname, char *nodename, int nnlen); int +bt_devopen(char const *devname) +{ + struct sockaddr_hci ha; + bdaddr_t ba; + int s; + + if (devname == NULL) { + errno = EINVAL; + return (-1); + } + + memset(&ha, 0, sizeof(ha)); + ha.hci_len = sizeof(ha); + ha.hci_family = AF_BLUETOOTH; + + if (bt_aton(devname, &ba)) { + if (!bt_devname(ha.hci_node, &ba)) + return (-1); + } else if (bt_dev2node(devname, ha.hci_node, + sizeof(ha.hci_node)) == NULL) { + errno = ENXIO; + return (-1); + } + + s = socket(PF_BLUETOOTH, SOCK_RAW, BLUETOOTH_PROTO_HCI); + if (s < 0) + return (-1); + + if (bind(s, (struct sockaddr *) &ha, sizeof(ha)) < 0 || + connect(s, (struct sockaddr *) &ha, sizeof(ha)) < 0) { + close(s); + return (-1); + } + + return (s); +} + +int +bt_devclose(int s) +{ + return (close(s)); +} + +int +bt_devsend(int s, uint16_t ogf, uint16_t ocf, int plen, void *param) +{ + ng_hci_cmd_pkt_t h; + struct iovec iv[2]; + int ivn; + + if (plen < 0 || (plen > 0 && param == NULL)) { + errno = EINVAL; + return (-1); + } + + iv[0].iov_base = &h; + iv[0].iov_len = sizeof(h); + ivn = 1; + + h.type = NG_HCI_CMD_PKT; + h.opcode = htole16(NG_HCI_OPCODE(ogf, ocf)); + if (plen > 0) { + h.length = plen; + + iv[1].iov_base = param; + iv[1].iov_len = plen; + ivn = 2; + } else + h.length = 0; + + while (writev(s, iv, ivn) < 0) { + if (errno == EAGAIN || errno == EINTR) + continue; + + return (-1); + } + + return (0); +} + +int +bt_devrecv(int s, uint8_t *buf, int size, time_t to) +{ + fd_set rfd; + struct timeval tv; + int n; + + if (buf == NULL || size <= 0 || to < 0) { + errno = EINVAL; + return (-1); + } + + FD_ZERO(&rfd); + FD_SET(s, &rfd); + + tv.tv_sec = to; + tv.tv_usec = 0; + + while ((n = select(s + 1, &rfd, NULL, NULL, &tv)) < 0) { + if (errno == EAGAIN || errno == EINTR) + continue; + + return (-1); + } + + if (n == 0) { + errno = ETIMEDOUT; + return (-1); + } + + assert(FD_ISSET(s, &rfd)); + + while ((n = read(s, buf, size)) < 0) { + if (errno == EAGAIN || errno == EINTR) + continue; + + return (-1); + } + + return (n); +} + +int +bt_devreq(int s, struct bt_devreq *r, time_t to) +{ + uint8_t buf[320]; /* more than enough */ + ng_hci_event_pkt_t *e = (ng_hci_event_pkt_t *) buf; + ng_hci_command_compl_ep *cc = (ng_hci_command_compl_ep *)(e+1); + ng_hci_command_status_ep *cs = (ng_hci_command_status_ep*)(e+1); + uint16_t opcode; + time_t t_end; + int n; + + if (s < 0 || r == NULL || to < 0) { + errno = EINVAL; + return (-1); + } + + if (r->rlen < 0 || (r->rlen > 0 && r->rparam == NULL)) { + errno = EINVAL; + return (-1); + } + + n = bt_devsend(s, r->ogf, r->ocf, r->clen, r->cparam); + if (n < 0) + return (-1); + + opcode = htole16(NG_HCI_OPCODE(r->ogf, r->ocf)); + + t_end = time(NULL) + to; + + do { + to = t_end - time(NULL); + if (to < 0) + to = 0; + + n = bt_devrecv(s, buf, sizeof(buf), to); + if (n < 0) + return (-1); + + if (n < sizeof(*e)) { + errno = EMSGSIZE; + return (-1); + } + + if (e->type != NG_HCI_EVENT_PKT) { + errno = EIO; + return (-1); + } + + n -= sizeof(*e); + + switch (e->event) { + case NG_HCI_EVENT_COMMAND_COMPL: + if (cc->opcode == opcode) { + n -= sizeof(*cc); + + if (r->rlen >= n) { + r->rlen = n; + memcpy(r->rparam, cc + 1, r->rlen); + } + + return (0); + } + break; + + case NG_HCI_EVENT_COMMAND_STATUS: + if (cs->opcode == opcode) { + if (r->event != NG_HCI_EVENT_COMMAND_STATUS) { + if (cs->status != 0) { + errno = EIO; + return (-1); + } + } else { + if (r->rlen >= n) { + r->rlen = n; + memcpy(r->rparam, cs, r->rlen); + } + + return (0); + } + } + break; + + default: + if (e->event == r->event) { + if (r->rlen >= n) { + r->rlen = n; + memcpy(r->rparam, e + 1, r->rlen); + } + + return (0); + } + break; + } + } while (to > 0); + + errno = ETIMEDOUT; + + return (-1); +} + +int +bt_devfilter(int s, struct bt_devfilter const *new, struct bt_devfilter *old) +{ + struct ng_btsocket_hci_raw_filter f; + socklen_t len; + int bit; + + if (new == NULL && old == NULL) { + errno = EINVAL; + return (-1); + } + + if (old != NULL) { + len = sizeof(f); + if (getsockopt(s, SOL_HCI_RAW, SO_HCI_RAW_FILTER, &f, &len) < 0) + return (-1); + + memset(old, 0, sizeof(*old)); + + for (bit = 0; bit < NG_HCI_EVENT_PKT; bit ++) + if (bit_test(f.packet_mask, bit)) + old->packet_mask |= (1 << bit); + + for (bit = 0; bit < NG_HCI_EVENT_MASK_SIZE * 8; bit ++) + if (bit_test(f.event_mask, bit)) + old->event_mask |= (1 << bit); + } + + if (new != NULL) { + memset(&f, 0, sizeof(f)); + + for (bit = 0; bit < NG_HCI_EVENT_PKT; bit ++) + if (new->packet_mask & (1 << bit)) + bit_set(f.packet_mask, bit); + + for (bit = 0; bit < (NG_HCI_EVENT_MASK_SIZE * 8); bit ++) + if (new->event_mask & (1 << bit)) + bit_set(f.event_mask, bit); + + len = sizeof(f); + if (setsockopt(s, SOL_HCI_RAW, SO_HCI_RAW_FILTER, &f, len) < 0) + return (-1); + } + + return (0); +} + +int +bt_devinquiry(char const *devname, int length, int num_rsp, + uint8_t const *lap, struct bt_devinquiry **ii) +{ + uint8_t buf[320]; + char _devname[HCI_DEVNAME_SIZE]; + struct bt_devfilter f; + ng_hci_inquiry_cp *cp = (ng_hci_inquiry_cp *) buf; + ng_hci_event_pkt_t *e = (ng_hci_event_pkt_t *) buf; + ng_hci_inquiry_result_ep *ep = (ng_hci_inquiry_result_ep *)(e+1); + ng_hci_inquiry_response *ir; + struct bt_devinquiry *i; + int s, n; + time_t to; + + if (ii == NULL) { + errno = EINVAL; + return (-1); + } + + if (devname == NULL) { + memset(_devname, 0, sizeof(_devname)); + devname = _devname; + + n = bt_devenum(bt_devany_cb, _devname); + if (n <= 0) { + if (n == 0) + *ii = NULL; + + return (n); + } + } + + s = bt_devopen(devname); + if (s < 0) + return (-1); + + if (bt_devfilter(s, NULL, &f) < 0) { + bt_devclose(s); + return (-1); + } + + f.event_mask |= (1 << (NG_HCI_EVENT_INQUIRY_COMPL - 1)); + f.event_mask |= (1 << (NG_HCI_EVENT_INQUIRY_RESULT - 1)); + + if (bt_devfilter(s, &f, NULL) < 0) { + bt_devclose(s); + return (-1); + } + + if (lap == NULL) { + cp->lap[0] = 0x33; + cp->lap[1] = 0x8b; + cp->lap[2] = 0x9e; + } else { + cp->lap[0] = lap[0]; + cp->lap[1] = lap[1]; + cp->lap[2] = lap[2]; + } + + if (length <= 0 || length > 255) + length = 4; /* 5.12 seconds */ + cp->inquiry_length = (uint8_t) length; + + to = (time_t)((double) length * 1.28) + 1; + + if (num_rsp <= 0 || num_rsp > 255) + num_rsp = 8; + cp->num_responses = (uint8_t) num_rsp; + + i = *ii = calloc(num_rsp, sizeof(struct bt_devinquiry)); + if (i == NULL) { + bt_devclose(s); + errno = ENOMEM; + return (-1); + } + + if (bt_devsend(s, NG_HCI_OGF_LINK_CONTROL, NG_HCI_OCF_INQUIRY, + sizeof(*cp), cp) < 0) { + free(i); + bt_devclose(s); + return (-1); + } + +wait_for_more: + + n = bt_devrecv(s, buf, sizeof(buf), to); + if (n < 0) { + free(i); + bt_devclose(s); + return (-1); + } + + if (n < sizeof(ng_hci_event_pkt_t)) { + free(i); + bt_devclose(s); + errno = EIO; + return (-1); + } + + switch (e->event) { + case NG_HCI_EVENT_INQUIRY_COMPL: + break; + + case NG_HCI_EVENT_INQUIRY_RESULT: + ir = (ng_hci_inquiry_response *)(ep + 1); + +#undef MIN +#define MIN(a, b) (((a) < (b))? (a) : (b)) + + for (n = 0; n < MIN(ep->num_responses, num_rsp); n ++) { + bdaddr_copy(&i->bdaddr, &ir->bdaddr); + i->pscan_rep_mode = ir->page_scan_rep_mode; + i->pscan_period_mode = ir->page_scan_period_mode; + i->pscan_mode = ir->page_scan_mode; + memcpy(i->dev_class, ir->uclass, sizeof(i->dev_class)); + i->clock_offset = le16toh(ir->clock_offset); + + ir ++; + i ++; + num_rsp --; + } + /* FALLTHROUGH */ + + default: + goto wait_for_more; + /* NOT REACHED */ + } + + bt_devclose(s); + + return (i - *ii); +} + +int bt_devinfo(struct bt_devinfo *di) { union { @@ -53,6 +459,7 @@ struct ng_btsocket_hci_raw_node_debug r8; } rp; struct sockaddr_hci ha; + socklen_t halen; int s, rval; if (di == NULL) { @@ -60,27 +467,14 @@ return (-1); } - memset(&ha, 0, sizeof(ha)); - ha.hci_len = sizeof(ha); - ha.hci_family = AF_BLUETOOTH; - - if (bt_aton(di->devname, &rp.r1.bdaddr)) { - if (!bt_devname(ha.hci_node, &rp.r1.bdaddr)) - return (-1); - } else if (bt_dev2node(di->devname, ha.hci_node, - sizeof(ha.hci_node)) == NULL) { - errno = ENXIO; - return (-1); - } - - s = socket(PF_BLUETOOTH, SOCK_RAW, BLUETOOTH_PROTO_HCI); + s = bt_devopen(di->devname); if (s < 0) return (-1); rval = -1; - if (bind(s, (struct sockaddr *) &ha, sizeof(ha)) < 0 || - connect(s, (struct sockaddr *) &ha, sizeof(ha)) < 0) + halen = sizeof(ha); + if (getsockname(s, (struct sockaddr *) &ha, &halen) < 0) goto bad; strlcpy(di->devname, ha.hci_node, sizeof(di->devname)); @@ -138,7 +532,7 @@ rval = 0; bad: - close(s); + bt_devclose(s); return (rval); } @@ -205,6 +599,13 @@ return (count); } +static int +bt_devany_cb(int s, struct bt_devinfo const *di, void *xdevname) +{ + strlcpy((char *) xdevname, di->devname, HCI_DEVNAME_SIZE); + return (1); +} + static char * bt_dev2node(char const *devname, char *nodename, int nnlen) { Index: bluetooth.3 =================================================================== --- bluetooth.3 (revision 190870) +++ bluetooth.3 (working copy) @@ -25,7 +25,7 @@ .\" $Id: bluetooth.3,v 1.5 2003/05/20 23:04:30 max Exp $ .\" $FreeBSD$ .\" -.Dd February 13, 2009 +.Dd April 9, 2009 .Dt BLUETOOTH 3 .Os .Sh NAME @@ -41,6 +41,17 @@ .Nm bt_endprotoent , .Nm bt_aton , .Nm bt_ntoa , +.Nm bt_devaddr , +.Nm bt_devname , +.Nm bt_devinfo , +.Nm bt_devenum , +.Nm bt_devopen , +.Nm bt_devclose , +.Nm bt_devsend , +.Nm bt_devrecv , +.Nm bt_devreq , +.Nm bt_devfilter , +.Nm bt_devinquiry , .Nm bdaddr_same , .Nm bdaddr_any , .Nm bdaddr_copy @@ -84,6 +95,20 @@ .Ft int .Fn bt_devenum "bt_devenum_cb_t *cb" "void *arg" .Ft int +.Fn bt_devopen "char const *devname" +.Ft int +.Fn bt_devclose "int s" +.Ft int +.Fn bt_devsend "int s" "uint16_t ogf" "uint16_t ocf" "int plen" "void *param" +.Ft int +.Fn bt_devrecv "int s" "uint8_t *buf" "int size" "time_t to" +.Ft int +.Fn bt_devreq "int s" "struct bt_devreq *r" "time_t to" +.Ft int +.Fn bt_devfilter "int s" "struct bt_devfilter const *new" "struct bt_devfilter *old" +.Ft int +.Fn bt_devinquiry "char const *devname" "int length" "int num_rsp" "uint8_t const *lap" "struct bt_devinquiry **ii" +.Ft int .Fn bdaddr_same "const bdaddr_t *a" "const bdaddr_t *b" .Ft int .Fn bdaddr_any "const bdaddr_t *a" @@ -311,6 +336,219 @@ or -1 if an error occurred. .Pp The +.Fn bt_devopen +function opens Bluetooth device with the given +.Fa devname +and returns connected and bound +.Dv HCI +socket. +The function returns -1 if an error has occurred. +.Pp +The +.Fn bt_devclose +closes passed +.Dv HCI +socket +.Fa s , +previously obtained with +.Xr bt_devopen 3 . +.Pp +The +.Fn bt_devsend +function sends Bluetooth +.Dv HCI +command with the OpCode Group Field +.Fa ogf +and +OpCode Command Field +.Fa ocf +to the provided socket +.Fa s , +previously obtained with +.Xr bt_devopen 3 . +The +.Fa plen +and +.Fa param +parameters specify command parameters. +The function returns 0 on success, +or -1 if an error occurred. +.Pp +The +.Fn bt_devrecv +function receives one Bluetooth +.Dv HCI +event packet from the socket +.Fa s , +previously obtained with +.Xr bt_devopen 3 . +The event packet is placed into the provided buffer +.Fa buf +of size +.Fa size . +The +.Fa to +parameter specifies receive timeout in seconds. +The function returns total number of bytes recevied, +or -1 if an error occurred. +.Pp +The +.Fn bt_devreq +function makes Bluetooth +.Dv HCI +request to the socket +.Fa s , +previously obtained with +.Xr bt_devopen 3 . +The function will send the specified command and will wait for the specified +event, +or timeout +.Fa to +seconds to occur. +The +.Vt bt_devreq +structure is defined as follows +.Bd -literal -offset indent +struct bt_devreq +{ + uint16_t ogf; + uint16_t ocf; + int event; + void *cparam; + int clen; + void *rparam; + int rlen; +}; +.Ed +.Pp +The +.Fa ogf +and +.Fa ocf +fields specify OpCode Group and Command Field respectively. +The +.Fa cparam +and +.Fa clen +fields specify command parameters data and command parameters data size +respectively. +The +.Fa event +field specifies which Bluetooth +.Dv HCI +event ID the function should wait for. +The +.Fa rparam +and +.Fa rlen +parameters specify buffer and buffer size respectively where return +parameters should be placed. +The function returns 0 on success, or -1 if an error occurred. +.Pp +The +.Fn bt_devfilter +controls the local +.Dv HCI +filter associated with the socket +.Fa s , +previously obtained with +.Xr bt_devopen 3 . +Filtering can be done on packet types, i.e. +.Dv ACL , +.Dv SCO or +.Dv HCI +event packets, and, in addition, on +.Dv HCI +event IDs. +Before applying +.Fa new +filter (if provided) the function will try to obtain current filter +from the socket +.Fa s +and place it into the +.Fa old +parameter (if provided). +The +.Vt bt_devfilter +structure is defined as follows +.Bd -literal -offset indent +struct bt_devfilter { + uint64_t event_mask; + uint8_t packet_mask; +}; +.Ed +.Pp +Both +.Fa event_mask +and +.Fa packet_mask +fields are bit masks. +If a bit +.Fa N +is cleared in the +.Fa event_mask +then the corresponding Bluetooth +.Dv HCI +event ID +.Fa N +is filtered out. +If a bit +.Fa N +is cleared in the +.Fa packet_mask +then all the packets with the corresponding packet indicator are filtered out. +The function returns 0 on success, or -1 if an error occurred. +.Pp +The +.Fn bt_devinquiry +function performs Bluetooth inquiry. +The +.Fa devname +parameter specifies which local Bluetooth device should perform an inquiry. +If not secified, i.e. +.Dv NULL , +then first available device will be used. +The +.Fa length +parameters specifies the total length of an inquiry in 1.28 second units. +If not specified, i.e. 0, default value will be used. +The +.Fa num_rsp +parameter specifies the number of responses that can be received before +the inquiry is halted. +If not specified, i.e. 0, default value will be used. +The +.Fa lap +parameter contains the LAP from which the inquiry access code will be +be derived when the inquiry procedure is made. +If not specified, i.e. +.Dv NULL , +then GIAC LAP 9e:8b:33 will be used. +The +.Fa ii +parameter specifies where to place inquiry results. +On success, the function will return total number of inquiry results, +will allocate buffer to store all the inquiry results and +will return pointer to the allocated buffer in the +.Fa ii +parameter. +It is up to the caller of the function to dispose of the buffer. +The function returns -1 if an error has occurred. +The +.Vt bt_devinquiry +structure is defined as follows +.Bd -literal -offset indent +struct bt_devinquiry { + bdaddr_t bdaddr; + uint8_t pscan_rep_mode; + uint8_t pscan_period_mode; + uint8_t pscan_mode; + uint8_t dev_class[3]; + uint16_t clock_offset; +}; +.Ed +.Pp +The .Fn bdaddr_same , .Fn bdaddr_any and Index: bluetooth.h =================================================================== --- bluetooth.h (revision 190870) +++ bluetooth.h (working copy) @@ -39,6 +39,7 @@ #include #include #include +#include #include #include #include @@ -46,6 +47,7 @@ #include #include #include +#include __BEGIN_DECLS @@ -129,8 +131,42 @@ uint8_t _padding[20]; /* leave space for future additions */ }; +struct bt_devreq +{ + uint16_t ogf; + uint16_t ocf; + int event; + void *cparam; + int clen; + void *rparam; + int rlen; +}; + +struct bt_devfilter { + uint64_t event_mask; + uint8_t packet_mask; +}; + +struct bt_devinquiry { + bdaddr_t bdaddr; + uint8_t pscan_rep_mode; + uint8_t pscan_period_mode; + uint8_t pscan_mode; + uint8_t dev_class[3]; + uint16_t clock_offset; +}; + typedef int (bt_devenum_cb_t)(int, struct bt_devinfo const *, void *); +int bt_devopen (char const *devname); +int bt_devclose(int s); +int bt_devsend (int s, uint16_t ogf, uint16_t ocf, int plen, void *param); +int bt_devrecv (int s, uint8_t *buf, int size, time_t to); +int bt_devreq (int s, struct bt_devreq *r, time_t to); +int bt_devfilter(int s, struct bt_devfilter const *new, + struct bt_devfilter *old); +int bt_devinquiry(char const *devname, int length, int num_rsp, + uint8_t const *lap, struct bt_devinquiry **ii); int bt_devinfo (struct bt_devinfo *di); int bt_devenum (bt_devenum_cb_t *cb, void *arg); Index: Makefile =================================================================== --- Makefile (revision 190870) +++ Makefile (working copy) @@ -33,6 +33,13 @@ MLINKS+= bluetooth.3 bt_devinfo.3 MLINKS+= bluetooth.3 bt_devenum.3 +MLINKS+= bluetooth.3 bt_devopen.3 +MLINKS+= bluetooth.3 bt_devclose.3 +MLINKS+= bluetooth.3 bt_devsend.3 +MLINKS+= bluetooth.3 bt_devreq.3 +MLINKS+= bluetooth.3 bt_devfilter.3 +MLINKS+= bluetooth.3 bt_devinquiry.3 + MLINKS+= bluetooth.3 bdaddr_same.3 MLINKS+= bluetooth.3 bdaddr_any.3 MLINKS+= bluetooth.3 bdaddr_copy.3 From maksim.yevmenkin at gmail.com Thu Apr 9 16:26:15 2009 From: maksim.yevmenkin at gmail.com (Maksim Yevmenkin) Date: Thu Apr 9 16:26:22 2009 Subject: obexapp 1.4.11 Message-ID: dear freebsd-bluetooth@ users, new version of obexapp can be download from http://www.geocities.com/m_evmenkin/obexapp-1.4.11.tar.gz it includes minor enhancement submitted by Mikhail T to remove warning about use mktemp(). Guido, please update the port. thanks, max From maksim.yevmenkin at gmail.com Thu Apr 9 16:29:11 2009 From: maksim.yevmenkin at gmail.com (Maksim Yevmenkin) Date: Thu Apr 9 16:29:20 2009 Subject: per-user directories for obexapp-server In-Reply-To: <49DE5ECD.8020202@aldan.algebra.com> References: <49DE5ECD.8020202@aldan.algebra.com> Message-ID: On Thu, Apr 9, 2009 at 12:47 PM, Mikhail T. wrote: > Hello! > > I was able to set up the Bluetooth-server on my main machine, but all of > the pushed files (Object Pushed) end up in the same root-only > directory... Is there, perhaps, a way to specify a different directory > for each device-ID (bdaddr)? not at the moment, no. > This way my wife and myself could have our own locations (under > home-directories somewhere) for pictures and phonebook backups... > Please, advise. Thanks! Yours, > > -mi > > P.S. Maybe, the hcsecd.conf's syntax can be extended to (optionally) > provide the username associated with each listed device? technically, nothing prevents you from running 2 or more instances of obexapp on different rfcomm channels. each instance would use different root directory. this way each of you can use service on different rfcomm channel to push data into appropriate root directory. i guess its possible to teach obexapp about mapping between client's bdaddr and root directory. ideally, of course, what we want it to have credentials passed over obex connection so the server knows exactly who is talking to it, but that, of course, will likely never happen :) thanks, max From plunky at rya-online.net Fri Apr 10 00:53:36 2009 From: plunky at rya-online.net (Iain Hibbert) Date: Fri Apr 10 00:53:43 2009 Subject: per-user directories for obexapp-server In-Reply-To: <49DE5ECD.8020202@aldan.algebra.com> References: <49DE5ECD.8020202@aldan.algebra.com> Message-ID: <1239348904.587506.623.nullmailer@galant.ukfsn.org> On Thu, 9 Apr 2009, Mikhail T. wrote: > I was able to set up the Bluetooth-server on my main machine, but all of > the pushed files (Object Pushed) end up in the same root-only > directory... Is there, perhaps, a way to specify a different directory > for each device-ID (bdaddr)? One trouble with bluetooth is that there is no finer identification than 'device id', so a multi-user system ends up allowing access by any user to all known devices. This could likely be worked out, I had some ideas about passing credentials around in the kernel to enable marking baseband links as private, and having link keys stored in users home directories but I'm not sure the effort required would be worth it as most systems (with bluetooth) are single-concurrent-user anyway. In the meantime I would suggest something like having a ttyaction(5) mechanism to arrange something like mount -t null -o mount /var/obex ${HOME}/obex for the user logged in on the console, so that incoming files are stored the logged in users directory (or /var/obex if nobody is logged in at the console). iain From plunky at rya-online.net Fri Apr 10 02:22:57 2009 From: plunky at rya-online.net (Iain Hibbert) Date: Fri Apr 10 02:23:03 2009 Subject: libhci update In-Reply-To: References: <49D92E26.2030508@incunabulum.net> <49DD40E2.5030403@incunabulum.net> <1239264003.862926.638.nullmailer@galant.ukfsn.org> <49DE4E2F.2000805@incunabulum.net> <49DE6D42.6000004@incunabulum.net> Message-ID: <1239355286.061485.927.nullmailer@galant.ukfsn.org> On Thu, 9 Apr 2009, Maksim Yevmenkin wrote: > On Thu, Apr 9, 2009 at 1:48 PM, Bruce Simpson wrote: > > If you look at all the APIs, they all end up building > > on-the-wire-records to advertise Bluetooth services via SDP -- be that > > L2CAP PSM's, RFCOMM channels, or anything else. > > oh, no :) please do not open this can of worms :) Iain knows its a > very touchy subject with me :) i dislike *intensely* the fact that sdp > records have to be built in on-the-wire format. My view is that the on-the-wire format is not actually difficult to deal with :) Certainly at the C level where the application programmer must generally deal with data formats directly anyway. and the library handles the common cases of talking to a remote device and fetching the data you need. Yes, I think it is desireable to create a higher level object oriented class based API (Python, C++, Java etc) but any such can build on top of the data manipulation primitives I've made (and, I'm a C programmer so its likely not me that will define it :) or use its own. > yes, we can. i was actually thinking to merge add the sdp stuff into > libbluetooth. definitely this and its not that difficult. > and while i'm at it, move bluetooth.h and sdp.h into > include/bluetooth/ where it belongs. this is more problematic though and I don't think it is necessary. Because include files are only needed at build time, the fact of filenames conflicting with build environment can be worked around by adjusting the build environment. (I think pkgsrc manages this, if ports does not then it needs to :) > so just enumerate all the radios (i.e. list all the hci nodes - there > is an ioctl for that) and unit will be the number before "hci" part of > the name. however, that will only work if if have devices of the same > type in the system (i.e. all the radios are bluetooth dongles). > otherwise, if, say, you have 3com pccard and bluetooth dongle, then > you will have btccc0hci and ubt0hci nodes both having 0 unit number. You could just use the enumeration number as device_id although that causes problems if you remove a device as it will change. Or, just assign an incrementing number for every device inserted. I don't see why you would have to care that the device had previously been inserted or try to produce the same number, as trying to manage a truly dynamic hardware configuration is going to be a nightmare however you look at it and I don't see why we should try to encourage it :) (nor do I concede in any way that requiring the user to *ever* provide a 'device number' is a good idea) > >> that is the thing. bd_addr is the only "unique" (it can't be easily > >> changed, but it still can be done) thing about bluetooth device. but > >> in order to get it, you need to address the device somehow. devname is > >> better, but still does not solve the problem as you pointed out it can > >> change. > > > > It would be nice to have a 'device name' registry or be able to > > renumber/rename the Bluetooth > > dongles in a manner similar to that of what folk end up doing for FreeBSD > > using ifconfig(8). > > i'm not quite follow why is that needed? In fact FreeBSD does this already, you can just list the device bdaddr in your hosts file and refer to it as 'white_dongle' or whatever you want. I resisted this in NetBSD (somebody asked for it once but he didn't want to have a public discussion about it so I remained unconvinced) and provided bt_devaddr(3) as I think it could be better to have a generic method of device mapping rather than a bunch of subsystem hacks. > > and also, BlueCove/JSR-82, PyBlueZ and the other high level language > > stuff currently wants to use dev_id as the unique endpoint identifier. > > :-( I don't understand this; BlueCove is a Java API and I don't see (looking at the website) anything about device_id in the API? They provide LocalDevice class which is obscure - it might use dev_id internally in the BlueZ module but surely the Windows module does not? iain From plunky at rya-online.net Fri Apr 10 03:32:00 2009 From: plunky at rya-online.net (Iain Hibbert) Date: Fri Apr 10 03:32:07 2009 Subject: Speeding up device discovery: paper In-Reply-To: <49DE4F44.8070707@incunabulum.net> References: <49D92E26.2030508@incunabulum.net> <49DD40E2.5030403@incunabulum.net> <1239264003.862926.638.nullmailer@galant.ukfsn.org> <49DE4F44.8070707@incunabulum.net> Message-ID: <1239359436.893706.893.nullmailer@galant.ukfsn.org> On Thu, 9 Apr 2009, Bruce Simpson wrote: > I keep wishing Bluetooth had passive scanning like 802.11 does. I always thought that was what 'periodic inquiry' was supposed to be, that a device would take time every n seconds to do a quick neighbor discovery.. btw something you might run into with multiple radios is that I find creating a baseband link does not always work first time, but if the radio has seen the other device recently via inquiry, it can connect very quickly. I'm not sure if this is the radio recording some information about the remote device or that the OS reusing the clock offset is helping, but neither will help if you have one radio doing the inquiry and the other doing the paging.. iain From bms at incunabulum.net Fri Apr 10 11:14:39 2009 From: bms at incunabulum.net (Bruce M Simpson) Date: Fri Apr 10 11:14:51 2009 Subject: Speeding up device discovery: paper In-Reply-To: <1239359436.893706.893.nullmailer@galant.ukfsn.org> References: <49D92E26.2030508@incunabulum.net> <49DD40E2.5030403@incunabulum.net> <1239264003.862926.638.nullmailer@galant.ukfsn.org> <49DE4F44.8070707@incunabulum.net> <1239359436.893706.893.nullmailer@galant.ukfsn.org> Message-ID: <49DF8C8C.5030006@incunabulum.net> Iain Hibbert wrote: > On Thu, 9 Apr 2009, Bruce Simpson wrote: > >> I keep wishing Bluetooth had passive scanning like 802.11 does. >> > I always thought that was what 'periodic inquiry' was supposed to be, that > a device would take time every n seconds to do a quick neighbor > discovery.. > Over the last 24 hours or so I've digested a number of interesting research papers on the subject... What is clear is that inquiry is perhaps the most expensive Bluetooth operation possible, in terms of power and baseband behaviour. The IrDA paper makes it clear that session initiation may still be faster if you can communicate the BD_ADDR of the station using a side-band, in this case, IrDA. However, this paper makes it clear that the 10.24s inquiry time quoted by the Bluetooth spec may be excessive, although their figures are based on models, not experimental work: Bluetooth Inquiry Time Characterization and Selection Peterson/Baldwin/Kharoufeh http://netlab.cs.ucla.edu/wiki/files/01661527.pdf In this paper they do mention that using the Bluetooth V1.2 'Interlaced Inquiry Scan' feature can improve discovery times for each station by several *seconds*, but of course making sure it's turned on depends entirely on your station vendor. ...of course, every time you want to turn stuff like this on, you are either relying on the host controller to turn it on for you, or you need to send explicit HCI commands to the HC to bring it up (Errg, libhci!) It is also a pity that you never get an HCI event from your HC when it enters Inquiry Scan state and actually responds to an inquiry -- but then again the Inquiry protocol is asymmetric, it's not peer-to-peer, it's a bit like ARP without any source/destination field on a purely broadcast medium, and you never get to find out anything about who inquires or why, so Inquiry presents no opportunities for passive endpoint discovery. Yuck. I haven't read far enough to determine what kind of admission control exists, if any, for preventing pre-V1.2 station(s) from forming an adjacency with the station(s) which one is running. It seems analogous to the problems caused by admitting 802.11b STAs into an 802.11a/g capable ESS. It would be nice if EDR use could be forced, although obviously that isn't backwards compatible. Of course, if any of this stuff were easy, everyone would be doing it by now. > btw something you might run into with multiple radios is that I find > creating a baseband link does not always work first time, but if the radio > has seen the other device recently via inquiry, it can connect very > quickly. I'm not sure if this is the radio recording some information > about the remote device or that the OS reusing the clock offset is > helping, but neither will help if you have one radio doing the inquiry and > the other doing the paging.. > That probably needs further inspection at baseband layer to figure out what's going on. It is more than likely that the host controller is tracking some info about the peer in some internal cache... I find that PalmOS 5 devices always try to inquire first, even if you have an entry in the persistent neighbour cache with which you could page. Paging is always more efficient than Inquiry, but of course requires you know the BD_ADDR. Nokia Series 60 handsets seem to do much better and can page with what they already have, in fact, out of most of the handsets I've worked with, Nokia's Bluetooth support seems more mature. cheers BMS From bms at incunabulum.net Sat Apr 11 11:01:12 2009 From: bms at incunabulum.net (Bruce Simpson) Date: Sat Apr 11 11:01:19 2009 Subject: BlueZ dbus binding is device dependent In-Reply-To: <49DF8C8C.5030006@incunabulum.net> References: <49D92E26.2030508@incunabulum.net> <49DD40E2.5030403@incunabulum.net> <1239264003.862926.638.nullmailer@galant.ukfsn.org> <49DE4F44.8070707@incunabulum.net> <1239359436.893706.893.nullmailer@galant.ukfsn.org> <49DF8C8C.5030006@incunabulum.net> Message-ID: <49E0DAE2.3030805@incunabulum.net> Hi, So, just to confirm, I read the code examples in this BlueZ Wiki article; and it does indeed appear that the naming convention BlueZ uses in DBus is BlueZ dependent:- http://wiki.bluez.org/wiki/HOWTO/DiscoveringDevices Hmmm! Whatever do we do about that. BMS From plunky at rya-online.net Sat Apr 11 12:07:21 2009 From: plunky at rya-online.net (Iain Hibbert) Date: Sat Apr 11 12:07:28 2009 Subject: BlueZ dbus binding is device dependent In-Reply-To: <49E0DAE2.3030805@incunabulum.net> References: <49D92E26.2030508@incunabulum.net> <49DD40E2.5030403@incunabulum.net> <1239264003.862926.638.nullmailer@galant.ukfsn.org> <49DE4F44.8070707@incunabulum.net> <1239359436.893706.893.nullmailer@galant.ukfsn.org> <49DF8C8C.5030006@incunabulum.net> <49E0DAE2.3030805@incunabulum.net> Message-ID: <1239476754.337291.1460.nullmailer@galant.ukfsn.org> On Sat, 11 Apr 2009, Bruce Simpson wrote: > So, just to confirm, I read the code examples in this BlueZ Wiki article; > and it does indeed appear that the naming convention BlueZ uses in DBus is > BlueZ dependent:- > http://wiki.bluez.org/wiki/HOWTO/DiscoveringDevices first, that API may not be the same as the BlueZ 4.x series which I think has been evolving. The wiki main page says the API is best defined by the documents in the bluez releases. second, I'm not sure if it is a total disaster. The device being referenced is at least a string "/org/bluez/hci0" rather than the integer dev_id that the bluez library uses. That can be worked on fairly easily, plus I might have seen a comment on the bluez list (which I subscribed to but don't really read) that hardcoded defs are to be frowned on, I think there is a way to find local devices. Then, the module name "org.bluez" is probably not needed to change - for instance if a GNOME application uses the bluez dbus module, I think that all calls end up passed to bluetoothd and 'all that is required' is having a bluez compatible bluetoothd. Whether the bluez bluetoothd can be easily patched or if a fork or a rewrite is required I don't know. I looked at the bluez sources but I don't have any context for it so am just confused.. iain From maksim.yevmenkin at gmail.com Mon Apr 13 16:40:14 2009 From: maksim.yevmenkin at gmail.com (Maksim Yevmenkin) Date: Mon Apr 13 17:20:05 2009 Subject: RFC: obexapp - virtual root folder for each device Message-ID: dear freebsd-bluetooth@ users, please find attached patch to obexapp port that add new feature of virtualizing root folder for each client device. this work was inspired by and based work done by 'mi' < mi -plus- thun -at- aldan -dot- algebra -dot- com> background various bluetooth obex profiles do not have notion of user credentials. that could be very inconvenient when multiple client devices want to store data on the same obex sever. the example that was given to me by 'mi' is when two people (say, wife and husband) want to back up their complete phone books onto the same server. the problem is that most devices use some well known name, such as phonebook.vcf and there is no obvious way to override it. how does it work obexapp now has new options '-R' that would turn new feature on. first, default root folder is set as it was before (see man page '-r' option). when client is connected, client's bd_addr is resolved to a human readable name via bt_gethostbyaddr(3) call. if bd_addr is resolved then obexapp will check for the subdirectory, under current root, with the resolved name. if name was not resolved or resolution has failed, then obexapp will look for a subdirectory that matches client's bd_addr, i.e. '01:02:03:04:05:06'. if that fails, then obexapp will look for "default" subdirectory. if later fails as well, connection is terminated. if virtual root is found, obexapp will chroot(2) into it. possible setup - create 'obex' user and 'obex' group - create '/var/spool/obex' (or whatever you want for default root) owned by 'obex' user/group - user 'foo' creates ~/private directory under his home directory with 0700 permissions - admin setups 'obex' directory under foo's ~/private/ directory with 0770 permissions, this directory is owned by 'obex' user. group is set to foo's group - admin setups symlink in /var/spool/obex/ called 'foo_cell' that points to ~foo/private/obex - admin adds entry in the /etc/bluetooth/hosts file to assign 'foo_cell' foo's cell phone bd_addr - admin run obexapp server as 'obexapp -s -r /var/spool/obex -R -u obex -C 1' every time foo's uses cell phone to send data to the obex server, the data will end up in foo's ~/private/obex directory. please give it a try and let me know it works. thanks, max -------------- next part -------------- Index: main.c =================================================================== RCS file: /usr/local/cvs/ports/obexapp/main.c,v retrieving revision 1.13 diff -u -r1.13 main.c --- main.c 23 Apr 2007 18:29:18 -0000 1.13 +++ main.c 13 Apr 2009 21:25:08 -0000 @@ -65,7 +65,7 @@ { struct sigaction sa; char *ep = NULL, *pri_name = NULL; - int n, service, noninteractive; + int n, service, noninteractive, detach; context_t context; obex_ctrans_t custfunc; @@ -83,7 +83,7 @@ /* Prepare context */ memset(&context, 0, sizeof(context)); context.tfd = context.sfd = -1; - context.detach = 1; + detach = 1; context.ls_size = OBEXAPP_BUFFER_SIZE; if ((context.ls = (char *) malloc(context.ls_size)) == NULL) @@ -119,7 +119,7 @@ /* Process command line options */ service = noninteractive = 0; - while ((n = getopt(argc, argv, "a:A:cC:dDfhl:m:nr:Ssu:")) != -1) { + while ((n = getopt(argc, argv, "a:A:cC:dDfhl:m:nr:RsSu:")) != -1) { switch (n) { case 'a': if (!bt_aton(optarg, &context.raddr)) { @@ -180,7 +180,7 @@ break; case 'd': /* do not detach server */ - context.detach = 0; + detach = 0; break; case 'D': /* use stdin/stdout */ @@ -217,6 +217,11 @@ err(1, "Could not realpath(%s)", optarg); break; + case 'R': /* virtualize root for each device */ + context.vroot = 1; + context.secure = 1; + break; + case 's': /* server */ if (noninteractive) usage(basename(argv[0])); @@ -269,23 +274,10 @@ log_open("obexapp", pri_name, 0); /* Detach server (if required) */ - if (context.server && context.detach) { - pid_t pid = fork(); - - if (pid == (pid_t) -1) { - log_err("%s(): Could not fork. %s (%d)", - __func__, strerror(errno), errno); - exit(1); - } - - if (pid != 0) - exit(0); - - if (daemon(0, 0) < 0) { - log_err("%s(): Could not daemon. %s (%d)", - __func__, strerror(errno), errno); - exit(1); - } + if (context.server && detach && daemon(0, 0) < 0) { + log_err("%s(): Could not daemon. %s (%d)", + __func__, strerror(errno), errno); + exit(1); } /* Initialize OBEX */ Index: obexapp.1 =================================================================== RCS file: /usr/local/cvs/ports/obexapp/obexapp.1,v retrieving revision 1.15 diff -u -r1.15 obexapp.1 --- obexapp.1 21 May 2007 15:55:35 -0000 1.15 +++ obexapp.1 13 Apr 2009 23:15:03 -0000 @@ -54,7 +54,7 @@ .Ar parameters .Nm .Fl s -.Op Fl dDSh +.Op Fl dDSRh .Op Fl A Ar BD_ADDR .Fl C Ar channel .Op Fl m Ar MTU @@ -193,6 +193,12 @@ Defaults to the maximum supported value. .It Fl n Work in the non-interactive client mode. +.It Fl R +Virtualize root folder for each client device in server mode. +Will automatically turn on secure mode, i.e. +.Fl S +option. +Please read section below for a complete description. .It Fl r Ar path Specify root folder. Default root folder in the server mode is @@ -216,6 +222,43 @@ The value specified may be either a username or a numeric user id. This only works if server was started as root. .El +.Sh VIRTUAL ROOT FOLDERS +When accepting connections in server mode, +.Nm +will attempt to find a subdirectory that would act as a virtual root +folder for the connecting device. +Virtual root folders must reside under default root folder which is set +with +.Fl r +option. +The rules are as follows: +.Bl -enum -offset indent -compact +.It +.Nm +will try to resolve connecting device's BD_ADDR using +.Xr bt_gethostbyaddr 3 +call and check for a subdirectory that matches resolved name (if any); +.It +.Nm +will check for a subdirectory that matches connecting device's BD_ADDR; +.It +.Nm +will check for a subdirectory, named +.Dq default ; +.El +If none of the above matches, then the connection to the client is terminated. +Otherwise, +.Nm +will change default root folder the the found subdirectory. +This allows the same system to intelligently distinguish different +client devices as belonging to different users. +An administrator can set up the subdirectories for +known devices under +.Pa /var/spool/obex +(or wherever, see +.Fl r +option) for each user, or even as symlinks to each user's home directory +(or a subdirectory thereof). .Sh LOCALE SUPPORT The .Nm @@ -325,6 +368,13 @@ .Dv ANY address and RFCOMM channel .Li 1 . +.It ln -s Ar /home/wallaby Ar /var/spool/obex/00:01:02:03:04:05 +.It chown -h wallaby Ar /var/spool/obex/00:01:02:03:04:05 +Whenever the device with BD_ADDR of 00:01:02:03:04:05 connects, +.Nm +running in server mode will switch to user ID +.Ar wallaby +and use their home directory as the top-level for the connection. .El .Ss Level 1 Information Access The first level involves the basic ability to put an object (such as a vCard) Index: obexapp.h =================================================================== RCS file: /usr/local/cvs/ports/obexapp/obexapp.h,v retrieving revision 1.9 diff -u -r1.9 obexapp.h --- obexapp.h 23 Apr 2007 18:29:18 -0000 1.9 +++ obexapp.h 13 Apr 2009 21:22:29 -0000 @@ -87,8 +87,8 @@ unsigned server : 1; /* server mode? */ unsigned secure : 1; /* secure mode? */ unsigned done : 1; /* done? */ - unsigned detach : 1; /* detach server? */ unsigned fbs : 1; /* Folder Browsing Service */ + unsigned vroot : 1; /* virtualize device's root */ unsigned reserved : 2; /* local SDP session (server only) */ Index: server.c =================================================================== RCS file: /usr/local/cvs/ports/obexapp/server.c,v retrieving revision 1.11 diff -u -r1.11 server.c --- server.c 9 Apr 2009 23:16:31 -0000 1.11 +++ server.c 13 Apr 2009 23:10:31 -0000 @@ -1,6 +1,8 @@ /* * server.c - * + */ + +/*- * Copyright (c) 2002 Maksim Yevmenkin * All rights reserved. * @@ -89,6 +91,11 @@ static char const * const ls_parent_folder = "\n"; +static int obexapp_server_set_root + (context_p context); +static int obexapp_server_set_device_root + (context_p context); + /* OBEX request handlers */ static obexapp_request_handler_t obexapp_server_request_connect; static obexapp_request_handler_t obexapp_server_request_disconnect; @@ -114,7 +121,6 @@ obexapp_server(obex_t *handle) { context_p context = (context_p) OBEX_GetUserData(handle); - struct passwd *pw = NULL; int error = -1; struct sockaddr_rfcomm addr; @@ -131,26 +137,6 @@ goto done; } - if (context->user != NULL) { - if (atoi(context->user) != 0) - pw = getpwuid(atoi(context->user)); - else - pw = getpwnam(context->user); - - if (pw == NULL) { - log_err("%s(): Unknown user %s", __func__, - context->user); - goto done; - } - } - - if (context->root[0] == '\0') { - if (pw == NULL) - strlcpy(context->root, OBEXAPP_ROOT_DIR, PATH_MAX); - else - strlcpy(context->root, pw->pw_dir, PATH_MAX); - } - log_info("%s: Starting OBEX server", __func__); if (OBEX_SetTransportMTU(handle, context->mtu, context->mtu) < 0) { @@ -162,7 +148,7 @@ addr.rfcomm_len = sizeof(addr); addr.rfcomm_family = AF_BLUETOOTH; addr.rfcomm_channel = context->channel; - memcpy(&addr.rfcomm_bdaddr, &context->raddr, sizeof(context->raddr)); + memcpy(&addr.rfcomm_bdaddr, &context->laddr, sizeof(context->laddr)); if (OBEX_ServerRegister(handle, (struct sockaddr *) &addr, sizeof(addr)) < 0) { @@ -170,40 +156,8 @@ goto done; } - if (getuid() == 0) { - if (context->secure) { - if (chroot(context->root) < 0) { - log_err("%s(): Could not chroot(%s). %s (%d)", - __func__, context->root, - strerror(errno), errno); - goto done; - } - - strlcpy(context->root, "/", PATH_MAX); - } - - if (pw != NULL) { - if (setgid(pw->pw_gid) < 0) { - log_err("%s(): Could not setgid(%d). %s (%d)", - __func__, pw->pw_gid, strerror(errno), - errno); - goto done; - } - - if (setuid(pw->pw_uid) < 0) { - log_err("%s(): Could not setuid(%d). %s (%d)", - __func__, pw->pw_uid, strerror(errno), - errno); - goto done; - } - } - } - - if (chdir(context->root) < 0) { - log_err("%s(): Could not chdir(%s). %s (%d)", - __func__, context->root, strerror(errno), errno); + if (obexapp_server_set_root(context) < 0) goto done; - } log_debug("%s(): Entering event processing loop...", __func__); @@ -227,6 +181,155 @@ } /* obexapp_server */ /* + * Set server root + */ + +int +obexapp_server_set_root(context_p context) +{ + struct passwd *pw; + char *ep; + uid_t uid; + + if (context->user != NULL) { + uid = strtoul(context->user, &ep, 10); + if (*ep != '\0') + pw = getpwnam(context->user); + else + pw = getpwuid(uid); + + if (pw == NULL) { + log_err("%s(): Unknown user %s", + __func__, context->user); + return (-1); + } + + log_debug("%s(): Requested to run as '%s' uid=%d, gid=%d", + __func__, context->user, pw->pw_uid, pw->pw_gid); + } else + pw = NULL; + + /* Set default root */ + if (context->root[0] == '\0') { + if (pw == NULL) + strlcpy(context->root, OBEXAPP_ROOT_DIR, PATH_MAX); + else + strlcpy(context->root, pw->pw_dir, PATH_MAX); + } + + if (chdir(context->root) < 0) { + log_err("%s(): Could not chdir(%s). %s (%d)", + __func__, context->root, strerror(errno), errno); + return (-1); + } + + /* Set device specific root */ + if (context->vroot && obexapp_server_set_device_root(context) <= 0) + return (-1); + + log_debug("%s(): Using root %s", __func__, context->root); + + if (context->secure) { + if (chroot(context->root) < 0) { + log_err("%s(): Could not chroot(%s). %s (%d)", + __func__, context->root, + strerror(errno), errno); + return (-1); + } + + strlcpy(context->root, "/", PATH_MAX); + + log_debug("%s(): Secure mode enabled", __func__); + } + + if (pw != NULL) { + if (setgid(pw->pw_gid) < 0) { + log_err("%s(): Could not setgid(%d). %s (%d)", + __func__, pw->pw_gid, strerror(errno), errno); + return (-1); + } + + if (setuid(pw->pw_uid) < 0) { + log_err("%s(): Could not setuid(%d). %s (%d)", + __func__, pw->pw_uid, strerror(errno), errno); + return (-1); + } + + log_debug("%s(): Running as uid=%d, gid=%d", + __func__, getuid(), getgid()); + } + + return (0); +} /* obexapp_server_set_root */ + +/* + * Set device specific server root + */ + +static int +obexapp_server_set_device_root(context_p context) +{ + char const *root[] = { NULL, NULL, NULL }; + struct hostent *he; + struct stat sb; + int n; + + n = 0; + + he = bt_gethostbyaddr((char const *) &context->raddr, + sizeof(bdaddr_t), AF_BLUETOOTH); + if (he != NULL) + root[n ++] = (char const *) he->h_name; + + root[n ++] = bt_ntoa(&context->raddr, NULL); + + root[n ++] = "default"; + + for (n = 0; n < 3; n ++) { + if (root[n] == NULL) + break; + + log_debug("%s(): Checking for %s/%s subdirectory", + __func__, context->root, root[n]); + + if (stat(root[n], &sb) < 0) { + if (errno == ENOENT) + continue; + + log_err("%s(): Could not lstat(%s/%s). %s (%d)", + __func__, context->root, root[n], + strerror(errno), errno); + + return (-1); + } + + if (!S_ISDIR(sb.st_mode)) { + log_debug("%s(): Ignoring %s/%s. Not a directory", + __func__, context->root, root[n]); + continue; + } + + strlcat(context->root, "/", PATH_MAX); + strlcat(context->root, root[n], PATH_MAX); + + if (chdir(root[n]) < 0) { + log_err("%s(): Could not chdir(%s). %s (%d)", + __func__, context->root, strerror(errno), + errno); + return (-1); + } + + return (1); + } + + log_notice("%s(): Could not set device specific root for the device " \ + "bdaddr %s (%s)", __func__, root[1], + root[0]? root[0] : "-no-name-"); + + return (0); +} /* obexapp_server_set_device_root */ + +/* * Process OBEX_EV_REQHINT event */ @@ -565,6 +668,15 @@ } } + if (chmod(context->temp, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP) < 0) { + log_err("%s(): Could not chmod(%s). %s (%d)", + __func__, context->temp, + strerror(errno), errno); + + codes = obexapp_util_errno2response(errno); + goto done; + } + if (rename(context->temp, context->file) < 0) { log_err("%s(): Could not rename(%s, %s). %s (%d)", __func__, context->temp, Index: transport.c =================================================================== RCS file: /usr/local/cvs/ports/obexapp/transport.c,v retrieving revision 1.13 diff -u -r1.13 transport.c --- transport.c 21 May 2007 15:55:35 -0000 1.13 +++ transport.c 10 Apr 2009 18:26:08 -0000 @@ -280,6 +280,9 @@ return (-1); } + memcpy(&context->raddr, &addr.rfcomm_bdaddr, + sizeof(context->raddr)); + return (1); } Index: util.c =================================================================== RCS file: /usr/local/cvs/ports/obexapp/util.c,v retrieving revision 1.14 diff -u -r1.14 util.c --- util.c 10 Apr 2009 17:26:03 -0000 1.14 +++ util.c 10 Apr 2009 18:05:23 -0000 @@ -425,9 +425,7 @@ * string, so always pass a copy. */ - strncpy(n, name, sizeof(n)); - n[sizeof(n) - 1] = '\0'; - + strlcpy(n, name, sizeof(n)); snprintf(temp, temp_size, "%s/XXXXXXXX", dirname(n)); return (mkstemp(temp)); From mi+thun at aldan.algebra.com Mon Apr 13 18:06:06 2009 From: mi+thun at aldan.algebra.com (Mikhail T.) Date: Mon Apr 13 18:13:46 2009 Subject: RFC: obexapp - virtual root folder for each device In-Reply-To: References: Message-ID: <49E3DB35.4030601@aldan.algebra.com> Maksim Yevmenkin ???????(??): > possible setup > > - create 'obex' user and 'obex' group > - create '/var/spool/obex' (or whatever you want for default root) > owned by 'obex' user/group > - user 'foo' creates ~/private directory under his home directory with > 0700 permissions > - admin setups 'obex' directory under foo's ~/private/ directory with > 0770 permissions, this directory is owned by 'obex' user. group is set > to foo's group > - admin setups symlink in /var/spool/obex/ called 'foo_cell' that > points to ~foo/private/obex > - admin adds entry in the /etc/bluetooth/hosts file to assign > 'foo_cell' foo's cell phone bd_addr > - admin run obexapp server as 'obexapp -s -r /var/spool/obex -R -u obex -C 1' > > every time foo's uses cell phone to send data to the obex server, the > data will end up in foo's ~/private/obex directory. > > please give it a try and let me know it works. > I think, Maksim's proposal is suboptimal, because the uploaded files end up owned by the new user obex, rather than the actual user (foo in the above example), which will make file-manipulations on the system itself more difficult (obex-owned will they not have 600 permissions?). It also necessitates creation of a new UID without the security benefit of having the daemon run under that UID permanently (but only switching after accepting each connection)... My proposal -- discussed with Maksim at length -- would *derive the user-ID from the ownership of the link*. The sample set up would then be as follows. Suppose, user named wallaby has a device with BD_ADDR 01:02:03:04:05:06. The user would create a subdirectory for their bluetooth files (or use something existing, like ~/Desktop/): wallaby@tasmania (11) mkdir ~/bluetooth root will then -- on wallaby's request -- tell obexapp about it: root@tasmania (111) ln -s ~wallaby/bluetooth /var/spool/obex/01:02:03:04:05:06 root@tasmania (112) chown -h wallaby ~wallaby/bluetooth /var/spool/obex/01:02:03:04:05:06 The second of the above root's lines is what will -- under my proposal -- tell obexapp-daemon, who shall own any files uploaded by the device. If started as root, instead of becoming 'obex', obexapp will become 'wallaby'... Upon accepting a connection, the server will do the same lookups as Maksim's version is doing, cd/chroot into the subdirectory, and setuid to the proper UID (such as wallaby's in this example). The attached patch can be dropped into /usr/ports/comms/obexapp/files/ . The only functionality still missing from it compared to Maksim's is the bt_gethostbyaddr(3) lookup -- only the numeric BD_ADDR is currently considered. This is not a significant difference between proposals, though... My proposal has other, not so significant differences -- it allows the BD_ADDR-entries in /var/spool/obex to be non-directories (files, broken links a'la /etc/make.conf, even sockets). Even if a chdir into such an entry fails, the ID of the entry's owner will still be used to determine, which user shall own any uploaded files, etc. even though all such files will end up in the same directory (as they do with the current version of obexapp). Maksim thought, offering such flexibility would be too confusing... I agree, that chroot-ing (rather than merely chdir-ing) into such a "virtual root" directory makes sense. The only material difference between our proposals is my deriving the desired UID from the ownership of the found BD_ADDR entry vs. Maksim's always using the user specified on command-line (such as 'obex'). Yours, -mi -------------- next part -------------- --- obexapp.1 2007-05-21 11:55:35.000000000 -0400 +++ obexapp.1 2009-04-09 22:15:22.000000000 -0400 @@ -217,4 +217,30 @@ This only works if server was started as root. .El +.Sh Per-user configurations +When accepting connections in server mode, +.Nm +will check, if there is an entry named after the connecting device's +BD_ADDR in the root-path (default or specified by the +.Fl r +option). If found, the entry will be used as follows: +.Bl -enum -offset indent -compact +.It +The group ID will be changed to that, which owns the entry. +.It +If +.Nm +is running as root, it will change to the user, that owns the entry. +.It +If the entry is itself a directory and chdir into it succeeds, it will +be used as the top-level. +.El +This allows the same system to intelligently distinguish different Bluetooth devices +as belonging to different users. An administrator can set up the subdirectories for +known devices under +.Pa /var/spool/obex +(or wherever, see +.Fl r +option) for each user, or even as symlinks to each user's home directory +(or a subdirectory thereof). .Sh LOCALE SUPPORT The @@ -326,4 +352,11 @@ address and RFCOMM channel .Li 1 . +.It ln -s Ar /home/wallaby Ar /var/spool/obex/00:01:02:03:04:05 +.It chown -h wallaby Ar /var/spool/obex/00:01:02:03:04:05 +Whenever the device with BD_ADDR of 00:01:02:03:04:05 connects, +.Nm +running in server mode will switch to user ID +.Ar wallaby +and use their home directory as the top-level for the connection. .El .Ss Level 1 Information Access --- transport.c 2007-05-21 11:55:35.000000000 -0400 +++ transport.c 2009-04-09 20:53:27.000000000 -0400 @@ -271,4 +272,7 @@ addr.rfcomm_channel, getpid()); + memcpy(&context->raddr, &addr.rfcomm_bdaddr, + sizeof(context->raddr)); + if (daemon(1, 0) < 0) { log_err("%s(): Could not daemon. %s (%d)", --- work/obexapp/server.c 2009-04-10 12:29:53.000000000 -0400 +++ work/obexapp/server.c 2009-04-13 20:33:16.000000000 -0400 @@ -110,5 +110,4 @@ * OBEX server */ - int obexapp_server(obex_t *handle) @@ -118,4 +117,8 @@ int error = -1; struct sockaddr_rfcomm addr; + struct stat sb; + const char *subdir = bt_ntoa(&context->raddr, NULL); + uid_t uid = 0; + gid_t gid = getgid(); context->ss = sdp_open_local(NULL); @@ -143,4 +146,6 @@ goto done; } + uid = pw->pw_uid; + gid = pw->pw_gid; } @@ -171,4 +176,36 @@ } + if (chdir(context->root) < 0) { + log_err("%s(): Could not chdir(%s). %s (%d)", + __func__, context->root, strerror(errno), errno); + goto done; + } + + log_debug("%s(): checking for %s/%s subdirectory", __func__, + context->root, subdir); + if (lstat(subdir, &sb) == 0) { + log_debug("%s(): %s/%s exists and belongs to uid %d", + __func__, context->root, subdir, sb.st_uid); + uid = sb.st_uid; + gid = sb.st_gid; + if (chdir(subdir)) + log_debug("%s(): chdir to %s failed: %s. Will remain " + "on top (uid %ld).", __func__, subdir, strerror(errno), + (long)uid); + else + getwd(context->root); + + log_debug("%s(): using %s", __func__, context->root); + } else switch (errno) { + case ENOENT: + log_debug("%s(): %s/%s not found", __func__, + context->root, subdir); + break; + default: + log_err("%s(): %s/%s: %s", __func__, context->root, subdir, + strerror(errno)); + goto done; + } + if (getuid() == 0) { if (context->secure) { @@ -183,25 +220,19 @@ } - if (pw != NULL) { - if (setgid(pw->pw_gid) < 0) { - log_err("%s(): Could not setgid(%d). %s (%d)", - __func__, pw->pw_gid, strerror(errno), - errno); - goto done; - } - - if (setuid(pw->pw_uid) < 0) { + if (uid) { + if (setuid(uid) < 0) { log_err("%s(): Could not setuid(%d). %s (%d)", - __func__, pw->pw_uid, strerror(errno), + __func__, uid, strerror(errno), errno); goto done; } } - } - if (chdir(context->root) < 0) { - log_err("%s(): Could not chdir(%s). %s (%d)", - __func__, context->root, strerror(errno), errno); - goto done; + if (gid != getgid() && setgid(gid) < 0) { + log_err("%s(): Could not setgid(%d). %s (%d)", + __func__, gid, strerror(errno), + errno); + goto done; + } } From maksim.yevmenkin at gmail.com Mon Apr 13 19:10:00 2009 From: maksim.yevmenkin at gmail.com (Maksim Yevmenkin) Date: Mon Apr 13 19:47:06 2009 Subject: RFC: obexapp - virtual root folder for each device In-Reply-To: <49E3DB35.4030601@aldan.algebra.com> References: <49E3DB35.4030601@aldan.algebra.com> Message-ID: 2009/4/13 Mikhail T. : > Maksim Yevmenkin ???????(??): >> possible setup >> >> - create 'obex' user and 'obex' group >> - create '/var/spool/obex' (or whatever you want for default root) >> owned by 'obex' user/group >> - user 'foo' creates ~/private directory under his home directory with >> 0700 permissions >> - admin setups 'obex' directory under foo's ~/private/ directory with >> 0770 permissions, this directory is owned by 'obex' user. group is set >> to foo's group >> - admin setups symlink in /var/spool/obex/ called 'foo_cell' that >> points to ~foo/private/obex >> - admin adds entry in the /etc/bluetooth/hosts file to assign >> 'foo_cell' foo's cell phone bd_addr >> - admin run obexapp server as 'obexapp -s -r /var/spool/obex -R -u obex -C 1' >> >> every time foo's uses cell phone to send data to the obex server, the >> data will end up in foo's ~/private/obex directory. >> >> please give it a try and let me know it works. >> > I think, Maksim's proposal is suboptimal, because the uploaded files end > up owned by the new user obex, rather than the actual user (foo in the > above example), which will make file-manipulations on the system itself > more difficult (obex-owned will they not have 600 permissions?). It also permissions on files will be set to 0660. > necessitates creation of a new UID without the security benefit of > having the daemon run under that UID permanently (but only switching > after accepting each connection)... i beg to differ. main process (the one that accepts connection) is running with elevated privileges, yes. however, the process that handles the actual connection from the client is running with reduced privileges. so, there is a security benefit. also, obexapp will chroot into virtual root. > My proposal -- discussed with Maksim at length -- would *derive the > user-ID from the ownership of the link*. The sample set up would then be > as follows. Suppose, user named wallaby has a device with BD_ADDR > 01:02:03:04:05:06. The user would create a subdirectory for their > bluetooth files (or use something existing, like ~/Desktop/): as i tried to explain, there are few problems (imo) with your patch. one example: what happens when remote client decides to create a directory which matches another client's bd_addr or name? i also do not like the fact that client are allowed to "escape" from their virtual root directory. another thing is that you pretty much force new behavior and not giving any control to disable it. what is worse, new behavior is controlled by file system elements which remote clients can potentially see, create and/or modify. [...] > My proposal has other, not so significant differences -- it allows the > BD_ADDR-entries in /var/spool/obex to be non-directories (files, broken > links a'la /etc/make.conf, even sockets). Even if a chdir into such an > entry fails, the ID of the entry's owner will still be used to > determine, which user shall own any uploaded files, etc. even though all > such files will end up in the same directory (as they do with the > current version of obexapp). > > Maksim thought, offering such flexibility would be too confusing... please explain what does it buy you exactly? so, great, you can control file ownership in the _same_ directory. like i said, unless you set sticky bit on the directory (which is evil, imo) and make it 0777 there is still a problem with sharing files (either obexapp running under new id won't be able to write to the root directory or everybody will be able to delete each others files). either way, its not that different from what obexapp does right now. > I agree, that chroot-ing (rather than merely chdir-ing) into such a > "virtual root" directory makes sense. The only material difference > between our proposals is my deriving the desired UID from the ownership > of the found BD_ADDR entry vs. Maksim's always using the user specified > on command-line (such as 'obex'). i'm actually not feeling very strongly about this. my initial choice is to be on a safe side. basically, what i'm trying to guard against is when someone put a symlink owned by root into the obexapp root folder. this is very easy thing to do, imo. i personally did it :) basically this type of misconfiguration will make obexapp continue to run with elevated privileges while servicing clients requests and that is a 'bad thing (tm)' :) i also must say that we are trying to solve the problem at the wrong level. more specifically, for phone book back up you probably want to use something else (like sync-ml for example) not not obexapp. bluetooth obex profiles are just completely oblivious to the fact that clients might need to supply credentials. its all strictly personal. so what i did, it basically provided mechanism to extend it in a similar manner. thanks, max From mi+thun at aldan.algebra.com Mon Apr 13 22:23:11 2009 From: mi+thun at aldan.algebra.com (Mikhail T.) Date: Mon Apr 13 22:32:22 2009 Subject: RFC: obexapp - virtual root folder for each device In-Reply-To: References: <49E3DB35.4030601@aldan.algebra.com> Message-ID: <49E41DBB.5090805@aldan.algebra.com> Maksim Yevmenkin ???????(??): >> I think, Maksim's proposal is suboptimal, because the uploaded files end >> up owned by the new user obex, rather than the actual user (foo in the >> above example), which will make file-manipulations on the system itself >> more difficult (obex-owned will they not have 600 permissions?). It also >> > permissions on files will be set to 0660. > I didn't see, where you do this -- simply using mkstemp will result in 600. But it can be done, of course. However, it is still imperfect, because all of the bluetooth users would have to be listed in obex' group and so there can be no more than 20 of them (I think, that's the limit on a group size). In any case, being owned by the actual user seems far more straightforward... >> necessitates creation of a new UID without the security benefit of >> having the daemon run under that UID permanently (but only switching >> after accepting each connection)... >> > i beg to differ. main process (the one that accepts connection) is > running with elevated privileges, yes. >From the security standpoint, this negates all/most benefits of having a dedicated UID for the service. The security people will tell you, that, as long as the root-owned process is always running and listening to connections, it can some day be fooled and so they don't like it anyway. > however, the process that > handles the actual connection from the client is running with reduced > privileges. so, there is a security benefit. It will drop the root-privileges under my proposal as well. It is just that the new UID will be that of the connecting user as determined by the BD_ADDR match, if such a match can be found. Otherwise, yes, it should become whatever user is specified with the -u switch. > also, obexapp will chroot > into virtual root. > Yes, I agree, this is advantageous. > i'm actually not feeling very strongly about this. my initial choice > is to be on a safe side. basically, what i'm trying to guard against > is when someone put a symlink owned by root into the obexapp root > folder. this is very easy thing to do, imo. i personally did it :) > The only thing I do feel strongly about, is that, when a matching directory entry is found, the server shall perform a setuid not to the generic 'obex' user, but to the owner of that entry (and chroot into it, yes). I do not think, you should give special meaning to entry named "default" -- because that would require special handling of the case, when somebody's BD_ADDR resolves to name "default". I'd say, it is Ok, if no match for the device is found, to remain in the top-level directory (after dropping the root-privileges). But, again, no strong feeling about this. My other ideas (such as the matching entries not necessarily being subdirectories) came from trying to maintain compatibility with the current operations (where everything lives in the same directory), while still allowing the created files to belong to different, cooperating users. > basically this type of misconfiguration will make obexapp continue to > run with elevated privileges while servicing clients requests and that > is a 'bad thing (tm)' :) > I agree, that continuing to run as root after accepting connection is bad -- if a matching entry is not found, the server, indeed, ought to setuid to some non-root user (as set by the -u switch or determined from the ownership of the top-level itself). However, the above-mentioned cooperation may still have its uses for some people. That said, they can have all their BD_ADDR symlinks point to the same directory and cooperate there, so I don't feel strongly about it either. > i also must say that we are trying to solve the problem at the wrong > level. more specifically, for phone book back up you probably want to > use something else (like sync-ml for example) not not obexapp. > Not sure, what "sync-ml" is (there is no such port). But what about dropping camera's pictures and videos? People would certainly expect to be able to do that straight from their devices (rather than by running a client of some sort), and find the files somewhere under their home directories (perhaps in ~/Desktop). So, I agree with most of your change, except the UID part -- in case a subdirectory-entry with a matching name is found, the argument of the setuid() (and setgid()) call should be the UID (and GID) of the entry (as determined by lstat(2), not stat(2), though). Yours, -mi From plunky at rya-online.net Tue Apr 14 09:16:50 2009 From: plunky at rya-online.net (Iain Hibbert) Date: Tue Apr 14 10:03:05 2009 Subject: RFC: obexapp - virtual root folder for each device In-Reply-To: <49E41DBB.5090805@aldan.algebra.com> References: <49E3DB35.4030601@aldan.algebra.com> <49E41DBB.5090805@aldan.algebra.com> Message-ID: <1239725723.321671.1132.nullmailer@galant.ukfsn.org> On Tue, 14 Apr 2009, Mikhail T. wrote: > > i beg to differ. main process (the one that accepts connection) is > > running with elevated privileges, yes. > >From the security standpoint, this negates all/most benefits of having a > dedicated UID for the service. The security people will tell you, that, I agree too, its not good to run a daemon (that can be accessed over a radio link and create/modify files no less) as root. > I agree, that continuing to run as root after accepting connection is > bad -- if a matching entry is not found, the server, indeed, ought to > setuid to some non-root user (as set by the -u switch or determined from > the ownership of the top-level itself). However, the above-mentioned > cooperation may still have its uses for some people. That said, they can > have all their BD_ADDR symlinks point to the same directory and > cooperate there, so I don't feel strongly about it either. If you did that, it would I think be better to have the bdaddr mapping in a config file. Symlinks are too much trouble. On the other hand, I'm not keen on config files either :) Also, it could be better in that case to have a master daemon (along the lines of inetd) that could accept connectins and start obex according to the remote device address. Thats perhaps a lot of work though. > > i also must say that we are trying to solve the problem at the wrong > > level. more specifically, for phone book back up you probably want to > > use something else (like sync-ml for example) not not obexapp. > > > Not sure, what "sync-ml" is (there is no such port). Try "msynctool" which uses libopensync, it has a pluggable API for different sync protocols (syncml is Nokia, synce is Windows) and is run from the computer end so you wouldn't have any issues with incoming files. > But what about dropping camera's pictures and videos? People would > certainly expect to be able to do that straight from their devices > (rather than by running a client of some sort), and find the files > somewhere under their home directories (perhaps in ~/Desktop). Do you want to send pictures to your account when your wife is logged in, or does she want to when you are logged in? I don't know what prior art is there, but it seems to me that (as I said before) the person logged in at the console owns the machine and should receive files being sent. As Max said, Bluetooth is not big on credentials, it is assumed that all users (normally a only one) have equal access. For this reason, Bluetooth cannot properly be a multi-user link unless all the users trust each other (while Alice is sending files to the computer, Bill could be checking her phone log) so I don't see a problem with having all the files in a master directory owned by the same user (but setting the default path per remote device seems a good compromise) regards, iain From maksim.yevmenkin at gmail.com Tue Apr 14 09:34:45 2009 From: maksim.yevmenkin at gmail.com (Maksim Yevmenkin) Date: Tue Apr 14 10:07:19 2009 Subject: RFC: obexapp - virtual root folder for each device In-Reply-To: <49E41DBB.5090805@aldan.algebra.com> References: <49E3DB35.4030601@aldan.algebra.com> <49E41DBB.5090805@aldan.algebra.com> Message-ID: 2009/4/13 Mikhail T. : > Maksim Yevmenkin ???????(??): >>> I think, Maksim's proposal is suboptimal, because the uploaded files end >>> up owned by the new user obex, rather than the actual user (foo in the >>> above example), which will make file-manipulations on the system itself >>> more difficult (obex-owned will they not have 600 permissions?). It also >>> >> permissions on files will be set to 0660. >> > I didn't see, where you do this -- simply using mkstemp will result in > 600. But it can be done, of course. However, it is still imperfect, perhaps you should take a closer look at the patch i posted? :-) in server.c @@ -565,6 +668,15 @@ } } + if (chmod(context->temp, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP) < 0) { + log_err("%s(): Could not chmod(%s). %s (%d)", + __func__, context->temp, + strerror(errno), errno); + + codes = obexapp_util_errno2response(errno); + goto done; + } + > because all of the bluetooth users would have to be listed in obex' > group and so there can be no more than 20 of them (I think, that's the > limit on a group size). not really, as i said in my original email user's private obex directory is owned by 'obex' user, but the group is still set to the user's group and permissions are set to 0770. > In any case, being owned by the actual user seems far more > straightforward... its all about security vs. usability to me :) please read below. >>> necessitates creation of a new UID without the security benefit of >>> having the daemon run under that UID permanently (but only switching >>> after accepting each connection)... >>> >> i beg to differ. main process (the one that accepts connection) is >> running with elevated privileges, yes. > > From the security standpoint, this negates all/most benefits of having a > dedicated UID for the service. The security people will tell you, that, > as long as the root-owned process is always running and listening to > connections, it can some day be fooled and so they don't like it anyway. i'm having a hard time follow this part of the argument. both patches (yours and mine) will accept connection with elevated privileges. there is absolutely no difference here. so, i'm going to dismiss this part of the argument, sorry :) >> however, the process that >> handles the actual connection from the client is running with reduced >> privileges. so, there is a security benefit. > > It will drop the root-privileges under my proposal as well. It is just > that the new UID will be that of the connecting user as determined by > the BD_ADDR match, if such a match can be found. Otherwise, yes, it > should become whatever user is specified with the -u switch. if you are arguing that having separate 'obex' user provides no security benefits, then i disagree. and here is why. to me security is about managing the risks. i have to assume that someone will break into someone's system using obexapp as attack vector. so, lets see what happens in this case (1) when obexapp is running as separate user 'obex' in chroot()ed environment, the question is: what kind of damage 'obex' user can do in chroot()ed environment? (2) when obexapp is running as potentially _any_ user in the system in _unrestricted_ environment, the question is: what kind of damage _any_ user can do while roaming free? keep in mind that 'bad guy' can populate his environment with all kind of files before obtaining shell. now, please put on your security person's hat and tell me honestly which option would you choose? >> i'm actually not feeling very strongly about this. my initial choice >> is to be on a safe side. basically, what i'm trying to guard against >> is when someone put a symlink owned by root into the obexapp root >> folder. this is very easy thing to do, imo. i personally did it :) >> > The only thing I do feel strongly about, is that, when a matching > directory entry is found, the server shall perform a setuid not to the > generic 'obex' user, but to the owner of that entry (and chroot into it, > yes). like i said, its security vs. usability. i probably can live with chroot()ing and changing uid to owner of the directory (and _not_ symlink). > I do not think, you should give special meaning to entry named "default" > -- because that would require special handling of the case, when > somebody's BD_ADDR resolves to name "default". I'd say, it is Ok, if no > match for the device is found, to remain in the top-level directory > (after dropping the root-privileges). But, again, no strong feeling > about this. i'm willing to take my chances of this one :) its strictly local configuration and if someone has, in fact, named his/her bluetooth gadget 'default' then its too bad :) i'm also not keen of keeping 'default' name. it can be anything, like '00:00:00:00:00:00' or whatever. > My other ideas (such as the matching entries not necessarily being > subdirectories) came from trying to maintain compatibility with the > current operations (where everything lives in the same directory), while > still allowing the created files to belong to different, cooperating users. no, just put new behavior under separate option and keep it disabled by default. >> basically this type of misconfiguration will make obexapp continue to >> run with elevated privileges while servicing clients requests and that >> is a 'bad thing (tm)' :) >> > I agree, that continuing to run as root after accepting connection is > bad -- if a matching entry is not found, the server, indeed, ought to > setuid to some non-root user (as set by the -u switch or determined from > the ownership of the top-level itself). However, the above-mentioned > cooperation may still have its uses for some people. That said, they can > have all their BD_ADDR symlinks point to the same directory and > cooperate there, so I don't feel strongly about it either. the 'default' virtual root stays. remote clients have no business to see, know about and/or try to mess with other clients virtual roots. not sure if you noticed, but 'default' virtual root can be also considered as another level of access. basically, if there is no 'default' virtual root, clients without virtual root won't be able to access the server. broken symlinks is a bad idea, imo. i still do not get what do you gain by changing ownership on files in the same directory. perhaps i'm missing something here. please give me an example. >> i also must say that we are trying to solve the problem at the wrong >> level. more specifically, for phone book back up you probably want to >> use something else (like sync-ml for example) not not obexapp. >> > Not sure, what "sync-ml" is (there is no such port). But what about there is something called multisync (or something like that) but it needs freebsd bluetooth plugin. basically sync-ml is a xml based protocol that runs over obex and is used for synchronizing objects on systems. > dropping camera's pictures and videos? People would certainly expect to > be able to do that straight from their devices (rather than by running a > client of some sort), and find the files somewhere under their home > directories (perhaps in ~/Desktop). sure and they can do it. > So, I agree with most of your change, except the UID part -- in case a > subdirectory-entry with a matching name is found, the argument of the > setuid() (and setgid()) call should be the UID (and GID) of the entry > (as determined by lstat(2), not stat(2), though). like i said, its probably ok to change uid to owner of the _directory_ and chroot() into it. but lstat(2) is out, sorry :) thanks, max From maksim.yevmenkin at gmail.com Tue Apr 14 10:07:15 2009 From: maksim.yevmenkin at gmail.com (Maksim Yevmenkin) Date: Tue Apr 14 10:28:41 2009 Subject: RFC: obexapp - virtual root folder for each device In-Reply-To: <1239725723.321671.1132.nullmailer@galant.ukfsn.org> References: <49E3DB35.4030601@aldan.algebra.com> <49E41DBB.5090805@aldan.algebra.com> <1239725723.321671.1132.nullmailer@galant.ukfsn.org> Message-ID: On Tue, Apr 14, 2009 at 9:15 AM, Iain Hibbert wrote: > On Tue, 14 Apr 2009, Mikhail T. wrote: > >> > i beg to differ. main process (the one that accepts connection) is >> > running with elevated privileges, yes. >> >From the security standpoint, this negates all/most benefits of having a >> dedicated UID for the service. The security people will tell you, that, > > I agree too, its not good to run a daemon (that can be accessed over a > radio link and create/modify files no less) as root. its only to call accept() and fork(). child process drops privileges right after that. in fact, i believe, there is no network i/o performed while child is running with elevated privileges. so, the window is small here. >> I agree, that continuing to run as root after accepting connection is >> bad -- if a matching entry is not found, the server, indeed, ought to >> setuid to some non-root user (as set by the -u switch or determined from >> the ownership of the top-level itself). However, the above-mentioned >> cooperation may still have its uses for some people. That said, they can >> have all their BD_ADDR symlinks point to the same directory and >> cooperate there, so I don't feel strongly about it either. > > If you did that, it would I think be better to have the bdaddr mapping in > a config file. Symlinks are too much trouble. On the other hand, I'm not > keen on config files either :) as i suggested, in private email, something like apache's .htaccess mechanism would be an ultimate solution here. however, its way too much complicated for what obexapp is :-) > Also, it could be better in that case to have a master daemon (along the > lines of inetd) that could accept connectins and start obex according to > the remote device address. Thats perhaps a lot of work though. that's, actually, an interesting idea. i could see how it could be applied to almost any bluetooth service. its not that much work, imo. obexapp already can use stdin/out as file descriptors (in client mode). configuration aspect of it is a pain though. perhaps we should think along the lines of bluetooth inetd that maps pair (protocol, protocol parameters) to service? something like (l2cap, 1) -> sdpd, (rfcomm, 1) -> obexapp, (rfcomm, 3) -> rfcomm_pppd etc.? >> But what about dropping camera's pictures and videos? People would >> certainly expect to be able to do that straight from their devices >> (rather than by running a client of some sort), and find the files >> somewhere under their home directories (perhaps in ~/Desktop). > > Do you want to send pictures to your account when your wife is logged in, > or does she want to when you are logged in? I don't know what prior art > is there, but it seems to me that (as I said before) the person logged in > at the console owns the machine and should receive files being sent. As > Max said, Bluetooth is not big on credentials, it is assumed that all > users (normally a only one) have equal access. > > For this reason, Bluetooth cannot properly be a multi-user link unless all > the users trust each other (while Alice is sending files to the computer, > Bill could be checking her phone log) so I don't see a problem with having > all the files in a master directory owned by the same user (but setting > the default path per remote device seems a good compromise) yes, that is what i was thinking too. its pretty much like running multiple copies of obexapp without actually running multiple copies of obexapp :) thanks, max > > regards, > iain > > From mi+thun at aldan.algebra.com Tue Apr 14 10:42:01 2009 From: mi+thun at aldan.algebra.com (Mikhail T.) Date: Tue Apr 14 11:09:27 2009 Subject: RFC: obexapp - virtual root folder for each device In-Reply-To: References: <49E3DB35.4030601@aldan.algebra.com> <49E41DBB.5090805@aldan.algebra.com> Message-ID: <49E4CAE1.6090506@aldan.algebra.com> Maksim Yevmenkin ???????(??): > 2009/4/13 Mikhail T. : > > not really, as i said in my original email user's private obex > directory is owned by 'obex' user, but the group is still set to the > user's group and permissions are set to 0770. Even if the GID of the files will be that of the user, it being owned by obex will cause inconvenience. > i'm having a hard time follow this part of the argument. both patches > (yours and mine) will accept connection with elevated privileges. > there is absolutely no difference here. so, i'm going to dismiss this > part of the argument, sorry :) > You are absolutely correct, that there is no security difference between our approaches, when a matching BD_ADDR entry is found. When one is not found, the user to switch to can be the owner of the default/ subdirectory, for example -- I don't really care for this case. >>> however, the process that >>> handles the actual connection from the client is running with reduced >>> privileges. so, there is a security benefit. >>> >> It will drop the root-privileges under my proposal as well. It is just >> that the new UID will be that of the connecting user as determined by >> the BD_ADDR match, if such a match can be found. Otherwise, yes, it >> should become whatever user is specified with the -u switch. >> > if you are arguing that having separate 'obex' user provides no > security benefits, then i disagree. and here is why. > > to me security is about managing the risks. i have to assume that > someone will break into someone's system using obexapp as attack > vector. so, lets see what happens in this case > > (1) when obexapp is running as separate user 'obex' in chroot()ed > environment, the question is: what kind of damage 'obex' user can do > in chroot()ed environment? > > (2) when obexapp is running as potentially _any_ user in the system in > _unrestricted_ environment, the question is: what kind of damage _any_ > user can do while roaming free? > I have already conceded in the previous e-mail(s), that I agree, that chroot-ing into the found subdirectory is a good idea. So, it would not "unrestricted" environment. The damage will be limited to the attacker's full access to the user's designated subdirectory. And it will be exactly the same as in your approach, because -- under your approach -- all of the files in the designated subdirectory will be obex-owned and thus accessible to an obex' process. >> The only thing I do feel strongly about, is that, when a matching >> directory entry is found, the server shall perform a setuid not to the >> generic 'obex' user, but to the owner of that entry (and chroot into it, >> yes). >> > > like i said, its security vs. usability. i probably can live with > chroot()ing and changing uid to owner of the directory (and _not_ > symlink). > This is wrong. Consider: wallaby@tasmania (11) mkdir ~/bluetooth wallaby@tasmania (12) echo 'Please, map my ~wallaby/bluetooth to my device' | write root ... root@tasmania (713) ln -s ~wallaby/bluetooth /var/spool/obex/Wallaby-Blackerry root@tasmania (714) echo 'Done, enjoy!' | write wallaby ... wallaby@tasmania (13) rmdir ~/bluetooth wallaby@tasmania (14) ln -s ~wombat/bluetooth ~/bluetooh Voila, because you trust stat(2) instead of lstat(2), Wallaby has just gained full access to Wombat's bluetooth files -- and can switch to anyone else's at will... When you use lstat, you make it root's responsibility to chown the BD_ADDR-symlinks under /var/spool/obex appropriately. Root may screw it up (and you may want to reject connections, if a particular entry belongs to root), but using stat(2) you give them no chance... > broken symlinks is a bad idea, imo. i still do not get what do you > gain by changing ownership on files in the same directory. perhaps i'm > missing something here. please give me an example. > Therein lies the problem -- the "broken symlinks" are a very useful thing, but suffer from the negative connotations of the word "broken". There is nothing wrong with them -- and /etc/make.conf provides a great example. They are very convenient in that they can be read and written in a single transaction (readlink(2) and symlink(2)) -- instead of open(2), read(2)/write(2), close(2). Their content is immediately readable with ls, and a directory containing them can also be modified atomically (even from scripts: with readlink(1), rm, ln) without locking. That's why "broken symlinks" aren't bad in general... Now, back to the topic at hand, my plan would've been an improvement over the current situation. Whereas the existing obexapp (and the new one, because you insist on the new behavior being optional) would dump everything into the same directory under the same ownership, my approach would allow different files to belong to different users, even if they still share the same directory. For example, a group of photographers dropping off pictures into the common area may want to use the same server-directory. But they would still prefer to be able to distinguish, who made which picture. That said, under my approach such multi-user sharing is still possible, even if you insist on BD_ADDR-entries being directories: root@tasmania (817) mkdir /home/dropoff root@tasmania (818) chmod 1777 /home/dropoff root@tasmania (819) foreach cam (Wallaby Wombat Pademelon Thylacine) foreach? ln -s /home/dropoff /var/spool/$cam-Camera foreach? chown -h $cam /var/spool/$cam-Camera foreach? end Under my proposal, the dropped-off pictures will belong to the proper users, while all residing in the same directory (for the editors to view). Under your proposal, such sharing would be far more involved to set up for no extra security... So, the only thing I insist on, is that lstat be used to determine the UID to switch to (after chroot-ing), when a matching BD_ADDR (or bt_hostname) is found. > like i said, its probably ok to change uid to owner of the _directory_ > and chroot() into it. but lstat(2) is out, sorry :) > Please, consider the above security example. stat is insecure for our purpose, while lstat is just fine. -mi From mi+thun at aldan.algebra.com Tue Apr 14 10:58:30 2009 From: mi+thun at aldan.algebra.com (Mikhail T.) Date: Tue Apr 14 11:14:32 2009 Subject: RFC: obexapp - virtual root folder for each device In-Reply-To: <1239725723.321671.1132.nullmailer@galant.ukfsn.org> References: <49E3DB35.4030601@aldan.algebra.com> <49E41DBB.5090805@aldan.algebra.com> <1239725723.321671.1132.nullmailer@galant.ukfsn.org> Message-ID: <49E4CEC2.6060505@aldan.algebra.com> Iain Hibbert ???????(??): > I agree too, its not good to run a daemon (that can be accessed over a > radio link and create/modify files no less) as root. > Both proposals being discussed currently presume the original daemon running as root, dropping privileges only after forking. Whatever mechanism is devised to avoid that, would be applicable to both, so lets put this part "outside of the parentheses" :-) > If you did that, it would I think be better to have the bdaddr mapping in > a config file. Symlinks are too much trouble. On the other hand, I'm not > keen on config files either :) > Config-files are a far greater "evil". Their maintenance by more than one person (or one with more than one terminal) requires locking, etc. -- for no additional benefit. Just use `ls -l' instead of `cat'... Symlinks (be they "broken" or not) can be maintained atomically and are thus superior. And each record keeps the timestamp and other useful info... >> But what about dropping camera's pictures and videos? People would certainly expect to be able to do that straight from their devices (rather than by running a client of some sort), and find the files somewhere under their home directories (perhaps in ~/Desktop). >> > > Do you want to send pictures to your account when your wife is logged in, > or does she want to when you are logged in? I don't see, how this is relevant. Everything, I and Maksim, are talking about is equally applicable even to a completely headless server. In my earlier e-mail (responding to Maksim), I give an example of a server set up to accept media uploads from multiple photographers (think of the guys with giant lenses around a football field, wirelessly pushing their snaps to the single box, from which editors pick them up for broadcasting/publishing nearly real-time) -- nobody needs to be logged-in to the box itself for that to work... > I don't know what prior art is there, but it seems to me that (as I said before) the person logged in at the console owns the machine and should receive files being sent. I don't know, what you mean. The two of us (and the babysitter, BTW) each have our own X-session, which are all started by KDM on demand. Ctrl-Alt-F9 is, usually, me, Ctrl-Alt-F10 -- the better third, and Ctrl-Alt-F11 -- whoever else needs access to their e-mail... None of this is relevant, though, I think... > For this reason, Bluetooth cannot properly be a multi-user link unless all the users trust each other (while Alice is sending files to the computer, Bill could be checking her phone log) so I don't see a problem with having all the files in a master directory owned by the same user (but setting the default path per remote device seems a good compromise) > Yes, it is a good compromise. And it can go one step further towards convenience (without compromising security), by using not just the device-specific PATH, but also the device-specific UID/GID... Yours, -mi From maksim.yevmenkin at gmail.com Tue Apr 14 11:55:06 2009 From: maksim.yevmenkin at gmail.com (Maksim Yevmenkin) Date: Tue Apr 14 12:19:20 2009 Subject: RFC: obexapp - virtual root folder for each device In-Reply-To: <49E4CAE1.6090506@aldan.algebra.com> References: <49E3DB35.4030601@aldan.algebra.com> <49E41DBB.5090805@aldan.algebra.com> <49E4CAE1.6090506@aldan.algebra.com> Message-ID: 2009/4/14 Mikhail T. : > Maksim Yevmenkin ???????(??): >> 2009/4/13 Mikhail T. : >> >> not really, as i said in my original email user's private obex >> directory is owned by 'obex' user, but the group is still set to the >> user's group and permissions are set to 0770. > > Even if the GID of the files will be that of the user, it being owned by > obex will cause inconvenience. something's gotta give :) can't please everybody :) [...] >>>> however, the process that >>>> handles the actual connection from the client is running with reduced >>>> privileges. so, there is a security benefit. >>>> >>> It will drop the root-privileges under my proposal as well. It is just >>> that the new UID will be that of the connecting user as determined by >>> the BD_ADDR match, if such a match can be found. Otherwise, yes, it >>> should become whatever user is specified with the -u switch. >>> >> if you are arguing that having separate 'obex' user provides no >> security benefits, then i disagree. and here is why. >> >> to me security is about managing the risks. i have to assume that >> someone will break into someone's system using obexapp as attack >> vector. so, lets see what happens in this case >> >> (1) when obexapp is running as separate user 'obex' in chroot()ed >> environment, the question is: what kind of damage 'obex' user can do >> in chroot()ed environment? >> >> (2) when obexapp is running as potentially _any_ user in the system in >> _unrestricted_ environment, the question is: what kind of damage _any_ >> user can do while roaming free? >> > I have already conceded in the previous e-mail(s), that I agree, that > chroot-ing into the found subdirectory is a good idea. So, it would not > "unrestricted" environment. The damage will be limited to the attacker's > full access to the user's designated subdirectory. And it will be > exactly the same as in your approach, because -- under your approach -- > all of the files in the designated subdirectory will be obex-owned and > thus accessible to an obex' process. ah, not exactly. its not only the files that 'bad guy' can upload into (possibly chroot()ed) environment. its the user id. for example, what if certain user id has access to a certain device node? 'bad guy' could just mknod(8) device node and ... >>> The only thing I do feel strongly about, is that, when a matching >>> directory entry is found, the server shall perform a setuid not to the >>> generic 'obex' user, but to the owner of that entry (and chroot into it, >>> yes). >> >> like i said, its security vs. usability. i probably can live with >> chroot()ing and changing uid to owner of the directory (and _not_ >> symlink). >> > This is wrong. Consider: > > wallaby@tasmania (11) mkdir ~/bluetooth > wallaby@tasmania (12) echo 'Please, map my ~wallaby/bluetooth to my > device' | write root > ... > root@tasmania (713) ln -s ~wallaby/bluetooth > /var/spool/obex/Wallaby-Blackerry > root@tasmania (714) echo 'Done, enjoy!' | write wallaby > ... > wallaby@tasmania (13) rmdir ~/bluetooth > wallaby@tasmania (14) ln -s ~wombat/bluetooth ~/bluetooh > > Voila, because you trust stat(2) instead of lstat(2), Wallaby has just > gained full access to Wombat's bluetooth files -- and can switch to > anyone else's at will... hmm.... perhaps, i'm missing something here. here is what i did == step 1 == %id uid=1004(wallaby) gid=1004(wallaby) groups=1004(wallaby) %mkdir ~/bluetooth %ls -l drwxr-xr-x 2 wallaby wallaby 512 Apr 14 11:20 bluetooth == step 2 == beetle# pwd /var/spool/obex beetle# ln -s ~wallaby/bluetooth Wallaby-Blackerry beetle# ls -l lrwxr-xr-x 1 root obex 23 Apr 14 11:21 Wallaby-Blackerry -> /home/wallaby/bluetooth beetle# stat -L Wallaby-Blackerry 99 1490343 drwxr-xr-x 2 wallaby wallaby 5953437 512 "Apr 14 11:20:37 2009" "Apr 14 11:20:37 2009" "Apr 14 11:20:37 2009" "Apr 14 11:20:37 2009" 4096 4 0 Wallaby-Blackerry so as you can see user/group ids are correct, i.e. wallaby/wallaby == step 3 == %id uid=1005(wombat) gid=1005(wombat) groups=1005(wombat) %mkdir ~/bluetooth %ls -l drwxr-xr-x 2 wombat wombat 512 Apr 14 11:22 bluetooth == step 4 == %id uid=1004(wallaby) gid=1004(wallaby) groups=1004(wallaby) %pwd /usr/home/wallaby %rmdir bluetooth/ %ln -s ~wombat/bluetooth ~/bluetooth %ls -l lrwxr-xr-x 1 wallaby wallaby 22 Apr 14 11:29 bluetooth -> /home/wombat/bluetooth == step 5 == beetle# pwd /var/spool/obex beetle# stat -L Wallaby-Blackerry 99 1490434 drwxr-xr-x 2 wombat wombat 5953822 512 "Apr 14 11:22:10 2009" "Apr 14 11:22:10 2009" "Apr 14 11:22:10 2009" "Apr 14 11:22:10 2009" 4096 4 0 Wallaby-Blackerry so, as you can see user/group ids are still correct, i.e. wombat/wombat. also i used stat -L which makes use of stat(2) instead of lstat(2). so from what i can see, and please feel free to correct me if i wrong, user wallaby just intentionally 'gave up' his/her files to user 'wombat'. i do not see any problem here, do you? or am i missing something obvious here? > When you use lstat, you make it root's responsibility to chown the > BD_ADDR-symlinks under /var/spool/obex appropriately. Root may screw it > up (and you may want to reject connections, if a particular entry > belongs to root), but using stat(2) you give them no chance... if 'root' screws up, then game is over :) period. you can make this argument with any piece of software out there :) >> broken symlinks is a bad idea, imo. i still do not get what do you >> gain by changing ownership on files in the same directory. perhaps i'm >> missing something here. please give me an example. >> > Therein lies the problem -- the "broken symlinks" are a very useful > thing, but suffer from the negative connotations of the word "broken". > There is nothing wrong with them -- and /etc/make.conf provides a great > example. They are very convenient in that they can be read and written please let it go already :) i heard your point about /etc/malloc.conf several times. you made it loud and clear. just because someone did it this way, does not mean the the same model should be applied for everything else. i'm sure whoever did this had good reasons for doing it this way. keep in mind that someone else (or possibly the same person) provided MALLOC_OPTIONS environmental variable and _malloc_options global variable to override /etc/malloc.conf. imo, its fine to use 'broken' symlinks to pass a few characters, however, you suggesting to use the same model for something that is more complicated. there probably are people who are confused about permission on symlink vs. permissions on the directory symlink points to. i was one of those people in not too distant past :-) > in a single transaction (readlink(2) and symlink(2)) -- instead of > open(2), read(2)/write(2), close(2). Their content is immediately > readable with ls, and a directory containing them can also be modified > atomically (even from scripts: with readlink(1), rm, ln) without > locking. That's why "broken symlinks" aren't bad in general... ok, 'broken' symlinks are bad idea, imo, when used as access control mechanism :) > Now, back to the topic at hand, my plan would've been an improvement > over the current situation. Whereas the existing obexapp (and the new > one, because you insist on the new behavior being optional) would dump > everything into the same directory under the same ownership, my approach > would allow different files to belong to different users, even if they > still share the same directory. > > For example, a group of photographers dropping off pictures into the > common area may want to use the same server-directory. But they would > still prefer to be able to distinguish, who made which picture. bad example - meta data in the pictures themselves tell much better story :) > That said, under my approach such multi-user sharing is still possible, > even if you insist on BD_ADDR-entries being directories: > > root@tasmania (817) mkdir /home/dropoff > root@tasmania (818) chmod 1777 /home/dropoff > root@tasmania (819) foreach cam (Wallaby Wombat Pademelon Thylacine) > foreach? ln -s /home/dropoff /var/spool/$cam-Camera > foreach? chown -h $cam /var/spool/$cam-Camera > foreach? end > > Under my proposal, the dropped-off pictures will belong to the proper > users, while all residing in the same directory (for the editors to > view). Under your proposal, such sharing would be far more involved to > set up for no extra security... again, bad example, imo. all pictures will end up in /home/dropoff owned by the same user (either 'obex' or owner of '/home/dropoff'). just point editors to /home/dropoff instead of /var/spool/obex and be done with it. the only difference is that all the files will be owned by the same user, but like i said, meta data in the pictures will tell far better story then user id :) i also do not understand why 'sticky bit' is required (its only needed in your version of the patch). > So, the only thing I insist on, is that lstat be used to determine the > UID to switch to (after chroot-ing), when a matching BD_ADDR (or > bt_hostname) is found. please see my example above. >> like i said, its probably ok to change uid to owner of the _directory_ >> and chroot() into it. but lstat(2) is out, sorry :) >> > Please, consider the above security example. stat is insecure for our > purpose, while lstat is just fine. hmm, for my example above beetle# stat Wallaby-Blackerry 98 47348 lrwxr-xr-x 1 root obex 1836017711 23 "Apr 14 11:21:23 2009" "Apr 14 11:21:23 2009" "Apr 14 11:21:23 2009" "Apr 14 11:21:23 2009" 4096 0 0 Wallaby-Blackerry and (with trailing /) beetle# stat Wallaby-Blackerry/ 99 1490343 lrwxr-xr-x 1 wallaby wallaby 1836017711 22 "Apr 14 11:29:08 2009" "Apr 14 11:29:08 2009" "Apr 14 11:29:08 2009" "Apr 14 11:29:08 2009" 4096 0 0 Wallaby-Blackerry/ stat(1) _without_ '-L' option uses lstat(2). thanks, max From mi+thun at aldan.algebra.com Tue Apr 14 12:55:04 2009 From: mi+thun at aldan.algebra.com (Mikhail T.) Date: Tue Apr 14 13:22:36 2009 Subject: RFC: obexapp - virtual root folder for each device In-Reply-To: References: <49E3DB35.4030601@aldan.algebra.com> <49E41DBB.5090805@aldan.algebra.com> <49E4CAE1.6090506@aldan.algebra.com> Message-ID: <49E4EA14.80300@aldan.algebra.com> Maksim Yevmenkin ???????(??): > something's gotta give :) can't please everybody :) > You can make sure, the uploaded files belong to the owner of the device, that uploaded them. >> I have already conceded in the previous e-mail(s), that I agree, that >> chroot-ing into the found subdirectory is a good idea. So, it would not >> "unrestricted" environment. The damage will be limited to the attacker's >> full access to the user's designated subdirectory. And it will be >> exactly the same as in your approach, because -- under your approach -- >> all of the files in the designated subdirectory will be obex-owned and >> thus accessible to an obex' process. >> > > ah, not exactly. its not only the files that 'bad guy' can upload into > (possibly chroot()ed) environment. its the user id. for example, what > if certain user id has access to a certain device node? 'bad guy' > could just mknod(8) device node and ... > First of all, mknod(8) utility is deprecated on modern FreeBSD systems -- in favor of devfs. So, you are worried, that a bad guy will impersonate wallaby and access a wallaby-owned device? I don't think, this is possible in default FreeBSD installations. And admins granting some users direct access to devices may chose to limit remote access to these user accounts. > beetle# stat -L Wallaby-Blackerry > 99 1490434 drwxr-xr-x 2 wombat wombat 5953822 512 "Apr 14 11:22:10 > 2009" "Apr 14 11:22:10 2009" "Apr 14 11:22:10 2009" "Apr 14 11:22:10 > 2009" 4096 4 0 Wallaby-Blackerry > > so, as you can see user/group ids are still correct, i.e. wombat/wombat. > Wombat UID for Wallaby-Blackberry is NOT correct! > also i used stat -L which makes use of stat(2) instead of lstat(2). so > from what i can see, and please feel free to correct me if i wrong, > user wallaby just intentionally 'gave up' his/her files to user > 'wombat'. i do not see any problem here, do you? or am i missing > something obvious here? > Yes, exactly as above! User of Wallaby-Blackberry will connect and -- depending on their own choice -- have full access to Wombat's files, because stat will tell obexapp, to use UID of wombat, whereas lstat would've told it to use the UID of Wallaby (and fail to access Wombat's 600-moded files). In fact, because Wallaby is free to make their own ~/bluetooth a symlink to ANYTHING, they may get access to ANYBODY's files, even those of users, who don't use Bluetooth. Once root sets up /var/spool/obex/Wallaby-Blackberry pointing at /home/wallaby/bluetooth, user Wallaby can change that link to anything -- except, perhaps, root themselves... >> When you use lstat, you make it root's responsibility to chown the >> BD_ADDR-symlinks under /var/spool/obex appropriately. Root may screw it >> up (and you may want to reject connections, if a particular entry >> belongs to root), but using stat(2) you give them no chance... >> > if 'root' screws up, then game is over :) period. you can make this argument with any piece of software out there :) > I agree. Which is why I keep saying, you should be using lstat (to read info on entries created by root under /var/spool/obex), instead of stat (which would give you info on entries maintained by the -- potentially malicious -- users). > there probably are people who are confused about > permission on symlink vs. permissions on the directory symlink points > to. i was one of those people in not too distant past :-) > Didn't we just agree, that root being confused is a "game-over" and shall not be mentioned? >> That's why "broken symlinks" aren't bad in general... >> > ok, 'broken' symlinks are bad idea, imo, when used as access control > mechanism :) > Slow, painful, but still progress... :-) >> For example, a group of photographers dropping off pictures into the >> common area may want to use the same server-directory. But they would >> still prefer to be able to distinguish, who made which picture. >> > > bad example - meta data in the pictures themselves tell much better story :) > You are nit-picking, but no, actually. The EXIF metadata (BTW, relying on file-format specific metadata to provide information, that filesystem itself can/should provide, seems wrong in itsefl) will give the camera's model and the time of the shot, but not the owner... It does not matter, again, because multiple BD_ADDR symlinks can still point to the same directory. >> That said, under my approach such multi-user sharing is still possible, >> even if you insist on BD_ADDR-entries being directories: >> >> root@tasmania (817) mkdir /home/dropoff >> root@tasmania (818) chmod 1777 /home/dropoff >> root@tasmania (819) foreach cam (Wallaby Wombat Pademelon Thylacine) >> foreach? ln -s /home/dropoff /var/spool/$cam-Camera >> foreach? chown -h $cam /var/spool/$cam-Camera >> foreach? end >> >> Under my proposal, the dropped-off pictures will belong to the proper >> users, while all residing in the same directory (for the editors to >> view). Under your proposal, such sharing would be far more involved to >> set up for no extra security... >> > > again, bad example, imo. all pictures will end up in /home/dropoff > owned by the same user (either 'obex' or owner of '/home/dropoff'). > They will under YOUR proposal (using stat). Under mine (using lstat), the files will have different owning UIDs... Which is good :-) You may not like a particular example, but you have to admit, that my proposal gives more flexibility to the admin setting up BT-access. They can, if they want to, allow meaningful directory-sharing for the files belonging to different users. And if they don't want to, they can keep all symlinks pointing to /home/dropoff belong to `obex' (or whoever). Under your proposal, there is no such flexibility and yet it is not any more secure (sorry, mknod is too superficial). Escaping from chroot is never easy, and for non-root users it is impossible -- without bugs in the kernel... > just point editors to /home/dropoff instead of /var/spool/obex and be done with it. Of course, the editors would be looking at /home/dropoff! I don't even understand, how what I wrote could've been read differently. The editors will be looking at /home/dropoff (or, more likely, something like \\dropoff\dropoff\). And in there they will be able to group files by users (owners of the matching entries under /var/spool/obex) -- if you take my approach (lstat). Under your approach (stat), the user would be the same (owner of /home/dropoff)... > i also do not understand why 'sticky bit' is required (its only needed in your version of the patch). > It is not required -- only to prevent people from (accidentally, perhaps) overwriting each other's files under certain circumstances. To summarize, you seem willing to consider the owner of the matching entry when determining, which UID to switch to when dropping root-privileges after chroot. The only remaining disagreement is whether to use lstat vs. stat for the purpose. It being, literally, a one-character change in the code, you can go ahead and begin coding the change to match your style preferences. In the mean time, consider the example I just gave, showing stat being a security hole... Yours, -mi From maksim.yevmenkin at gmail.com Tue Apr 14 14:38:35 2009 From: maksim.yevmenkin at gmail.com (Maksim Yevmenkin) Date: Tue Apr 14 15:18:05 2009 Subject: RFC: obexapp - virtual root folder for each device In-Reply-To: <49E4EA14.80300@aldan.algebra.com> References: <49E3DB35.4030601@aldan.algebra.com> <49E41DBB.5090805@aldan.algebra.com> <49E4CAE1.6090506@aldan.algebra.com> <49E4EA14.80300@aldan.algebra.com> Message-ID: 2009/4/14 Mikhail T. : [...] > Slow, painful, but still progress... :-) please find attached revised patch that implements mi's (aka Mikhail) initial suggestion, i.e. use lstat(2) instead of stat(2). i've also changed it a bit to allow both cases, i.e. (1) when virtual root folder is requested and -u option is set, obexapp will always run as ; (2) when virtual root folder is requested and _no_ -u option was specified, obexapp will run as the owner of the found virtual root folder entry (where entry is either symlink or actual subdirectory under default root folder); i've also included a patch, submitted by Ronald Klop to disable spinner in client mode for non-interactive client sessions. [...] > To summarize, you seem willing to consider the owner of the matching > entry when determining, which UID to switch to when dropping > root-privileges after chroot. The only remaining disagreement is whether > to use lstat vs. stat for the purpose. It being, literally, a > one-character change in the code, you can go ahead and begin coding the > change to match your style preferences. > > In the mean time, consider the example I just gave, showing stat being a > security hole... yes, now i get it :) sorry for being such a bonehead :) hopefully the latest patch will work for everyone. thanks, max -------------- next part -------------- Index: event.c =================================================================== RCS file: /usr/local/cvs/ports/obexapp/event.c,v retrieving revision 1.6 diff -u -r1.6 event.c --- event.c 5 Jan 2009 16:37:25 -0000 1.6 +++ event.c 14 Apr 2009 21:23:16 -0000 @@ -1,7 +1,7 @@ /* * event.c * - * Copyright (c) 2002 Maksim Yevmenkin + * Copyright (c) 2002-2009 Maksim Yevmenkin * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -32,6 +32,7 @@ #include #include #include +#include #include "compat.h" #include "obexapp.h" @@ -131,7 +132,7 @@ log_debug("%s(): Made some progress...", __func__); - if (!context->server) { + if (!context->server && !context->ni && isatty(STDOUT_FILENO)) { static char spinner[] = "\\|/-"; static uint32_t spinner_idx = 0; Index: main.c =================================================================== RCS file: /usr/local/cvs/ports/obexapp/main.c,v retrieving revision 1.13 diff -u -r1.13 main.c --- main.c 23 Apr 2007 18:29:18 -0000 1.13 +++ main.c 14 Apr 2009 21:23:38 -0000 @@ -1,7 +1,7 @@ /* * main.c * - * Copyright (c) 2002 Maksim Yevmenkin + * Copyright (c) 2002-2009 Maksim Yevmenkin * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -65,7 +65,7 @@ { struct sigaction sa; char *ep = NULL, *pri_name = NULL; - int n, service, noninteractive; + int n, service, detach; context_t context; obex_ctrans_t custfunc; @@ -83,7 +83,7 @@ /* Prepare context */ memset(&context, 0, sizeof(context)); context.tfd = context.sfd = -1; - context.detach = 1; + detach = 1; context.ls_size = OBEXAPP_BUFFER_SIZE; if ((context.ls = (char *) malloc(context.ls_size)) == NULL) @@ -118,8 +118,8 @@ custfunc.customdata = &context; /* Process command line options */ - service = noninteractive = 0; - while ((n = getopt(argc, argv, "a:A:cC:dDfhl:m:nr:Ssu:")) != -1) { + service = 0; + while ((n = getopt(argc, argv, "a:A:cC:dDfhl:m:nr:RsSu:")) != -1) { switch (n) { case 'a': if (!bt_aton(optarg, &context.raddr)) { @@ -180,7 +180,7 @@ break; case 'd': /* do not detach server */ - context.detach = 0; + detach = 0; break; case 'D': /* use stdin/stdout */ @@ -209,7 +209,7 @@ usage(basename(argv[0])); /* NOT REACHED */ - noninteractive = 1; + context.ni = 1; break; case 'r': /* root */ @@ -217,8 +217,13 @@ err(1, "Could not realpath(%s)", optarg); break; + case 'R': /* virtualize root for each device */ + context.vroot = 1; + context.secure = 1; + break; + case 's': /* server */ - if (noninteractive) + if (context.ni) usage(basename(argv[0])); /* NOT REACHED */ @@ -269,23 +274,10 @@ log_open("obexapp", pri_name, 0); /* Detach server (if required) */ - if (context.server && context.detach) { - pid_t pid = fork(); - - if (pid == (pid_t) -1) { - log_err("%s(): Could not fork. %s (%d)", - __func__, strerror(errno), errno); - exit(1); - } - - if (pid != 0) - exit(0); - - if (daemon(0, 0) < 0) { - log_err("%s(): Could not daemon. %s (%d)", - __func__, strerror(errno), errno); - exit(1); - } + if (context.server && detach && daemon(0, 0) < 0) { + log_err("%s(): Could not daemon. %s (%d)", + __func__, strerror(errno), errno); + exit(1); } /* Initialize OBEX */ @@ -305,7 +297,7 @@ if (context.server) n = obexapp_server(context.handle); - else if (noninteractive) + else if (context.ni) n = obexapp_non_interactive_client(context.handle, argc, argv); else n = obexapp_client(context.handle); Index: obexapp.1 =================================================================== RCS file: /usr/local/cvs/ports/obexapp/obexapp.1,v retrieving revision 1.15 diff -u -r1.15 obexapp.1 --- obexapp.1 21 May 2007 15:55:35 -0000 1.15 +++ obexapp.1 14 Apr 2009 21:24:08 -0000 @@ -1,6 +1,6 @@ .\" obexapp.1 .\" -.\" Copyright (c) 2001-2003 Maksim Yevmenkin +.\" Copyright (c) 2001-2009 Maksim Yevmenkin .\" All rights reserved. .\" .\" Redistribution and use in source and binary forms, with or without @@ -27,7 +27,7 @@ .\" $Id: obexapp.1,v 1.15 2007/05/21 15:55:35 max Exp $ .\" $FreeBSD$ .\" -.Dd April 10, 2007 +.Dd April 14, 2009 .Dt OBEXAPP 1 .Os .Sh NAME @@ -54,7 +54,7 @@ .Ar parameters .Nm .Fl s -.Op Fl dDSh +.Op Fl dDSRh .Op Fl A Ar BD_ADDR .Fl C Ar channel .Op Fl m Ar MTU @@ -193,6 +193,12 @@ Defaults to the maximum supported value. .It Fl n Work in the non-interactive client mode. +.It Fl R +Virtualize root folder for each client device in server mode. +Will automatically turn on secure mode, i.e. +.Fl S +option. +Please read section below for a complete description. .It Fl r Ar path Specify root folder. Default root folder in the server mode is @@ -216,6 +222,57 @@ The value specified may be either a username or a numeric user id. This only works if server was started as root. .El +.Sh VIRTUAL ROOT FOLDERS +When accepting connections in server mode, +.Nm +will attempt to find an entry that would act as a virtual root +folder for the connecting device. +Virtual root folders must reside under default root folder which is set +with +.Fl r +option. +The rules are as follows: +.Bl -enum -offset indent -compact +.It +.Nm +will try to resolve connecting device's BD_ADDR using +.Xr bt_gethostbyaddr 3 +call and check for an entry that matches resolved name (if any); +.It +.Nm +will check for an entry that matches connecting device's BD_ADDR; +.It +.Nm +will check for an entry, named +.Dq default ; +.El +If none of the above matches, then the connection to the client is terminated. +Otherwise, +.Nm +will try to change default root folder the the found entry. +.Pp +If +.Fl u +option was specified, the +.Nm +will try to change to the specified user. +Otherwise +.Nm +will try change to the user, that owns the found entry. +That is, if the found entry is a symlink, the +.Nm +will try change to the user, that owns symlink and not to the user, that +owns the entry symlink points to. +.Pp +This allows the same system to intelligently distinguish different +client devices as belonging to different users. +An administrator can set up the subdirectories for +known devices under +.Pa /var/spool/obex +(or wherever, see +.Fl r +option) for each user, or even as symlinks to each user's home directory +(or a subdirectory thereof). .Sh LOCALE SUPPORT The .Nm @@ -325,6 +382,13 @@ .Dv ANY address and RFCOMM channel .Li 1 . +.It ln -s Ar /home/wallaby Ar /var/spool/obex/00:01:02:03:04:05 +.It chown -h wallaby Ar /var/spool/obex/00:01:02:03:04:05 +Whenever the device with BD_ADDR of 00:01:02:03:04:05 connects, +.Nm +running in server mode will switch to user ID +.Ar wallaby +and use their home directory as the top-level for the connection. .El .Ss Level 1 Information Access The first level involves the basic ability to put an object (such as a vCard) Index: obexapp.h =================================================================== RCS file: /usr/local/cvs/ports/obexapp/obexapp.h,v retrieving revision 1.9 diff -u -r1.9 obexapp.h --- obexapp.h 23 Apr 2007 18:29:18 -0000 1.9 +++ obexapp.h 14 Apr 2009 21:26:44 -0000 @@ -1,7 +1,7 @@ /* * obexapp.h * - * Copyright (c) 2002 Maksim Yevmenkin + * Copyright (c) 2002-2009 Maksim Yevmenkin * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -87,9 +87,10 @@ unsigned server : 1; /* server mode? */ unsigned secure : 1; /* secure mode? */ unsigned done : 1; /* done? */ - unsigned detach : 1; /* detach server? */ unsigned fbs : 1; /* Folder Browsing Service */ - unsigned reserved : 2; + unsigned vroot : 1; /* virtualize device's root */ + unsigned ni : 1; /* non-interactive? */ + unsigned reserved : 1; /* local SDP session (server only) */ void *ss; @@ -111,6 +112,10 @@ uint8_t *sbuffer; int mtu; /* OBEX MTU */ + + /* credentials */ + uid_t uid; + gid_t gid; }; typedef struct context context_t; typedef struct context * context_p; Index: server.c =================================================================== RCS file: /usr/local/cvs/ports/obexapp/server.c,v retrieving revision 1.11 diff -u -r1.11 server.c --- server.c 9 Apr 2009 23:16:31 -0000 1.11 +++ server.c 14 Apr 2009 20:55:37 -0000 @@ -1,7 +1,7 @@ /* * server.c * - * Copyright (c) 2002 Maksim Yevmenkin + * Copyright (c) 2002-2009 Maksim Yevmenkin * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -89,6 +89,10 @@ static char const * const ls_parent_folder = "\n"; +static int obexapp_server_set_initial_root (context_p context); +static int obexapp_server_set_device_root (context_p context); +static int obexapp_server_set_final_root (context_p context); + /* OBEX request handlers */ static obexapp_request_handler_t obexapp_server_request_connect; static obexapp_request_handler_t obexapp_server_request_disconnect; @@ -114,7 +118,6 @@ obexapp_server(obex_t *handle) { context_p context = (context_p) OBEX_GetUserData(handle); - struct passwd *pw = NULL; int error = -1; struct sockaddr_rfcomm addr; @@ -131,26 +134,6 @@ goto done; } - if (context->user != NULL) { - if (atoi(context->user) != 0) - pw = getpwuid(atoi(context->user)); - else - pw = getpwnam(context->user); - - if (pw == NULL) { - log_err("%s(): Unknown user %s", __func__, - context->user); - goto done; - } - } - - if (context->root[0] == '\0') { - if (pw == NULL) - strlcpy(context->root, OBEXAPP_ROOT_DIR, PATH_MAX); - else - strlcpy(context->root, pw->pw_dir, PATH_MAX); - } - log_info("%s: Starting OBEX server", __func__); if (OBEX_SetTransportMTU(handle, context->mtu, context->mtu) < 0) { @@ -162,7 +145,7 @@ addr.rfcomm_len = sizeof(addr); addr.rfcomm_family = AF_BLUETOOTH; addr.rfcomm_channel = context->channel; - memcpy(&addr.rfcomm_bdaddr, &context->raddr, sizeof(context->raddr)); + memcpy(&addr.rfcomm_bdaddr, &context->laddr, sizeof(context->laddr)); if (OBEX_ServerRegister(handle, (struct sockaddr *) &addr, sizeof(addr)) < 0) { @@ -170,40 +153,12 @@ goto done; } - if (getuid() == 0) { - if (context->secure) { - if (chroot(context->root) < 0) { - log_err("%s(): Could not chroot(%s). %s (%d)", - __func__, context->root, - strerror(errno), errno); - goto done; - } - - strlcpy(context->root, "/", PATH_MAX); - } - - if (pw != NULL) { - if (setgid(pw->pw_gid) < 0) { - log_err("%s(): Could not setgid(%d). %s (%d)", - __func__, pw->pw_gid, strerror(errno), - errno); - goto done; - } - - if (setuid(pw->pw_uid) < 0) { - log_err("%s(): Could not setuid(%d). %s (%d)", - __func__, pw->pw_uid, strerror(errno), - errno); - goto done; - } - } - } - - if (chdir(context->root) < 0) { - log_err("%s(): Could not chdir(%s). %s (%d)", - __func__, context->root, strerror(errno), errno); + if (obexapp_server_set_initial_root(context) < 0) + goto done; + if (context->vroot && obexapp_server_set_device_root(context) < 0) + goto done; + if (obexapp_server_set_final_root(context) < 0) goto done; - } log_debug("%s(): Entering event processing loop...", __func__); @@ -227,6 +182,165 @@ } /* obexapp_server */ /* + * Set initial server root + */ + +static int +obexapp_server_set_initial_root(context_p context) +{ + struct passwd *pw = NULL; + char *ep; + + if (context->user != NULL) { + context->uid = strtoul(context->user, &ep, 10); + if (*ep != '\0') + pw = getpwnam(context->user); + else + pw = getpwuid(context->uid); + + if (pw == NULL) { + log_err("%s(): Unknown user '%s'", + __func__, context->user); + return (-1); + } + + log_debug("%s(): Requested to run as '%s', uid=%d, gid=%d", + __func__, context->user, pw->pw_uid, pw->pw_gid); + + context->uid = pw->pw_uid; + context->gid = pw->pw_gid; + } else { + context->uid = getuid(); + context->gid = getgid(); + } + + /* Set default root */ + if (context->root[0] == '\0') { + if (pw == NULL) + strlcpy(context->root, OBEXAPP_ROOT_DIR, PATH_MAX); + else + strlcpy(context->root, pw->pw_dir, PATH_MAX); + } + + if (chdir(context->root) < 0) { + log_err("%s(): Could not chdir(%s). %s (%d)", + __func__, context->root, strerror(errno), errno); + return (-1); + } + + log_debug("%s(): Using initial root %s", __func__, context->root); + + return (0); +} /* obexapp_server_set_initial_root */ + +/* + * Set device specific server root + */ + +static int +obexapp_server_set_device_root(context_p context) +{ + char const *root[] = { NULL, NULL, NULL }; + struct hostent *he; + struct stat sb; + int n; + + n = 0; + + he = bt_gethostbyaddr((char const *) &context->raddr, + sizeof(bdaddr_t), AF_BLUETOOTH); + if (he != NULL) + root[n ++] = (char const *) he->h_name; + + root[n ++] = bt_ntoa(&context->raddr, NULL); + + root[n ++] = "default"; + + for (n = 0; n < 3; n ++) { + if (root[n] == NULL) + break; + + log_debug("%s(): Checking for %s/%s subdirectory", + __func__, context->root, root[n]); + + if (lstat(root[n], &sb) < 0) { + if (errno == ENOENT) + continue; + + log_err("%s(): Could not lstat(%s/%s). %s (%d)", + __func__, context->root, root[n], + strerror(errno), errno); + + return (-1); + } + + strlcat(context->root, "/", PATH_MAX); + strlcat(context->root, root[n], PATH_MAX); + + if (chdir(root[n]) < 0) { + log_err("%s(): Could not chdir(%s). %s (%d)", + __func__, context->root, strerror(errno), + errno); + return (-1); + } + + /* If user was not set before, take it from lstat() data */ + if (context->user == NULL) { + context->uid = sb.st_uid; + context->gid = sb.st_gid; + } + + log_debug("%s(): Using device specific root %s, uid=%d, gid=%d", + __func__, context->root, context->uid, context->gid); + + return (1); + } + + log_err("%s(): Could not find device specific root for the device " \ + "bdaddr %s (%s)", __func__, root[1], + root[0]? root[0] : "-no-name-"); + + return (-1); +} /* obexapp_server_set_device_root */ + +/* + * Finalize server root + */ + +static int +obexapp_server_set_final_root(context_p context) +{ + if (context->secure) { + if (chroot(context->root) < 0) { + log_err("%s(): Could not chroot(%s). %s (%d)", + __func__, context->root, + strerror(errno), errno); + return (-1); + } + + strlcpy(context->root, "/", PATH_MAX); + } + + if (context->gid != getgid() && setgid(context->gid) < 0) { + log_err("%s(): Could not setgid(%d). %s (%d)", + __func__, context->gid, strerror(errno), errno); + return (-1); + } + + if (context->uid != getuid() && setuid(context->uid) < 0) { + log_err("%s(): Could not setuid(%d). %s (%d)", + __func__, context->uid, strerror(errno), errno); + return (-1); + } + + log_notice("%s(): Using root %s; Secure mode %s; " + "Running as uid=%d, gid=%d", __func__, context->root, + context->secure? "enabled" : "disabled", getuid(), getgid()); + + return (0); +} /* obexapp_server_set_final_root */ + +/* * Process OBEX_EV_REQHINT event */ @@ -565,6 +679,15 @@ } } + if (chmod(context->temp, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP) < 0) { + log_err("%s(): Could not chmod(%s). %s (%d)", + __func__, context->temp, + strerror(errno), errno); + + codes = obexapp_util_errno2response(errno); + goto done; + } + if (rename(context->temp, context->file) < 0) { log_err("%s(): Could not rename(%s, %s). %s (%d)", __func__, context->temp, Index: transport.c =================================================================== RCS file: /usr/local/cvs/ports/obexapp/transport.c,v retrieving revision 1.13 diff -u -r1.13 transport.c --- transport.c 21 May 2007 15:55:35 -0000 1.13 +++ transport.c 14 Apr 2009 21:26:02 -0000 @@ -1,7 +1,7 @@ /* * transport.c * - * Copyright (c) 2001 Maksim Yevmenkin + * Copyright (c) 2001-2009 Maksim Yevmenkin * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -280,6 +280,9 @@ return (-1); } + memcpy(&context->raddr, &addr.rfcomm_bdaddr, + sizeof(context->raddr)); + return (1); } Index: util.c =================================================================== RCS file: /usr/local/cvs/ports/obexapp/util.c,v retrieving revision 1.14 diff -u -r1.14 util.c --- util.c 10 Apr 2009 17:26:03 -0000 1.14 +++ util.c 14 Apr 2009 21:26:08 -0000 @@ -1,7 +1,7 @@ /* * util.c * - * Copyright (c) 2002 Maksim Yevmenkin + * Copyright (c) 2002-2009 Maksim Yevmenkin * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -425,9 +425,7 @@ * string, so always pass a copy. */ - strncpy(n, name, sizeof(n)); - n[sizeof(n) - 1] = '\0'; - + strlcpy(n, name, sizeof(n)); snprintf(temp, temp_size, "%s/XXXXXXXX", dirname(n)); return (mkstemp(temp)); From mi+thun at aldan.algebra.com Tue Apr 14 15:12:46 2009 From: mi+thun at aldan.algebra.com (Mikhail T.) Date: Tue Apr 14 15:25:38 2009 Subject: RFC: obexapp - virtual root folder for each device In-Reply-To: References: <49E3DB35.4030601@aldan.algebra.com> <49E41DBB.5090805@aldan.algebra.com> <49E4CAE1.6090506@aldan.algebra.com> <49E4EA14.80300@aldan.algebra.com> Message-ID: <49E50A59.9000606@aldan.algebra.com> Maksim Yevmenkin ???????(??): > hopefully the latest patch will work for everyone. > Little nits this time... 1. Even when the service is not running as root to begin with (and thus chroot is impossible), a user owning multiple devices may wish to have separate directories for each one. Yet, the new -R switch, as proposed, requires successful chroot into a matching entry... It does not need to. How about: + case 'R': /* virtualize root for each device */ + context.vroot = 1; + if (getuid() == 0) + context.secure = 1; + break; + chdir should be used instead of chroot in this case (-R was given, and a matching entry is found, but we aren't running as root). 2. When going through the list of possible subdirectories-candidates, hardcoding the number 3 is a bit dangerous. Using either sizeof(root)/sizeof(root[0]) or simply going, until hitting NULL: char const *root[] = { NULL, NULL, NULL, NULL }, **r; ... foreach (r -> root; *r; r++) { log_debug("%s(): Checking for %s/%s subdirectory", __func__, context->root, *r); ... would be a bit safer going forward -- what if some other parameter (such as an environment variable) may some day be added to the list of considerations? 3. After starting up, with the -R (or the -r) option, should/does not the daemon chdir into the specified top-level directory? And if so, there is no need to assemble the context->root with strlcat -- just perform chdir into the relative root[n] (or *r in my example). The chroot can then happen to ".". After chdir-ing, you can populate the actual contet->root with getcwd(context->root, PATH_MAX) -- a faster equivalent to using realpath(3). This is not material, but the fewer cases, where a hard-coded PATH_MAX is used instead of the POSIX-approved pathconf(2), the better... Yours, -mi P.S. Thank you very much for keeping the wallabies in the manual's examples. Better creatures are not to be found, and it is certainly nicer to use them instead of the ubiquitous "foo" and "bar". From maksim.yevmenkin at gmail.com Tue Apr 14 15:46:45 2009 From: maksim.yevmenkin at gmail.com (Maksim Yevmenkin) Date: Tue Apr 14 16:18:40 2009 Subject: RFC: obexapp - virtual root folder for each device In-Reply-To: <49E50A59.9000606@aldan.algebra.com> References: <49E3DB35.4030601@aldan.algebra.com> <49E41DBB.5090805@aldan.algebra.com> <49E4CAE1.6090506@aldan.algebra.com> <49E4EA14.80300@aldan.algebra.com> <49E50A59.9000606@aldan.algebra.com> Message-ID: 2009/4/14 Mikhail T. : > Maksim Yevmenkin ???????(??): >> hopefully the latest patch will work for everyone. >> > Little nits this time... > > 1. Even when the service is not running as root to begin with (and thus > chroot is impossible), a user owning multiple devices may wish to have > separate directories for each one. Yet, the new -R switch, as proposed, > requires successful chroot into a matching entry... It does not need to. > How about: > > + case 'R': /* virtualize root for each device */ > + context.vroot = 1; > + if (getuid() == 0) > + context.secure = 1; > + break; > + > > chdir should be used instead of chroot in this case (-R was given, and a > matching entry is found, but we aren't running as root). well, a couple of things. for now, we always have to start obexapp as root because it needs to talk to sdpd(8) to register services. sdpd(8) is checking credentials (passed via unix sockets) and makes sure that the process, that is trying to register the service, has uid of 'root' user. so, strictly speaking this change is a no-op because getuid() will always be 0 or else obexapp will not start in server mode. also, i'd _really_ like to keep clients "jailed" under their virtual root folders. at least for now. as far as keeping and sharing files under the same root folder, i just thought of a way to "break" the latest patch: set up symlink that points to '.' under default root folder. obviously chdir() and chroot() conditions will be satisfied and you get your files dumped in the same directory. > 2. When going through the list of possible subdirectories-candidates, > hardcoding the number 3 is a bit dangerous. Using either > > sizeof(root)/sizeof(root[0]) > > or simply going, until hitting NULL: > > char const *root[] = { NULL, NULL, NULL, NULL }, **r; > ... > foreach (r -> root; *r; r++) { > > log_debug("%s(): Checking for %s/%s subdirectory", > __func__, context->root, *r); > ... > > would be a bit safer going forward -- what if some other parameter (such > as an environment variable) may some day be added to the list of > considerations? that's fine. i will fix it. > 3. After starting up, with the -R (or the -r) option, should/does not > the daemon chdir into the specified top-level directory? And if so, > there is no need to assemble the context->root with strlcat -- just > perform chdir into the relative root[n] (or *r in my example). The > chroot can then happen to ".". After chdir-ing, you can populate the > actual contet->root with getcwd(context->root, PATH_MAX) -- a faster > equivalent to using realpath(3). it does, chdir(), i.e. obexapp_server_set_initial_root() does it. strlcat() is not that expensive, imo. it can be changed, i guess. > This is not material, but the fewer cases, where a hard-coded PATH_MAX > is used instead of the POSIX-approved pathconf(2), the better... PATH_MAX comes from sys/syslimits.h, so, i thought it would be ok to use. another alternative was MAXPATHLEN which was the same. thanks, max From mwm at mired.org Tue Apr 14 16:13:32 2009 From: mwm at mired.org (Mike Meyer) Date: Tue Apr 14 16:23:59 2009 Subject: RFC: obexapp - virtual root folder for each device In-Reply-To: References: <49E3DB35.4030601@aldan.algebra.com> <49E41DBB.5090805@aldan.algebra.com> Message-ID: <20090414184507.5afc4a88@bhuda.mired.org> On Tue, 14 Apr 2009 09:34:43 -0700 Maksim Yevmenkin wrote: > > Maksim Yevmenkin ???????(??): > > because all of the bluetooth users would have to be listed in obex' > > group and so there can be no more than 20 of them (I think, that's the > > limit on a group size). Just so this doesn't wind up being quoted as accurate: There are no limits on how many people can be in a group. There is a limit on how many groups a person can be in (or was; I haven't verified that this hasn't been changed recently). BSD was used at UC Berkeley (the B in BSD) on machines with thousands of users, and groups with hundreds of members. We had to db'ify the password file, but the groups code worked just fine. http://www.mired.org/consulting.html Independent Network/Unix/Perforce consultant, email for more information. O< ascii ribbon campaign - stop html mail - www.asciiribbon.org From mi+thun at aldan.algebra.com Tue Apr 14 16:22:27 2009 From: mi+thun at aldan.algebra.com (Mikhail T.) Date: Tue Apr 14 16:54:08 2009 Subject: RFC: obexapp - virtual root folder for each device In-Reply-To: References: <49E3DB35.4030601@aldan.algebra.com> <49E41DBB.5090805@aldan.algebra.com> <49E4CAE1.6090506@aldan.algebra.com> <49E4EA14.80300@aldan.algebra.com> <49E50A59.9000606@aldan.algebra.com> Message-ID: <49E51AAF.4060907@aldan.algebra.com> Maksim Yevmenkin ???????(??): > 2009/4/14 Mikhail T. : > >> Maksim Yevmenkin ???????(??): >> >>> hopefully the latest patch will work for everyone. >>> >> Little nits this time... >> >> 1. Even when the service is not running as root to begin with (and thus >> chroot is impossible), a user owning multiple devices may wish to have >> separate directories for each one. Yet, the new -R switch, as proposed, >> requires successful chroot into a matching entry... It does not need to. >> How about: >> >> + case 'R': /* virtualize root for each device */ >> + context.vroot = 1; >> + if (getuid() == 0) >> + context.secure = 1; >> + break; >> + >> >> chdir should be used instead of chroot in this case (-R was given, and a >> matching entry is found, but we aren't running as root). >> > > well, a couple of things. for now, we always have to start obexapp as > root because it needs to talk to sdpd(8) to register services. sdpd(8) > is checking credentials (passed via unix sockets) and makes sure that > the process, that is trying to register the service, has uid of 'root' > user. so, strictly speaking this change is a no-op because getuid() > will always be 0 or else obexapp will not start in server mode. > Oh, that must be why I couldn't start obexapp as myself before... So, all of those getuid() calls are no-ops too? Either they need to be eliminated, or, if other ways of authenticating to sdp are in the works, ability to chroot should not be a requirement. > also, i'd _really_ like to keep clients "jailed" under their virtual > root folders. at least for now. as far as keeping and sharing files > under the same root folder, i just thought of a way to "break" the > latest patch: set up symlink that points to '.' under default root > folder. obviously chdir() and chroot() conditions will be satisfied > and you get your files dumped in the same directory. > Right, of course. The symlinks can point to . or to some external /home/dropoff -- same thing. > >> 3. After starting up, with the -R (or the -r) option, should/does not >> the daemon chdir into the specified top-level directory? And if so, >> there is no need to assemble the context->root with strlcat -- just >> perform chdir into the relative root[n] (or *r in my example). The >> chroot can then happen to ".". After chdir-ing, you can populate the >> actual contet->root with getcwd(context->root, PATH_MAX) -- a faster >> equivalent to using realpath(3). >> > > it does, chdir(), i.e. obexapp_server_set_initial_root() does it. > strlcat() is not that expensive, imo. it can be changed, i guess. > It is not expensive, but not needed either :) After a chdir succeeds, the chroot can go simply into "." >> This is not material, but the fewer cases, where a hard-coded PATH_MAX >> is used instead of the POSIX-approved pathconf(2), the better... >> > PATH_MAX comes from sys/syslimits.h, so, i thought it would be ok to > use. another alternative was MAXPATHLEN which was the same. > The gist of the PATH_MAX/MAXPATHLEN vs. pathconf() controversy is that the latter may change after the program is compiled. Thus relying on ANY compile-time buffer-length as being "long enough" is potentially dangerous (and almost always wasteful). Logging the actual directory (as can be determined by getwd(NULL) after chdir-ing, but before chroot-ing), rather than the name of the symlink can be seen as beneficial too. Yours, -mi From mi+thun at aldan.algebra.com Tue Apr 14 16:29:43 2009 From: mi+thun at aldan.algebra.com (Mikhail T.) Date: Tue Apr 14 17:13:51 2009 Subject: group-related limits (Re: RFC: obexapp - virtual root folder for each device) In-Reply-To: <20090414184507.5afc4a88@bhuda.mired.org> References: <49E3DB35.4030601@aldan.algebra.com> <49E41DBB.5090805@aldan.algebra.com> <20090414184507.5afc4a88@bhuda.mired.org> Message-ID: <49E51C60.5020302@aldan.algebra.com> Mike Meyer ???????(??): > On Tue, 14 Apr 2009 09:34:43 -0700 > Maksim Yevmenkin wrote: > > >>> Maksim Yevmenkin ???????(??): >>> because all of the bluetooth users would have to be listed in obex' >>> group and so there can be no more than 20 of them (I think, that's the >>> limit on a group size). >>> > > Just so this doesn't wind up being quoted as accurate: > > There are no limits on how many people can be in a group. There is a > limit on how many groups a person can be in (or was; I haven't > verified that this hasn't been changed recently). Thanks for the correction, Mike. I had only a vague memory of there being some limits related to groups -- from years ago :-) But still, if a person can only participate in so many groups, forcing participation in yet another one for common, non-administrative functionality should be avoided... Fortunately, we seem to have just agreed on a more straightforward way anyway. Yours, -mi From plunky at rya-online.net Wed Apr 15 00:20:15 2009 From: plunky at rya-online.net (Iain Hibbert) Date: Wed Apr 15 00:41:39 2009 Subject: group-related limits (Re: RFC: obexapp - virtual root folder for each device) In-Reply-To: <49E51C60.5020302@aldan.algebra.com> References: <49E3DB35.4030601@aldan.algebra.com> <49E41DBB.5090805@aldan.algebra.com> <20090414184507.5afc4a88@bhuda.mired.org> <49E51C60.5020302@aldan.algebra.com> Message-ID: <1239779929.872107.654.nullmailer@galant.ukfsn.org> On Tue, 14 Apr 2009, Mikhail T. wrote: > Mike Meyer ??????????????(????): > > There are no limits on how many people can be in a group. There is a > > limit on how many groups a person can be in (or was; I haven't > > verified that this hasn't been changed recently). > > Thanks for the correction, Mike. I had only a vague memory of there > being some limits related to groups -- from years ago :-) I don't know about FreeBSD but on NetBSD there is AFAIK no limit to the number of groups that a user can be in. The "20 groups" limit comes from NFS which used a fixed array in its specifications (and so is a problem on any operating system) iain From maksim.yevmenkin at gmail.com Wed Apr 15 13:59:36 2009 From: maksim.yevmenkin at gmail.com (Maksim Yevmenkin) Date: Wed Apr 15 14:29:40 2009 Subject: libhci update In-Reply-To: References: <49D92E26.2030508@incunabulum.net> <49DD40E2.5030403@incunabulum.net> <1239264003.862926.638.nullmailer@galant.ukfsn.org> <49DE4E2F.2000805@incunabulum.net> <49DE6D42.6000004@incunabulum.net> Message-ID: dear freebsd-bluetoth@ users, please find attached patch that implements more compatibility shims. any comments are greatly appreciated. thanks max -------------- next part -------------- Index: hci.c =================================================================== --- hci.c (revision 190870) +++ hci.c (working copy) @@ -30,15 +30,421 @@ * $FreeBSD$ */ +#include #include #include #include #include #include +static int bt_devany_cb(int s, struct bt_devinfo const *di, void *xdevname); static char * bt_dev2node (char const *devname, char *nodename, int nnlen); int +bt_devopen(char const *devname) +{ + struct sockaddr_hci ha; + bdaddr_t ba; + int s; + + if (devname == NULL) { + errno = EINVAL; + return (-1); + } + + memset(&ha, 0, sizeof(ha)); + ha.hci_len = sizeof(ha); + ha.hci_family = AF_BLUETOOTH; + + if (bt_aton(devname, &ba)) { + if (!bt_devname(ha.hci_node, &ba)) + return (-1); + } else if (bt_dev2node(devname, ha.hci_node, + sizeof(ha.hci_node)) == NULL) { + errno = ENXIO; + return (-1); + } + + s = socket(PF_BLUETOOTH, SOCK_RAW, BLUETOOTH_PROTO_HCI); + if (s < 0) + return (-1); + + if (bind(s, (struct sockaddr *) &ha, sizeof(ha)) < 0 || + connect(s, (struct sockaddr *) &ha, sizeof(ha)) < 0) { + close(s); + return (-1); + } + + return (s); +} + +int +bt_devclose(int s) +{ + return (close(s)); +} + +int +bt_devsend(int s, uint16_t ogf, uint16_t ocf, int plen, void *param) +{ + ng_hci_cmd_pkt_t h; + struct iovec iv[2]; + int ivn; + + if (plen < 0 || (plen > 0 && param == NULL)) { + errno = EINVAL; + return (-1); + } + + iv[0].iov_base = &h; + iv[0].iov_len = sizeof(h); + ivn = 1; + + h.type = NG_HCI_CMD_PKT; + h.opcode = htole16(NG_HCI_OPCODE(ogf, ocf)); + if (plen > 0) { + h.length = plen; + + iv[1].iov_base = param; + iv[1].iov_len = plen; + ivn = 2; + } else + h.length = 0; + + while (writev(s, iv, ivn) < 0) { + if (errno == EAGAIN || errno == EINTR) + continue; + + return (-1); + } + + return (0); +} + +int +bt_devrecv(int s, uint8_t *buf, int size, time_t to) +{ + fd_set rfd; + struct timeval tv; + int n; + + if (buf == NULL || size <= 0 || to < 0) { + errno = EINVAL; + return (-1); + } + + FD_ZERO(&rfd); + FD_SET(s, &rfd); + + tv.tv_sec = to; + tv.tv_usec = 0; + + while ((n = select(s + 1, &rfd, NULL, NULL, &tv)) < 0) { + if (errno == EAGAIN || errno == EINTR) + continue; + + return (-1); + } + + if (n == 0) { + errno = ETIMEDOUT; + return (-1); + } + + assert(FD_ISSET(s, &rfd)); + + while ((n = read(s, buf, size)) < 0) { + if (errno == EAGAIN || errno == EINTR) + continue; + + return (-1); + } + + return (n); +} + +int +bt_devreq(int s, struct bt_devreq *r, time_t to) +{ + uint8_t buf[320]; /* more than enough */ + ng_hci_event_pkt_t *e = (ng_hci_event_pkt_t *) buf; + ng_hci_command_compl_ep *cc = (ng_hci_command_compl_ep *)(e+1); + ng_hci_command_status_ep *cs = (ng_hci_command_status_ep*)(e+1); + uint16_t opcode; + time_t t_end; + int n; + + if (s < 0 || r == NULL || to < 0) { + errno = EINVAL; + return (-1); + } + + if (r->rlen < 0 || (r->rlen > 0 && r->rparam == NULL)) { + errno = EINVAL; + return (-1); + } + + n = bt_devsend(s, r->ogf, r->ocf, r->clen, r->cparam); + if (n < 0) + return (-1); + + opcode = htole16(NG_HCI_OPCODE(r->ogf, r->ocf)); + + t_end = time(NULL) + to; + + do { + to = t_end - time(NULL); + if (to < 0) + to = 0; + + n = bt_devrecv(s, buf, sizeof(buf), to); + if (n < 0) + return (-1); + + if (n < sizeof(*e)) { + errno = EMSGSIZE; + return (-1); + } + + if (e->type != NG_HCI_EVENT_PKT) { + errno = EIO; + return (-1); + } + + n -= sizeof(*e); + + switch (e->event) { + case NG_HCI_EVENT_COMMAND_COMPL: + if (cc->opcode == opcode) { + n -= sizeof(*cc); + + if (r->rlen >= n) { + r->rlen = n; + memcpy(r->rparam, cc + 1, r->rlen); + } + + return (0); + } + break; + + case NG_HCI_EVENT_COMMAND_STATUS: + if (cs->opcode == opcode) { + if (r->event != NG_HCI_EVENT_COMMAND_STATUS) { + if (cs->status != 0) { + errno = EIO; + return (-1); + } + } else { + if (r->rlen >= n) { + r->rlen = n; + memcpy(r->rparam, cs, r->rlen); + } + + return (0); + } + } + break; + + default: + if (e->event == r->event) { + if (r->rlen >= n) { + r->rlen = n; + memcpy(r->rparam, e + 1, r->rlen); + } + + return (0); + } + break; + } + } while (to > 0); + + errno = ETIMEDOUT; + + return (-1); +} + +int +bt_devfilter(int s, struct bt_devfilter const *new, struct bt_devfilter *old) +{ + struct ng_btsocket_hci_raw_filter f; + socklen_t len; + int bit; + + if (new == NULL && old == NULL) { + errno = EINVAL; + return (-1); + } + + if (old != NULL) { + len = sizeof(f); + if (getsockopt(s, SOL_HCI_RAW, SO_HCI_RAW_FILTER, &f, &len) < 0) + return (-1); + + memset(old, 0, sizeof(*old)); + + for (bit = 0; bit < NG_HCI_EVENT_PKT; bit ++) + if (bit_test(f.packet_mask, bit)) + old->packet_mask |= (1 << bit); + + for (bit = 0; bit < NG_HCI_EVENT_MASK_SIZE * 8; bit ++) + if (bit_test(f.event_mask, bit)) + old->event_mask |= (1 << bit); + } + + if (new != NULL) { + memset(&f, 0, sizeof(f)); + + for (bit = 0; bit < NG_HCI_EVENT_PKT; bit ++) + if (new->packet_mask & (1 << bit)) + bit_set(f.packet_mask, bit); + + for (bit = 0; bit < (NG_HCI_EVENT_MASK_SIZE * 8); bit ++) + if (new->event_mask & (1 << bit)) + bit_set(f.event_mask, bit); + + len = sizeof(f); + if (setsockopt(s, SOL_HCI_RAW, SO_HCI_RAW_FILTER, &f, len) < 0) + return (-1); + } + + return (0); +} + +int +bt_devinquiry(char const *devname, int length, int num_rsp, + uint8_t const *lap, struct bt_devinquiry **ii) +{ + uint8_t buf[320]; + char _devname[HCI_DEVNAME_SIZE]; + struct bt_devfilter f; + ng_hci_inquiry_cp *cp = (ng_hci_inquiry_cp *) buf; + ng_hci_event_pkt_t *e = (ng_hci_event_pkt_t *) buf; + ng_hci_inquiry_result_ep *ep = (ng_hci_inquiry_result_ep *)(e+1); + ng_hci_inquiry_response *ir; + struct bt_devinquiry *i; + int s, n; + time_t to; + + if (ii == NULL) { + errno = EINVAL; + return (-1); + } + + if (devname == NULL) { + memset(_devname, 0, sizeof(_devname)); + devname = _devname; + + n = bt_devenum(bt_devany_cb, _devname); + if (n <= 0) { + if (n == 0) + *ii = NULL; + + return (n); + } + } + + s = bt_devopen(devname); + if (s < 0) + return (-1); + + if (bt_devfilter(s, NULL, &f) < 0) { + bt_devclose(s); + return (-1); + } + + f.event_mask |= (1 << (NG_HCI_EVENT_INQUIRY_COMPL - 1)); + f.event_mask |= (1 << (NG_HCI_EVENT_INQUIRY_RESULT - 1)); + + if (bt_devfilter(s, &f, NULL) < 0) { + bt_devclose(s); + return (-1); + } + + if (lap == NULL) { + cp->lap[0] = 0x33; + cp->lap[1] = 0x8b; + cp->lap[2] = 0x9e; + } else { + cp->lap[0] = lap[0]; + cp->lap[1] = lap[1]; + cp->lap[2] = lap[2]; + } + + if (length <= 0 || length > 255) + length = 4; /* 5.12 seconds */ + cp->inquiry_length = (uint8_t) length; + + to = (time_t)((double) length * 1.28) + 1; + + if (num_rsp <= 0 || num_rsp > 255) + num_rsp = 8; + cp->num_responses = (uint8_t) num_rsp; + + i = *ii = calloc(num_rsp, sizeof(struct bt_devinquiry)); + if (i == NULL) { + bt_devclose(s); + errno = ENOMEM; + return (-1); + } + + if (bt_devsend(s, NG_HCI_OGF_LINK_CONTROL, NG_HCI_OCF_INQUIRY, + sizeof(*cp), cp) < 0) { + free(i); + bt_devclose(s); + return (-1); + } + +wait_for_more: + + n = bt_devrecv(s, buf, sizeof(buf), to); + if (n < 0) { + free(i); + bt_devclose(s); + return (-1); + } + + if (n < sizeof(ng_hci_event_pkt_t)) { + free(i); + bt_devclose(s); + errno = EIO; + return (-1); + } + + switch (e->event) { + case NG_HCI_EVENT_INQUIRY_COMPL: + break; + + case NG_HCI_EVENT_INQUIRY_RESULT: + ir = (ng_hci_inquiry_response *)(ep + 1); + +#undef MIN +#define MIN(a, b) (((a) < (b))? (a) : (b)) + + for (n = 0; n < MIN(ep->num_responses, num_rsp); n ++) { + bdaddr_copy(&i->bdaddr, &ir->bdaddr); + i->pscan_rep_mode = ir->page_scan_rep_mode; + i->pscan_period_mode = ir->page_scan_period_mode; + i->pscan_mode = ir->page_scan_mode; + memcpy(i->dev_class, ir->uclass, sizeof(i->dev_class)); + i->clock_offset = le16toh(ir->clock_offset); + + ir ++; + i ++; + num_rsp --; + } + /* FALLTHROUGH */ + + default: + goto wait_for_more; + /* NOT REACHED */ + } + + bt_devclose(s); + + return (i - *ii); +} + +int bt_devinfo(struct bt_devinfo *di) { union { @@ -53,6 +459,7 @@ struct ng_btsocket_hci_raw_node_debug r8; } rp; struct sockaddr_hci ha; + socklen_t halen; int s, rval; if (di == NULL) { @@ -60,27 +467,14 @@ return (-1); } - memset(&ha, 0, sizeof(ha)); - ha.hci_len = sizeof(ha); - ha.hci_family = AF_BLUETOOTH; - - if (bt_aton(di->devname, &rp.r1.bdaddr)) { - if (!bt_devname(ha.hci_node, &rp.r1.bdaddr)) - return (-1); - } else if (bt_dev2node(di->devname, ha.hci_node, - sizeof(ha.hci_node)) == NULL) { - errno = ENXIO; - return (-1); - } - - s = socket(PF_BLUETOOTH, SOCK_RAW, BLUETOOTH_PROTO_HCI); + s = bt_devopen(di->devname); if (s < 0) return (-1); rval = -1; - if (bind(s, (struct sockaddr *) &ha, sizeof(ha)) < 0 || - connect(s, (struct sockaddr *) &ha, sizeof(ha)) < 0) + halen = sizeof(ha); + if (getsockname(s, (struct sockaddr *) &ha, &halen) < 0) goto bad; strlcpy(di->devname, ha.hci_node, sizeof(di->devname)); @@ -138,7 +532,7 @@ rval = 0; bad: - close(s); + bt_devclose(s); return (rval); } @@ -205,6 +599,13 @@ return (count); } +static int +bt_devany_cb(int s, struct bt_devinfo const *di, void *xdevname) +{ + strlcpy((char *) xdevname, di->devname, HCI_DEVNAME_SIZE); + return (1); +} + static char * bt_dev2node(char const *devname, char *nodename, int nnlen) { Index: bluetooth.3 =================================================================== --- bluetooth.3 (revision 190870) +++ bluetooth.3 (working copy) @@ -25,7 +25,7 @@ .\" $Id: bluetooth.3,v 1.5 2003/05/20 23:04:30 max Exp $ .\" $FreeBSD$ .\" -.Dd February 13, 2009 +.Dd April 9, 2009 .Dt BLUETOOTH 3 .Os .Sh NAME @@ -41,6 +41,17 @@ .Nm bt_endprotoent , .Nm bt_aton , .Nm bt_ntoa , +.Nm bt_devaddr , +.Nm bt_devname , +.Nm bt_devinfo , +.Nm bt_devenum , +.Nm bt_devopen , +.Nm bt_devclose , +.Nm bt_devsend , +.Nm bt_devrecv , +.Nm bt_devreq , +.Nm bt_devfilter , +.Nm bt_devinquiry , .Nm bdaddr_same , .Nm bdaddr_any , .Nm bdaddr_copy @@ -84,6 +95,20 @@ .Ft int .Fn bt_devenum "bt_devenum_cb_t *cb" "void *arg" .Ft int +.Fn bt_devopen "char const *devname" +.Ft int +.Fn bt_devclose "int s" +.Ft int +.Fn bt_devsend "int s" "uint16_t ogf" "uint16_t ocf" "int plen" "void *param" +.Ft int +.Fn bt_devrecv "int s" "uint8_t *buf" "int size" "time_t to" +.Ft int +.Fn bt_devreq "int s" "struct bt_devreq *r" "time_t to" +.Ft int +.Fn bt_devfilter "int s" "struct bt_devfilter const *new" "struct bt_devfilter *old" +.Ft int +.Fn bt_devinquiry "char const *devname" "int length" "int num_rsp" "uint8_t const *lap" "struct bt_devinquiry **ii" +.Ft int .Fn bdaddr_same "const bdaddr_t *a" "const bdaddr_t *b" .Ft int .Fn bdaddr_any "const bdaddr_t *a" @@ -311,6 +336,219 @@ or -1 if an error occurred. .Pp The +.Fn bt_devopen +function opens Bluetooth device with the given +.Fa devname +and returns connected and bound +.Dv HCI +socket. +The function returns -1 if an error has occurred. +.Pp +The +.Fn bt_devclose +closes passed +.Dv HCI +socket +.Fa s , +previously obtained with +.Xr bt_devopen 3 . +.Pp +The +.Fn bt_devsend +function sends Bluetooth +.Dv HCI +command with the OpCode Group Field +.Fa ogf +and +OpCode Command Field +.Fa ocf +to the provided socket +.Fa s , +previously obtained with +.Xr bt_devopen 3 . +The +.Fa plen +and +.Fa param +parameters specify command parameters. +The function returns 0 on success, +or -1 if an error occurred. +.Pp +The +.Fn bt_devrecv +function receives one Bluetooth +.Dv HCI +event packet from the socket +.Fa s , +previously obtained with +.Xr bt_devopen 3 . +The event packet is placed into the provided buffer +.Fa buf +of size +.Fa size . +The +.Fa to +parameter specifies receive timeout in seconds. +The function returns total number of bytes recevied, +or -1 if an error occurred. +.Pp +The +.Fn bt_devreq +function makes Bluetooth +.Dv HCI +request to the socket +.Fa s , +previously obtained with +.Xr bt_devopen 3 . +The function will send the specified command and will wait for the specified +event, +or timeout +.Fa to +seconds to occur. +The +.Vt bt_devreq +structure is defined as follows +.Bd -literal -offset indent +struct bt_devreq +{ + uint16_t ogf; + uint16_t ocf; + int event; + void *cparam; + int clen; + void *rparam; + int rlen; +}; +.Ed +.Pp +The +.Fa ogf +and +.Fa ocf +fields specify OpCode Group and Command Field respectively. +The +.Fa cparam +and +.Fa clen +fields specify command parameters data and command parameters data size +respectively. +The +.Fa event +field specifies which Bluetooth +.Dv HCI +event ID the function should wait for. +The +.Fa rparam +and +.Fa rlen +parameters specify buffer and buffer size respectively where return +parameters should be placed. +The function returns 0 on success, or -1 if an error occurred. +.Pp +The +.Fn bt_devfilter +controls the local +.Dv HCI +filter associated with the socket +.Fa s , +previously obtained with +.Xr bt_devopen 3 . +Filtering can be done on packet types, i.e. +.Dv ACL , +.Dv SCO or +.Dv HCI +event packets, and, in addition, on +.Dv HCI +event IDs. +Before applying +.Fa new +filter (if provided) the function will try to obtain current filter +from the socket +.Fa s +and place it into the +.Fa old +parameter (if provided). +The +.Vt bt_devfilter +structure is defined as follows +.Bd -literal -offset indent +struct bt_devfilter { + uint64_t event_mask; + uint8_t packet_mask; +}; +.Ed +.Pp +Both +.Fa event_mask +and +.Fa packet_mask +fields are bit masks. +If a bit +.Fa N +is cleared in the +.Fa event_mask +then the corresponding Bluetooth +.Dv HCI +event ID +.Fa N +is filtered out. +If a bit +.Fa N +is cleared in the +.Fa packet_mask +then all the packets with the corresponding packet indicator are filtered out. +The function returns 0 on success, or -1 if an error occurred. +.Pp +The +.Fn bt_devinquiry +function performs Bluetooth inquiry. +The +.Fa devname +parameter specifies which local Bluetooth device should perform an inquiry. +If not secified, i.e. +.Dv NULL , +then first available device will be used. +The +.Fa length +parameters specifies the total length of an inquiry in 1.28 second units. +If not specified, i.e. 0, default value will be used. +The +.Fa num_rsp +parameter specifies the number of responses that can be received before +the inquiry is halted. +If not specified, i.e. 0, default value will be used. +The +.Fa lap +parameter contains the LAP from which the inquiry access code will be +be derived when the inquiry procedure is made. +If not specified, i.e. +.Dv NULL , +then GIAC LAP 9e:8b:33 will be used. +The +.Fa ii +parameter specifies where to place inquiry results. +On success, the function will return total number of inquiry results, +will allocate buffer to store all the inquiry results and +will return pointer to the allocated buffer in the +.Fa ii +parameter. +It is up to the caller of the function to dispose of the buffer. +The function returns -1 if an error has occurred. +The +.Vt bt_devinquiry +structure is defined as follows +.Bd -literal -offset indent +struct bt_devinquiry { + bdaddr_t bdaddr; + uint8_t pscan_rep_mode; + uint8_t pscan_period_mode; + uint8_t pscan_mode; + uint8_t dev_class[3]; + uint16_t clock_offset; +}; +.Ed +.Pp +The .Fn bdaddr_same , .Fn bdaddr_any and Index: bluetooth.h =================================================================== --- bluetooth.h (revision 190870) +++ bluetooth.h (working copy) @@ -39,6 +39,7 @@ #include #include #include +#include #include #include #include @@ -46,6 +47,7 @@ #include #include #include +#include __BEGIN_DECLS @@ -129,8 +131,42 @@ uint8_t _padding[20]; /* leave space for future additions */ }; +struct bt_devreq +{ + uint16_t ogf; + uint16_t ocf; + int event; + void *cparam; + int clen; + void *rparam; + int rlen; +}; + +struct bt_devfilter { + uint64_t event_mask; + uint8_t packet_mask; +}; + +struct bt_devinquiry { + bdaddr_t bdaddr; + uint8_t pscan_rep_mode; + uint8_t pscan_period_mode; + uint8_t pscan_mode; + uint8_t dev_class[3]; + uint16_t clock_offset; +}; + typedef int (bt_devenum_cb_t)(int, struct bt_devinfo const *, void *); +int bt_devopen (char const *devname); +int bt_devclose(int s); +int bt_devsend (int s, uint16_t ogf, uint16_t ocf, int plen, void *param); +int bt_devrecv (int s, uint8_t *buf, int size, time_t to); +int bt_devreq (int s, struct bt_devreq *r, time_t to); +int bt_devfilter(int s, struct bt_devfilter const *new, + struct bt_devfilter *old); +int bt_devinquiry(char const *devname, int length, int num_rsp, + uint8_t const *lap, struct bt_devinquiry **ii); int bt_devinfo (struct bt_devinfo *di); int bt_devenum (bt_devenum_cb_t *cb, void *arg); Index: Makefile =================================================================== --- Makefile (revision 190870) +++ Makefile (working copy) @@ -33,6 +33,13 @@ MLINKS+= bluetooth.3 bt_devinfo.3 MLINKS+= bluetooth.3 bt_devenum.3 +MLINKS+= bluetooth.3 bt_devopen.3 +MLINKS+= bluetooth.3 bt_devclose.3 +MLINKS+= bluetooth.3 bt_devsend.3 +MLINKS+= bluetooth.3 bt_devreq.3 +MLINKS+= bluetooth.3 bt_devfilter.3 +MLINKS+= bluetooth.3 bt_devinquiry.3 + MLINKS+= bluetooth.3 bdaddr_same.3 MLINKS+= bluetooth.3 bdaddr_any.3 MLINKS+= bluetooth.3 bdaddr_copy.3 From plunky at rya-online.net Thu Apr 16 01:47:28 2009 From: plunky at rya-online.net (Iain Hibbert) Date: Thu Apr 16 02:07:50 2009 Subject: libhci update In-Reply-To: References: <49D92E26.2030508@incunabulum.net> <49DD40E2.5030403@incunabulum.net> <1239264003.862926.638.nullmailer@galant.ukfsn.org> <49DE4E2F.2000805@incunabulum.net> <49DE6D42.6000004@incunabulum.net> Message-ID: <1239871569.560012.1086.nullmailer@galant.ukfsn.org> On Wed, 15 Apr 2009, Maksim Yevmenkin wrote: > please find attached patch that implements more compatibility shims. > any comments are greatly appreciated. +int +bt_devsend(int s, uint16_t ogf, uint16_t ocf, int plen, void *param) One thing that I did when writing the NetBSD stack which IMHO made source somewhat cleaner was provide HCI_CMD_xxxx definintions that included the OGF and OCF directly, eg #define HCI_OGF_LINK_CONTROL 0x01 #define HCI_OCF_INQUIRY 0x0001 #define HCI_CMD_INQUIRY 0x0401 It could be considered a remote possibility of a namespace conflict (ie same command in different groups) but I doubt that it will ever happen (and would be easily handled). That would make this into +int +bt_devsend(int s, uint16_t cmd, int plen, void *param) thoughts? Also, plen should be a size_t and it should come after the pointer, see write, read, memcpy, snprintf etc for prior art :) +int +bt_devrecv(int s, uint8_t *buf, int size, time_t to) and ditto for size_t here +int +bt_devinquiry(char const *devname, int length, int num_rsp, + uint8_t const *lap, struct bt_devinquiry **ii) I wonder if allowing for a different LAP is at all useful? I would say, for the very remote possibility that somebody wants to do that, they could just cut and past the code.. Also with inquiry, would it make sense to just pass a time_t and calculate the 'inquiry length' internally? +struct bt_devinquiry { + bdaddr_t bdaddr; + uint8_t pscan_rep_mode; + uint8_t pscan_period_mode; + uint8_t pscan_mode; + uint8_t dev_class[3]; + uint16_t clock_offset; +}; Does this structure need to be a direct copy of the inquiry result? page_scan_period_mode and page_scan_mode are deprecated since a long time so its probably not worth providing the values (most devices return 0 that I've seen). We also need to [be able] to handle the inquiry-result-with-rssi which gives an extra int8_t RSSI value, and the 2.1 extended inquiry result data. For both of those, I think its ok if they are zero if not provided. (ie actual support can be added later) struct bt_devinquiry { bdaddr_t bdaddr; uint8_t pscan_rep_mode; uint8_t class[HCI_CLASS_SIZE]; uint16_t clock_offset; int8_t rssi; uint8_t data[HCI_EXTENDED_INQUIRY_DATA_SIZE]; }; ? +int +bt_devfilter(int s, struct bt_devfilter const *new, struct bt_devfilter *old) And finally, the HCI filter is slightly different in NetBSD (I provided independent PKT and EVT filters each of 256 bits) and I'm going to think about that.. regards, iain From plunky at rya-online.net Thu Apr 16 05:16:34 2009 From: plunky at rya-online.net (Iain Hibbert) Date: Thu Apr 16 05:38:55 2009 Subject: libhci update In-Reply-To: <1239871569.560012.1086.nullmailer@galant.ukfsn.org> References: <49D92E26.2030508@incunabulum.net> <49DD40E2.5030403@incunabulum.net> <1239264003.862926.638.nullmailer@galant.ukfsn.org> <49DE4E2F.2000805@incunabulum.net> <49DE6D42.6000004@incunabulum.net> <1239871569.560012.1086.nullmailer@galant.ukfsn.org> Message-ID: <1239884118.412855.3144.nullmailer@galant.ukfsn.org> On Thu, 16 Apr 2009, Iain Hibbert wrote: > +int > +bt_devfilter(int s, struct bt_devfilter const *new, struct bt_devfilter *old) > > And finally, the HCI filter is slightly different in NetBSD (I provided > independent PKT and EVT filters each of 256 bits) and I'm going to think > about that.. Ok, I'm not objecting in priniciple to coupling the two filters together but I think that bt_devfilter should be opaque enough that the API does not depend about its internal structure. Ie, requiring the caller to subtract 1 and manage the bitwise manipulation + f.event_mask |= (1 << (NG_HCI_EVENT_INQUIRY_COMPL - 1)); + f.event_mask |= (1 << (NG_HCI_EVENT_INQUIRY_RESULT - 1)); is too specific and makes the callers somewhat messy. Can we provide some kind of accessor functions to do that? I include below what I used in NetBSD for an example.. Also, at least for events, the full 256 bits is required because there are events such as 0xfe (BT Logo) and 0xff (Vendor) that may currently be returned and the highest value (in 2.1 spec) is 0x3d, dangerously close to the 64 bit limit. Although its not likely that there will be many packet types added, it could be that some manufacturers would introduce custom packet types with similarly high end values.. (eg for private device configuration?) regards, iain /* * HCI socket filter and get/set routines * * for ease of use, we filter 256 possible events/packets */ struct hci_filter { uint32_t mask[8]; /* 256 bits */ }; static __inline void hci_filter_set(uint8_t bit, struct hci_filter *filter) { uint8_t off = bit - 1; off >>= 5; filter->mask[off] |= (1 << ((bit - 1) & 0x1f)); } static __inline void hci_filter_clr(uint8_t bit, struct hci_filter *filter) { uint8_t off = bit - 1; off >>= 5; filter->mask[off] &= ~(1 << ((bit - 1) & 0x1f)); } static __inline int hci_filter_test(uint8_t bit, struct hci_filter *filter) { uint8_t off = bit - 1; off >>= 5; return (filter->mask[off] & (1 << ((bit - 1) & 0x1f))); } From maksim.yevmenkin at gmail.com Fri Apr 17 16:21:27 2009 From: maksim.yevmenkin at gmail.com (Maksim Yevmenkin) Date: Fri Apr 17 16:29:30 2009 Subject: libhci update In-Reply-To: <1239884118.412855.3144.nullmailer@galant.ukfsn.org> References: <49D92E26.2030508@incunabulum.net> <1239264003.862926.638.nullmailer@galant.ukfsn.org> <49DE4E2F.2000805@incunabulum.net> <49DE6D42.6000004@incunabulum.net> <1239871569.560012.1086.nullmailer@galant.ukfsn.org> <1239884118.412855.3144.nullmailer@galant.ukfsn.org> Message-ID: On Thu, Apr 16, 2009 at 5:15 AM, Iain Hibbert wrote: > On Thu, 16 Apr 2009, Iain Hibbert wrote: > >> +int >> +bt_devfilter(int s, struct bt_devfilter const *new, struct bt_devfilter *old) >> >> And finally, the HCI filter is slightly different in NetBSD (I provided >> independent PKT and EVT filters each of 256 bits) and I'm going to think >> about that.. > > Ok, I'm not objecting in priniciple to coupling the two filters together > but I think that bt_devfilter should be opaque enough that the API does > not depend about its internal structure. Ie, requiring the caller to > subtract 1 and manage the bitwise manipulation > > + f.event_mask |= (1 << (NG_HCI_EVENT_INQUIRY_COMPL - 1)); > + f.event_mask |= (1 << (NG_HCI_EVENT_INQUIRY_RESULT - 1)); > > is too specific and makes the callers somewhat messy. Can we provide some > kind of accessor functions to do that? I include below what I used in > NetBSD for an example.. > > Also, at least for events, the full 256 bits is required because there are > events such as 0xfe (BT Logo) and 0xff (Vendor) that may currently be > returned and the highest value (in 2.1 spec) is 0x3d, dangerously close to > the 64 bit limit. Although its not likely that there will be many packet > types added, it could be that some manufacturers would introduce custom > packet types with similarly high end values.. (eg for private device > configuration?) thanks for the feedback. i'm attaching revisited patch. please take a look and let me know if this is something you happy with. thanks, max -------------- next part -------------- Index: hci.c =================================================================== --- hci.c (revision 191012) +++ hci.c (working copy) @@ -30,15 +30,445 @@ * $FreeBSD$ */ +#include #include #include #include #include #include +static int bt_devany_cb(int s, struct bt_devinfo const *di, void *xdevname); static char * bt_dev2node (char const *devname, char *nodename, int nnlen); int +bt_devopen(char const *devname) +{ + struct sockaddr_hci ha; + bdaddr_t ba; + int s; + + if (devname == NULL) { + errno = EINVAL; + return (-1); + } + + memset(&ha, 0, sizeof(ha)); + ha.hci_len = sizeof(ha); + ha.hci_family = AF_BLUETOOTH; + + if (bt_aton(devname, &ba)) { + if (!bt_devname(ha.hci_node, &ba)) + return (-1); + } else if (bt_dev2node(devname, ha.hci_node, + sizeof(ha.hci_node)) == NULL) { + errno = ENXIO; + return (-1); + } + + s = socket(PF_BLUETOOTH, SOCK_RAW, BLUETOOTH_PROTO_HCI); + if (s < 0) + return (-1); + + if (bind(s, (struct sockaddr *) &ha, sizeof(ha)) < 0 || + connect(s, (struct sockaddr *) &ha, sizeof(ha)) < 0) { + close(s); + return (-1); + } + + return (s); +} + +int +bt_devclose(int s) +{ + return (close(s)); +} + +int +bt_devsend(int s, uint16_t opcode, void *param, size_t plen) +{ + ng_hci_cmd_pkt_t h; + struct iovec iv[2]; + int ivn; + + if (plen < 0 || (plen > 0 && param == NULL)) { + errno = EINVAL; + return (-1); + } + + iv[0].iov_base = &h; + iv[0].iov_len = sizeof(h); + ivn = 1; + + h.type = NG_HCI_CMD_PKT; + h.opcode = htole16(opcode); + if (plen > 0) { + h.length = plen; + + iv[1].iov_base = param; + iv[1].iov_len = plen; + ivn = 2; + } else + h.length = 0; + + while (writev(s, iv, ivn) < 0) { + if (errno == EAGAIN || errno == EINTR) + continue; + + return (-1); + } + + return (0); +} + +int +bt_devrecv(int s, uint8_t *buf, size_t size, time_t to) +{ + fd_set rfd; + struct timeval tv; + int n; + + if (buf == NULL || size <= 0 || to < 0) { + errno = EINVAL; + return (-1); + } + + FD_ZERO(&rfd); + FD_SET(s, &rfd); + + tv.tv_sec = to; + tv.tv_usec = 0; + + while ((n = select(s + 1, &rfd, NULL, NULL, &tv)) < 0) { + if (errno == EAGAIN || errno == EINTR) + continue; + + return (-1); + } + + if (n == 0) { + errno = ETIMEDOUT; + return (-1); + } + + assert(FD_ISSET(s, &rfd)); + + while ((n = read(s, buf, size)) < 0) { + if (errno == EAGAIN || errno == EINTR) + continue; + + return (-1); + } + + return (n); +} + +int +bt_devreq(int s, struct bt_devreq *r, time_t to) +{ + uint8_t buf[320]; /* more than enough */ + ng_hci_event_pkt_t *e = (ng_hci_event_pkt_t *) buf; + ng_hci_command_compl_ep *cc = (ng_hci_command_compl_ep *)(e+1); + ng_hci_command_status_ep *cs = (ng_hci_command_status_ep*)(e+1); + time_t t_end; + uint16_t opcode; + int n; + + if (s < 0 || r == NULL || to < 0) { + errno = EINVAL; + return (-1); + } + + if (r->rlen < 0 || (r->rlen > 0 && r->rparam == NULL)) { + errno = EINVAL; + return (-1); + } + + n = bt_devsend(s, r->opcode, r->cparam, r->clen); + if (n < 0) + return (-1); + + opcode = htole16(r->opcode); + t_end = time(NULL) + to; + + do { + to = t_end - time(NULL); + if (to < 0) + to = 0; + + n = bt_devrecv(s, buf, sizeof(buf), to); + if (n < 0) + return (-1); + + if (n < sizeof(*e)) { + errno = EMSGSIZE; + return (-1); + } + + if (e->type != NG_HCI_EVENT_PKT) { + errno = EIO; + return (-1); + } + + n -= sizeof(*e); + + switch (e->event) { + case NG_HCI_EVENT_COMMAND_COMPL: + if (cc->opcode == opcode) { + n -= sizeof(*cc); + + if (r->rlen >= n) { + r->rlen = n; + memcpy(r->rparam, cc + 1, r->rlen); + } + + return (0); + } + break; + + case NG_HCI_EVENT_COMMAND_STATUS: + if (cs->opcode == opcode) { + if (r->event != NG_HCI_EVENT_COMMAND_STATUS) { + if (cs->status != 0) { + errno = EIO; + return (-1); + } + } else { + if (r->rlen >= n) { + r->rlen = n; + memcpy(r->rparam, cs, r->rlen); + } + + return (0); + } + } + break; + + default: + if (e->event == r->event) { + if (r->rlen >= n) { + r->rlen = n; + memcpy(r->rparam, e + 1, r->rlen); + } + + return (0); + } + break; + } + } while (to > 0); + + errno = ETIMEDOUT; + + return (-1); +} + +int +bt_devfilter(int s, struct bt_devfilter const *new, struct bt_devfilter *old) +{ + struct ng_btsocket_hci_raw_filter f; + socklen_t len; + + if (new == NULL && old == NULL) { + errno = EINVAL; + return (-1); + } + + if (old != NULL) { + len = sizeof(f); + if (getsockopt(s, SOL_HCI_RAW, SO_HCI_RAW_FILTER, &f, &len) < 0) + return (-1); + + memset(old, 0, sizeof(*old)); + memcpy(old->packet_mask, &f.packet_mask, + sizeof(old->packet_mask)); + memcpy(old->event_mask, &f.event_mask, + sizeof(old->event_mask)); + } + + if (new != NULL) { + memset(&f, 0, sizeof(f)); + memcpy(&f.packet_mask, new->packet_mask, sizeof(f.packet_mask)); + memcpy(&f.event_mask, new->event_mask, sizeof(f.event_mask)); + + len = sizeof(f); + if (setsockopt(s, SOL_HCI_RAW, SO_HCI_RAW_FILTER, &f, len) < 0) + return (-1); + } + + return (0); +} + +void +bt_devfilter_pkt_set(struct bt_devfilter *filter, int type) +{ + bit_set(filter->packet_mask, type - 1); +} + +void +bt_devfilter_pkt_clr(struct bt_devfilter *filter, int type) +{ + bit_clear(filter->packet_mask, type - 1); +} + +int +bt_devfilter_pkt_tst(struct bt_devfilter const *filter, int type) +{ + return (bit_test(filter->packet_mask, type - 1)); +} + +void +bt_devfilter_evt_set(struct bt_devfilter *filter, int event) +{ + bit_set(filter->event_mask, event - 1); +} + +void +bt_devfilter_evt_clr(struct bt_devfilter *filter, int event) +{ + bit_clear(filter->event_mask, event - 1); +} + +int +bt_devfilter_evt_tst(struct bt_devfilter const *filter, int event) +{ + return (bit_test(filter->event_mask, event - 1)); +} + +int +bt_devinquiry(char const *devname, time_t length, int num_rsp, + struct bt_devinquiry **ii) +{ + uint8_t buf[320]; + char _devname[HCI_DEVNAME_SIZE]; + struct bt_devfilter f; + ng_hci_inquiry_cp *cp = (ng_hci_inquiry_cp *) buf; + ng_hci_event_pkt_t *e = (ng_hci_event_pkt_t *) buf; + ng_hci_inquiry_result_ep *ep = (ng_hci_inquiry_result_ep *)(e+1); + ng_hci_inquiry_response *ir; + struct bt_devinquiry *i; + int s, n; + time_t to; + + if (ii == NULL) { + errno = EINVAL; + return (-1); + } + + if (devname == NULL) { + memset(_devname, 0, sizeof(_devname)); + devname = _devname; + + n = bt_devenum(bt_devany_cb, _devname); + if (n <= 0) { + if (n == 0) + *ii = NULL; + + return (n); + } + } + + s = bt_devopen(devname); + if (s < 0) + return (-1); + + if (bt_devfilter(s, NULL, &f) < 0) { + bt_devclose(s); + return (-1); + } + + bt_devfilter_evt_set(&f, NG_HCI_EVENT_INQUIRY_COMPL); + bt_devfilter_evt_set(&f, NG_HCI_EVENT_INQUIRY_RESULT); + + if (bt_devfilter(s, &f, NULL) < 0) { + bt_devclose(s); + return (-1); + } + + /* Always use GIAC LAP */ + cp->lap[0] = 0x33; + cp->lap[1] = 0x8b; + cp->lap[2] = 0x9e; + + /* Calculate inquire length in 1.28 second units */ + to = (time_t) ((double) length / 1.28); + if (to <= 0) + cp->inquiry_length = 4; /* 5.12 seconds */ + else if (to > 254) + cp->inquiry_length = 255; /* 326.40 seconds */ + else + cp->inquiry_length = to + 1; + + to = (time_t)((double) cp->inquiry_length * 1.28) + 1; + + if (num_rsp <= 0 || num_rsp > 255) + num_rsp = 8; + cp->num_responses = (uint8_t) num_rsp; + + i = *ii = calloc(num_rsp, sizeof(struct bt_devinquiry)); + if (i == NULL) { + bt_devclose(s); + errno = ENOMEM; + return (-1); + } + + if (bt_devsend(s, + NG_HCI_OPCODE(NG_HCI_OGF_LINK_CONTROL, NG_HCI_OCF_INQUIRY), + cp, sizeof(*cp)) < 0) { + free(i); + bt_devclose(s); + return (-1); + } + +wait_for_more: + + n = bt_devrecv(s, buf, sizeof(buf), to); + if (n < 0) { + free(i); + bt_devclose(s); + return (-1); + } + + if (n < sizeof(ng_hci_event_pkt_t)) { + free(i); + bt_devclose(s); + errno = EIO; + return (-1); + } + + switch (e->event) { + case NG_HCI_EVENT_INQUIRY_COMPL: + break; + + case NG_HCI_EVENT_INQUIRY_RESULT: + ir = (ng_hci_inquiry_response *)(ep + 1); + +#undef MIN +#define MIN(a, b) (((a) < (b))? (a) : (b)) + + for (n = 0; n < MIN(ep->num_responses, num_rsp); n ++) { + bdaddr_copy(&i->bdaddr, &ir->bdaddr); + i->pscan_rep_mode = ir->page_scan_rep_mode; + i->pscan_period_mode = ir->page_scan_period_mode; + memcpy(i->dev_class, ir->uclass, sizeof(i->dev_class)); + i->clock_offset = le16toh(ir->clock_offset); + + ir ++; + i ++; + num_rsp --; + } + /* FALLTHROUGH */ + + default: + goto wait_for_more; + /* NOT REACHED */ + } + + bt_devclose(s); + + return (i - *ii); +} + +int bt_devinfo(struct bt_devinfo *di) { union { @@ -53,6 +483,7 @@ struct ng_btsocket_hci_raw_node_debug r8; } rp; struct sockaddr_hci ha; + socklen_t halen; int s, rval; if (di == NULL) { @@ -60,27 +491,14 @@ return (-1); } - memset(&ha, 0, sizeof(ha)); - ha.hci_len = sizeof(ha); - ha.hci_family = AF_BLUETOOTH; - - if (bt_aton(di->devname, &rp.r1.bdaddr)) { - if (!bt_devname(ha.hci_node, &rp.r1.bdaddr)) - return (-1); - } else if (bt_dev2node(di->devname, ha.hci_node, - sizeof(ha.hci_node)) == NULL) { - errno = ENXIO; - return (-1); - } - - s = socket(PF_BLUETOOTH, SOCK_RAW, BLUETOOTH_PROTO_HCI); + s = bt_devopen(di->devname); if (s < 0) return (-1); rval = -1; - if (bind(s, (struct sockaddr *) &ha, sizeof(ha)) < 0 || - connect(s, (struct sockaddr *) &ha, sizeof(ha)) < 0) + halen = sizeof(ha); + if (getsockname(s, (struct sockaddr *) &ha, &halen) < 0) goto bad; strlcpy(di->devname, ha.hci_node, sizeof(di->devname)); @@ -138,7 +556,7 @@ rval = 0; bad: - close(s); + bt_devclose(s); return (rval); } @@ -205,6 +623,13 @@ return (count); } +static int +bt_devany_cb(int s, struct bt_devinfo const *di, void *xdevname) +{ + strlcpy((char *) xdevname, di->devname, HCI_DEVNAME_SIZE); + return (1); +} + static char * bt_dev2node(char const *devname, char *nodename, int nnlen) { Index: bluetooth.3 =================================================================== --- bluetooth.3 (revision 191012) +++ bluetooth.3 (working copy) @@ -25,7 +25,7 @@ .\" $Id: bluetooth.3,v 1.5 2003/05/20 23:04:30 max Exp $ .\" $FreeBSD$ .\" -.Dd February 13, 2009 +.Dd April 9, 2009 .Dt BLUETOOTH 3 .Os .Sh NAME @@ -41,6 +41,23 @@ .Nm bt_endprotoent , .Nm bt_aton , .Nm bt_ntoa , +.Nm bt_devaddr , +.Nm bt_devname , +.Nm bt_devinfo , +.Nm bt_devenum , +.Nm bt_devopen , +.Nm bt_devclose , +.Nm bt_devsend , +.Nm bt_devrecv , +.Nm bt_devreq , +.Nm bt_devfilter , +.Nm bt_devfilter_pkt_set , +.Nm bt_devfilter_pkt_clr , +.Nm bt_devfilter_pkt_tst , +.Nm bt_devfilter_evt_set , +.Nm bt_devfilter_evt_clr , +.Nm bt_devfilter_evt_tst , +.Nm bt_devinquiry , .Nm bdaddr_same , .Nm bdaddr_any , .Nm bdaddr_copy @@ -84,6 +101,32 @@ .Ft int .Fn bt_devenum "bt_devenum_cb_t *cb" "void *arg" .Ft int +.Fn bt_devopen "char const *devname" +.Ft int +.Fn bt_devclose "int s" +.Ft int +.Fn bt_devsend "int s" "uint16_t opcode" "void *param" "size_t plen" +.Ft int +.Fn bt_devrecv "int s" "uint8_t *buf" "size_t size" "time_t to" +.Ft int +.Fn bt_devreq "int s" "struct bt_devreq *r" "time_t to" +.Ft int +.Fn bt_devfilter "int s" "struct bt_devfilter const *new" "struct bt_devfilter *old" +.Ft void +.Fn bt_devfilter_pkt_set "struct bt_devfilter *filter" "int type" +.Ft void +.Fn bt_devfilter_pkt_clt "struct bt_devfilter *filter" "int type" +.Ft int +.Fn bt_devfilter_pkt_tst "struct bt_devfilter const *filter" "int type" +.Ft void +.Fn bt_devfilter_evt_set "struct bt_devfilter *filter" "int event" +.Ft void +.Fn bt_devfilter_evt_clt "struct bt_devfilter *filter" "int event" +.Ft int +.Fn bt_devfilter_evt_tst "struct bt_devfilter const *filter" "int event" +.Ft int +.Fn bt_devinquiry "char const *devname" "time_t length" "int num_rsp" "struct bt_devinquiry **ii" +.Ft int .Fn bdaddr_same "const bdaddr_t *a" "const bdaddr_t *b" .Ft int .Fn bdaddr_any "const bdaddr_t *a" @@ -311,6 +354,211 @@ or -1 if an error occurred. .Pp The +.Fn bt_devopen +function opens Bluetooth device with the given +.Fa devname +and returns connected and bound +.Dv HCI +socket. +The function returns -1 if an error has occurred. +.Pp +The +.Fn bt_devclose +closes passed +.Dv HCI +socket +.Fa s , +previously obtained with +.Xr bt_devopen 3 . +.Pp +The +.Fn bt_devsend +function sends Bluetooth +.Dv HCI +command with the given +.Fa opcode +to the provided socket +.Fa s , +previously obtained with +.Xr bt_devopen 3 . +The +.Fa opcode +parameter is exppected to be in the host byte order. +The +.Fa param +and +.Fa plen +parameters specify command parameters. +The function returns 0 on success, +or -1 if an error occurred. +.Pp +The +.Fn bt_devrecv +function receives one Bluetooth +.Dv HCI +event packet from the socket +.Fa s , +previously obtained with +.Xr bt_devopen 3 . +The event packet is placed into the provided buffer +.Fa buf +of size +.Fa size . +The +.Fa to +parameter specifies receive timeout in seconds. +The function returns total number of bytes recevied, +or -1 if an error occurred. +.Pp +The +.Fn bt_devreq +function makes Bluetooth +.Dv HCI +request to the socket +.Fa s , +previously obtained with +.Xr bt_devopen 3 . +The function will send the specified command and will wait for the specified +event, +or timeout +.Fa to +seconds to occur. +The +.Vt bt_devreq +structure is defined as follows +.Bd -literal -offset indent +struct bt_devreq +{ + uint16_t opcode; + uint16_t event; + void *cparam; + int clen; + void *rparam; + int rlen; +}; +.Ed +.Pp +The +.Fa opcode +field specifies the command and is expected to be in the host byte order. +The +.Fa cparam +and +.Fa clen +fields specify command parameters data and command parameters data size +respectively. +The +.Fa event +field specifies which Bluetooth +.Dv HCI +event ID the function should wait for. +The +.Fa rparam +and +.Fa rlen +parameters specify buffer and buffer size respectively where return +parameters should be placed. +The function returns 0 on success, or -1 if an error occurred. +.Pp +The +.Fn bt_devfilter +controls the local +.Dv HCI +filter associated with the socket +.Fa s , +previously obtained with +.Xr bt_devopen 3 . +Filtering can be done on packet types, i.e. +.Dv ACL , +.Dv SCO or +.Dv HCI +event packets, and, in addition, on +.Dv HCI +event IDs. +Before applying +.Fa new +filter (if provided) the function will try to obtain current filter +from the socket +.Fa s +and place it into the +.Fa old +parameter (if provided). +The function returns 0 on success, or -1 if an error occurred. +.Pp +The +.Fn bt_devfilter_pkt_set , +.Fn bt_devfilter_pkt_clr +and +.Fn bt_devfilter_pkt_tst +functions can be used to modify and test +.Dv HCI +filter +.Fa filter . +The +.Fa type +parameter specifies +.Dv HCI +packet type. +.Pp +The +.Fn bt_devfilter_evt_set , +.Fn bt_devfilter_evt_clr +and +.Fn bt_devfilter_evt_tst +functions can be used to modify and test +.Dv HCI +event filter +.Fa filter . +The +.Fa event +parameter specifies +.Dv HCI +event ID. +.Pp +The +.Fn bt_devinquiry +function performs Bluetooth inquiry. +The +.Fa devname +parameter specifies which local Bluetooth device should perform an inquiry. +If not secified, i.e. +.Dv NULL , +then first available device will be used. +The +.Fa length +parameters specifies the total length of an inquiry in seconds. +If not specified, i.e. 0, default value will be used. +The +.Fa num_rsp +parameter specifies the number of responses that can be received before +the inquiry is halted. +If not specified, i.e. 0, default value will be used. +The +.Fa ii +parameter specifies where to place inquiry results. +On success, the function will return total number of inquiry results, +will allocate buffer to store all the inquiry results and +will return pointer to the allocated buffer in the +.Fa ii +parameter. +It is up to the caller of the function to dispose of the buffer. +The function returns -1 if an error has occurred. +The +.Vt bt_devinquiry +structure is defined as follows +.Bd -literal -offset indent +struct bt_devinquiry { + bdaddr_t bdaddr; + uint8_t pscan_rep_mode; + uint8_t pscan_period_mode; + uint8_t dev_class[3]; + uint16_t clock_offset; + int8_t rssi; + uint8_t data[240]; +}; +.Ed +.Pp +The .Fn bdaddr_same , .Fn bdaddr_any and @@ -444,6 +692,6 @@ .Sh AUTHORS .An Maksim Yevmenkin Aq m_evmenkin@yahoo.com .Sh BUGS -These functions use static data storage; +Some of those functions use static data storage; if the data is needed for future use, it should be copied before any subsequent calls overwrite it. Index: bluetooth.h =================================================================== --- bluetooth.h (revision 191012) +++ bluetooth.h (working copy) @@ -39,6 +39,7 @@ #include #include #include +#include #include #include #include @@ -46,6 +47,7 @@ #include #include #include +#include __BEGIN_DECLS @@ -129,8 +131,48 @@ uint8_t _padding[20]; /* leave space for future additions */ }; +struct bt_devreq +{ + uint16_t opcode; + uint16_t event; + void *cparam; + size_t clen; + void *rparam; + size_t rlen; +}; + +struct bt_devfilter { + bitstr_t bit_decl(packet_mask, 8); + bitstr_t bit_decl(event_mask, 256); +}; + +struct bt_devinquiry { + bdaddr_t bdaddr; + uint8_t pscan_rep_mode; + uint8_t pscan_period_mode; + uint8_t dev_class[3]; + uint16_t clock_offset; + int8_t rssi; + uint8_t data[240]; +}; + typedef int (bt_devenum_cb_t)(int, struct bt_devinfo const *, void *); +int bt_devopen (char const *devname); +int bt_devclose(int s); +int bt_devsend (int s, uint16_t opcode, void *param, size_t plen); +int bt_devrecv (int s, uint8_t *buf, size_t size, time_t to); +int bt_devreq (int s, struct bt_devreq *r, time_t to); +int bt_devfilter(int s, struct bt_devfilter const *new, + struct bt_devfilter *old); +void bt_devfilter_pkt_set(struct bt_devfilter *filter, int type); +void bt_devfilter_pkt_clr(struct bt_devfilter *filter, int type); +int bt_devfilter_pkt_tst(struct bt_devfilter const *filter, int type); +void bt_devfilter_evt_set(struct bt_devfilter *filter, int event); +void bt_devfilter_evt_clr(struct bt_devfilter *filter, int event); +int bt_devfilter_evt_tst(struct bt_devfilter const *filter, int event); +int bt_devinquiry(char const *devname, time_t length, int num_rsp, + struct bt_devinquiry **ii); int bt_devinfo (struct bt_devinfo *di); int bt_devenum (bt_devenum_cb_t *cb, void *arg); Index: Makefile =================================================================== --- Makefile (revision 191012) +++ Makefile (working copy) @@ -33,6 +33,19 @@ MLINKS+= bluetooth.3 bt_devinfo.3 MLINKS+= bluetooth.3 bt_devenum.3 +MLINKS+= bluetooth.3 bt_devopen.3 +MLINKS+= bluetooth.3 bt_devclose.3 +MLINKS+= bluetooth.3 bt_devsend.3 +MLINKS+= bluetooth.3 bt_devreq.3 +MLINKS+= bluetooth.3 bt_devfilter.3 +MLINKS+= bluetooth.3 bt_devfilter_pkt_set.3 +MLINKS+= bluetooth.3 bt_devfilter_pkt_clr.3 +MLINKS+= bluetooth.3 bt_devfilter_pkt_tst.3 +MLINKS+= bluetooth.3 bt_devfilter_evt_set.3 +MLINKS+= bluetooth.3 bt_devfilter_evt_clr.3 +MLINKS+= bluetooth.3 bt_devfilter_evt_tst.3 +MLINKS+= bluetooth.3 bt_devinquiry.3 + MLINKS+= bluetooth.3 bdaddr_same.3 MLINKS+= bluetooth.3 bdaddr_any.3 MLINKS+= bluetooth.3 bdaddr_copy.3 From plunky at rya-online.net Sat Apr 18 00:52:30 2009 From: plunky at rya-online.net (Iain Hibbert) Date: Sat Apr 18 05:15:02 2009 Subject: libhci update Message-ID: > thanks for the feedback. i'm attaching revisited patch. please take a > look and let me know if this is something you happy with. (pls ignore typos & bad formatting, am on mobile device :) Bt_devrecv() should probably take void * for buffer? Also manpage suggests it will only receive hci events. Do you think its worth doing some validation of received data? (eg return EIO for truncated reads?) Bt_devinquiry() should probably restore the filter after use? Also needs to ensure hci event packets are enabled? Bt_devreq() needs to set/restore a filter too In bt_devreq structure event should be uint8_t? Also clen and rlen should be size_t Do you think its worth to cook dev_class into a normalised host numeric value rather than 3 bytes ? Probably need to specifically mention that the inquiry response to be released using free() in manpage? Something i thought about on the train yesterday but can't visualise on the small screen. For device inquiry did you consider using a callback method (as per devenum) in stead of returning the structure array? At least i recall my nokia would show the list building (but perhaps that was when it got the names) and this windows mobile device does show the raw list in progress (though stupidly just displays a list of 'unknown device' until it gets the names :) Regards, iain From maksim.yevmenkin at gmail.com Sun Apr 19 23:05:57 2009 From: maksim.yevmenkin at gmail.com (Maksim Yevmenkin) Date: Sun Apr 19 23:06:10 2009 Subject: sb_mbtail is not set in sbappendrecord_locked() Message-ID: hello, i'm fairly certain that sbappendrecord_locked() has bug in it. the bug is triggered by the kernel SOCKBUF_DEBUG option. the easiest way to trigger the bug is to 1) compile kernel with the SOCKBUF_DEBUG option enabled; 2) call sbappendrecord_locked() and try to append mbuf chain with exactly one mbuf (i.e. m0->m_next is NULL) to an empty sockbuf; there is a kern/126742 pr that basically shows the problem and stack traces. initial investigation was done by "pluknet" < pluknet -at- gmail -dot- com >, who confirmed the bug. see http://lists.freebsd.org/pipermail/freebsd-net/2008-August/019345.html for more details. i'm proposing the following patch. please review. == > svn diff Index: uipc_sockbuf.c =================================================================== --- uipc_sockbuf.c (revision 191012) +++ uipc_sockbuf.c (working copy) @@ -577,10 +577,6 @@ if (m0 == 0) return; - m = sb->sb_mb; - if (m) - while (m->m_nextpkt) - m = m->m_nextpkt; /* * Put the first mbuf on the queue. Note this permits zero length * records. @@ -588,17 +584,17 @@ sballoc(sb, m0); SBLASTRECORDCHK(sb); SBLINKRECORD(sb, m0); - if (m) - m->m_nextpkt = m0; - else - sb->sb_mb = m0; + sb->sb_mbtail = m0; m = m0->m_next; m0->m_next = 0; - if (m && (m0->m_flags & M_EOR)) { - m0->m_flags &= ~M_EOR; - m->m_flags |= M_EOR; + if (m != NULL) { + if (m0->m_flags & M_EOR) { + m0->m_flags &= ~M_EOR; + m->m_flags |= M_EOR; + } + + sbcompress(sb, m, m0); } - sbcompress(sb, m, m0); } /* == thanks, max From maksim.yevmenkin at gmail.com Mon Apr 20 17:53:27 2009 From: maksim.yevmenkin at gmail.com (Maksim Yevmenkin) Date: Mon Apr 20 17:53:35 2009 Subject: libhci update In-Reply-To: References: Message-ID: >> thanks for the feedback. i'm attaching revisited patch. please take a >> look and let me know if this is something you happy with. > > (pls ignore typos & bad formatting, am on mobile device :) > > Bt_devrecv() should probably take void * for buffer? Also manpage suggests it > will only receive hci events. Do you think its worth doing some validation of received > data? (eg return EIO for truncated reads?) i will change uint8_t * to void *, no problem. i also updated man page and removed 'event' word :) > Bt_devinquiry() should probably restore the filter after use? Also needs to ensure hci > event packets are enabled? its an implementation detail :) bt_devinqury() opens completely new socket. in freebsd, filter is attached to the socket and is already set to "sensible" defaults. so, all we need to do is to enable events that we care about, i.e. inquiry result and inquiry complete. also since bt_devinqury() is closing the socket after its done, there is no need to restore original filter. > Bt_devreq() needs to set/restore a filter too well, maybe. bt_devreq() operates on already opened socket. the assumption i'm making here is that application will set appropriate filter before calling bt_devreq(). otherwise, application would have to always set 'event' field to acceptable value (or zero). i could go either way here. just need to document implemented behavior better. > In bt_devreq structure event should be uint8_t? Also clen and rlen should be size_t ok :) i was trying to get rid of hole with uint16_t event, but it does not matter. also clen/rlen are size_t in bluetooth.h, its just i did not update man page :) > Do you think its worth to cook dev_class into a normalised host numeric value rather than 3 bytes ? right, hmm, i guess we could turn dev_class into uint32_t in le16 byte order (since everything else in bluetooth hci is in le16 order), but maybe its too much? spec breaks out dev_class as 3 bytes array and talks about bytes and bits within each byte. i'm kinda leaning towards leaving it as is, because otherwise application would have to translate byte/bit into uint32_t mask. it could be somewhat convenient, i guess. again, no strong feeling about it. could go either way. > Probably need to specifically mention that the inquiry response to be released using free() in manpage? it says so, i.e. The .Fa ii parameter specifies where to place inquiry results. On success, the function will return total number of inquiry results, will allocate buffer to store all the inquiry results and will return pointer to the allocated buffer in the .Fa ii parameter. It is up to the caller of the function to dispose of the buffer. > Something i thought about on the train yesterday but can't visualise on the small > screen. For device inquiry did you consider using a callback method (as per devenum) > in stead of returning the structure array? At least i recall my nokia would show the > list building (but perhaps that was when it got the names) and this windows mobile > device does show the raw list in progress (though stupidly just displays a list of > 'unknown device' until it gets the names :) yes, i thought about it. the only difference is that it would be not so close to linux/bluez api. i guess, we could provide another function? i'm attaching revised diff for your review. thanks, max -------------- next part -------------- Index: hci.c =================================================================== --- hci.c (revision 191328) +++ hci.c (working copy) @@ -30,15 +30,445 @@ * $FreeBSD$ */ +#include #include #include #include #include #include +static int bt_devany_cb(int s, struct bt_devinfo const *di, void *xdevname); static char * bt_dev2node (char const *devname, char *nodename, int nnlen); int +bt_devopen(char const *devname) +{ + struct sockaddr_hci ha; + bdaddr_t ba; + int s; + + if (devname == NULL) { + errno = EINVAL; + return (-1); + } + + memset(&ha, 0, sizeof(ha)); + ha.hci_len = sizeof(ha); + ha.hci_family = AF_BLUETOOTH; + + if (bt_aton(devname, &ba)) { + if (!bt_devname(ha.hci_node, &ba)) + return (-1); + } else if (bt_dev2node(devname, ha.hci_node, + sizeof(ha.hci_node)) == NULL) { + errno = ENXIO; + return (-1); + } + + s = socket(PF_BLUETOOTH, SOCK_RAW, BLUETOOTH_PROTO_HCI); + if (s < 0) + return (-1); + + if (bind(s, (struct sockaddr *) &ha, sizeof(ha)) < 0 || + connect(s, (struct sockaddr *) &ha, sizeof(ha)) < 0) { + close(s); + return (-1); + } + + return (s); +} + +int +bt_devclose(int s) +{ + return (close(s)); +} + +int +bt_devsend(int s, uint16_t opcode, void *param, size_t plen) +{ + ng_hci_cmd_pkt_t h; + struct iovec iv[2]; + int ivn; + + if (plen < 0 || (plen > 0 && param == NULL)) { + errno = EINVAL; + return (-1); + } + + iv[0].iov_base = &h; + iv[0].iov_len = sizeof(h); + ivn = 1; + + h.type = NG_HCI_CMD_PKT; + h.opcode = htole16(opcode); + if (plen > 0) { + h.length = plen; + + iv[1].iov_base = param; + iv[1].iov_len = plen; + ivn = 2; + } else + h.length = 0; + + while (writev(s, iv, ivn) < 0) { + if (errno == EAGAIN || errno == EINTR) + continue; + + return (-1); + } + + return (0); +} + +int +bt_devrecv(int s, void *buf, size_t size, time_t to) +{ + fd_set rfd; + struct timeval tv; + int n; + + if (buf == NULL || size <= 0 || to < 0) { + errno = EINVAL; + return (-1); + } + + FD_ZERO(&rfd); + FD_SET(s, &rfd); + + tv.tv_sec = to; + tv.tv_usec = 0; + + while ((n = select(s + 1, &rfd, NULL, NULL, &tv)) < 0) { + if (errno == EAGAIN || errno == EINTR) + continue; + + return (-1); + } + + if (n == 0) { + errno = ETIMEDOUT; + return (-1); + } + + assert(FD_ISSET(s, &rfd)); + + while ((n = read(s, buf, size)) < 0) { + if (errno == EAGAIN || errno == EINTR) + continue; + + return (-1); + } + + return (n); +} + +int +bt_devreq(int s, struct bt_devreq *r, time_t to) +{ + uint8_t buf[320]; /* more than enough */ + ng_hci_event_pkt_t *e = (ng_hci_event_pkt_t *) buf; + ng_hci_command_compl_ep *cc = (ng_hci_command_compl_ep *)(e+1); + ng_hci_command_status_ep *cs = (ng_hci_command_status_ep*)(e+1); + time_t t_end; + uint16_t opcode; + int n; + + if (s < 0 || r == NULL || to < 0) { + errno = EINVAL; + return (-1); + } + + if (r->rlen < 0 || (r->rlen > 0 && r->rparam == NULL)) { + errno = EINVAL; + return (-1); + } + + n = bt_devsend(s, r->opcode, r->cparam, r->clen); + if (n < 0) + return (-1); + + opcode = htole16(r->opcode); + t_end = time(NULL) + to; + + do { + to = t_end - time(NULL); + if (to < 0) + to = 0; + + n = bt_devrecv(s, buf, sizeof(buf), to); + if (n < 0) + return (-1); + + if (n < sizeof(*e)) { + errno = EMSGSIZE; + return (-1); + } + + if (e->type != NG_HCI_EVENT_PKT) { + errno = EIO; + return (-1); + } + + n -= sizeof(*e); + + switch (e->event) { + case NG_HCI_EVENT_COMMAND_COMPL: + if (cc->opcode == opcode) { + n -= sizeof(*cc); + + if (r->rlen >= n) { + r->rlen = n; + memcpy(r->rparam, cc + 1, r->rlen); + } + + return (0); + } + break; + + case NG_HCI_EVENT_COMMAND_STATUS: + if (cs->opcode == opcode) { + if (r->event != NG_HCI_EVENT_COMMAND_STATUS) { + if (cs->status != 0) { + errno = EIO; + return (-1); + } + } else { + if (r->rlen >= n) { + r->rlen = n; + memcpy(r->rparam, cs, r->rlen); + } + + return (0); + } + } + break; + + default: + if (e->event == r->event) { + if (r->rlen >= n) { + r->rlen = n; + memcpy(r->rparam, e + 1, r->rlen); + } + + return (0); + } + break; + } + } while (to > 0); + + errno = ETIMEDOUT; + + return (-1); +} + +int +bt_devfilter(int s, struct bt_devfilter const *new, struct bt_devfilter *old) +{ + struct ng_btsocket_hci_raw_filter f; + socklen_t len; + + if (new == NULL && old == NULL) { + errno = EINVAL; + return (-1); + } + + if (old != NULL) { + len = sizeof(f); + if (getsockopt(s, SOL_HCI_RAW, SO_HCI_RAW_FILTER, &f, &len) < 0) + return (-1); + + memset(old, 0, sizeof(*old)); + memcpy(old->packet_mask, &f.packet_mask, + sizeof(old->packet_mask)); + memcpy(old->event_mask, &f.event_mask, + sizeof(old->event_mask)); + } + + if (new != NULL) { + memset(&f, 0, sizeof(f)); + memcpy(&f.packet_mask, new->packet_mask, sizeof(f.packet_mask)); + memcpy(&f.event_mask, new->event_mask, sizeof(f.event_mask)); + + len = sizeof(f); + if (setsockopt(s, SOL_HCI_RAW, SO_HCI_RAW_FILTER, &f, len) < 0) + return (-1); + } + + return (0); +} + +void +bt_devfilter_pkt_set(struct bt_devfilter *filter, int type) +{ + bit_set(filter->packet_mask, type - 1); +} + +void +bt_devfilter_pkt_clr(struct bt_devfilter *filter, int type) +{ + bit_clear(filter->packet_mask, type - 1); +} + +int +bt_devfilter_pkt_tst(struct bt_devfilter const *filter, int type) +{ + return (bit_test(filter->packet_mask, type - 1)); +} + +void +bt_devfilter_evt_set(struct bt_devfilter *filter, int event) +{ + bit_set(filter->event_mask, event - 1); +} + +void +bt_devfilter_evt_clr(struct bt_devfilter *filter, int event) +{ + bit_clear(filter->event_mask, event - 1); +} + +int +bt_devfilter_evt_tst(struct bt_devfilter const *filter, int event) +{ + return (bit_test(filter->event_mask, event - 1)); +} + +int +bt_devinquiry(char const *devname, time_t length, int num_rsp, + struct bt_devinquiry **ii) +{ + uint8_t buf[320]; + char _devname[HCI_DEVNAME_SIZE]; + struct bt_devfilter f; + ng_hci_inquiry_cp *cp = (ng_hci_inquiry_cp *) buf; + ng_hci_event_pkt_t *e = (ng_hci_event_pkt_t *) buf; + ng_hci_inquiry_result_ep *ep = (ng_hci_inquiry_result_ep *)(e+1); + ng_hci_inquiry_response *ir; + struct bt_devinquiry *i; + int s, n; + time_t to; + + if (ii == NULL) { + errno = EINVAL; + return (-1); + } + + if (devname == NULL) { + memset(_devname, 0, sizeof(_devname)); + devname = _devname; + + n = bt_devenum(bt_devany_cb, _devname); + if (n <= 0) { + if (n == 0) + *ii = NULL; + + return (n); + } + } + + s = bt_devopen(devname); + if (s < 0) + return (-1); + + if (bt_devfilter(s, NULL, &f) < 0) { + bt_devclose(s); + return (-1); + } + + bt_devfilter_evt_set(&f, NG_HCI_EVENT_INQUIRY_COMPL); + bt_devfilter_evt_set(&f, NG_HCI_EVENT_INQUIRY_RESULT); + + if (bt_devfilter(s, &f, NULL) < 0) { + bt_devclose(s); + return (-1); + } + + /* Always use GIAC LAP */ + cp->lap[0] = 0x33; + cp->lap[1] = 0x8b; + cp->lap[2] = 0x9e; + + /* Calculate inquire length in 1.28 second units */ + to = (time_t) ((double) length / 1.28); + if (to <= 0) + cp->inquiry_length = 4; /* 5.12 seconds */ + else if (to > 254) + cp->inquiry_length = 255; /* 326.40 seconds */ + else + cp->inquiry_length = to + 1; + + to = (time_t)((double) cp->inquiry_length * 1.28) + 1; + + if (num_rsp <= 0 || num_rsp > 255) + num_rsp = 8; + cp->num_responses = (uint8_t) num_rsp; + + i = *ii = calloc(num_rsp, sizeof(struct bt_devinquiry)); + if (i == NULL) { + bt_devclose(s); + errno = ENOMEM; + return (-1); + } + + if (bt_devsend(s, + NG_HCI_OPCODE(NG_HCI_OGF_LINK_CONTROL, NG_HCI_OCF_INQUIRY), + cp, sizeof(*cp)) < 0) { + free(i); + bt_devclose(s); + return (-1); + } + +wait_for_more: + + n = bt_devrecv(s, buf, sizeof(buf), to); + if (n < 0) { + free(i); + bt_devclose(s); + return (-1); + } + + if (n < sizeof(ng_hci_event_pkt_t)) { + free(i); + bt_devclose(s); + errno = EIO; + return (-1); + } + + switch (e->event) { + case NG_HCI_EVENT_INQUIRY_COMPL: + break; + + case NG_HCI_EVENT_INQUIRY_RESULT: + ir = (ng_hci_inquiry_response *)(ep + 1); + +#undef MIN +#define MIN(a, b) (((a) < (b))? (a) : (b)) + + for (n = 0; n < MIN(ep->num_responses, num_rsp); n ++) { + bdaddr_copy(&i->bdaddr, &ir->bdaddr); + i->pscan_rep_mode = ir->page_scan_rep_mode; + i->pscan_period_mode = ir->page_scan_period_mode; + memcpy(i->dev_class, ir->uclass, sizeof(i->dev_class)); + i->clock_offset = le16toh(ir->clock_offset); + + ir ++; + i ++; + num_rsp --; + } + /* FALLTHROUGH */ + + default: + goto wait_for_more; + /* NOT REACHED */ + } + + bt_devclose(s); + + return (i - *ii); +} + +int bt_devinfo(struct bt_devinfo *di) { union { @@ -53,6 +483,7 @@ struct ng_btsocket_hci_raw_node_debug r8; } rp; struct sockaddr_hci ha; + socklen_t halen; int s, rval; if (di == NULL) { @@ -60,27 +491,14 @@ return (-1); } - memset(&ha, 0, sizeof(ha)); - ha.hci_len = sizeof(ha); - ha.hci_family = AF_BLUETOOTH; - - if (bt_aton(di->devname, &rp.r1.bdaddr)) { - if (!bt_devname(ha.hci_node, &rp.r1.bdaddr)) - return (-1); - } else if (bt_dev2node(di->devname, ha.hci_node, - sizeof(ha.hci_node)) == NULL) { - errno = ENXIO; - return (-1); - } - - s = socket(PF_BLUETOOTH, SOCK_RAW, BLUETOOTH_PROTO_HCI); + s = bt_devopen(di->devname); if (s < 0) return (-1); rval = -1; - if (bind(s, (struct sockaddr *) &ha, sizeof(ha)) < 0 || - connect(s, (struct sockaddr *) &ha, sizeof(ha)) < 0) + halen = sizeof(ha); + if (getsockname(s, (struct sockaddr *) &ha, &halen) < 0) goto bad; strlcpy(di->devname, ha.hci_node, sizeof(di->devname)); @@ -138,7 +556,7 @@ rval = 0; bad: - close(s); + bt_devclose(s); return (rval); } @@ -205,6 +623,13 @@ return (count); } +static int +bt_devany_cb(int s, struct bt_devinfo const *di, void *xdevname) +{ + strlcpy((char *) xdevname, di->devname, HCI_DEVNAME_SIZE); + return (1); +} + static char * bt_dev2node(char const *devname, char *nodename, int nnlen) { Index: bluetooth.3 =================================================================== --- bluetooth.3 (revision 191328) +++ bluetooth.3 (working copy) @@ -25,7 +25,7 @@ .\" $Id: bluetooth.3,v 1.5 2003/05/20 23:04:30 max Exp $ .\" $FreeBSD$ .\" -.Dd February 13, 2009 +.Dd April 9, 2009 .Dt BLUETOOTH 3 .Os .Sh NAME @@ -41,6 +41,23 @@ .Nm bt_endprotoent , .Nm bt_aton , .Nm bt_ntoa , +.Nm bt_devaddr , +.Nm bt_devname , +.Nm bt_devinfo , +.Nm bt_devenum , +.Nm bt_devopen , +.Nm bt_devclose , +.Nm bt_devsend , +.Nm bt_devrecv , +.Nm bt_devreq , +.Nm bt_devfilter , +.Nm bt_devfilter_pkt_set , +.Nm bt_devfilter_pkt_clr , +.Nm bt_devfilter_pkt_tst , +.Nm bt_devfilter_evt_set , +.Nm bt_devfilter_evt_clr , +.Nm bt_devfilter_evt_tst , +.Nm bt_devinquiry , .Nm bdaddr_same , .Nm bdaddr_any , .Nm bdaddr_copy @@ -84,6 +101,32 @@ .Ft int .Fn bt_devenum "bt_devenum_cb_t *cb" "void *arg" .Ft int +.Fn bt_devopen "char const *devname" +.Ft int +.Fn bt_devclose "int s" +.Ft int +.Fn bt_devsend "int s" "uint16_t opcode" "void *param" "size_t plen" +.Ft int +.Fn bt_devrecv "int s" "void *buf" "size_t size" "time_t to" +.Ft int +.Fn bt_devreq "int s" "struct bt_devreq *r" "time_t to" +.Ft int +.Fn bt_devfilter "int s" "struct bt_devfilter const *new" "struct bt_devfilter *old" +.Ft void +.Fn bt_devfilter_pkt_set "struct bt_devfilter *filter" "int type" +.Ft void +.Fn bt_devfilter_pkt_clt "struct bt_devfilter *filter" "int type" +.Ft int +.Fn bt_devfilter_pkt_tst "struct bt_devfilter const *filter" "int type" +.Ft void +.Fn bt_devfilter_evt_set "struct bt_devfilter *filter" "int event" +.Ft void +.Fn bt_devfilter_evt_clt "struct bt_devfilter *filter" "int event" +.Ft int +.Fn bt_devfilter_evt_tst "struct bt_devfilter const *filter" "int event" +.Ft int +.Fn bt_devinquiry "char const *devname" "time_t length" "int num_rsp" "struct bt_devinquiry **ii" +.Ft int .Fn bdaddr_same "const bdaddr_t *a" "const bdaddr_t *b" .Ft int .Fn bdaddr_any "const bdaddr_t *a" @@ -311,6 +354,229 @@ or -1 if an error occurred. .Pp The +.Fn bt_devopen +function opens Bluetooth device with the given +.Fa devname +and returns connected and bound +.Dv HCI +socket. +The function returns -1 if an error has occurred. +.Pp +The +.Fn bt_devclose +closes passed +.Dv HCI +socket +.Fa s , +previously obtained with +.Xr bt_devopen 3 . +.Pp +The +.Fn bt_devsend +function sends Bluetooth +.Dv HCI +command with the given +.Fa opcode +to the provided socket +.Fa s , +previously obtained with +.Xr bt_devopen 3 . +The +.Fa opcode +parameter is exppected to be in the host byte order. +The +.Fa param +and +.Fa plen +parameters specify command parameters. +The +.Fn bt_devsend +function does not modify +.Dv HCI +filter on the provided socket +.Fa s . +The function returns 0 on success, +or -1 if an error occurred. +.Pp +The +.Fn bt_devrecv +function receives one Bluetooth +.Dv HCI +packet from the socket +.Fa s , +previously obtained with +.Xr bt_devopen 3 . +The packet is placed into the provided buffer +.Fa buf +of size +.Fa size . +The +.Fa to +parameter specifies receive timeout in seconds. +The +.Fn bt_devrecv +function does not modify +.Dv HCI +filter on the provided socket +.Fa s . +The function returns total number of bytes recevied, +or -1 if an error occurred. +.Pp +The +.Fn bt_devreq +function makes Bluetooth +.Dv HCI +request to the socket +.Fa s , +previously obtained with +.Xr bt_devopen 3 . +The function will send the specified command and will wait for the specified +event, +or timeout +.Fa to +seconds to occur. +The +.Vt bt_devreq +structure is defined as follows +.Bd -literal -offset indent +struct bt_devreq +{ + uint16_t opcode; + uint8_t event; + void *cparam; + size_t clen; + void *rparam; + size_t rlen; +}; +.Ed +.Pp +The +.Fa opcode +field specifies the command and is expected to be in the host byte order. +The +.Fa cparam +and +.Fa clen +fields specify command parameters data and command parameters data size +respectively. +The +.Fa event +field specifies which Bluetooth +.Dv HCI +event ID the function should wait for. +The +.Fa rparam +and +.Fa rlen +parameters specify buffer and buffer size respectively where return +parameters should be placed. +The +.Fn bt_devreq +function does not modify filter on the provided +.Dv HCI +socket +.Fa s . +The function returns 0 on success, or -1 if an error occurred. +.Pp +The +.Fn bt_devfilter +controls the local +.Dv HCI +filter associated with the socket +.Fa s , +previously obtained with +.Xr bt_devopen 3 . +Filtering can be done on packet types, i.e. +.Dv ACL , +.Dv SCO or +.Dv HCI +event packets, and, in addition, on +.Dv HCI +event IDs. +Before applying +.Fa new +filter (if provided) the function will try to obtain current filter +from the socket +.Fa s +and place it into the +.Fa old +parameter (if provided). +The function returns 0 on success, or -1 if an error occurred. +.Pp +The +.Fn bt_devfilter_pkt_set , +.Fn bt_devfilter_pkt_clr +and +.Fn bt_devfilter_pkt_tst +functions can be used to modify and test +.Dv HCI +filter +.Fa filter . +The +.Fa type +parameter specifies +.Dv HCI +packet type. +.Pp +The +.Fn bt_devfilter_evt_set , +.Fn bt_devfilter_evt_clr +and +.Fn bt_devfilter_evt_tst +functions can be used to modify and test +.Dv HCI +event filter +.Fa filter . +The +.Fa event +parameter specifies +.Dv HCI +event ID. +.Pp +The +.Fn bt_devinquiry +function performs Bluetooth inquiry. +The +.Fa devname +parameter specifies which local Bluetooth device should perform an inquiry. +If not secified, i.e. +.Dv NULL , +then first available device will be used. +The +.Fa length +parameters specifies the total length of an inquiry in seconds. +If not specified, i.e. 0, default value will be used. +The +.Fa num_rsp +parameter specifies the number of responses that can be received before +the inquiry is halted. +If not specified, i.e. 0, default value will be used. +The +.Fa ii +parameter specifies where to place inquiry results. +On success, the function will return total number of inquiry results, +will allocate buffer to store all the inquiry results and +will return pointer to the allocated buffer in the +.Fa ii +parameter. +It is up to the caller of the function to dispose of the buffer. +The function returns -1 if an error has occurred. +The +.Vt bt_devinquiry +structure is defined as follows +.Bd -literal -offset indent +struct bt_devinquiry { + bdaddr_t bdaddr; + uint8_t pscan_rep_mode; + uint8_t pscan_period_mode; + uint8_t dev_class[3]; + uint16_t clock_offset; + int8_t rssi; + uint8_t data[240]; +}; +.Ed +.Pp +The .Fn bdaddr_same , .Fn bdaddr_any and @@ -444,6 +710,6 @@ .Sh AUTHORS .An Maksim Yevmenkin Aq m_evmenkin@yahoo.com .Sh BUGS -These functions use static data storage; +Some of those functions use static data storage; if the data is needed for future use, it should be copied before any subsequent calls overwrite it. Index: bluetooth.h =================================================================== --- bluetooth.h (revision 191328) +++ bluetooth.h (working copy) @@ -39,6 +39,7 @@ #include #include #include +#include #include #include #include @@ -46,6 +47,7 @@ #include #include #include +#include __BEGIN_DECLS @@ -129,8 +131,48 @@ uint8_t _padding[20]; /* leave space for future additions */ }; +struct bt_devreq +{ + uint16_t opcode; + uint8_t event; + void *cparam; + size_t clen; + void *rparam; + size_t rlen; +}; + +struct bt_devfilter { + bitstr_t bit_decl(packet_mask, 8); + bitstr_t bit_decl(event_mask, 256); +}; + +struct bt_devinquiry { + bdaddr_t bdaddr; + uint8_t pscan_rep_mode; + uint8_t pscan_period_mode; + uint8_t dev_class[3]; + uint16_t clock_offset; + int8_t rssi; + uint8_t data[240]; +}; + typedef int (bt_devenum_cb_t)(int, struct bt_devinfo const *, void *); +int bt_devopen (char const *devname); +int bt_devclose(int s); +int bt_devsend (int s, uint16_t opcode, void *param, size_t plen); +int bt_devrecv (int s, void *buf, size_t size, time_t to); +int bt_devreq (int s, struct bt_devreq *r, time_t to); +int bt_devfilter(int s, struct bt_devfilter const *new, + struct bt_devfilter *old); +void bt_devfilter_pkt_set(struct bt_devfilter *filter, int type); +void bt_devfilter_pkt_clr(struct bt_devfilter *filter, int type); +int bt_devfilter_pkt_tst(struct bt_devfilter const *filter, int type); +void bt_devfilter_evt_set(struct bt_devfilter *filter, int event); +void bt_devfilter_evt_clr(struct bt_devfilter *filter, int event); +int bt_devfilter_evt_tst(struct bt_devfilter const *filter, int event); +int bt_devinquiry(char const *devname, time_t length, int num_rsp, + struct bt_devinquiry **ii); int bt_devinfo (struct bt_devinfo *di); int bt_devenum (bt_devenum_cb_t *cb, void *arg); Index: Makefile =================================================================== --- Makefile (revision 191328) +++ Makefile (working copy) @@ -33,6 +33,19 @@ MLINKS+= bluetooth.3 bt_devinfo.3 MLINKS+= bluetooth.3 bt_devenum.3 +MLINKS+= bluetooth.3 bt_devopen.3 +MLINKS+= bluetooth.3 bt_devclose.3 +MLINKS+= bluetooth.3 bt_devsend.3 +MLINKS+= bluetooth.3 bt_devreq.3 +MLINKS+= bluetooth.3 bt_devfilter.3 +MLINKS+= bluetooth.3 bt_devfilter_pkt_set.3 +MLINKS+= bluetooth.3 bt_devfilter_pkt_clr.3 +MLINKS+= bluetooth.3 bt_devfilter_pkt_tst.3 +MLINKS+= bluetooth.3 bt_devfilter_evt_set.3 +MLINKS+= bluetooth.3 bt_devfilter_evt_clr.3 +MLINKS+= bluetooth.3 bt_devfilter_evt_tst.3 +MLINKS+= bluetooth.3 bt_devinquiry.3 + MLINKS+= bluetooth.3 bdaddr_same.3 MLINKS+= bluetooth.3 bdaddr_any.3 MLINKS+= bluetooth.3 bdaddr_copy.3 From plunky at rya-online.net Tue Apr 21 10:54:41 2009 From: plunky at rya-online.net (Iain Hibbert) Date: Tue Apr 21 10:54:48 2009 Subject: libhci update In-Reply-To: References: Message-ID: <1240311202.361300.1366.nullmailer@galant.ukfsn.org> On Mon, 20 Apr 2009, Maksim Yevmenkin wrote: > >> thanks for the feedback. i'm attaching revisited patch. please take a > >> look and let me know if this is something you happy with. > > > > (pls ignore typos & bad formatting, am on mobile device :) > > > > Bt_devrecv() should probably take void * for buffer? Also manpage suggests it > > will only receive hci events. Do you think its worth doing some validation of received > > data? (eg return EIO for truncated reads?) > > i will change uint8_t * to void *, no problem. i also updated man page > and removed 'event' word :) Ok also in bt_devrecv() - return type should be ssize_t - size cannot be < 0 (use "size == 0")? and what of validation of received data here? It could be worth checking that the buffer is not truncated so that the caller does not have to worry about it, but that does require checking the packet type.. > > Bt_devreq() needs to set/restore a filter too > > well, maybe. bt_devreq() operates on already opened socket. the > assumption i'm making here is that application will set appropriate > filter before calling bt_devreq(). otherwise, application would have > to always set 'event' field to acceptable value (or zero). i could go > either way here. just need to document implemented behavior better. Mm, its a good point - there are arguments either way (bloat vs guaranteed success) but I think since the difference between devreq() and devrecv() is that devreq() handles all the fiddly details for you, I think its worth doing that aswell.. > > Do you think its worth to cook dev_class into a normalised host > > numeric value rather than 3 bytes ? > > right, hmm, i guess we could turn dev_class into uint32_t in le16 byte > order (since everything else in bluetooth hci is in le16 order), but > maybe its too much? spec breaks out dev_class as 3 bytes array and talks > about bytes and bits within each byte. i'm kinda leaning towards leaving > it as is, because otherwise application would have to translate byte/bit > into uint32_t mask. it could be somewhat convenient, i guess. again, no > strong feeling about it. could go either way. My thought was that some of the 'fields' do seem to cross over into multiple bytes (eg the service class flags in the default format type) and perhaps its easier to interpret as a single value where bits can be tested in host order by such as (class & (1 << N))? I don't know how much that helps but the assigned numbers document does seem to show it as a bit array. (I'm not having a strong opinion either :) > > Probably need to specifically mention that the inquiry response to be > > released using free() in manpage? > > it says so, i.e. > > The > .Fa ii > parameter specifies where to place inquiry results. > On success, the function will return total number of inquiry results, > will allocate buffer to store all the inquiry results and > will return pointer to the allocated buffer in the > .Fa ii > parameter. > It is up to the caller of the function to dispose of the buffer. No, I mean to specifically mention ".Xr free 3" as the method used to dispose of the buffer. > > Something i thought about on the train yesterday but can't visualise > > on the small screen. For device inquiry did you consider using a > > callback method (as per devenum) in stead of returning the structure > > array? At least i recall my nokia would show the list building (but > > perhaps that was when it got the names) and this windows mobile device > > does show the raw list in progress (though stupidly just displays a > > list of 'unknown device' until it gets the names :) > > yes, i thought about it. the only difference is that it would be not so > close to linux/bluez api. i guess, we could provide another function? I thought about that some yesterday (on another train :) and I think there could be some other problems in any case, eg some devices (at least, maybe all?) can not handle RemoteNameReq etc while in inquiry mode so the amount of things that can be done is minimal - btconfig(8) does print a dot each time a result is returned but I think thats probably about the limit of what is useful. So, perhaps just leave it for now.. if somebody requires something like that its not difficult to cut and paste library code to work something out. iain From plunky at rya-online.net Tue Apr 21 11:02:59 2009 From: plunky at rya-online.net (Iain Hibbert) Date: Tue Apr 21 11:03:06 2009 Subject: libhci update In-Reply-To: <1240311202.361300.1366.nullmailer@galant.ukfsn.org> References: <1240311202.361300.1366.nullmailer@galant.ukfsn.org> Message-ID: <1240311710.547959.2200.nullmailer@galant.ukfsn.org> On Tue, 21 Apr 2009, Iain Hibbert wrote: > > > Bt_devreq() needs to set/restore a filter too > > > > well, maybe. bt_devreq() operates on already opened socket. the > > assumption i'm making here is that application will set appropriate > > filter before calling bt_devreq(). otherwise, application would have > > to always set 'event' field to acceptable value (or zero). i could go > > either way here. just need to document implemented behavior better. > > Mm, its a good point - there are arguments either way (bloat vs guaranteed > success) but I think since the difference between devreq() and devrecv() > is that devreq() handles all the fiddly details for you, I think its worth > doing that aswell.. the bluez hci_send_req() does set the filters btw iain From maksim.yevmenkin at gmail.com Tue Apr 21 16:59:51 2009 From: maksim.yevmenkin at gmail.com (Maksim Yevmenkin) Date: Tue Apr 21 16:59:59 2009 Subject: libhci update In-Reply-To: <1240311202.361300.1366.nullmailer@galant.ukfsn.org> References: <1240311202.361300.1366.nullmailer@galant.ukfsn.org> Message-ID: On Tue, Apr 21, 2009 at 3:53 AM, Iain Hibbert wrote: > On Mon, 20 Apr 2009, Maksim Yevmenkin wrote: > >> >> thanks for the feedback. i'm attaching revisited patch. please take a >> >> look and let me know if this is something you happy with. >> > >> > (pls ignore typos & bad formatting, am on mobile device :) >> > >> > Bt_devrecv() should probably take void * for buffer? Also manpage suggests it >> > will only receive hci events. Do you think its worth doing some validation of received >> > data? (eg return EIO for truncated reads?) >> >> i will change uint8_t * to void *, no problem. i also updated man page >> and removed 'event' word :) > > Ok also in bt_devrecv() > - return type should be ssize_t > - size cannot be < 0 (use "size == 0")? sure. i changed it. > and what of validation of received data here? It could be worth checking > that the buffer is not truncated so that the caller does not have to worry > about it, but that does require checking the packet type.. why not? :) i added it as well. >> > Bt_devreq() needs to set/restore a filter too >> >> well, maybe. bt_devreq() operates on already opened socket. the >> assumption i'm making here is that application will set appropriate >> filter before calling bt_devreq(). otherwise, application would have >> to always set 'event' field to acceptable value (or zero). i could go >> either way here. just need to document implemented behavior better. > > Mm, its a good point - there are arguments either way (bloat vs guaranteed > success) but I think since the difference between devreq() and devrecv() > is that devreq() handles all the fiddly details for you, I think its worth > doing that aswell.. fine. it now sets and restores filter :) >> > Do you think its worth to cook dev_class into a normalised host >> > numeric value rather than 3 bytes ? >> >> right, hmm, i guess we could turn dev_class into uint32_t in le16 byte >> order (since everything else in bluetooth hci is in le16 order), but >> maybe its too much? spec breaks out dev_class as 3 bytes array and talks >> about bytes and bits within each byte. i'm kinda leaning towards leaving >> it as is, because otherwise application would have to translate byte/bit >> into uint32_t mask. it could be somewhat convenient, i guess. again, no >> strong feeling about it. could go either way. > > My thought was that some of the 'fields' do seem to cross over into > multiple bytes (eg the service class flags in the default format type) and > perhaps its easier to interpret as a single value where bits can be tested > in host order by such as (class & (1 << N))? I don't know how much that > helps but the assigned numbers document does seem to show it as a bit > array. (I'm not having a strong opinion either :) left it as 3 bytes array for now. >> > Probably need to specifically mention that the inquiry response to be >> > released using free() in manpage? >> >> it says so, i.e. >> >> The >> .Fa ii >> parameter specifies where to place inquiry results. >> On success, the function will return total number of inquiry results, >> will allocate buffer to store all the inquiry results and >> will return pointer to the allocated buffer in the >> .Fa ii >> parameter. >> It is up to the caller of the function to dispose of the buffer. > > No, I mean to specifically mention ".Xr free 3" as the method used to > dispose of the buffer. ah, i see. man page was updated. the revised diff is attached. please review. thanks, max -------------- next part -------------- Index: hci.c =================================================================== --- hci.c (revision 191329) +++ hci.c (working copy) @@ -30,15 +30,497 @@ * $FreeBSD$ */ +#include #include #include #include #include #include +static int bt_devany_cb(int s, struct bt_devinfo const *di, void *xdevname); static char * bt_dev2node (char const *devname, char *nodename, int nnlen); int +bt_devopen(char const *devname) +{ + struct sockaddr_hci ha; + bdaddr_t ba; + int s; + + if (devname == NULL) { + errno = EINVAL; + return (-1); + } + + memset(&ha, 0, sizeof(ha)); + ha.hci_len = sizeof(ha); + ha.hci_family = AF_BLUETOOTH; + + if (bt_aton(devname, &ba)) { + if (!bt_devname(ha.hci_node, &ba)) + return (-1); + } else if (bt_dev2node(devname, ha.hci_node, + sizeof(ha.hci_node)) == NULL) { + errno = ENXIO; + return (-1); + } + + s = socket(PF_BLUETOOTH, SOCK_RAW, BLUETOOTH_PROTO_HCI); + if (s < 0) + return (-1); + + if (bind(s, (struct sockaddr *) &ha, sizeof(ha)) < 0 || + connect(s, (struct sockaddr *) &ha, sizeof(ha)) < 0) { + close(s); + return (-1); + } + + return (s); +} + +int +bt_devclose(int s) +{ + return (close(s)); +} + +int +bt_devsend(int s, uint16_t opcode, void *param, size_t plen) +{ + ng_hci_cmd_pkt_t h; + struct iovec iv[2]; + int ivn; + + if ((plen == 0 && param != NULL) || (plen > 0 && param == NULL)) { + errno = EINVAL; + return (-1); + } + + iv[0].iov_base = &h; + iv[0].iov_len = sizeof(h); + ivn = 1; + + h.type = NG_HCI_CMD_PKT; + h.opcode = htole16(opcode); + if (plen > 0) { + h.length = plen; + + iv[1].iov_base = param; + iv[1].iov_len = plen; + ivn = 2; + } else + h.length = 0; + + while (writev(s, iv, ivn) < 0) { + if (errno == EAGAIN || errno == EINTR) + continue; + + return (-1); + } + + return (0); +} + +ssize_t +bt_devrecv(int s, void *buf, size_t size, time_t to) +{ + fd_set rfd; + struct timeval tv; + ssize_t n; + + if (buf == NULL || size == 0 || to < 0) { + errno = EINVAL; + return (-1); + } + + FD_ZERO(&rfd); + FD_SET(s, &rfd); + + tv.tv_sec = to; + tv.tv_usec = 0; + + while ((n = select(s + 1, &rfd, NULL, NULL, &tv)) < 0) { + if (errno == EAGAIN || errno == EINTR) + continue; + + return (-1); + } + + if (n == 0) { + errno = ETIMEDOUT; + return (-1); + } + + assert(FD_ISSET(s, &rfd)); + + while ((n = read(s, buf, size)) < 0) { + if (errno == EAGAIN || errno == EINTR) + continue; + + return (-1); + } + + switch (*((uint8_t *) buf)) { + case NG_HCI_CMD_PKT: { + ng_hci_cmd_pkt_t *h = (ng_hci_cmd_pkt_t *) buf; + + if (n >= sizeof(*h) && n == (sizeof(*h) + h->length)) + return (n); + } break; + + case NG_HCI_ACL_DATA_PKT: { + ng_hci_acldata_pkt_t *h = (ng_hci_acldata_pkt_t *) buf; + + if (n >= sizeof(*h) && n == (sizeof(*h) + le16toh(h->length))) + return (n); + } break; + + case NG_HCI_SCO_DATA_PKT: { + ng_hci_scodata_pkt_t *h = (ng_hci_scodata_pkt_t *) buf; + + if (n >= sizeof(*h) && n == (sizeof(*h) + h->length)) + return (n); + } break; + + case NG_HCI_EVENT_PKT: { + ng_hci_event_pkt_t *h = (ng_hci_event_pkt_t *) buf; + + if (n >= sizeof(*h) && n == (sizeof(*h) + h->length)) + return (n); + } break; + } + + errno = EIO; + return (-1); +} + +int +bt_devreq(int s, struct bt_devreq *r, time_t to) +{ + uint8_t buf[320]; /* more than enough */ + ng_hci_event_pkt_t *e = (ng_hci_event_pkt_t *) buf; + ng_hci_command_compl_ep *cc = (ng_hci_command_compl_ep *)(e+1); + ng_hci_command_status_ep *cs = (ng_hci_command_status_ep*)(e+1); + struct bt_devfilter old, new; + time_t t_end; + uint16_t opcode; + ssize_t n; + int error; + + if (s < 0 || r == NULL || to < 0) { + errno = EINVAL; + return (-1); + } + + if ((r->rlen == 0 && r->rparam != NULL) || + (r->rlen > 0 && r->rparam == NULL)) { + errno = EINVAL; + return (-1); + } + + memset(&new, 0, sizeof(new)); + bt_devfilter_pkt_set(&new, NG_HCI_EVENT_PKT); + bt_devfilter_evt_set(&new, NG_HCI_EVENT_COMMAND_COMPL); + bt_devfilter_evt_set(&new, NG_HCI_EVENT_COMMAND_STATUS); + if (r->event != 0) + bt_devfilter_evt_set(&new, r->event); + + if (bt_devfilter(s, &new, &old) < 0) + return (-1); + + error = 0; + + n = bt_devsend(s, r->opcode, r->cparam, r->clen); + if (n < 0) { + error = errno; + goto out; + } + + opcode = htole16(r->opcode); + t_end = time(NULL) + to; + + do { + to = t_end - time(NULL); + if (to < 0) + to = 0; + + n = bt_devrecv(s, buf, sizeof(buf), to); + if (n < 0) { + error = errno; + goto out; + } + + if (e->type != NG_HCI_EVENT_PKT) { + error = EIO; + goto out; + } + + n -= sizeof(*e); + + switch (e->event) { + case NG_HCI_EVENT_COMMAND_COMPL: + if (cc->opcode == opcode) { + n -= sizeof(*cc); + + if (r->rlen >= n) { + r->rlen = n; + memcpy(r->rparam, cc + 1, r->rlen); + } + + goto out; + } + break; + + case NG_HCI_EVENT_COMMAND_STATUS: + if (cs->opcode == opcode) { + if (r->event != NG_HCI_EVENT_COMMAND_STATUS) { + if (cs->status != 0) { + error = EIO; + goto out; + } + } else { + if (r->rlen >= n) { + r->rlen = n; + memcpy(r->rparam, cs, r->rlen); + } + + goto out; + } + } + break; + + default: + if (e->event == r->event) { + if (r->rlen >= n) { + r->rlen = n; + memcpy(r->rparam, e + 1, r->rlen); + } + + goto out; + } + break; + } + } while (to > 0); + + error = ETIMEDOUT; +out: + bt_devfilter(s, &old, NULL); + + if (error != 0) { + errno = error; + return (-1); + } + + return (0); +} + +int +bt_devfilter(int s, struct bt_devfilter const *new, struct bt_devfilter *old) +{ + struct ng_btsocket_hci_raw_filter f; + socklen_t len; + + if (new == NULL && old == NULL) { + errno = EINVAL; + return (-1); + } + + if (old != NULL) { + len = sizeof(f); + if (getsockopt(s, SOL_HCI_RAW, SO_HCI_RAW_FILTER, &f, &len) < 0) + return (-1); + + memset(old, 0, sizeof(*old)); + memcpy(old->packet_mask, &f.packet_mask, + sizeof(old->packet_mask)); + memcpy(old->event_mask, &f.event_mask, + sizeof(old->event_mask)); + } + + if (new != NULL) { + memset(&f, 0, sizeof(f)); + memcpy(&f.packet_mask, new->packet_mask, sizeof(f.packet_mask)); + memcpy(&f.event_mask, new->event_mask, sizeof(f.event_mask)); + + len = sizeof(f); + if (setsockopt(s, SOL_HCI_RAW, SO_HCI_RAW_FILTER, &f, len) < 0) + return (-1); + } + + return (0); +} + +void +bt_devfilter_pkt_set(struct bt_devfilter *filter, uint8_t type) +{ + bit_set(filter->packet_mask, type - 1); +} + +void +bt_devfilter_pkt_clr(struct bt_devfilter *filter, uint8_t type) +{ + bit_clear(filter->packet_mask, type - 1); +} + +int +bt_devfilter_pkt_tst(struct bt_devfilter const *filter, uint8_t type) +{ + return (bit_test(filter->packet_mask, type - 1)); +} + +void +bt_devfilter_evt_set(struct bt_devfilter *filter, uint8_t event) +{ + bit_set(filter->event_mask, event - 1); +} + +void +bt_devfilter_evt_clr(struct bt_devfilter *filter, uint8_t event) +{ + bit_clear(filter->event_mask, event - 1); +} + +int +bt_devfilter_evt_tst(struct bt_devfilter const *filter, uint8_t event) +{ + return (bit_test(filter->event_mask, event - 1)); +} + +int +bt_devinquiry(char const *devname, time_t length, int num_rsp, + struct bt_devinquiry **ii) +{ + uint8_t buf[320]; + char _devname[HCI_DEVNAME_SIZE]; + struct bt_devfilter f; + ng_hci_inquiry_cp *cp = (ng_hci_inquiry_cp *) buf; + ng_hci_event_pkt_t *e = (ng_hci_event_pkt_t *) buf; + ng_hci_inquiry_result_ep *ep = (ng_hci_inquiry_result_ep *)(e+1); + ng_hci_inquiry_response *ir; + struct bt_devinquiry *i; + int s, n; + time_t to; + + if (ii == NULL) { + errno = EINVAL; + return (-1); + } + + if (devname == NULL) { + memset(_devname, 0, sizeof(_devname)); + devname = _devname; + + n = bt_devenum(bt_devany_cb, _devname); + if (n <= 0) { + if (n == 0) + *ii = NULL; + + return (n); + } + } + + s = bt_devopen(devname); + if (s < 0) + return (-1); + + if (bt_devfilter(s, NULL, &f) < 0) { + bt_devclose(s); + return (-1); + } + + bt_devfilter_evt_set(&f, NG_HCI_EVENT_INQUIRY_COMPL); + bt_devfilter_evt_set(&f, NG_HCI_EVENT_INQUIRY_RESULT); + + if (bt_devfilter(s, &f, NULL) < 0) { + bt_devclose(s); + return (-1); + } + + /* Always use GIAC LAP */ + cp->lap[0] = 0x33; + cp->lap[1] = 0x8b; + cp->lap[2] = 0x9e; + + /* Calculate inquire length in 1.28 second units */ + to = (time_t) ((double) length / 1.28); + if (to <= 0) + cp->inquiry_length = 4; /* 5.12 seconds */ + else if (to > 254) + cp->inquiry_length = 255; /* 326.40 seconds */ + else + cp->inquiry_length = to + 1; + + to = (time_t)((double) cp->inquiry_length * 1.28) + 1; + + if (num_rsp <= 0 || num_rsp > 255) + num_rsp = 8; + cp->num_responses = (uint8_t) num_rsp; + + i = *ii = calloc(num_rsp, sizeof(struct bt_devinquiry)); + if (i == NULL) { + bt_devclose(s); + errno = ENOMEM; + return (-1); + } + + if (bt_devsend(s, + NG_HCI_OPCODE(NG_HCI_OGF_LINK_CONTROL, NG_HCI_OCF_INQUIRY), + cp, sizeof(*cp)) < 0) { + free(i); + bt_devclose(s); + return (-1); + } + +wait_for_more: + + n = bt_devrecv(s, buf, sizeof(buf), to); + if (n < 0) { + free(i); + bt_devclose(s); + return (-1); + } + + if (n < sizeof(ng_hci_event_pkt_t)) { + free(i); + bt_devclose(s); + errno = EIO; + return (-1); + } + + switch (e->event) { + case NG_HCI_EVENT_INQUIRY_COMPL: + break; + + case NG_HCI_EVENT_INQUIRY_RESULT: + ir = (ng_hci_inquiry_response *)(ep + 1); + +#undef MIN +#define MIN(a, b) (((a) < (b))? (a) : (b)) + + for (n = 0; n < MIN(ep->num_responses, num_rsp); n ++) { + bdaddr_copy(&i->bdaddr, &ir->bdaddr); + i->pscan_rep_mode = ir->page_scan_rep_mode; + i->pscan_period_mode = ir->page_scan_period_mode; + memcpy(i->dev_class, ir->uclass, sizeof(i->dev_class)); + i->clock_offset = le16toh(ir->clock_offset); + + ir ++; + i ++; + num_rsp --; + } + /* FALLTHROUGH */ + + default: + goto wait_for_more; + /* NOT REACHED */ + } + + bt_devclose(s); + + return (i - *ii); +} + +int bt_devinfo(struct bt_devinfo *di) { union { @@ -53,6 +535,7 @@ struct ng_btsocket_hci_raw_node_debug r8; } rp; struct sockaddr_hci ha; + socklen_t halen; int s, rval; if (di == NULL) { @@ -60,27 +543,14 @@ return (-1); } - memset(&ha, 0, sizeof(ha)); - ha.hci_len = sizeof(ha); - ha.hci_family = AF_BLUETOOTH; - - if (bt_aton(di->devname, &rp.r1.bdaddr)) { - if (!bt_devname(ha.hci_node, &rp.r1.bdaddr)) - return (-1); - } else if (bt_dev2node(di->devname, ha.hci_node, - sizeof(ha.hci_node)) == NULL) { - errno = ENXIO; - return (-1); - } - - s = socket(PF_BLUETOOTH, SOCK_RAW, BLUETOOTH_PROTO_HCI); + s = bt_devopen(di->devname); if (s < 0) return (-1); rval = -1; - if (bind(s, (struct sockaddr *) &ha, sizeof(ha)) < 0 || - connect(s, (struct sockaddr *) &ha, sizeof(ha)) < 0) + halen = sizeof(ha); + if (getsockname(s, (struct sockaddr *) &ha, &halen) < 0) goto bad; strlcpy(di->devname, ha.hci_node, sizeof(di->devname)); @@ -138,7 +608,7 @@ rval = 0; bad: - close(s); + bt_devclose(s); return (rval); } @@ -205,6 +675,13 @@ return (count); } +static int +bt_devany_cb(int s, struct bt_devinfo const *di, void *xdevname) +{ + strlcpy((char *) xdevname, di->devname, HCI_DEVNAME_SIZE); + return (1); +} + static char * bt_dev2node(char const *devname, char *nodename, int nnlen) { Index: bluetooth.3 =================================================================== --- bluetooth.3 (revision 191329) +++ bluetooth.3 (working copy) @@ -25,7 +25,7 @@ .\" $Id: bluetooth.3,v 1.5 2003/05/20 23:04:30 max Exp $ .\" $FreeBSD$ .\" -.Dd February 13, 2009 +.Dd April 9, 2009 .Dt BLUETOOTH 3 .Os .Sh NAME @@ -41,6 +41,23 @@ .Nm bt_endprotoent , .Nm bt_aton , .Nm bt_ntoa , +.Nm bt_devaddr , +.Nm bt_devname , +.Nm bt_devinfo , +.Nm bt_devenum , +.Nm bt_devopen , +.Nm bt_devclose , +.Nm bt_devsend , +.Nm bt_devrecv , +.Nm bt_devreq , +.Nm bt_devfilter , +.Nm bt_devfilter_pkt_set , +.Nm bt_devfilter_pkt_clr , +.Nm bt_devfilter_pkt_tst , +.Nm bt_devfilter_evt_set , +.Nm bt_devfilter_evt_clr , +.Nm bt_devfilter_evt_tst , +.Nm bt_devinquiry , .Nm bdaddr_same , .Nm bdaddr_any , .Nm bdaddr_copy @@ -84,6 +101,32 @@ .Ft int .Fn bt_devenum "bt_devenum_cb_t *cb" "void *arg" .Ft int +.Fn bt_devopen "char const *devname" +.Ft int +.Fn bt_devclose "int s" +.Ft int +.Fn bt_devsend "int s" "uint16_t opcode" "void *param" "size_t plen" +.Ft ssize_t +.Fn bt_devrecv "int s" "void *buf" "size_t size" "time_t to" +.Ft int +.Fn bt_devreq "int s" "struct bt_devreq *r" "time_t to" +.Ft int +.Fn bt_devfilter "int s" "struct bt_devfilter const *new" "struct bt_devfilter *old" +.Ft void +.Fn bt_devfilter_pkt_set "struct bt_devfilter *filter" "uint8_t type" +.Ft void +.Fn bt_devfilter_pkt_clt "struct bt_devfilter *filter" "uint8_t type" +.Ft int +.Fn bt_devfilter_pkt_tst "struct bt_devfilter const *filter" "uint8_t type" +.Ft void +.Fn bt_devfilter_evt_set "struct bt_devfilter *filter" "uint8_t event" +.Ft void +.Fn bt_devfilter_evt_clt "struct bt_devfilter *filter" "uint8_t event" +.Ft int +.Fn bt_devfilter_evt_tst "struct bt_devfilter const *filter" "uint8_t event" +.Ft int +.Fn bt_devinquiry "char const *devname" "time_t length" "int num_rsp" "struct bt_devinquiry **ii" +.Ft int .Fn bdaddr_same "const bdaddr_t *a" "const bdaddr_t *b" .Ft int .Fn bdaddr_any "const bdaddr_t *a" @@ -311,6 +354,237 @@ or -1 if an error occurred. .Pp The +.Fn bt_devopen +function opens Bluetooth device with the given +.Fa devname +and returns connected and bound +.Dv HCI +socket. +The function returns -1 if an error has occurred. +.Pp +The +.Fn bt_devclose +closes passed +.Dv HCI +socket +.Fa s , +previously obtained with +.Xr bt_devopen 3 . +.Pp +The +.Fn bt_devsend +function sends Bluetooth +.Dv HCI +command with the given +.Fa opcode +to the provided socket +.Fa s , +previously obtained with +.Xr bt_devopen 3 . +The +.Fa opcode +parameter is exppected to be in the host byte order. +The +.Fa param +and +.Fa plen +parameters specify command parameters. +The +.Fn bt_devsend +function does not modify +.Dv HCI +filter on the provided socket +.Fa s . +The function returns 0 on success, +or -1 if an error occurred. +.Pp +The +.Fn bt_devrecv +function receives one Bluetooth +.Dv HCI +packet from the socket +.Fa s , +previously obtained with +.Xr bt_devopen 3 . +The packet is placed into the provided buffer +.Fa buf +of size +.Fa size . +The +.Fa to +parameter specifies receive timeout in seconds. +The +.Fn bt_devrecv +function does not modify +.Dv HCI +filter on the provided socket +.Fa s . +The function returns total number of bytes recevied, +or -1 if an error occurred. +.Pp +The +.Fn bt_devreq +function makes Bluetooth +.Dv HCI +request to the socket +.Fa s , +previously obtained with +.Xr bt_devopen 3 . +The function will send the specified command and will wait for the specified +event, +or timeout +.Fa to +seconds to occur. +The +.Vt bt_devreq +structure is defined as follows +.Bd -literal -offset indent +struct bt_devreq +{ + uint16_t opcode; + uint8_t event; + void *cparam; + size_t clen; + void *rparam; + size_t rlen; +}; +.Ed +.Pp +The +.Fa opcode +field specifies the command and is expected to be in the host byte order. +The +.Fa cparam +and +.Fa clen +fields specify command parameters data and command parameters data size +respectively. +The +.Fa event +field specifies which Bluetooth +.Dv HCI +event ID the function should wait for, otherwise it should be set to zero. +The +.Dv HCI +Command Complete and Command Status events are enabled by default. +The +.Fa rparam +and +.Fa rlen +parameters specify buffer and buffer size respectively where return +parameters should be placed. +The +.Fn bt_devreq +function temporarily modifies filter on the provided +.Dv HCI +socket +.Fa s . +The function returns 0 on success, or -1 if an error occurred. +.Pp +The +.Fn bt_devfilter +controls the local +.Dv HCI +filter associated with the socket +.Fa s , +previously obtained with +.Xr bt_devopen 3 . +Filtering can be done on packet types, i.e. +.Dv ACL , +.Dv SCO or +.Dv HCI +event packets, and, in addition, on +.Dv HCI +event IDs. +Before applying +.Fa new +filter (if provided) the function will try to obtain current filter +from the socket +.Fa s +and place it into the +.Fa old +parameter (if provided). +The function returns 0 on success, or -1 if an error occurred. +.Pp +The +.Fn bt_devfilter_pkt_set , +.Fn bt_devfilter_pkt_clr +and +.Fn bt_devfilter_pkt_tst +functions can be used to modify and test +.Dv HCI +filter +.Fa filter . +The +.Fa type +parameter specifies +.Dv HCI +packet type. +.Pp +The +.Fn bt_devfilter_evt_set , +.Fn bt_devfilter_evt_clr +and +.Fn bt_devfilter_evt_tst +functions can be used to modify and test +.Dv HCI +event filter +.Fa filter . +The +.Fa event +parameter specifies +.Dv HCI +event ID. +.Pp +The +.Fn bt_devinquiry +function performs Bluetooth inquiry. +The +.Fa devname +parameter specifies which local Bluetooth device should perform an inquiry. +If not secified, i.e. +.Dv NULL , +then first available device will be used. +The +.Fa length +parameters specifies the total length of an inquiry in seconds. +If not specified, i.e. 0, default value will be used. +The +.Fa num_rsp +parameter specifies the number of responses that can be received before +the inquiry is halted. +If not specified, i.e. 0, default value will be used. +The +.Fa ii +parameter specifies where to place inquiry results. +On success, the function will return total number of inquiry results, +will allocate, +using +.Xr calloc 3 , +buffer to store all the inquiry results and +will return pointer to the allocated buffer in the +.Fa ii +parameter. +It is up to the caller of the function to dispose of the buffer using +.Xr free 3 +call. +The function returns -1 if an error has occurred. +The +.Vt bt_devinquiry +structure is defined as follows +.Bd -literal -offset indent +struct bt_devinquiry { + bdaddr_t bdaddr; + uint8_t pscan_rep_mode; + uint8_t pscan_period_mode; + uint8_t dev_class[3]; + uint16_t clock_offset; + int8_t rssi; + uint8_t data[240]; +}; +.Ed +.Pp +The .Fn bdaddr_same , .Fn bdaddr_any and @@ -444,6 +718,6 @@ .Sh AUTHORS .An Maksim Yevmenkin Aq m_evmenkin@yahoo.com .Sh BUGS -These functions use static data storage; +Some of those functions use static data storage; if the data is needed for future use, it should be copied before any subsequent calls overwrite it. Index: bluetooth.h =================================================================== --- bluetooth.h (revision 191329) +++ bluetooth.h (working copy) @@ -39,6 +39,7 @@ #include #include #include +#include #include #include #include @@ -46,6 +47,7 @@ #include #include #include +#include __BEGIN_DECLS @@ -129,8 +131,48 @@ uint8_t _padding[20]; /* leave space for future additions */ }; +struct bt_devreq +{ + uint16_t opcode; + uint8_t event; + void *cparam; + size_t clen; + void *rparam; + size_t rlen; +}; + +struct bt_devfilter { + bitstr_t bit_decl(packet_mask, 8); + bitstr_t bit_decl(event_mask, 256); +}; + +struct bt_devinquiry { + bdaddr_t bdaddr; + uint8_t pscan_rep_mode; + uint8_t pscan_period_mode; + uint8_t dev_class[3]; + uint16_t clock_offset; + int8_t rssi; + uint8_t data[240]; +}; + typedef int (bt_devenum_cb_t)(int, struct bt_devinfo const *, void *); +int bt_devopen (char const *devname); +int bt_devclose(int s); +int bt_devsend (int s, uint16_t opcode, void *param, size_t plen); +ssize_t bt_devrecv (int s, void *buf, size_t size, time_t to); +int bt_devreq (int s, struct bt_devreq *r, time_t to); +int bt_devfilter(int s, struct bt_devfilter const *new, + struct bt_devfilter *old); +void bt_devfilter_pkt_set(struct bt_devfilter *filter, uint8_t type); +void bt_devfilter_pkt_clr(struct bt_devfilter *filter, uint8_t type); +int bt_devfilter_pkt_tst(struct bt_devfilter const *filter, uint8_t type); +void bt_devfilter_evt_set(struct bt_devfilter *filter, uint8_t event); +void bt_devfilter_evt_clr(struct bt_devfilter *filter, uint8_t event); +int bt_devfilter_evt_tst(struct bt_devfilter const *filter, uint8_t event); +int bt_devinquiry(char const *devname, time_t length, int num_rsp, + struct bt_devinquiry **ii); int bt_devinfo (struct bt_devinfo *di); int bt_devenum (bt_devenum_cb_t *cb, void *arg); Index: Makefile =================================================================== --- Makefile (revision 191329) +++ Makefile (working copy) @@ -33,6 +33,19 @@ MLINKS+= bluetooth.3 bt_devinfo.3 MLINKS+= bluetooth.3 bt_devenum.3 +MLINKS+= bluetooth.3 bt_devopen.3 +MLINKS+= bluetooth.3 bt_devclose.3 +MLINKS+= bluetooth.3 bt_devsend.3 +MLINKS+= bluetooth.3 bt_devreq.3 +MLINKS+= bluetooth.3 bt_devfilter.3 +MLINKS+= bluetooth.3 bt_devfilter_pkt_set.3 +MLINKS+= bluetooth.3 bt_devfilter_pkt_clr.3 +MLINKS+= bluetooth.3 bt_devfilter_pkt_tst.3 +MLINKS+= bluetooth.3 bt_devfilter_evt_set.3 +MLINKS+= bluetooth.3 bt_devfilter_evt_clr.3 +MLINKS+= bluetooth.3 bt_devfilter_evt_tst.3 +MLINKS+= bluetooth.3 bt_devinquiry.3 + MLINKS+= bluetooth.3 bdaddr_same.3 MLINKS+= bluetooth.3 bdaddr_any.3 MLINKS+= bluetooth.3 bdaddr_copy.3 From plunky at rya-online.net Tue Apr 21 22:18:50 2009 From: plunky at rya-online.net (Iain Hibbert) Date: Tue Apr 21 22:18:56 2009 Subject: libhci update In-Reply-To: References: <1240311202.361300.1366.nullmailer@galant.ukfsn.org> Message-ID: <1240352254.082638.416.nullmailer@galant.ukfsn.org> On Tue, 21 Apr 2009, Maksim Yevmenkin wrote: > the revised diff is attached. please review. Its all looking good, though some small tuneups still occurring :) in bt_devsend() you might also want to check for "plen > UINT8_MAX"? for bt_devrecv() should there also be a method for not timing out? I can see that being used to wait for any packet which could be some time. (bt_devreq() does not need that I think?) In bt_devreq() you used buf[320] is that truly enough? I didn't check that part of the radio specification dedicated to baseband packet size but the Broadcom BCM2045B device in my laptop claims to have max_acl_size of 1017.. Also, I think rlen and rparam can be empty in bt_devreq(), is that intentional and what does it mean? and finally some language improvements in the manpage below iain +.Fn bt_devopen +function opens Bluetooth device with the given ^a +.Fa devname +and returns connected and bound ^a +.Dv HCI +socket. ^handle +The function returns -1 if an error has occurred. +.Pp +The +.Fn bt_devclose +closes passed ^the +.Dv HCI +socket ^handle +.Fa s , +previously obtained with +.Xr bt_devopen 3 . +.Pp +The +.Fn bt_devsend +function sends Bluetooth ^a +.Dv HCI +command with the given +.Fa opcode +to the provided socket +.Fa s , +previously obtained with +.Xr bt_devopen 3 . +The +.Fa opcode +parameter is exppected to be in the host byte order. +The +.Fa param +and +.Fa plen +parameters specify command parameters. +The +.Fn bt_devsend +function does not modify ^the +.Dv HCI +filter on the provided socket +.Fa s . +The function returns 0 on success, +or -1 if an error occurred. +.Pp +The +.Fn bt_devrecv +function receives one Bluetooth +.Dv HCI +packet from the socket +.Fa s , +previously obtained with +.Xr bt_devopen 3 . +The packet is placed into the provided buffer +.Fa buf +of size +.Fa size . +The +.Fa to +parameter specifies receive timeout in seconds. +The +.Fn bt_devrecv +function does not modify ^the +.Dv HCI +filter on the provided socket +.Fa s . +The function returns total number of bytes recevied, +or -1 if an error occurred. +.Pp +The +.Fn bt_devreq +function makes Bluetooth ^a +.Dv HCI +request to the socket +.Fa s , +previously obtained with +.Xr bt_devopen 3 . +The function will send the specified command and will wait for the specified +event, +or timeout +.Fa to +seconds to occur. +The +.Vt bt_devreq +structure is defined as follows +.Bd -literal -offset indent +struct bt_devreq +{ + uint16_t opcode; + uint8_t event; + void *cparam; + size_t clen; + void *rparam; + size_t rlen; +}; +.Ed +.Pp +The +.Fa opcode +field specifies the command and is expected to be in the host byte order. +The +.Fa cparam +and +.Fa clen +fields specify command parameters data and command parameters data size +respectively. +The +.Fa event +field specifies which Bluetooth +.Dv HCI +event ID the function should wait for, otherwise it should be set to zero. +The +.Dv HCI +Command Complete and Command Status events are enabled by default. +The +.Fa rparam +and +.Fa rlen +parameters specify buffer and buffer size respectively where return +parameters should be placed. +The +.Fn bt_devreq +function temporarily modifies filter on the provided +.Dv HCI +socket +.Fa s . +The function returns 0 on success, or -1 if an error occurred. +.Pp +The +.Fn bt_devfilter +controls the local +.Dv HCI +filter associated with the socket +.Fa s , +previously obtained with +.Xr bt_devopen 3 . +Filtering can be done on packet types, i.e. +.Dv ACL , +.Dv SCO or +.Dv HCI +event packets, and, in addition, on ^command and +.Dv HCI +event IDs. +Before applying ^the +.Fa new +filter (if provided) the function will try to obtain current filter ^the +from the socket +.Fa s +and place it into the +.Fa old +parameter (if provided). +The function returns 0 on success, or -1 if an error occurred. +.Pp +The +.Fn bt_devfilter_pkt_set , +.Fn bt_devfilter_pkt_clr +and +.Fn bt_devfilter_pkt_tst +functions can be used to modify and test ^the +.Dv HCI +filter +.Fa filter . +The +.Fa type +parameter specifies +.Dv HCI +packet type. +.Pp +The +.Fn bt_devfilter_evt_set , +.Fn bt_devfilter_evt_clr +and +.Fn bt_devfilter_evt_tst +functions can be used to modify and test ^the +.Dv HCI +event filter +.Fa filter . +The +.Fa event +parameter specifies +.Dv HCI +event ID. +.Pp +The +.Fn bt_devinquiry +function performs Bluetooth inquiry. +The +.Fa devname +parameter specifies which local Bluetooth device should perform an inquiry. +If not secified, i.e. +.Dv NULL , +then first available device will be used. +The +.Fa length +parameters specifies the total length of an inquiry in seconds. +If not specified, i.e. 0, default value will be used. +The +.Fa num_rsp +parameter specifies the number of responses that can be received before +the inquiry is halted. +If not specified, i.e. 0, default value will be used. +The +.Fa ii +parameter specifies where to place inquiry results. +On success, the function will return total number of inquiry results, +will allocate, +using +.Xr calloc 3 , +buffer to store all the inquiry results and +will return pointer to the allocated buffer in the +.Fa ii +parameter. +It is up to the caller of the function to dispose of the buffer using +.Xr free 3 +call. +The function returns -1 if an error has occurred. +The +.Vt bt_devinquiry +structure is defined as follows +.Bd -literal -offset indent +struct bt_devinquiry { + bdaddr_t bdaddr; + uint8_t pscan_rep_mode; + uint8_t pscan_period_mode; + uint8_t dev_class[3]; + uint16_t clock_offset; + int8_t rssi; + uint8_t data[240]; +}; +.Ed +.Pp +The .Fn bdaddr_same , .Fn bdaddr_any and @@ -444,6 +718,6 @@ .Sh AUTHORS .An Maksim Yevmenkin Aq m_evmenkin@yahoo.com .Sh BUGS -These functions use static data storage; +Some of those functions use static data storage; if the data is needed for future use, it should be copied before any subsequent calls overwrite it. Index: bluetooth.h From maksim.yevmenkin at gmail.com Tue Apr 21 23:51:11 2009 From: maksim.yevmenkin at gmail.com (Maksim Yevmenkin) Date: Tue Apr 21 23:51:17 2009 Subject: libhci update In-Reply-To: <1240352254.082638.416.nullmailer@galant.ukfsn.org> References: <1240311202.361300.1366.nullmailer@galant.ukfsn.org> <1240352254.082638.416.nullmailer@galant.ukfsn.org> Message-ID: On Tue, Apr 21, 2009 at 3:17 PM, Iain Hibbert wrote: > On Tue, 21 Apr 2009, Maksim Yevmenkin wrote: > >> the revised diff is attached. please review. > > Its all looking good, though some small tuneups still occurring :) thanks for the review. > in bt_devsend() you might also want to check for "plen > UINT8_MAX"? done > for bt_devrecv() should there also be a method for not timing out? I can > see that being used to wait for any packet which could be some time. > (bt_devreq() does not need that I think?) done > In bt_devreq() you used buf[320] is that truly enough? I didn't check that > part of the radio specification dedicated to baseband packet size but the > Broadcom BCM2045B device in my laptop claims to have max_acl_size of 1017.. yes, but we are not dealing with acl packets in bt_devreq() only commands and event that have up to 255 bytes of payload. > Also, I think rlen and rparam can be empty in bt_devreq(), is that > intentional and what does it mean? if someone wants to just send request and does not really care about return parameters. > and finally some language improvements in the manpage below fixed. please see attached revised patch. thanks, max -------------- next part -------------- Index: hci.c =================================================================== --- hci.c (revision 191363) +++ hci.c (working copy) @@ -30,15 +30,505 @@ * $FreeBSD$ */ +#include #include +#include #include #include #include #include +#undef MIN +#define MIN(a, b) (((a) < (b))? (a) : (b)) + +static int bt_devany_cb(int s, struct bt_devinfo const *di, void *xdevname); static char * bt_dev2node (char const *devname, char *nodename, int nnlen); int +bt_devopen(char const *devname) +{ + struct sockaddr_hci ha; + bdaddr_t ba; + int s; + + if (devname == NULL) { + errno = EINVAL; + return (-1); + } + + memset(&ha, 0, sizeof(ha)); + ha.hci_len = sizeof(ha); + ha.hci_family = AF_BLUETOOTH; + + if (bt_aton(devname, &ba)) { + if (!bt_devname(ha.hci_node, &ba)) + return (-1); + } else if (bt_dev2node(devname, ha.hci_node, + sizeof(ha.hci_node)) == NULL) { + errno = ENXIO; + return (-1); + } + + s = socket(PF_BLUETOOTH, SOCK_RAW, BLUETOOTH_PROTO_HCI); + if (s < 0) + return (-1); + + if (bind(s, (struct sockaddr *) &ha, sizeof(ha)) < 0 || + connect(s, (struct sockaddr *) &ha, sizeof(ha)) < 0) { + close(s); + return (-1); + } + + return (s); +} + +int +bt_devclose(int s) +{ + return (close(s)); +} + +int +bt_devsend(int s, uint16_t opcode, void *param, size_t plen) +{ + ng_hci_cmd_pkt_t h; + struct iovec iv[2]; + int ivn; + + if ((plen == 0 && param != NULL) || + (plen > 0 && param == NULL) || + plen > UINT8_MAX) { + errno = EINVAL; + return (-1); + } + + iv[0].iov_base = &h; + iv[0].iov_len = sizeof(h); + ivn = 1; + + h.type = NG_HCI_CMD_PKT; + h.opcode = htole16(opcode); + if (plen > 0) { + h.length = plen; + + iv[1].iov_base = param; + iv[1].iov_len = plen; + ivn = 2; + } else + h.length = 0; + + while (writev(s, iv, ivn) < 0) { + if (errno == EAGAIN || errno == EINTR) + continue; + + return (-1); + } + + return (0); +} + +ssize_t +bt_devrecv(int s, void *buf, size_t size, time_t to) +{ + ssize_t n; + + if (buf == NULL || size == 0) { + errno = EINVAL; + return (-1); + } + + if (to >= 0) { + fd_set rfd; + struct timeval tv; + + FD_ZERO(&rfd); + FD_SET(s, &rfd); + + tv.tv_sec = to; + tv.tv_usec = 0; + + while ((n = select(s + 1, &rfd, NULL, NULL, &tv)) < 0) { + if (errno == EAGAIN || errno == EINTR) + continue; + + return (-1); + } + + if (n == 0) { + errno = ETIMEDOUT; + return (-1); + } + + assert(FD_ISSET(s, &rfd)); + } + + while ((n = read(s, buf, size)) < 0) { + if (errno == EAGAIN || errno == EINTR) + continue; + + return (-1); + } + + switch (*((uint8_t *) buf)) { + case NG_HCI_CMD_PKT: { + ng_hci_cmd_pkt_t *h = (ng_hci_cmd_pkt_t *) buf; + + if (n >= sizeof(*h) && n == (sizeof(*h) + h->length)) + return (n); + } break; + + case NG_HCI_ACL_DATA_PKT: { + ng_hci_acldata_pkt_t *h = (ng_hci_acldata_pkt_t *) buf; + + if (n >= sizeof(*h) && n == (sizeof(*h) + le16toh(h->length))) + return (n); + } break; + + case NG_HCI_SCO_DATA_PKT: { + ng_hci_scodata_pkt_t *h = (ng_hci_scodata_pkt_t *) buf; + + if (n >= sizeof(*h) && n == (sizeof(*h) + h->length)) + return (n); + } break; + + case NG_HCI_EVENT_PKT: { + ng_hci_event_pkt_t *h = (ng_hci_event_pkt_t *) buf; + + if (n >= sizeof(*h) && n == (sizeof(*h) + h->length)) + return (n); + } break; + } + + errno = EIO; + return (-1); +} + +int +bt_devreq(int s, struct bt_devreq *r, time_t to) +{ + uint8_t buf[320]; /* more than enough */ + ng_hci_event_pkt_t *e = (ng_hci_event_pkt_t *) buf; + ng_hci_command_compl_ep *cc = (ng_hci_command_compl_ep *)(e+1); + ng_hci_command_status_ep *cs = (ng_hci_command_status_ep*)(e+1); + struct bt_devfilter old, new; + time_t t_end; + uint16_t opcode; + ssize_t n; + int error; + + if (s < 0 || r == NULL || to < 0) { + errno = EINVAL; + return (-1); + } + + if ((r->rlen == 0 && r->rparam != NULL) || + (r->rlen > 0 && r->rparam == NULL)) { + errno = EINVAL; + return (-1); + } + + memset(&new, 0, sizeof(new)); + bt_devfilter_pkt_set(&new, NG_HCI_EVENT_PKT); + bt_devfilter_evt_set(&new, NG_HCI_EVENT_COMMAND_COMPL); + bt_devfilter_evt_set(&new, NG_HCI_EVENT_COMMAND_STATUS); + if (r->event != 0) + bt_devfilter_evt_set(&new, r->event); + + if (bt_devfilter(s, &new, &old) < 0) + return (-1); + + error = 0; + + n = bt_devsend(s, r->opcode, r->cparam, r->clen); + if (n < 0) { + error = errno; + goto out; + } + + opcode = htole16(r->opcode); + t_end = time(NULL) + to; + + do { + to = t_end - time(NULL); + if (to < 0) + to = 0; + + n = bt_devrecv(s, buf, sizeof(buf), to); + if (n < 0) { + error = errno; + goto out; + } + + if (e->type != NG_HCI_EVENT_PKT) { + error = EIO; + goto out; + } + + n -= sizeof(*e); + + switch (e->event) { + case NG_HCI_EVENT_COMMAND_COMPL: + if (cc->opcode == opcode) { + n -= sizeof(*cc); + + if (r->rlen >= n) { + r->rlen = n; + memcpy(r->rparam, cc + 1, r->rlen); + } + + goto out; + } + break; + + case NG_HCI_EVENT_COMMAND_STATUS: + if (cs->opcode == opcode) { + if (r->event != NG_HCI_EVENT_COMMAND_STATUS) { + if (cs->status != 0) { + error = EIO; + goto out; + } + } else { + if (r->rlen >= n) { + r->rlen = n; + memcpy(r->rparam, cs, r->rlen); + } + + goto out; + } + } + break; + + default: + if (e->event == r->event) { + if (r->rlen >= n) { + r->rlen = n; + memcpy(r->rparam, e + 1, r->rlen); + } + + goto out; + } + break; + } + } while (to > 0); + + error = ETIMEDOUT; +out: + bt_devfilter(s, &old, NULL); + + if (error != 0) { + errno = error; + return (-1); + } + + return (0); +} + +int +bt_devfilter(int s, struct bt_devfilter const *new, struct bt_devfilter *old) +{ + struct ng_btsocket_hci_raw_filter f; + socklen_t len; + + if (new == NULL && old == NULL) { + errno = EINVAL; + return (-1); + } + + if (old != NULL) { + len = sizeof(f); + if (getsockopt(s, SOL_HCI_RAW, SO_HCI_RAW_FILTER, &f, &len) < 0) + return (-1); + + memset(old, 0, sizeof(*old)); + memcpy(old->packet_mask, &f.packet_mask, + MIN(sizeof(old->packet_mask), sizeof(f.packet_mask))); + memcpy(old->event_mask, &f.event_mask, + MIN(sizeof(old->event_mask), sizeof(f.packet_mask))); + } + + if (new != NULL) { + memset(&f, 0, sizeof(f)); + memcpy(&f.packet_mask, new->packet_mask, + MIN(sizeof(f.packet_mask), sizeof(new->event_mask))); + memcpy(&f.event_mask, new->event_mask, + MIN(sizeof(f.event_mask), sizeof(new->event_mask))); + + len = sizeof(f); + if (setsockopt(s, SOL_HCI_RAW, SO_HCI_RAW_FILTER, &f, len) < 0) + return (-1); + } + + return (0); +} + +void +bt_devfilter_pkt_set(struct bt_devfilter *filter, uint8_t type) +{ + bit_set(filter->packet_mask, type - 1); +} + +void +bt_devfilter_pkt_clr(struct bt_devfilter *filter, uint8_t type) +{ + bit_clear(filter->packet_mask, type - 1); +} + +int +bt_devfilter_pkt_tst(struct bt_devfilter const *filter, uint8_t type) +{ + return (bit_test(filter->packet_mask, type - 1)); +} + +void +bt_devfilter_evt_set(struct bt_devfilter *filter, uint8_t event) +{ + bit_set(filter->event_mask, event - 1); +} + +void +bt_devfilter_evt_clr(struct bt_devfilter *filter, uint8_t event) +{ + bit_clear(filter->event_mask, event - 1); +} + +int +bt_devfilter_evt_tst(struct bt_devfilter const *filter, uint8_t event) +{ + return (bit_test(filter->event_mask, event - 1)); +} + +int +bt_devinquiry(char const *devname, time_t length, int num_rsp, + struct bt_devinquiry **ii) +{ + uint8_t buf[320]; + char _devname[HCI_DEVNAME_SIZE]; + struct bt_devfilter f; + ng_hci_inquiry_cp *cp = (ng_hci_inquiry_cp *) buf; + ng_hci_event_pkt_t *e = (ng_hci_event_pkt_t *) buf; + ng_hci_inquiry_result_ep *ep = (ng_hci_inquiry_result_ep *)(e+1); + ng_hci_inquiry_response *ir; + struct bt_devinquiry *i; + int s, n; + time_t to; + + if (ii == NULL) { + errno = EINVAL; + return (-1); + } + + if (devname == NULL) { + memset(_devname, 0, sizeof(_devname)); + devname = _devname; + + n = bt_devenum(bt_devany_cb, _devname); + if (n <= 0) { + if (n == 0) + *ii = NULL; + + return (n); + } + } + + s = bt_devopen(devname); + if (s < 0) + return (-1); + + if (bt_devfilter(s, NULL, &f) < 0) { + bt_devclose(s); + return (-1); + } + + bt_devfilter_evt_set(&f, NG_HCI_EVENT_INQUIRY_COMPL); + bt_devfilter_evt_set(&f, NG_HCI_EVENT_INQUIRY_RESULT); + + if (bt_devfilter(s, &f, NULL) < 0) { + bt_devclose(s); + return (-1); + } + + /* Always use GIAC LAP */ + cp->lap[0] = 0x33; + cp->lap[1] = 0x8b; + cp->lap[2] = 0x9e; + + /* Calculate inquire length in 1.28 second units */ + to = (time_t) ((double) length / 1.28); + if (to <= 0) + cp->inquiry_length = 4; /* 5.12 seconds */ + else if (to > 254) + cp->inquiry_length = 255; /* 326.40 seconds */ + else + cp->inquiry_length = to + 1; + + to = (time_t)((double) cp->inquiry_length * 1.28) + 1; + + if (num_rsp <= 0 || num_rsp > 255) + num_rsp = 8; + cp->num_responses = (uint8_t) num_rsp; + + i = *ii = calloc(num_rsp, sizeof(struct bt_devinquiry)); + if (i == NULL) { + bt_devclose(s); + errno = ENOMEM; + return (-1); + } + + if (bt_devsend(s, + NG_HCI_OPCODE(NG_HCI_OGF_LINK_CONTROL, NG_HCI_OCF_INQUIRY), + cp, sizeof(*cp)) < 0) { + free(i); + bt_devclose(s); + return (-1); + } + +wait_for_more: + + n = bt_devrecv(s, buf, sizeof(buf), to); + if (n < 0) { + free(i); + bt_devclose(s); + return (-1); + } + + if (n < sizeof(ng_hci_event_pkt_t)) { + free(i); + bt_devclose(s); + errno = EIO; + return (-1); + } + + switch (e->event) { + case NG_HCI_EVENT_INQUIRY_COMPL: + break; + + case NG_HCI_EVENT_INQUIRY_RESULT: + ir = (ng_hci_inquiry_response *)(ep + 1); + + for (n = 0; n < MIN(ep->num_responses, num_rsp); n ++) { + bdaddr_copy(&i->bdaddr, &ir->bdaddr); + i->pscan_rep_mode = ir->page_scan_rep_mode; + i->pscan_period_mode = ir->page_scan_period_mode; + memcpy(i->dev_class, ir->uclass, sizeof(i->dev_class)); + i->clock_offset = le16toh(ir->clock_offset); + + ir ++; + i ++; + num_rsp --; + } + /* FALLTHROUGH */ + + default: + goto wait_for_more; + /* NOT REACHED */ + } + + bt_devclose(s); + + return (i - *ii); +} + +int bt_devinfo(struct bt_devinfo *di) { union { @@ -53,6 +543,7 @@ struct ng_btsocket_hci_raw_node_debug r8; } rp; struct sockaddr_hci ha; + socklen_t halen; int s, rval; if (di == NULL) { @@ -60,27 +551,14 @@ return (-1); } - memset(&ha, 0, sizeof(ha)); - ha.hci_len = sizeof(ha); - ha.hci_family = AF_BLUETOOTH; - - if (bt_aton(di->devname, &rp.r1.bdaddr)) { - if (!bt_devname(ha.hci_node, &rp.r1.bdaddr)) - return (-1); - } else if (bt_dev2node(di->devname, ha.hci_node, - sizeof(ha.hci_node)) == NULL) { - errno = ENXIO; - return (-1); - } - - s = socket(PF_BLUETOOTH, SOCK_RAW, BLUETOOTH_PROTO_HCI); + s = bt_devopen(di->devname); if (s < 0) return (-1); rval = -1; - if (bind(s, (struct sockaddr *) &ha, sizeof(ha)) < 0 || - connect(s, (struct sockaddr *) &ha, sizeof(ha)) < 0) + halen = sizeof(ha); + if (getsockname(s, (struct sockaddr *) &ha, &halen) < 0) goto bad; strlcpy(di->devname, ha.hci_node, sizeof(di->devname)); @@ -138,7 +616,7 @@ rval = 0; bad: - close(s); + bt_devclose(s); return (rval); } @@ -205,6 +683,13 @@ return (count); } +static int +bt_devany_cb(int s, struct bt_devinfo const *di, void *xdevname) +{ + strlcpy((char *) xdevname, di->devname, HCI_DEVNAME_SIZE); + return (1); +} + static char * bt_dev2node(char const *devname, char *nodename, int nnlen) { Index: bluetooth.3 =================================================================== --- bluetooth.3 (revision 191363) +++ bluetooth.3 (working copy) @@ -25,7 +25,7 @@ .\" $Id: bluetooth.3,v 1.5 2003/05/20 23:04:30 max Exp $ .\" $FreeBSD$ .\" -.Dd February 13, 2009 +.Dd April 9, 2009 .Dt BLUETOOTH 3 .Os .Sh NAME @@ -41,6 +41,23 @@ .Nm bt_endprotoent , .Nm bt_aton , .Nm bt_ntoa , +.Nm bt_devaddr , +.Nm bt_devname , +.Nm bt_devinfo , +.Nm bt_devenum , +.Nm bt_devopen , +.Nm bt_devclose , +.Nm bt_devsend , +.Nm bt_devrecv , +.Nm bt_devreq , +.Nm bt_devfilter , +.Nm bt_devfilter_pkt_set , +.Nm bt_devfilter_pkt_clr , +.Nm bt_devfilter_pkt_tst , +.Nm bt_devfilter_evt_set , +.Nm bt_devfilter_evt_clr , +.Nm bt_devfilter_evt_tst , +.Nm bt_devinquiry , .Nm bdaddr_same , .Nm bdaddr_any , .Nm bdaddr_copy @@ -84,6 +101,32 @@ .Ft int .Fn bt_devenum "bt_devenum_cb_t *cb" "void *arg" .Ft int +.Fn bt_devopen "char const *devname" +.Ft int +.Fn bt_devclose "int s" +.Ft int +.Fn bt_devsend "int s" "uint16_t opcode" "void *param" "size_t plen" +.Ft ssize_t +.Fn bt_devrecv "int s" "void *buf" "size_t size" "time_t to" +.Ft int +.Fn bt_devreq "int s" "struct bt_devreq *r" "time_t to" +.Ft int +.Fn bt_devfilter "int s" "struct bt_devfilter const *new" "struct bt_devfilter *old" +.Ft void +.Fn bt_devfilter_pkt_set "struct bt_devfilter *filter" "uint8_t type" +.Ft void +.Fn bt_devfilter_pkt_clt "struct bt_devfilter *filter" "uint8_t type" +.Ft int +.Fn bt_devfilter_pkt_tst "struct bt_devfilter const *filter" "uint8_t type" +.Ft void +.Fn bt_devfilter_evt_set "struct bt_devfilter *filter" "uint8_t event" +.Ft void +.Fn bt_devfilter_evt_clt "struct bt_devfilter *filter" "uint8_t event" +.Ft int +.Fn bt_devfilter_evt_tst "struct bt_devfilter const *filter" "uint8_t event" +.Ft int +.Fn bt_devinquiry "char const *devname" "time_t length" "int num_rsp" "struct bt_devinquiry **ii" +.Ft int .Fn bdaddr_same "const bdaddr_t *a" "const bdaddr_t *b" .Ft int .Fn bdaddr_any "const bdaddr_t *a" @@ -311,6 +354,240 @@ or -1 if an error occurred. .Pp The +.Fn bt_devopen +function opens a Bluetooth device with the given +.Fa devname +and returns a connected and bound +.Dv HCI +socket handle. +The function returns -1 if an error has occurred. +.Pp +The +.Fn bt_devclose +closes the passed +.Dv HCI +socket handle +.Fa s , +previously obtained with +.Xr bt_devopen 3 . +.Pp +The +.Fn bt_devsend +function sends a Bluetooth +.Dv HCI +command with the given +.Fa opcode +to the provided socket +.Fa s , +previously obtained with +.Xr bt_devopen 3 . +The +.Fa opcode +parameter is exppected to be in the host byte order. +The +.Fa param +and +.Fa plen +parameters specify command parameters. +The +.Fn bt_devsend +function does not modify the +.Dv HCI +filter on the provided socket +.Fa s . +The function returns 0 on success, +or -1 if an error occurred. +.Pp +The +.Fn bt_devrecv +function receives one Bluetooth +.Dv HCI +packet from the socket +.Fa s , +previously obtained with +.Xr bt_devopen 3 . +The packet is placed into the provided buffer +.Fa buf +of size +.Fa size . +The +.Fa to +parameter specifies receive timeout in seconds. +Infinite timeout can be specified by passing negative value in the +.Fa to +parameter. +The +.Fn bt_devrecv +function does not modify the +.Dv HCI +filter on the provided socket +.Fa s . +The function returns total number of bytes recevied, +or -1 if an error occurred. +.Pp +The +.Fn bt_devreq +function makes a Bluetooth +.Dv HCI +request to the socket +.Fa s , +previously obtained with +.Xr bt_devopen 3 . +The function will send the specified command and will wait for the specified +event, +or timeout +.Fa to +seconds to occur. +The +.Vt bt_devreq +structure is defined as follows +.Bd -literal -offset indent +struct bt_devreq +{ + uint16_t opcode; + uint8_t event; + void *cparam; + size_t clen; + void *rparam; + size_t rlen; +}; +.Ed +.Pp +The +.Fa opcode +field specifies the command and is expected to be in the host byte order. +The +.Fa cparam +and +.Fa clen +fields specify command parameters data and command parameters data size +respectively. +The +.Fa event +field specifies which Bluetooth +.Dv HCI +event ID the function should wait for, otherwise it should be set to zero. +The +.Dv HCI +Command Complete and Command Status events are enabled by default. +The +.Fa rparam +and +.Fa rlen +parameters specify buffer and buffer size respectively where return +parameters should be placed. +The +.Fn bt_devreq +function temporarily modifies filter on the provided +.Dv HCI +socket +.Fa s . +The function returns 0 on success, or -1 if an error occurred. +.Pp +The +.Fn bt_devfilter +controls the local +.Dv HCI +filter associated with the socket +.Fa s , +previously obtained with +.Xr bt_devopen 3 . +Filtering can be done on packet types, i.e. +.Dv ACL , +.Dv SCO or +.Dv HCI , +command and event packets, and, in addition, on +.Dv HCI +event IDs. +Before applying the +.Fa new +filter (if provided) the function will try to obtain the current filter +from the socket +.Fa s +and place it into the +.Fa old +parameter (if provided). +The function returns 0 on success, or -1 if an error occurred. +.Pp +The +.Fn bt_devfilter_pkt_set , +.Fn bt_devfilter_pkt_clr +and +.Fn bt_devfilter_pkt_tst +functions can be used to modify and test the +.Dv HCI +filter +.Fa filter . +The +.Fa type +parameter specifies +.Dv HCI +packet type. +.Pp +The +.Fn bt_devfilter_evt_set , +.Fn bt_devfilter_evt_clr +and +.Fn bt_devfilter_evt_tst +functions can be used to modify and test the +.Dv HCI +event filter +.Fa filter . +The +.Fa event +parameter specifies +.Dv HCI +event ID. +.Pp +The +.Fn bt_devinquiry +function performs Bluetooth inquiry. +The +.Fa devname +parameter specifies which local Bluetooth device should perform an inquiry. +If not secified, i.e. +.Dv NULL , +then first available device will be used. +The +.Fa length +parameters specifies the total length of an inquiry in seconds. +If not specified, i.e. 0, default value will be used. +The +.Fa num_rsp +parameter specifies the number of responses that can be received before +the inquiry is halted. +If not specified, i.e. 0, default value will be used. +The +.Fa ii +parameter specifies where to place inquiry results. +On success, the function will return total number of inquiry results, +will allocate, +using +.Xr calloc 3 , +buffer to store all the inquiry results and +will return pointer to the allocated buffer in the +.Fa ii +parameter. +It is up to the caller of the function to dispose of the buffer using +.Xr free 3 +call. +The function returns -1 if an error has occurred. +The +.Vt bt_devinquiry +structure is defined as follows +.Bd -literal -offset indent +struct bt_devinquiry { + bdaddr_t bdaddr; + uint8_t pscan_rep_mode; + uint8_t pscan_period_mode; + uint8_t dev_class[3]; + uint16_t clock_offset; + int8_t rssi; + uint8_t data[240]; +}; +.Ed +.Pp +The .Fn bdaddr_same , .Fn bdaddr_any and @@ -444,6 +721,6 @@ .Sh AUTHORS .An Maksim Yevmenkin Aq m_evmenkin@yahoo.com .Sh BUGS -These functions use static data storage; +Some of those functions use static data storage; if the data is needed for future use, it should be copied before any subsequent calls overwrite it. Index: bluetooth.h =================================================================== --- bluetooth.h (revision 191363) +++ bluetooth.h (working copy) @@ -39,6 +39,7 @@ #include #include #include +#include #include #include #include @@ -46,6 +47,7 @@ #include #include #include +#include __BEGIN_DECLS @@ -129,8 +131,48 @@ uint8_t _padding[20]; /* leave space for future additions */ }; +struct bt_devreq +{ + uint16_t opcode; + uint8_t event; + void *cparam; + size_t clen; + void *rparam; + size_t rlen; +}; + +struct bt_devfilter { + bitstr_t bit_decl(packet_mask, 8); + bitstr_t bit_decl(event_mask, 256); +}; + +struct bt_devinquiry { + bdaddr_t bdaddr; + uint8_t pscan_rep_mode; + uint8_t pscan_period_mode; + uint8_t dev_class[3]; + uint16_t clock_offset; + int8_t rssi; + uint8_t data[240]; +}; + typedef int (bt_devenum_cb_t)(int, struct bt_devinfo const *, void *); +int bt_devopen (char const *devname); +int bt_devclose(int s); +int bt_devsend (int s, uint16_t opcode, void *param, size_t plen); +ssize_t bt_devrecv (int s, void *buf, size_t size, time_t to); +int bt_devreq (int s, struct bt_devreq *r, time_t to); +int bt_devfilter(int s, struct bt_devfilter const *new, + struct bt_devfilter *old); +void bt_devfilter_pkt_set(struct bt_devfilter *filter, uint8_t type); +void bt_devfilter_pkt_clr(struct bt_devfilter *filter, uint8_t type); +int bt_devfilter_pkt_tst(struct bt_devfilter const *filter, uint8_t type); +void bt_devfilter_evt_set(struct bt_devfilter *filter, uint8_t event); +void bt_devfilter_evt_clr(struct bt_devfilter *filter, uint8_t event); +int bt_devfilter_evt_tst(struct bt_devfilter const *filter, uint8_t event); +int bt_devinquiry(char const *devname, time_t length, int num_rsp, + struct bt_devinquiry **ii); int bt_devinfo (struct bt_devinfo *di); int bt_devenum (bt_devenum_cb_t *cb, void *arg); Index: Makefile =================================================================== --- Makefile (revision 191363) +++ Makefile (working copy) @@ -33,6 +33,19 @@ MLINKS+= bluetooth.3 bt_devinfo.3 MLINKS+= bluetooth.3 bt_devenum.3 +MLINKS+= bluetooth.3 bt_devopen.3 +MLINKS+= bluetooth.3 bt_devclose.3 +MLINKS+= bluetooth.3 bt_devsend.3 +MLINKS+= bluetooth.3 bt_devreq.3 +MLINKS+= bluetooth.3 bt_devfilter.3 +MLINKS+= bluetooth.3 bt_devfilter_pkt_set.3 +MLINKS+= bluetooth.3 bt_devfilter_pkt_clr.3 +MLINKS+= bluetooth.3 bt_devfilter_pkt_tst.3 +MLINKS+= bluetooth.3 bt_devfilter_evt_set.3 +MLINKS+= bluetooth.3 bt_devfilter_evt_clr.3 +MLINKS+= bluetooth.3 bt_devfilter_evt_tst.3 +MLINKS+= bluetooth.3 bt_devinquiry.3 + MLINKS+= bluetooth.3 bdaddr_same.3 MLINKS+= bluetooth.3 bdaddr_any.3 MLINKS+= bluetooth.3 bdaddr_copy.3 From plunky at rya-online.net Wed Apr 22 07:50:41 2009 From: plunky at rya-online.net (Iain Hibbert) Date: Wed Apr 22 07:50:48 2009 Subject: libhci update In-Reply-To: References: <1240311202.361300.1366.nullmailer@galant.ukfsn.org> <1240352254.082638.416.nullmailer@galant.ukfsn.org> Message-ID: <1240386569.369073.696.nullmailer@galant.ukfsn.org> On Tue, 21 Apr 2009, Maksim Yevmenkin wrote: > > In bt_devreq() you used buf[320] is that truly enough? I didn't check that > > part of the radio specification dedicated to baseband packet size but the > > Broadcom BCM2045B device in my laptop claims to have max_acl_size of 1017.. > > yes, but we are not dealing with acl packets in bt_devreq() only > commands and event that have up to 255 bytes of payload. Doh > please see attached revised patch. I've got no more comments here iain From maksim.yevmenkin at gmail.com Wed Apr 22 16:09:52 2009 From: maksim.yevmenkin at gmail.com (Maksim Yevmenkin) Date: Wed Apr 22 16:09:58 2009 Subject: libhci update In-Reply-To: <1240386569.369073.696.nullmailer@galant.ukfsn.org> References: <1240311202.361300.1366.nullmailer@galant.ukfsn.org> <1240352254.082638.416.nullmailer@galant.ukfsn.org> <1240386569.369073.696.nullmailer@galant.ukfsn.org> Message-ID: Iain & All, >> please see attached revised patch. > > I've got no more comments here thanks for the review. i committed it to -head. thanks, max