/* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2002-2004 Marcel Holtmann * * * 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 #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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 [channel]\n" "\thstest record [channel]\n" "\thstest server [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; }