2005-04-12 15:38:22

by Frederic Danis

[permalink] [raw]
Subject: [Bluez-devel] RFComm doesn't disconnect in server mode

/*
*
* BlueZ - Bluetooth protocol stack for Linux
*
* Copyright (C) 2002-2004 Marcel Holtmann <[email protected]>
*
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation;
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER(S) AND AUTHOR(S) BE LIABLE FOR ANY
* CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* ALL LIABILITY, INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PATENTS,
* COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS, RELATING TO USE OF THIS
* SOFTWARE IS DISCLAIMED.
*
*
* $Id: hstest.c,v 1.6 2004/12/25 17:43:20 holtmann Exp $
*/

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
#include <termios.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <sys/socket.h>

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

static volatile int terminate = 0;

static void sig_term(int sig) {
terminate = 1;
}

static int rfcomm_connect(bdaddr_t *src, bdaddr_t *dst, uint8_t channel)
{
struct sockaddr_rc addr;
int s;

if ((s = socket(PF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM)) < 0) {
return -1;
}

memset(&addr, 0, sizeof(addr));
addr.rc_family = AF_BLUETOOTH;
bacpy(&addr.rc_bdaddr, src);
addr.rc_channel = 0;
if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
close(s);
return -1;
}

memset(&addr, 0, sizeof(addr));
addr.rc_family = AF_BLUETOOTH;
bacpy(&addr.rc_bdaddr, dst);
addr.rc_channel = channel;
if (connect(s, (struct sockaddr *)&addr, sizeof(addr)) < 0 ){
close(s);
return -1;
}

return s;
}

static int sco_connect(bdaddr_t *src, bdaddr_t *dst, uint16_t *handle, uint16_t *mtu)
{
struct sockaddr_sco addr;
struct sco_conninfo conn;
struct sco_options opts;
int s, size;

if ((s = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_SCO)) < 0) {
return -1;
}

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

if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
close(s);
return -1;
}

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

if (connect(s, (struct sockaddr *)&addr, sizeof(addr)) < 0 ){
close(s);
return -1;
}

memset(&conn, 0, sizeof(conn));
size = sizeof(conn);

if (getsockopt(s, SOL_SCO, SCO_CONNINFO, &conn, &size) < 0) {
close(s);
return -1;
}

memset(&opts, 0, sizeof(opts));
size = sizeof(opts);

if (getsockopt(s, SOL_SCO, SCO_OPTIONS, &opts, &size) < 0) {
close(s);
return -1;
}

if (handle)
*handle = conn.hci_handle;

if (mtu)
*mtu = opts.mtu;

return s;
}

#define PLAY 1
#define RECORD 2
#define SERVER 3

static int client(bdaddr_t bdaddr, uint8_t channel, int fd, int mode)
{
fd_set rfds;
struct timeval timeout;
unsigned char buf[2048], *p;
int maxfd, sel, rlen, wlen;

int rd, sd;
uint16_t sco_handle, sco_mtu, vs;

if ((rd = rfcomm_connect(BDADDR_ANY, &bdaddr, channel)) < 0) {
perror("Can't connect RFCOMM channel");
return -1;
}

fprintf(stderr, "RFCOMM channel connected\n");

if ((sd = sco_connect(BDADDR_ANY, &bdaddr, &sco_handle, &sco_mtu)) < 0) {
perror("Can't connect SCO audio channel");
close(rd);
return -1;
}

fprintf(stderr, "SCO audio channel connected (handle %d, mtu %d)\n", sco_handle, sco_mtu);

if (mode == RECORD)
write(rd, "RING\r\n", 6);

maxfd = (rd > sd) ? rd : sd;

while (!terminate) {

FD_ZERO(&rfds);
FD_SET(rd, &rfds);
FD_SET(sd, &rfds);

timeout.tv_sec = 0;
timeout.tv_usec = 10000;

if ((sel = select(maxfd + 1, &rfds, NULL, NULL, &timeout)) > 0) {

if (FD_ISSET(rd, &rfds)) {
memset(buf, 0, sizeof(buf));
rlen = read(rd, buf, sizeof(buf));
if (rlen > 0) {
fprintf(stderr, "%s\n", buf);
wlen = write(rd, "OK\r\n", 4);
}
}

if (FD_ISSET(sd, &rfds)) {
memset(buf, 0, sizeof(buf));
rlen = read(sd, buf, sizeof(buf));
if (rlen > 0)
switch (mode) {
case PLAY:
rlen = read(fd, buf, rlen);

wlen = 0;
p = buf;
while (rlen > sco_mtu) {
wlen += write(sd, p, sco_mtu);
rlen -= sco_mtu;
p += sco_mtu;
}
wlen += write(sd, p, rlen);
break;
case RECORD:
wlen = write(fd, buf, rlen);
break;
default:
break;
}
}

}

}

close(sd);
sleep(5);
close(rd);

return 0;
}

