#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/mbuf.h>
#include <sys/proc.h>
#include <sys/systm.h>
#include <sys/socketvar.h>
#include <netbt/bluetooth.h>
#include <netbt/hci.h>
#include <netbt/l2cap.h>
#include <netbt/rfcomm.h>
int
rfcomm_attach(struct rfcomm_dlc **handle,
const struct btproto *proto, void *upper)
{
struct rfcomm_dlc *dlc;
KKASSERT(handle != NULL);
KKASSERT(proto != NULL);
KKASSERT(upper != NULL);
dlc = kmalloc(sizeof(*dlc), M_BLUETOOTH, M_NOWAIT | M_ZERO);
if (dlc == NULL)
return ENOMEM;
dlc->rd_state = RFCOMM_DLC_CLOSED;
dlc->rd_mtu = rfcomm_mtu_default;
dlc->rd_proto = proto;
dlc->rd_upper = upper;
dlc->rd_laddr.bt_len = sizeof(struct sockaddr_bt);
dlc->rd_laddr.bt_family = AF_BLUETOOTH;
dlc->rd_laddr.bt_psm = L2CAP_PSM_RFCOMM;
dlc->rd_raddr.bt_len = sizeof(struct sockaddr_bt);
dlc->rd_raddr.bt_family = AF_BLUETOOTH;
dlc->rd_raddr.bt_psm = L2CAP_PSM_RFCOMM;
dlc->rd_lmodem = RFCOMM_MSC_RTC | RFCOMM_MSC_RTR | RFCOMM_MSC_DV;
callout_init(&dlc->rd_timeout);
*handle = dlc;
return 0;
}
int
rfcomm_bind(struct rfcomm_dlc *dlc, struct sockaddr_bt *addr)
{
memcpy(&dlc->rd_laddr, addr, sizeof(struct sockaddr_bt));
return 0;
}
int
rfcomm_sockaddr(struct rfcomm_dlc *dlc, struct sockaddr_bt *addr)
{
memcpy(addr, &dlc->rd_laddr, sizeof(struct sockaddr_bt));
return 0;
}
int
rfcomm_connect(struct rfcomm_dlc *dlc, struct sockaddr_bt *dest)
{
struct rfcomm_session *rs;
int err = 0;
if (dlc->rd_state != RFCOMM_DLC_CLOSED)
return EISCONN;
memcpy(&dlc->rd_raddr, dest, sizeof(struct sockaddr_bt));
if (dlc->rd_raddr.bt_channel < RFCOMM_CHANNEL_MIN
|| dlc->rd_raddr.bt_channel > RFCOMM_CHANNEL_MAX
|| bdaddr_any(&dlc->rd_raddr.bt_bdaddr))
return EDESTADDRREQ;
if (dlc->rd_raddr.bt_psm == L2CAP_PSM_ANY)
dlc->rd_raddr.bt_psm = L2CAP_PSM_RFCOMM;
else if (dlc->rd_raddr.bt_psm != L2CAP_PSM_RFCOMM
&& (dlc->rd_raddr.bt_psm < 0x1001
|| L2CAP_PSM_INVALID(dlc->rd_raddr.bt_psm)))
return EINVAL;
rs = rfcomm_session_lookup(&dlc->rd_laddr, &dlc->rd_raddr);
if (rs == NULL) {
rs = rfcomm_session_alloc(&rfcomm_session_active,
&dlc->rd_laddr);
if (rs == NULL)
return ENOMEM;
rs->rs_flags |= RFCOMM_SESSION_INITIATOR;
rs->rs_state = RFCOMM_SESSION_WAIT_CONNECT;
err = l2cap_connect(rs->rs_l2cap, &dlc->rd_raddr);
if (err) {
rfcomm_session_free(rs);
return err;
}
}
dlc->rd_dlci = RFCOMM_MKDLCI(IS_INITIATOR(rs) ? 0:1, dest->bt_channel);
if (rfcomm_dlc_lookup(rs, dlc->rd_dlci))
return EBUSY;
l2cap_sockaddr(rs->rs_l2cap, &dlc->rd_laddr);
dlc->rd_session = rs;
dlc->rd_state = RFCOMM_DLC_WAIT_SESSION;
LIST_INSERT_HEAD(&rs->rs_dlcs, dlc, rd_next);
if (rs->rs_state == RFCOMM_SESSION_OPEN)
err = rfcomm_dlc_connect(dlc);
return err;
}
int
rfcomm_peeraddr(struct rfcomm_dlc *dlc, struct sockaddr_bt *addr)
{
memcpy(addr, &dlc->rd_raddr, sizeof(struct sockaddr_bt));
return 0;
}
int
rfcomm_disconnect(struct rfcomm_dlc *dlc, int linger)
{
struct rfcomm_session *rs = dlc->rd_session;
int err = 0;
KKASSERT(dlc != NULL);
switch (dlc->rd_state) {
case RFCOMM_DLC_CLOSED:
case RFCOMM_DLC_LISTEN:
return EINVAL;
case RFCOMM_DLC_WAIT_SEND_UA:
err = rfcomm_session_send_frame(rs,
RFCOMM_FRAME_DM, dlc->rd_dlci);
case RFCOMM_DLC_WAIT_SESSION:
case RFCOMM_DLC_WAIT_CONNECT:
case RFCOMM_DLC_WAIT_SEND_SABM:
rfcomm_dlc_close(dlc, 0);
break;
case RFCOMM_DLC_OPEN:
if (dlc->rd_txbuf != NULL && linger != 0) {
dlc->rd_flags |= RFCOMM_DLC_SHUTDOWN;
break;
}
case RFCOMM_DLC_WAIT_RECV_UA:
dlc->rd_state = RFCOMM_DLC_WAIT_DISCONNECT;
err = rfcomm_session_send_frame(rs, RFCOMM_FRAME_DISC,
dlc->rd_dlci);
callout_reset(&dlc->rd_timeout, rfcomm_ack_timeout * hz,
rfcomm_dlc_timeout, dlc);
break;
case RFCOMM_DLC_WAIT_DISCONNECT:
err = EALREADY;
break;
default:
UNKNOWN(dlc->rd_state);
break;
}
return err;
}
int
rfcomm_detach(struct rfcomm_dlc **handle)
{
struct rfcomm_dlc *dlc = *handle;
if (dlc->rd_state != RFCOMM_DLC_CLOSED)
rfcomm_dlc_close(dlc, 0);
if (dlc->rd_txbuf != NULL) {
m_freem(dlc->rd_txbuf);
dlc->rd_txbuf = NULL;
}
dlc->rd_upper = NULL;
*handle = NULL;
if (callout_active(&dlc->rd_timeout))
dlc->rd_flags |= RFCOMM_DLC_DETACH;
else
kfree(dlc, M_BLUETOOTH);
return 0;
}
int
rfcomm_listen(struct rfcomm_dlc *dlc)
{
struct rfcomm_session *rs;
struct rfcomm_dlc *used;
struct sockaddr_bt addr;
int err, channel;
if (dlc->rd_state != RFCOMM_DLC_CLOSED)
return EISCONN;
if (dlc->rd_laddr.bt_channel != RFCOMM_CHANNEL_ANY
&& (dlc->rd_laddr.bt_channel < RFCOMM_CHANNEL_MIN
|| dlc->rd_laddr.bt_channel > RFCOMM_CHANNEL_MAX))
return EADDRNOTAVAIL;
if (dlc->rd_laddr.bt_psm == L2CAP_PSM_ANY)
dlc->rd_laddr.bt_psm = L2CAP_PSM_RFCOMM;
else if (dlc->rd_laddr.bt_psm != L2CAP_PSM_RFCOMM
&& (dlc->rd_laddr.bt_psm < 0x1001
|| L2CAP_PSM_INVALID(dlc->rd_laddr.bt_psm)))
return EADDRNOTAVAIL;
LIST_FOREACH(rs, &rfcomm_session_listen, rs_next) {
l2cap_sockaddr(rs->rs_l2cap, &addr);
if (addr.bt_psm != dlc->rd_laddr.bt_psm)
continue;
if (bdaddr_same(&dlc->rd_laddr.bt_bdaddr, &addr.bt_bdaddr))
break;
}
if (rs == NULL) {
rs = rfcomm_session_alloc(&rfcomm_session_listen,
&dlc->rd_laddr);
if (rs == NULL)
return ENOMEM;
rs->rs_state = RFCOMM_SESSION_LISTEN;
err = l2cap_listen(rs->rs_l2cap);
if (err) {
rfcomm_session_free(rs);
return err;
}
}
if (dlc->rd_laddr.bt_channel == RFCOMM_CHANNEL_ANY) {
channel = RFCOMM_CHANNEL_MIN;
used = LIST_FIRST(&rs->rs_dlcs);
while (used != NULL) {
if (used->rd_laddr.bt_channel == channel) {
if (channel++ == RFCOMM_CHANNEL_MAX)
return EADDRNOTAVAIL;
used = LIST_FIRST(&rs->rs_dlcs);
} else {
used = LIST_NEXT(used, rd_next);
}
}
dlc->rd_laddr.bt_channel = channel;
}
dlc->rd_session = rs;
dlc->rd_state = RFCOMM_DLC_LISTEN;
LIST_INSERT_HEAD(&rs->rs_dlcs, dlc, rd_next);
return 0;
}
int
rfcomm_send(struct rfcomm_dlc *dlc, struct mbuf *m)
{
if (dlc->rd_txbuf != NULL) {
dlc->rd_txbuf->m_pkthdr.len += m->m_pkthdr.len;
m_cat(dlc->rd_txbuf, m);
} else {
dlc->rd_txbuf = m;
}
if (dlc->rd_state == RFCOMM_DLC_OPEN)
rfcomm_dlc_start(dlc);
return 0;
}
int
rfcomm_rcvd(struct rfcomm_dlc *dlc, size_t space)
{
KKASSERT(dlc != NULL);
dlc->rd_rxsize = space;
if (dlc->rd_state == RFCOMM_DLC_OPEN
&& (dlc->rd_session->rs_flags & RFCOMM_SESSION_CFC))
rfcomm_dlc_start(dlc);
return 0;
}
int
rfcomm_setopt(struct rfcomm_dlc *dlc, int opt, void *addr)
{
int mode, err = 0;
uint16_t mtu;
switch (opt) {
case SO_RFCOMM_MTU:
mtu = *(uint16_t *)addr;
if (mtu < RFCOMM_MTU_MIN || mtu > RFCOMM_MTU_MAX)
err = EINVAL;
else if (dlc->rd_state == RFCOMM_DLC_CLOSED)
dlc->rd_mtu = mtu;
else
err = EBUSY;
break;
case SO_RFCOMM_LM:
mode = *(int *)addr;
mode &= (RFCOMM_LM_SECURE | RFCOMM_LM_ENCRYPT | RFCOMM_LM_AUTH);
if (mode & RFCOMM_LM_SECURE)
mode |= RFCOMM_LM_ENCRYPT;
if (mode & RFCOMM_LM_ENCRYPT)
mode |= RFCOMM_LM_AUTH;
dlc->rd_mode = mode;
if (dlc->rd_state == RFCOMM_DLC_OPEN)
err = rfcomm_dlc_setmode(dlc);
break;
default:
err = ENOPROTOOPT;
break;
}
return err;
}
int
rfcomm_setopt2(struct rfcomm_dlc *dlc, int opt, struct socket *so,
struct sockopt *sopt)
{
int mode, err = 0;
uint16_t mtu;
switch (opt) {
case SO_RFCOMM_MTU:
err = soopt_to_kbuf(sopt, &mtu, sizeof(uint16_t),
sizeof(uint16_t));
if (err) break;
if (mtu < RFCOMM_MTU_MIN || mtu > RFCOMM_MTU_MAX)
err = EINVAL;
else if (dlc->rd_state == RFCOMM_DLC_CLOSED)
dlc->rd_mtu = mtu;
else
err = EBUSY;
break;
case SO_RFCOMM_LM:
err = soopt_to_kbuf(sopt, &mode, sizeof(int), sizeof(int));
if (err) break;
mode &= (RFCOMM_LM_SECURE | RFCOMM_LM_ENCRYPT | RFCOMM_LM_AUTH);
if (mode & RFCOMM_LM_SECURE)
mode |= RFCOMM_LM_ENCRYPT;
if (mode & RFCOMM_LM_ENCRYPT)
mode |= RFCOMM_LM_AUTH;
dlc->rd_mode = mode;
if (dlc->rd_state == RFCOMM_DLC_OPEN)
err = rfcomm_dlc_setmode(dlc);
break;
default:
err = ENOPROTOOPT;
break;
}
return err;
}
int
rfcomm_getopt(struct rfcomm_dlc *dlc, int opt, void *addr)
{
struct rfcomm_fc_info *fc;
switch (opt) {
case SO_RFCOMM_MTU:
*(uint16_t *)addr = dlc->rd_mtu;
return sizeof(uint16_t);
case SO_RFCOMM_FC_INFO:
fc = addr;
memset(fc, 0, sizeof(*fc));
fc->lmodem = dlc->rd_lmodem;
fc->rmodem = dlc->rd_rmodem;
fc->tx_cred = max(dlc->rd_txcred, 0xff);
fc->rx_cred = max(dlc->rd_rxcred, 0xff);
if (dlc->rd_session
&& (dlc->rd_session->rs_flags & RFCOMM_SESSION_CFC))
fc->cfc = 1;
return sizeof(*fc);
case SO_RFCOMM_LM:
*(int *)addr = dlc->rd_mode;
return sizeof(int);
default:
break;
}
return 0;
}