#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: rfcomm_session.c,v 1.30 2024/07/05 04:31:53 rin Exp $");
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/mbuf.h>
#include <sys/proc.h>
#include <sys/socketvar.h>
#include <sys/systm.h>
#include <sys/types.h>
#include <netbt/bluetooth.h>
#include <netbt/hci.h>
#include <netbt/l2cap.h>
#include <netbt/rfcomm.h>
static void rfcomm_session_timeout(void *);
static void rfcomm_session_recv_sabm(struct rfcomm_session *, int);
static void rfcomm_session_recv_disc(struct rfcomm_session *, int);
static void rfcomm_session_recv_ua(struct rfcomm_session *, int);
static void rfcomm_session_recv_dm(struct rfcomm_session *, int);
static void rfcomm_session_recv_uih(struct rfcomm_session *, int, int, struct mbuf *, int);
static void rfcomm_session_recv_mcc(struct rfcomm_session *, struct mbuf *);
static void rfcomm_session_recv_mcc_test(struct rfcomm_session *, int, struct mbuf *);
static void rfcomm_session_recv_mcc_fcon(struct rfcomm_session *, int);
static void rfcomm_session_recv_mcc_fcoff(struct rfcomm_session *, int);
static void rfcomm_session_recv_mcc_msc(struct rfcomm_session *, int, struct mbuf *);
static void rfcomm_session_recv_mcc_rpn(struct rfcomm_session *, int, struct mbuf *);
static void rfcomm_session_recv_mcc_rls(struct rfcomm_session *, int, struct mbuf *);
static void rfcomm_session_recv_mcc_pn(struct rfcomm_session *, int, struct mbuf *);
static void rfcomm_session_recv_mcc_nsc(struct rfcomm_session *, int, struct mbuf *);
static void rfcomm_session_connecting(void *);
static void rfcomm_session_connected(void *);
static void rfcomm_session_disconnected(void *, int);
static void *rfcomm_session_newconn(void *, struct sockaddr_bt *, struct sockaddr_bt *);
static void rfcomm_session_complete(void *, int);
static void rfcomm_session_linkmode(void *, int);
static void rfcomm_session_input(void *, struct mbuf *);
static const struct btproto rfcomm_session_proto = {
rfcomm_session_connecting,
rfcomm_session_connected,
rfcomm_session_disconnected,
rfcomm_session_newconn,
rfcomm_session_complete,
rfcomm_session_linkmode,
rfcomm_session_input,
};
struct rfcomm_session_list
rfcomm_session_active = LIST_HEAD_INITIALIZER(rfcomm_session_active);
struct rfcomm_session_list
rfcomm_session_listen = LIST_HEAD_INITIALIZER(rfcomm_session_listen);
static struct pool rfcomm_credit_pool;
int rfcomm_mtu_default = 127;
int rfcomm_ack_timeout = 20;
int rfcomm_mcc_timeout = 20;
static const uint8_t crctable[256] = {
0x00, 0x91, 0xe3, 0x72, 0x07, 0x96, 0xe4, 0x75,
0x0e, 0x9f, 0xed, 0x7c, 0x09, 0x98, 0xea, 0x7b,
0x1c, 0x8d, 0xff, 0x6e, 0x1b, 0x8a, 0xf8, 0x69,
0x12, 0x83, 0xf1, 0x60, 0x15, 0x84, 0xf6, 0x67,
0x38, 0xa9, 0xdb, 0x4a, 0x3f, 0xae, 0xdc, 0x4d,
0x36, 0xa7, 0xd5, 0x44, 0x31, 0xa0, 0xd2, 0x43,
0x24, 0xb5, 0xc7, 0x56, 0x23, 0xb2, 0xc0, 0x51,
0x2a, 0xbb, 0xc9, 0x58, 0x2d, 0xbc, 0xce, 0x5f,
0x70, 0xe1, 0x93, 0x02, 0x77, 0xe6, 0x94, 0x05,
0x7e, 0xef, 0x9d, 0x0c, 0x79, 0xe8, 0x9a, 0x0b,
0x6c, 0xfd, 0x8f, 0x1e, 0x6b, 0xfa, 0x88, 0x19,
0x62, 0xf3, 0x81, 0x10, 0x65, 0xf4, 0x86, 0x17,
0x48, 0xd9, 0xab, 0x3a, 0x4f, 0xde, 0xac, 0x3d,
0x46, 0xd7, 0xa5, 0x34, 0x41, 0xd0, 0xa2, 0x33,
0x54, 0xc5, 0xb7, 0x26, 0x53, 0xc2, 0xb0, 0x21,
0x5a, 0xcb, 0xb9, 0x28, 0x5d, 0xcc, 0xbe, 0x2f,
0xe0, 0x71, 0x03, 0x92, 0xe7, 0x76, 0x04, 0x95,
0xee, 0x7f, 0x0d, 0x9c, 0xe9, 0x78, 0x0a, 0x9b,
0xfc, 0x6d, 0x1f, 0x8e, 0xfb, 0x6a, 0x18, 0x89,
0xf2, 0x63, 0x11, 0x80, 0xf5, 0x64, 0x16, 0x87,
0xd8, 0x49, 0x3b, 0xaa, 0xdf, 0x4e, 0x3c, 0xad,
0xd6, 0x47, 0x35, 0xa4, 0xd1, 0x40, 0x32, 0xa3,
0xc4, 0x55, 0x27, 0xb6, 0xc3, 0x52, 0x20, 0xb1,
0xca, 0x5b, 0x29, 0xb8, 0xcd, 0x5c, 0x2e, 0xbf,
0x90, 0x01, 0x73, 0xe2, 0x97, 0x06, 0x74, 0xe5,
0x9e, 0x0f, 0x7d, 0xec, 0x99, 0x08, 0x7a, 0xeb,
0x8c, 0x1d, 0x6f, 0xfe, 0x8b, 0x1a, 0x68, 0xf9,
0x82, 0x13, 0x61, 0xf0, 0x85, 0x14, 0x66, 0xf7,
0xa8, 0x39, 0x4b, 0xda, 0xaf, 0x3e, 0x4c, 0xdd,
0xa6, 0x37, 0x45, 0xd4, 0xa1, 0x30, 0x42, 0xd3,
0xb4, 0x25, 0x57, 0xc6, 0xb3, 0x22, 0x50, 0xc1,
0xba, 0x2b, 0x59, 0xc8, 0xbd, 0x2c, 0x5e, 0xcf
};
#define FCS(f, d) crctable[(f) ^ (d)]
void
rfcomm_init(void)
{
pool_init(&rfcomm_credit_pool, sizeof(struct rfcomm_credit),
0, 0, 0, "rfcomm_credit", NULL, IPL_SOFTNET);
}
struct rfcomm_session *
rfcomm_session_alloc(struct rfcomm_session_list *list,
struct sockaddr_bt *laddr)
{
struct rfcomm_session *rs;
struct sockopt sopt;
int err;
rs = malloc(sizeof(*rs), M_BLUETOOTH, M_NOWAIT | M_ZERO);
if (rs == NULL)
return NULL;
rs->rs_state = RFCOMM_SESSION_CLOSED;
callout_init(&rs->rs_timeout, 0);
callout_setfunc(&rs->rs_timeout, rfcomm_session_timeout, rs);
SIMPLEQ_INIT(&rs->rs_credits);
LIST_INIT(&rs->rs_dlcs);
err = l2cap_attach_pcb(&rs->rs_l2cap, &rfcomm_session_proto, rs);
if (err) {
free(rs, M_BLUETOOTH);
return NULL;
}
sockopt_init(&sopt, BTPROTO_L2CAP, SO_L2CAP_OMTU, 0);
(void)l2cap_getopt(rs->rs_l2cap, &sopt);
(void)sockopt_get(&sopt, &rs->rs_mtu, sizeof(rs->rs_mtu));
sockopt_destroy(&sopt);
if (laddr->bt_psm == L2CAP_PSM_ANY)
laddr->bt_psm = L2CAP_PSM_RFCOMM;
(void)l2cap_bind_pcb(rs->rs_l2cap, laddr);
LIST_INSERT_HEAD(list, rs, rs_next);
return rs;
}
void
rfcomm_session_free(struct rfcomm_session *rs)
{
struct rfcomm_credit *credit;
KASSERT(rs != NULL);
KASSERT(LIST_EMPTY(&rs->rs_dlcs));
rs->rs_state = RFCOMM_SESSION_CLOSED;
callout_stop(&rs->rs_timeout);
if (callout_invoking(&rs->rs_timeout))
return;
if (rs->rs_flags & RFCOMM_SESSION_FREE)
return;
rs->rs_flags |= RFCOMM_SESSION_FREE;
while ((credit = SIMPLEQ_FIRST(&rs->rs_credits)) != NULL) {
SIMPLEQ_REMOVE_HEAD(&rs->rs_credits, rc_next);
pool_put(&rfcomm_credit_pool, credit);
}
KASSERT(SIMPLEQ_EMPTY(&rs->rs_credits));
LIST_REMOVE(rs, rs_next);
l2cap_detach_pcb(&rs->rs_l2cap);
callout_destroy(&rs->rs_timeout);
free(rs, M_BLUETOOTH);
}
struct rfcomm_session *
rfcomm_session_lookup(struct sockaddr_bt *src, struct sockaddr_bt *dest)
{
struct rfcomm_session *rs;
struct sockaddr_bt addr;
LIST_FOREACH(rs, &rfcomm_session_active, rs_next) {
if (rs->rs_state == RFCOMM_SESSION_CLOSED)
continue;
l2cap_sockaddr_pcb(rs->rs_l2cap, &addr);
if (bdaddr_same(&src->bt_bdaddr, &addr.bt_bdaddr) == 0)
if (bdaddr_any(&src->bt_bdaddr) == 0)
continue;
l2cap_peeraddr_pcb(rs->rs_l2cap, &addr);
if (addr.bt_psm != dest->bt_psm)
continue;
if (bdaddr_same(&dest->bt_bdaddr, &addr.bt_bdaddr))
break;
}
return rs;
}
static void
rfcomm_session_timeout(void *arg)
{
struct rfcomm_session *rs = arg;
struct rfcomm_dlc *dlc;
KASSERT(rs != NULL);
mutex_enter(bt_lock);
callout_ack(&rs->rs_timeout);
if (rs->rs_state != RFCOMM_SESSION_OPEN) {
DPRINTF("timeout\n");
rs->rs_state = RFCOMM_SESSION_CLOSED;
while (!LIST_EMPTY(&rs->rs_dlcs)) {
dlc = LIST_FIRST(&rs->rs_dlcs);
rfcomm_dlc_close(dlc, ETIMEDOUT);
}
}
if (LIST_EMPTY(&rs->rs_dlcs)) {
DPRINTF("expiring\n");
rfcomm_session_free(rs);
}
mutex_exit(bt_lock);
}
static void
rfcomm_session_connecting(void *arg)
{
DPRINTF("Connecting\n");
}
static void
rfcomm_session_connected(void *arg)
{
struct rfcomm_session *rs = arg;
struct sockopt sopt;
DPRINTF("Connected\n");
sockopt_init(&sopt, BTPROTO_L2CAP, SO_L2CAP_OMTU, 0);
(void)l2cap_getopt(rs->rs_l2cap, &sopt);
(void)sockopt_get(&sopt, &rs->rs_mtu, sizeof(rs->rs_mtu));
sockopt_destroy(&sopt);
rs->rs_mtu -= 6;
if (rs->rs_mtu < RFCOMM_MTU_MIN) {
rfcomm_session_disconnected(rs, EINVAL);
return;
}
if (IS_INITIATOR(rs)) {
int err;
err = rfcomm_session_send_frame(rs, RFCOMM_FRAME_SABM, 0);
if (err)
rfcomm_session_disconnected(rs, err);
callout_schedule(&rs->rs_timeout, rfcomm_ack_timeout * hz);
}
}
static void
rfcomm_session_disconnected(void *arg, int err)
{
struct rfcomm_session *rs = arg;
struct rfcomm_dlc *dlc;
DPRINTF("Disconnected\n");
if (err == 0)
err = ECONNRESET;
rs->rs_state = RFCOMM_SESSION_CLOSED;
while (!LIST_EMPTY(&rs->rs_dlcs)) {
dlc = LIST_FIRST(&rs->rs_dlcs);
rfcomm_dlc_close(dlc, err);
}
rfcomm_session_free(rs);
}
static void *
rfcomm_session_newconn(void *arg, struct sockaddr_bt *laddr,
struct sockaddr_bt *raddr)
{
struct rfcomm_session *new, *rs = arg;
DPRINTF("New Connection\n");
new = rfcomm_session_lookup(laddr, raddr);
if (new != NULL)
return NULL;
new = rfcomm_session_alloc(&rfcomm_session_active, laddr);
if (new == NULL)
return NULL;
new->rs_mtu = rs->rs_mtu;
new->rs_state = RFCOMM_SESSION_WAIT_CONNECT;
callout_schedule(&new->rs_timeout, rfcomm_mcc_timeout * hz);
return new->rs_l2cap;
}
static void
rfcomm_session_complete(void *arg, int count)
{
struct rfcomm_session *rs = arg;
struct rfcomm_credit *credit;
struct rfcomm_dlc *dlc;
while (count-- > 0) {
credit = SIMPLEQ_FIRST(&rs->rs_credits);
if (credit == NULL) {
printf("%s: too many packets completed!\n", __func__);
break;
}
dlc = credit->rc_dlc;
if (dlc != NULL) {
dlc->rd_pending--;
(*dlc->rd_proto->complete)
(dlc->rd_upper, credit->rc_len);
if ((rs->rs_flags & RFCOMM_SESSION_CFC) == 0
&& dlc->rd_state == RFCOMM_DLC_OPEN) {
rfcomm_dlc_start(dlc);
}
if ((dlc->rd_flags & RFCOMM_DLC_SHUTDOWN)
&& dlc->rd_txbuf == NULL && dlc->rd_pending == 0) {
dlc->rd_state = RFCOMM_DLC_WAIT_DISCONNECT;
rfcomm_session_send_frame(rs, RFCOMM_FRAME_DISC,
dlc->rd_dlci);
callout_schedule(&dlc->rd_timeout,
rfcomm_ack_timeout * hz);
}
}
SIMPLEQ_REMOVE_HEAD(&rs->rs_credits, rc_next);
pool_put(&rfcomm_credit_pool, credit);
}
if (rs->rs_state == RFCOMM_SESSION_CLOSED) {
if (SIMPLEQ_EMPTY(&rs->rs_credits))
l2cap_disconnect_pcb(rs->rs_l2cap, 0);
}
}
static void
rfcomm_session_linkmode(void *arg, int new)
{
struct rfcomm_session *rs = arg;
struct rfcomm_dlc *dlc, *next;
int err, mode = 0;
DPRINTF("auth %s, encrypt %s, secure %s\n",
(new & L2CAP_LM_AUTH ? "on" : "off"),
(new & L2CAP_LM_ENCRYPT ? "on" : "off"),
(new & L2CAP_LM_SECURE ? "on" : "off"));
if (new & L2CAP_LM_AUTH)
mode |= RFCOMM_LM_AUTH;
if (new & L2CAP_LM_ENCRYPT)
mode |= RFCOMM_LM_ENCRYPT;
if (new & L2CAP_LM_SECURE)
mode |= RFCOMM_LM_SECURE;
next = LIST_FIRST(&rs->rs_dlcs);
while ((dlc = next) != NULL) {
next = LIST_NEXT(dlc, rd_next);
switch (dlc->rd_state) {
case RFCOMM_DLC_WAIT_SEND_SABM:
if ((mode & dlc->rd_mode) != dlc->rd_mode) {
rfcomm_dlc_close(dlc, ECONNABORTED);
} else {
err = rfcomm_session_send_frame(rs,
RFCOMM_FRAME_SABM, dlc->rd_dlci);
if (err) {
rfcomm_dlc_close(dlc, err);
} else {
dlc->rd_state = RFCOMM_DLC_WAIT_RECV_UA;
callout_schedule(&dlc->rd_timeout,
rfcomm_ack_timeout * hz);
break;
}
}
if (!LIST_EMPTY(&rs->rs_dlcs))
break;
rs->rs_state = RFCOMM_SESSION_WAIT_DISCONNECT;
rfcomm_session_send_frame(rs, RFCOMM_FRAME_DISC, 0);
callout_schedule(&rs->rs_timeout, rfcomm_ack_timeout * hz);
break;
case RFCOMM_DLC_WAIT_SEND_UA:
if ((mode & dlc->rd_mode) != dlc->rd_mode) {
rfcomm_session_send_frame(rs,
RFCOMM_FRAME_DM, dlc->rd_dlci);
rfcomm_dlc_close(dlc, ECONNABORTED);
break;
}
err = rfcomm_session_send_frame(rs,
RFCOMM_FRAME_UA, dlc->rd_dlci);
if (err) {
rfcomm_session_send_frame(rs,
RFCOMM_FRAME_DM, dlc->rd_dlci);
rfcomm_dlc_close(dlc, err);
break;
}
err = rfcomm_dlc_open(dlc);
if (err) {
rfcomm_session_send_frame(rs,
RFCOMM_FRAME_DM, dlc->rd_dlci);
rfcomm_dlc_close(dlc, err);
break;
}
break;
case RFCOMM_DLC_WAIT_RECV_UA:
case RFCOMM_DLC_OPEN:
(*dlc->rd_proto->linkmode)(dlc->rd_upper, mode);
break;
default:
break;
}
}
}
static void
rfcomm_session_input(void *arg, struct mbuf *m)
{
struct rfcomm_session *rs = arg;
int dlci, len, type, pf;
uint8_t fcs, b;
KASSERT(m != NULL);
KASSERT(rs != NULL);
fcs = 0xff;
if (m->m_pkthdr.len < 4) {
DPRINTF("short frame (%d), discarded\n", m->m_pkthdr.len);
goto done;
}
m_copydata(m, 0, 1, &b);
m_adj(m, 1);
fcs = FCS(fcs, b);
dlci = RFCOMM_DLCI(b);
m_copydata(m, 0, 1, &b);
m_adj(m, 1);
fcs = FCS(fcs, b);
type = RFCOMM_TYPE(b);
pf = RFCOMM_PF(b);
m_copydata(m, 0, 1, &b);
m_adj(m, 1);
if (type != RFCOMM_FRAME_UIH)
fcs = FCS(fcs, b);
len = (b >> 1) & 0x7f;
if (RFCOMM_EA(b) == 0) {
if (m->m_pkthdr.len < 2) {
DPRINTF("short frame (%d, EA = 0), discarded\n",
m->m_pkthdr.len);
goto done;
}
m_copydata(m, 0, 1, &b);
m_adj(m, 1);
if (type != RFCOMM_FRAME_UIH)
fcs = FCS(fcs, b);
len |= (b << 7);
}
m_copydata(m, m->m_pkthdr.len - 1, 1, &b);
m_adj(m, -1);
fcs = FCS(fcs, b);
if (fcs != 0xcf) {
DPRINTF("Bad FCS value (%#2.2x), frame discarded\n", fcs);
goto done;
}
DPRINTFN(10, "dlci %d, type %2.2x, len = %d\n", dlci, type, len);
switch (type) {
case RFCOMM_FRAME_SABM:
if (pf)
rfcomm_session_recv_sabm(rs, dlci);
break;
case RFCOMM_FRAME_DISC:
if (pf)
rfcomm_session_recv_disc(rs, dlci);
break;
case RFCOMM_FRAME_UA:
if (pf)
rfcomm_session_recv_ua(rs, dlci);
break;
case RFCOMM_FRAME_DM:
rfcomm_session_recv_dm(rs, dlci);
break;
case RFCOMM_FRAME_UIH:
rfcomm_session_recv_uih(rs, dlci, pf, m, len);
return;
default:
UNKNOWN(type);
break;
}
done:
m_freem(m);
}
static void
rfcomm_session_recv_sabm(struct rfcomm_session *rs, int dlci)
{
struct rfcomm_dlc *dlc;
int err;
DPRINTFN(5, "SABM(%d)\n", dlci);
if (dlci == 0) {
rs->rs_state = RFCOMM_SESSION_OPEN;
rfcomm_session_send_frame(rs, RFCOMM_FRAME_UA, 0);
LIST_FOREACH(dlc, &rs->rs_dlcs, rd_next) {
if (dlc->rd_state == RFCOMM_DLC_WAIT_SESSION)
rfcomm_dlc_connect(dlc);
}
return;
}
if (rs->rs_state != RFCOMM_SESSION_OPEN) {
DPRINTF("session was not even open!\n");
return;
}
if ((IS_INITIATOR(rs) && !RFCOMM_DIRECTION(dlci))
|| (!IS_INITIATOR(rs) && RFCOMM_DIRECTION(dlci))) {
DPRINTF("Invalid direction bit on DLCI\n");
return;
}
dlc = rfcomm_dlc_lookup(rs, dlci);
if (dlc == NULL) {
dlc = rfcomm_dlc_newconn(rs, dlci);
if (dlc == NULL)
return;
}
if (dlc->rd_state != RFCOMM_DLC_WAIT_CONNECT)
return;
err = rfcomm_dlc_setmode(dlc);
if (err == EINPROGRESS) {
dlc->rd_state = RFCOMM_DLC_WAIT_SEND_UA;
(*dlc->rd_proto->connecting)(dlc->rd_upper);
return;
}
if (err)
goto close;
err = rfcomm_session_send_frame(rs, RFCOMM_FRAME_UA, dlci);
if (err)
goto close;
err = rfcomm_dlc_open(dlc);
if (err)
goto close;
return;
close:
rfcomm_dlc_close(dlc, err);
}
static void
rfcomm_session_recv_disc(struct rfcomm_session *rs, int dlci)
{
struct rfcomm_dlc *dlc;
DPRINTFN(5, "DISC(%d)\n", dlci);
if (dlci == 0) {
rfcomm_session_send_frame(rs, RFCOMM_FRAME_UA, 0);
rs->rs_state = RFCOMM_SESSION_CLOSED;
return;
}
dlc = rfcomm_dlc_lookup(rs, dlci);
if (dlc == NULL) {
rfcomm_session_send_frame(rs, RFCOMM_FRAME_DM, dlci);
return;
}
rfcomm_dlc_close(dlc, 0);
rfcomm_session_send_frame(rs, RFCOMM_FRAME_UA, dlci);
}
static void
rfcomm_session_recv_ua(struct rfcomm_session *rs, int dlci)
{
struct rfcomm_dlc *dlc;
DPRINTFN(5, "UA(%d)\n", dlci);
if (dlci == 0) {
switch (rs->rs_state) {
case RFCOMM_SESSION_WAIT_CONNECT:
callout_stop(&rs->rs_timeout);
rs->rs_state = RFCOMM_SESSION_OPEN;
LIST_FOREACH(dlc, &rs->rs_dlcs, rd_next) {
if (dlc->rd_state == RFCOMM_DLC_WAIT_SESSION)
rfcomm_dlc_connect(dlc);
}
break;
case RFCOMM_SESSION_WAIT_DISCONNECT:
callout_stop(&rs->rs_timeout);
rs->rs_state = RFCOMM_SESSION_CLOSED;
l2cap_disconnect_pcb(rs->rs_l2cap, 0);
break;
default:
DPRINTF("Received spurious UA(0)!\n");
break;
}
return;
}
dlc = rfcomm_dlc_lookup(rs, dlci);
if (dlc == NULL)
goto check;
switch (dlc->rd_state) {
case RFCOMM_DLC_WAIT_RECV_UA:
rfcomm_dlc_open(dlc);
return;
case RFCOMM_DLC_WAIT_DISCONNECT:
rfcomm_dlc_close(dlc, 0);
break;
default:
DPRINTF("Received spurious UA(%d)!\n", dlci);
return;
}
check:
if (LIST_EMPTY(&rs->rs_dlcs)) {
rs->rs_state = RFCOMM_SESSION_WAIT_DISCONNECT;
rfcomm_session_send_frame(rs, RFCOMM_FRAME_DISC, 0);
callout_schedule(&rs->rs_timeout, rfcomm_ack_timeout * hz);
}
}
static void
rfcomm_session_recv_dm(struct rfcomm_session *rs, int dlci)
{
struct rfcomm_dlc *dlc;
DPRINTFN(5, "DM(%d)\n", dlci);
dlc = rfcomm_dlc_lookup(rs, dlci);
if (dlc == NULL)
return;
if (dlc->rd_state == RFCOMM_DLC_WAIT_CONNECT)
rfcomm_dlc_close(dlc, ECONNREFUSED);
else
rfcomm_dlc_close(dlc, ECONNRESET);
}
static void
rfcomm_session_recv_uih(struct rfcomm_session *rs, int dlci,
int pf, struct mbuf *m, int len)
{
struct rfcomm_dlc *dlc;
uint8_t credits = 0;
DPRINTFN(10, "UIH(%d)\n", dlci);
if (dlci == 0) {
rfcomm_session_recv_mcc(rs, m);
return;
}
if (m->m_pkthdr.len != len + pf) {
DPRINTF("Bad Frame Length (%d), frame discarded\n",
m->m_pkthdr.len);
goto discard;
}
dlc = rfcomm_dlc_lookup(rs, dlci);
if (dlc == NULL) {
DPRINTF("UIH received for non existent DLC, discarded\n");
rfcomm_session_send_frame(rs, RFCOMM_FRAME_DM, dlci);
goto discard;
}
if (dlc->rd_state != RFCOMM_DLC_OPEN) {
DPRINTF("non-open DLC (state = %d), discarded\n",
dlc->rd_state);
goto discard;
}
if (rs->rs_flags & RFCOMM_SESSION_CFC) {
if (pf != 0) {
if (m->m_pkthdr.len < sizeof(credits)) {
DPRINTF("Bad PF value, UIH discarded\n");
goto discard;
}
m_copydata(m, 0, sizeof(credits), &credits);
m_adj(m, sizeof(credits));
dlc->rd_txcred += credits;
if (credits > 0 && dlc->rd_txbuf != NULL)
rfcomm_dlc_start(dlc);
}
if (len == 0)
goto discard;
if (dlc->rd_rxcred == 0) {
DPRINTF("Credit limit reached, UIH discarded\n");
goto discard;
}
if (len > dlc->rd_rxsize) {
DPRINTF("UIH frame exceeds rxsize, discarded\n");
goto discard;
}
dlc->rd_rxcred--;
dlc->rd_rxsize -= len;
}
(*dlc->rd_proto->input)(dlc->rd_upper, m);
return;
discard:
m_freem(m);
}
static void
rfcomm_session_recv_mcc(struct rfcomm_session *rs, struct mbuf *m)
{
int type, cr, len;
uint8_t b;
if (m->m_pkthdr.len < sizeof(b)) {
DPRINTF("Short MCC header, discarded\n");
goto release;
}
m_copydata(m, 0, sizeof(b), &b);
m_adj(m, sizeof(b));
if (RFCOMM_EA(b) == 0) {
DPRINTF("MCC type EA = 0, discarded\n");
goto release;
}
type = RFCOMM_MCC_TYPE(b);
cr = RFCOMM_CR(b);
len = 0;
do {
if (m->m_pkthdr.len < sizeof(b)) {
DPRINTF("Short MCC header, discarded\n");
goto release;
}
m_copydata(m, 0, sizeof(b), &b);
m_adj(m, sizeof(b));
len = (len << 7) | (b >> 1);
len = uimin(len, RFCOMM_MTU_MAX);
} while (RFCOMM_EA(b) == 0);
if (len != m->m_pkthdr.len) {
DPRINTF("Incorrect MCC length, discarded\n");
goto release;
}
DPRINTFN(2, "MCC %s type %2.2x (%d bytes)\n",
(cr ? "command" : "response"), type, len);
switch(type) {
case RFCOMM_MCC_TEST:
rfcomm_session_recv_mcc_test(rs, cr, m);
break;
case RFCOMM_MCC_FCON:
rfcomm_session_recv_mcc_fcon(rs, cr);
break;
case RFCOMM_MCC_FCOFF:
rfcomm_session_recv_mcc_fcoff(rs, cr);
break;
case RFCOMM_MCC_MSC:
rfcomm_session_recv_mcc_msc(rs, cr, m);
break;
case RFCOMM_MCC_RPN:
rfcomm_session_recv_mcc_rpn(rs, cr, m);
break;
case RFCOMM_MCC_RLS:
rfcomm_session_recv_mcc_rls(rs, cr, m);
break;
case RFCOMM_MCC_PN:
rfcomm_session_recv_mcc_pn(rs, cr, m);
break;
case RFCOMM_MCC_NSC:
rfcomm_session_recv_mcc_nsc(rs, cr, m);
break;
default:
b = RFCOMM_MKMCC_TYPE(cr, type);
rfcomm_session_send_mcc(rs, 0, RFCOMM_MCC_NSC, &b, sizeof(b));
}
release:
m_freem(m);
}
static void
rfcomm_session_recv_mcc_test(struct rfcomm_session *rs, int cr, struct mbuf *m)
{
void *data;
int len;
if (cr == 0)
return;
len = m->m_pkthdr.len;
if (len > RFCOMM_MTU_MAX)
return;
data = malloc(len, M_BLUETOOTH, M_NOWAIT);
if (data == NULL)
return;
m_copydata(m, 0, len, data);
rfcomm_session_send_mcc(rs, 0, RFCOMM_MCC_TEST, data, len);
free(data, M_BLUETOOTH);
}
static void
rfcomm_session_recv_mcc_fcon(struct rfcomm_session *rs, int cr)
{
if (cr == 0)
return;
rs->rs_flags |= RFCOMM_SESSION_RFC;
rfcomm_session_send_mcc(rs, 0, RFCOMM_MCC_FCON, NULL, 0);
}
static void
rfcomm_session_recv_mcc_fcoff(struct rfcomm_session *rs, int cr)
{
if (cr == 0)
return;
rs->rs_flags &= ~RFCOMM_SESSION_RFC;
rfcomm_session_send_mcc(rs, 0, RFCOMM_MCC_FCOFF, NULL, 0);
}
static void
rfcomm_session_recv_mcc_msc(struct rfcomm_session *rs, int cr, struct mbuf *m)
{
struct rfcomm_mcc_msc msc;
struct rfcomm_dlc *dlc;
int len = 0;
if (m->m_pkthdr.len < sizeof(msc.address))
return;
m_copydata(m, 0, sizeof(msc.address), &msc.address);
m_adj(m, sizeof(msc.address));
len += sizeof(msc.address);
dlc = rfcomm_dlc_lookup(rs, RFCOMM_DLCI(msc.address));
if (cr == 0) {
if (dlc != NULL)
callout_stop(&dlc->rd_timeout);
return;
}
if (dlc == NULL) {
rfcomm_session_send_frame(rs, RFCOMM_FRAME_DM,
RFCOMM_DLCI(msc.address));
return;
}
if (m->m_pkthdr.len < sizeof(msc.modem))
return;
m_copydata(m, 0, sizeof(msc.modem), &msc.modem);
m_adj(m, sizeof(msc.modem));
len += sizeof(msc.modem);
dlc->rd_rmodem = msc.modem;
if (RFCOMM_EA(msc.modem) == 0) {
if (m->m_pkthdr.len < sizeof(msc.brk))
return;
m_copydata(m, 0, sizeof(msc.brk), &msc.brk);
m_adj(m, sizeof(msc.brk));
len += sizeof(msc.brk);
}
rfcomm_session_send_mcc(rs, 0, RFCOMM_MCC_MSC, &msc, len);
}
static void
rfcomm_session_recv_mcc_rpn(struct rfcomm_session *rs, int cr, struct mbuf *m)
{
struct rfcomm_mcc_rpn rpn;
uint16_t mask;
if (cr == 0)
return;
rpn.bit_rate = RFCOMM_RPN_BR_9600;
rpn.line_settings = RFCOMM_RPN_8_N_1;
rpn.flow_control = RFCOMM_RPN_FLOW_NONE;
rpn.xon_char = RFCOMM_RPN_XON_CHAR;
rpn.xoff_char = RFCOMM_RPN_XOFF_CHAR;
if (m->m_pkthdr.len == sizeof(rpn)) {
m_copydata(m, 0, sizeof(rpn), &rpn);
rpn.param_mask = le16toh(rpn.param_mask);
} else if (m->m_pkthdr.len == 1) {
m_copydata(m, 0, 1, &rpn.dlci);
rpn.param_mask = RFCOMM_RPN_PM_ALL;
} else {
DPRINTF("Bad RPN length (%d)\n", m->m_pkthdr.len);
return;
}
mask = 0;
if (rpn.param_mask & RFCOMM_RPN_PM_RATE)
mask |= RFCOMM_RPN_PM_RATE;
if (rpn.param_mask & RFCOMM_RPN_PM_DATA
&& RFCOMM_RPN_DATA_BITS(rpn.line_settings) == RFCOMM_RPN_DATA_8)
mask |= RFCOMM_RPN_PM_DATA;
if (rpn.param_mask & RFCOMM_RPN_PM_STOP
&& RFCOMM_RPN_STOP_BITS(rpn.line_settings) == RFCOMM_RPN_STOP_1)
mask |= RFCOMM_RPN_PM_STOP;
if (rpn.param_mask & RFCOMM_RPN_PM_PARITY
&& RFCOMM_RPN_PARITY(rpn.line_settings) == RFCOMM_RPN_PARITY_NONE)
mask |= RFCOMM_RPN_PM_PARITY;
if (rpn.param_mask & RFCOMM_RPN_PM_XON
&& rpn.xon_char == RFCOMM_RPN_XON_CHAR)
mask |= RFCOMM_RPN_PM_XON;
if (rpn.param_mask & RFCOMM_RPN_PM_XOFF
&& rpn.xoff_char == RFCOMM_RPN_XOFF_CHAR)
mask |= RFCOMM_RPN_PM_XOFF;
if (rpn.param_mask & RFCOMM_RPN_PM_FLOW
&& rpn.flow_control == RFCOMM_RPN_FLOW_NONE)
mask |= RFCOMM_RPN_PM_FLOW;
rpn.param_mask = htole16(mask);
rfcomm_session_send_mcc(rs, 0, RFCOMM_MCC_RPN, &rpn, sizeof(rpn));
}
static void
rfcomm_session_recv_mcc_rls(struct rfcomm_session *rs, int cr, struct mbuf *m)
{
struct rfcomm_mcc_rls rls;
if (cr == 0)
return;
if (m->m_pkthdr.len != sizeof(rls)) {
DPRINTF("Bad RLS length %d\n", m->m_pkthdr.len);
return;
}
m_copydata(m, 0, sizeof(rls), &rls);
rls.address |= 0x03;
rls.status &= 0x0f;
rfcomm_session_send_mcc(rs, 0, RFCOMM_MCC_RLS, &rls, sizeof(rls));
}
static void
rfcomm_session_recv_mcc_pn(struct rfcomm_session *rs, int cr, struct mbuf *m)
{
struct rfcomm_dlc *dlc;
struct rfcomm_mcc_pn pn;
int err;
if (m->m_pkthdr.len != sizeof(pn)) {
DPRINTF("Bad PN length %d\n", m->m_pkthdr.len);
return;
}
m_copydata(m, 0, sizeof(pn), &pn);
pn.dlci &= 0x3f;
pn.mtu = le16toh(pn.mtu);
dlc = rfcomm_dlc_lookup(rs, pn.dlci);
if (cr) {
if (dlc == NULL) {
dlc = rfcomm_dlc_newconn(rs, pn.dlci);
if (dlc == NULL)
return;
}
pn.mtu = uimin(pn.mtu, RFCOMM_MTU_MAX);
pn.mtu = uimin(pn.mtu, rs->rs_mtu);
pn.mtu = uimax(pn.mtu, RFCOMM_MTU_MIN);
dlc->rd_mtu = pn.mtu;
pn.mtu = htole16(pn.mtu);
if (dlc->rd_state == RFCOMM_DLC_WAIT_CONNECT
&& (pn.flow_control & 0xf0) == 0xf0) {
rs->rs_flags |= RFCOMM_SESSION_CFC;
dlc->rd_txcred = pn.credits & 0x07;
dlc->rd_rxcred = (dlc->rd_rxsize / dlc->rd_mtu);
dlc->rd_rxcred = uimin(dlc->rd_rxcred,
RFCOMM_CREDITS_DEFAULT);
pn.flow_control = 0xe0;
pn.credits = dlc->rd_rxcred;
} else {
pn.flow_control = 0x00;
pn.credits = 0x00;
}
pn.ack_timer = 0;
pn.max_retrans = 0;
err = rfcomm_session_send_mcc(rs, 0,
RFCOMM_MCC_PN, &pn, sizeof(pn));
if (err)
goto close;
} else {
if (dlc == NULL)
return;
callout_stop(&dlc->rd_timeout);
if (pn.mtu < RFCOMM_MTU_MIN || pn.mtu > dlc->rd_mtu) {
dlc->rd_state = RFCOMM_DLC_WAIT_DISCONNECT;
err = rfcomm_session_send_frame(rs, RFCOMM_FRAME_DISC,
pn.dlci);
if (err)
goto close;
callout_schedule(&dlc->rd_timeout,
rfcomm_ack_timeout * hz);
return;
}
dlc->rd_mtu = pn.mtu;
if (dlc->rd_state != RFCOMM_DLC_WAIT_CONNECT)
return;
if ((pn.flow_control & 0xf0) == 0xe0) {
rs->rs_flags |= RFCOMM_SESSION_CFC;
dlc->rd_txcred = (pn.credits & 0x07);
}
callout_schedule(&dlc->rd_timeout, rfcomm_ack_timeout * hz);
err = rfcomm_dlc_setmode(dlc);
if (err == EINPROGRESS) {
dlc->rd_state = RFCOMM_DLC_WAIT_SEND_SABM;
(*dlc->rd_proto->connecting)(dlc->rd_upper);
return;
}
if (err)
goto close;
err = rfcomm_session_send_frame(rs, RFCOMM_FRAME_SABM, pn.dlci);
if (err)
goto close;
dlc->rd_state = RFCOMM_DLC_WAIT_RECV_UA;
}
return;
close:
rfcomm_dlc_close(dlc, err);
}
static void
rfcomm_session_recv_mcc_nsc(struct rfcomm_session *rs,
int cr, struct mbuf *m)
{
struct rfcomm_dlc *dlc, *next;
next = LIST_FIRST(&rs->rs_dlcs);
while ((dlc = next) != NULL) {
next = LIST_NEXT(dlc, rd_next);
rfcomm_dlc_close(dlc, ECONNABORTED);
}
rfcomm_session_free(rs);
}
int
rfcomm_session_send_frame(struct rfcomm_session *rs, int type, int dlci)
{
struct rfcomm_cmd_hdr *hdr;
struct rfcomm_credit *credit;
struct mbuf *m;
uint8_t fcs, cr;
credit = pool_get(&rfcomm_credit_pool, PR_NOWAIT);
if (credit == NULL)
return ENOMEM;
m = m_gethdr(M_DONTWAIT, MT_DATA);
if (m == NULL) {
pool_put(&rfcomm_credit_pool, credit);
return ENOMEM;
}
if (type == RFCOMM_FRAME_UA || type == RFCOMM_FRAME_DM)
cr = IS_INITIATOR(rs) ? 0 : 1;
else
cr = IS_INITIATOR(rs) ? 1 : 0;
hdr = mtod(m, struct rfcomm_cmd_hdr *);
hdr->address = RFCOMM_MKADDRESS(cr, dlci);
hdr->control = RFCOMM_MKCONTROL(type, 1);
hdr->length = (0x00 << 1) | 0x01;
fcs = 0xff;
fcs = FCS(fcs, hdr->address);
fcs = FCS(fcs, hdr->control);
fcs = FCS(fcs, hdr->length);
fcs = 0xff - fcs;
hdr->fcs = fcs;
m->m_pkthdr.len = m->m_len = sizeof(struct rfcomm_cmd_hdr);
credit->rc_dlc = NULL;
credit->rc_len = m->m_pkthdr.len;
SIMPLEQ_INSERT_TAIL(&rs->rs_credits, credit, rc_next);
DPRINTFN(5, "dlci %d type %2.2x (%d bytes, fcs=%#2.2x)\n",
dlci, type, m->m_pkthdr.len, fcs);
return l2cap_send_pcb(rs->rs_l2cap, m);
}
int
rfcomm_session_send_uih(struct rfcomm_session *rs, struct rfcomm_dlc *dlc,
int credits, struct mbuf *m)
{
struct rfcomm_credit *credit;
struct mbuf *m0 = NULL;
int err, len;
uint8_t fcs, *hdr;
KASSERT(rs != NULL);
len = (m == NULL) ? 0 : m->m_pkthdr.len;
KASSERT(!(credits == 0 && len == 0));
credit = pool_get(&rfcomm_credit_pool, PR_NOWAIT);
if (credit == NULL)
goto nomem;
credit->rc_len = len;
credit->rc_dlc = dlc;
m0 = m_gethdr(M_DONTWAIT, MT_DATA);
if (m0 == NULL)
goto nomem;
m_align(m0, 5);
hdr = mtod(m0, uint8_t *);
*hdr = RFCOMM_MKADDRESS((IS_INITIATOR(rs) ? 1 : 0),
(dlc ? dlc->rd_dlci : 0));
fcs = FCS(0xff, *hdr);
hdr++;
*hdr = RFCOMM_MKCONTROL(RFCOMM_FRAME_UIH, (credits > 0 ? 1 : 0));
fcs = FCS(fcs, *hdr);
hdr++;
if (len < (1 << 7)) {
*hdr++ = ((len << 1) & 0xfe) | 0x01;
} else {
*hdr++ = ((len << 1) & 0xfe);
*hdr++ = ((len >> 7) & 0xff);
}
if (credits > 0)
*hdr++ = (uint8_t)credits;
m0->m_len = hdr - mtod(m0, uint8_t *);
m0->m_next = m;
m = NULL;
m0->m_pkthdr.len = m0->m_len + len;
fcs = 0xff - fcs;
len = m0->m_pkthdr.len;
m_copyback(m0, len, sizeof(fcs), &fcs);
if (m0->m_pkthdr.len != len + sizeof(fcs))
goto nomem;
DPRINTFN(10, "dlci %d, pktlen %d (%d data, %d credits), fcs=%#2.2x\n",
dlc ? dlc->rd_dlci : 0, m0->m_pkthdr.len, credit->rc_len,
credits, fcs);
err = l2cap_send_pcb(rs->rs_l2cap, m0);
if (err)
goto fail;
SIMPLEQ_INSERT_TAIL(&rs->rs_credits, credit, rc_next);
return 0;
nomem:
err = ENOMEM;
m_freem(m0);
m_freem(m);
fail:
if (credit != NULL)
pool_put(&rfcomm_credit_pool, credit);
return err;
}
int
rfcomm_session_send_mcc(struct rfcomm_session *rs, int cr,
uint8_t type, void *data, int len)
{
struct mbuf *m;
uint8_t *hdr;
int hlen;
m = m_gethdr(M_DONTWAIT, MT_DATA);
if (m == NULL)
return ENOMEM;
hdr = mtod(m, uint8_t *);
*hdr++ = RFCOMM_MKMCC_TYPE(cr, type);
if (len < (1 << 7)) {
*hdr++ = ((len << 1) & 0xfe) | 0x01;
} else if (len < (1 << 14)) {
*hdr++ = ((len << 1) & 0xfe);
*hdr++ = ((len >> 6) & 0xfe) | 0x01;
} else if (len < (1 << 15)) {
*hdr++ = ((len << 1) & 0xfe);
*hdr++ = ((len >> 6) & 0xfe);
*hdr++ = ((len >> 13) & 0x02) | 0x01;
} else {
DPRINTF("incredible length! (%d)\n", len);
m_freem(m);
return EMSGSIZE;
}
hlen = hdr - mtod(m, uint8_t *);
if (len > 0) {
m->m_pkthdr.len = m->m_len = MHLEN;
m_copyback(m, hlen, len, data);
if (m->m_pkthdr.len != uimax(MHLEN, hlen + len)) {
m_freem(m);
return ENOMEM;
}
}
m->m_pkthdr.len = hlen + len;
m->m_len = uimin(MHLEN, m->m_pkthdr.len);
DPRINTFN(5, "%s type %2.2x len %d\n",
(cr ? "command" : "response"), type, m->m_pkthdr.len);
return rfcomm_session_send_uih(rs, NULL, 0, m);
}