static int server(int fd)
{
int rsd, rd;
struct sockaddr_rc addr;
socklen_t addrlen;
sdp_session_t *sdpSession;
sdp_record_t *recordP;
sdp_list_t *svclass, *pfseq, *apseq, *root, *aproto;
uuid_t root_uuid, l2cap, rfcomm, svcuuid;
sdp_profile_desc_t sdp_profile[1];
sdp_list_t *proto[2];
fd_set rfds;
struct timeval timeout;
int maxfd, sel, rlen, wlen;
unsigned char buf[2048];
int error;

// Create incoming RFComm socket
rsd = socket(PF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM);
if (rsd < 0)
{
perror("ERROR failed to create socket");
return -1;
}

/* Bind to local address */
addr.rc_family = AF_BLUETOOTH;
bacpy(&addr.rc_bdaddr, BDADDR_ANY);
addr.rc_channel = 0;

if (bind(rsd, (struct sockaddr *) &addr, sizeof(addr)) < 0)
{
perror("ERROR Server RFCOMM binding error");
close(rsd);
return -1;
}

/* Listen for connections */
if (listen(rsd, 1))
{
perror("ERROR Server RFCOMM listen error");
close(rsd);
return -1;
}

memset(&addr, 0, sizeof(addr));
addrlen = sizeof(addr);
if (getsockname(rsd, (struct sockaddr *) &addr, &addrlen) < 0)
{
perror("ERROR Server RFCOMM getsockname error");
close(rsd);
return -1;
}

fprintf(stderr, "Server RFCOMM socket %lu listen on RFComm %lu\n", (unsigned long) rsd, (unsigned long) addr.rc_channel);

// Prepare SDP record
sdpSession = sdp_connect(BDADDR_ANY, BDADDR_LOCAL, 0);
if (!sdpSession)
{
perror("ERROR Can not start SDP session");
return -1;
}

recordP = sdp_record_alloc();
if (!recordP)
{
perror("ERROR Allocate for service description failed");
return -1;
}

// Add to Public Browse Group
sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
root = sdp_list_append(NULL, &root_uuid);
sdp_set_browse_groups(recordP, root);

// Set Protocol descriptors
sdp_uuid16_create(&l2cap, L2CAP_UUID);
proto[0] = sdp_list_append(NULL, &l2cap);
apseq = sdp_list_append(NULL, proto[0]);

sdp_uuid16_create(&rfcomm, RFCOMM_UUID);
proto[1] = sdp_list_append(NULL, &rfcomm);
proto[1] = sdp_list_append(proto[1], sdp_data_alloc(SDP_UINT8, &addr.rc_channel));
apseq = sdp_list_append(apseq, proto[1]);

aproto = sdp_list_append(NULL, apseq);
sdp_set_access_protos(recordP, aproto);

// Set service class
sdp_uuid16_create(&svcuuid, HEADSET_AGW_SVCLASS_ID);
svclass = sdp_list_append(NULL, &svcuuid);
sdp_uuid16_create(&svcuuid, GENERIC_AUDIO_SVCLASS_ID);
svclass = sdp_list_append(svclass, &svcuuid);
sdp_set_service_classes(recordP, svclass);

// Set profile descriptor
sdp_uuid16_create(&sdp_profile[0].uuid, HEADSET_AGW_PROFILE_ID);
sdp_profile[0].version = 0x0100;
pfseq = sdp_list_append(NULL, &sdp_profile[0]);
sdp_set_profile_descs(recordP, pfseq);

sdp_set_info_attr(recordP, "Voice gateway", NULL, NULL);

// Advertise SDP record
error = sdp_record_register(sdpSession, recordP, 0);
if (error)
{
perror("ERROR Unable to advertise service");
sdp_record_free(recordP);
recordP = NULL;
return -1;
}

while (!terminate)
{
FD_ZERO(&rfds);
FD_SET(rsd, &rfds);

timeout.tv_sec = 0;
timeout.tv_usec = 10000;

if ((sel = select(rsd + 1, &rfds, NULL, NULL, &timeout)) > 0)
{
if (FD_ISSET(rsd, &rfds))
{
if ((rd = accept(rsd, (struct sockaddr *) &addr, &addrlen)) < 0)
{
perror("ERROR accept failed");
break;
}

fprintf(stderr, "RFComm connected\n");

while (!terminate)
{
FD_ZERO(&rfds);
FD_SET(rd, &rfds);

timeout.tv_sec = 0;
timeout.tv_usec = 10000;

if ((sel = select(rd + 1, &rfds, NULL, NULL, &timeout)) > 0)
{
if (FD_ISSET(rd, &rfds))
{
memset(buf, 0, sizeof(buf));
rlen = read(rd, buf, sizeof(buf));
if (rlen > 0)
{
fprintf(stderr, "%s\n", buf);
wlen = write(rd, "OK\r\n", 4);
}
}
}
}

fprintf(stderr, "RFComm disconnected\n");

sleep(5);
close(rd);
}
}
}

sleep(5);
close(rsd);

sdp_record_unregister(sdpSession, recordP);
sdp_close(sdpSession);
}

static void usage(void)
{
printf("Usage:\n"
"\thstest play <file> <bdaddr> [channel]\n"
"\thstest record <file> <bdaddr> [channel]\n"
"\thstest server <file> <bdaddr> [channel]\n");
}

int main(int argc, char *argv[])
{
struct sigaction sa;

bdaddr_t local;
bdaddr_t bdaddr;
uint8_t channel;

char *filename;
mode_t filemode;
int mode = 0;
int dd, fd;

switch (argc) {
case 4:
str2ba(argv[3], &bdaddr);
channel = 6;
break;
case 5:
str2ba(argv[3], &bdaddr);
channel = atoi(argv[4]);
break;
default:
usage();
exit(-1);
}

if (strncmp(argv[1], "play", 4) == 0) {
mode = PLAY;
filemode = O_RDONLY;
} else if (strncmp(argv[1], "rec", 3) == 0) {
mode = RECORD;
filemode = O_WRONLY | O_CREAT | O_TRUNC;
} else if (strncmp(argv[1], "serv", 4) == 0) {
mode = SERVER;
filemode = O_WRONLY | O_CREAT | O_TRUNC;
} else {
usage();
exit(-1);
}

filename = argv[2];

/* hci_devba(0, &local);
dd = hci_open_dev(0);
hci_read_voice_setting(dd, &vs, 1000);
vs = htobs(vs);
fprintf(stderr, "Voice setting: 0x%04x\n", vs);
close(dd);
/* if (vs != 0x0060) {
fprintf(stderr, "The voice setting must be 0x0060\n");
return -1;
}
*/
if (strcmp(filename, "-") == 0) {
switch (mode) {
case PLAY:
fd = 0;
break;
case RECORD:
case SERVER:
fd = 1;
break;
default:
return -1;
}
} else {
if ((fd = open(filename, filemode)) < 0) {
perror("Can't open input/output file");
return -1;
}
}

memset(&sa, 0, sizeof(sa));
sa.sa_flags = SA_NOCLDSTOP;
sa.sa_handler = sig_term;
sigaction(SIGTERM, &sa, NULL);
sigaction(SIGINT, &sa, NULL);

sa.sa_handler = SIG_IGN;
sigaction(SIGCHLD, &sa, NULL);
sigaction(SIGPIPE, &sa, NULL);

if (mode == SERVER)
server(fd);
else
client(bdaddr, channel, fd, mode);

close(fd);

return 0;
}


Attachments:
hci-dump.txt (9.04 kB)
hstest.c (10.71 kB)
Download all attachments

2005-04-13 11:33:57

by Marcel Holtmann

[permalink] [raw]
Subject: Re: [Bluez-devel] RFComm doesn't disconnect in server mode

Hi Frederic,

> I am working on headset profile implementation. In server mode, the
> headset (Jabra BT200) connects fine, but once my program close the
> RFComm socket, the ACL link is still up, until I stop the headset.
>
> I get in console :
> fdanis:> ./hstest server - 00:07:A4:05:B6:DA
> Server RFCOMM socket 3 listen on RFComm 2
> RFComm connected
> AT+CKPD=200
> RFComm disconnected
> fdanis:>
>
> Here are the last traces I got before stopping headset :
> > HCI Event: Mode Change (0x14) plen 6
> 0000: 00 29 00 02 00 08 .)....
> < ACL data: handle 0x0029 flags 0x02 dlen 8
> L2CAP(d): cid 0x0042 len 4 [psm 3]
> RFCOMM(s): DISC: cr 0 dlci 4 pf 1 ilen 0 fcs 0x16
> > HCI Event: Number of Completed Packets (0x13) plen 5
> 0000: 01 29 00 01 00 .)...
> > ACL data: handle 0x0029 flags 0x02 dlen 8
> L2CAP(d): cid 0x0041 len 4 [psm 3]
> RFCOMM(s): UA: cr 0 dlci 4 pf 1 ilen 0 fcs 0x3c
>
> Maybe a RFCOMM DISC frame on the DLCI 0 is missing.

the guys from TomTom discovered the same problem some time ago. This is
a reference counting problem on our side and a fix for that was posted
to the mailing list and is in the 2.6.12-rc2 kernel. However it seems
that this fix introduces an oops at some point. I haven't had the time
to investigate this any further.

The funny things with these kind of problems are that the specification
misses some words on who is responsible for terminating a signal channel
or the underlaying ACL link. Basically in your case the headset is the
initiator and it is too stupid too terminate its own RFCOMM signal
channel that it created previous. I have no idea why this is not caught
by the qualification tests.

I think the Jabra headsets are CSR based and maybe the CSR developers
are interested to hear about it. Please post the "hcitool info" output
for them.

Regards

Marcel




-------------------------------------------------------
SF email is sponsored by - The IT Product Guide
Read honest & candid reviews on hundreds of IT Products from real users.
Discover which products truly live up to the hype. Start reading now.
http://ads.osdn.com/?ad_id=6595&alloc_id=14396&op=click
_______________________________________________
Bluez-devel mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/bluez-devel