#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/endian.h>
#include <sys/malloc.h>
#include <sys/mbuf.h>
#include <sys/queue.h>
#include <netgraph/ng_message.h>
#include <netgraph/netgraph.h>
#include <netgraph/bluetooth/include/ng_bluetooth.h>
#include <netgraph/bluetooth/include/ng_hci.h>
#include <netgraph/bluetooth/include/ng_l2cap.h>
#include <netgraph/bluetooth/l2cap/ng_l2cap_var.h>
#include <netgraph/bluetooth/l2cap/ng_l2cap_cmds.h>
#include <netgraph/bluetooth/l2cap/ng_l2cap_evnt.h>
#include <netgraph/bluetooth/l2cap/ng_l2cap_llpi.h>
#include <netgraph/bluetooth/l2cap/ng_l2cap_ulpi.h>
#include <netgraph/bluetooth/l2cap/ng_l2cap_misc.h>
static int ng_l2cap_process_signal_cmd (ng_l2cap_con_p);
static int ng_l2cap_process_lesignal_cmd (ng_l2cap_con_p);
static int ng_l2cap_process_cmd_rej (ng_l2cap_con_p, u_int8_t);
static int ng_l2cap_process_cmd_urq (ng_l2cap_con_p, u_int8_t);
static int ng_l2cap_process_cmd_urs (ng_l2cap_con_p, u_int8_t);
static int ng_l2cap_process_con_req (ng_l2cap_con_p, u_int8_t);
static int ng_l2cap_process_con_rsp (ng_l2cap_con_p, u_int8_t);
static int ng_l2cap_process_cfg_req (ng_l2cap_con_p, u_int8_t);
static int ng_l2cap_process_cfg_rsp (ng_l2cap_con_p, u_int8_t);
static int ng_l2cap_process_discon_req (ng_l2cap_con_p, u_int8_t);
static int ng_l2cap_process_discon_rsp (ng_l2cap_con_p, u_int8_t);
static int ng_l2cap_process_echo_req (ng_l2cap_con_p, u_int8_t);
static int ng_l2cap_process_echo_rsp (ng_l2cap_con_p, u_int8_t);
static int ng_l2cap_process_info_req (ng_l2cap_con_p, u_int8_t);
static int ng_l2cap_process_info_rsp (ng_l2cap_con_p, u_int8_t);
static int send_l2cap_reject
(ng_l2cap_con_p, u_int8_t, u_int16_t, u_int16_t, u_int16_t, u_int16_t);
static int send_l2cap_con_rej
(ng_l2cap_con_p, u_int8_t, u_int16_t, u_int16_t, u_int16_t);
static int send_l2cap_cfg_rsp
(ng_l2cap_con_p, u_int8_t, u_int16_t, u_int16_t, struct mbuf *);
static int send_l2cap_param_urs
(ng_l2cap_con_p , u_int8_t , u_int16_t);
static int get_next_l2cap_opt
(struct mbuf *, int *, ng_l2cap_cfg_opt_p, ng_l2cap_cfg_opt_val_p);
int
ng_l2cap_receive(ng_l2cap_con_p con)
{
ng_l2cap_p l2cap = con->l2cap;
ng_l2cap_hdr_t *hdr = NULL;
int error = 0;
if (con->rx_pkt->m_pkthdr.len < sizeof(*hdr)) {
NG_L2CAP_ERR(
"%s: %s - invalid L2CAP packet. Packet too small, len=%d\n",
__func__, NG_NODE_NAME(l2cap->node),
con->rx_pkt->m_pkthdr.len);
error = EMSGSIZE;
goto drop;
}
NG_L2CAP_M_PULLUP(con->rx_pkt, sizeof(*hdr));
if (con->rx_pkt == NULL)
return (ENOBUFS);
hdr = mtod(con->rx_pkt, ng_l2cap_hdr_t *);
hdr->length = le16toh(hdr->length);
hdr->dcid = le16toh(hdr->dcid);
if (hdr->length != con->rx_pkt->m_pkthdr.len - sizeof(*hdr)) {
NG_L2CAP_ERR(
"%s: %s - invalid L2CAP packet. Payload length mismatch, length=%d, len=%zd\n",
__func__, NG_NODE_NAME(l2cap->node), hdr->length,
con->rx_pkt->m_pkthdr.len - sizeof(*hdr));
error = EMSGSIZE;
goto drop;
}
switch (hdr->dcid) {
case NG_L2CAP_SIGNAL_CID:
m_adj(con->rx_pkt, sizeof(*hdr));
error = ng_l2cap_process_signal_cmd(con);
break;
case NG_L2CAP_LESIGNAL_CID:
m_adj(con->rx_pkt, sizeof(*hdr));
error = ng_l2cap_process_lesignal_cmd(con);
break;
case NG_L2CAP_CLT_CID:
error = ng_l2cap_l2ca_clt_receive(con);
break;
default:
error = ng_l2cap_l2ca_receive(con);
break;
}
return (error);
drop:
NG_FREE_M(con->rx_pkt);
return (error);
}
static int
ng_l2cap_process_signal_cmd(ng_l2cap_con_p con)
{
ng_l2cap_p l2cap = con->l2cap;
ng_l2cap_cmd_hdr_t *hdr = NULL;
struct mbuf *m = NULL;
while (con->rx_pkt != NULL) {
if (con->rx_pkt->m_pkthdr.len < sizeof(*hdr)) {
NG_L2CAP_ERR(
"%s: %s - invalid L2CAP signaling command. Packet too small, len=%d\n",
__func__, NG_NODE_NAME(l2cap->node),
con->rx_pkt->m_pkthdr.len);
NG_FREE_M(con->rx_pkt);
return (EMSGSIZE);
}
NG_L2CAP_M_PULLUP(con->rx_pkt, sizeof(*hdr));
if (con->rx_pkt == NULL)
return (ENOBUFS);
hdr = mtod(con->rx_pkt, ng_l2cap_cmd_hdr_t *);
hdr->length = le16toh(hdr->length);
m_adj(con->rx_pkt, sizeof(*hdr));
if (con->rx_pkt->m_pkthdr.len < hdr->length) {
NG_L2CAP_ERR(
"%s: %s - invalid L2CAP signaling command, code=%#x, ident=%d. " \
"Invalid command length=%d, m_pkthdr.len=%d\n",
__func__, NG_NODE_NAME(l2cap->node),
hdr->code, hdr->ident, hdr->length,
con->rx_pkt->m_pkthdr.len);
NG_FREE_M(con->rx_pkt);
return (EMSGSIZE);
}
if (con->rx_pkt->m_pkthdr.len > hdr->length)
m = m_split(con->rx_pkt, hdr->length, M_NOWAIT);
else
m = NULL;
switch (hdr->code) {
case NG_L2CAP_CMD_REJ:
ng_l2cap_process_cmd_rej(con, hdr->ident);
break;
case NG_L2CAP_CON_REQ:
ng_l2cap_process_con_req(con, hdr->ident);
break;
case NG_L2CAP_CON_RSP:
ng_l2cap_process_con_rsp(con, hdr->ident);
break;
case NG_L2CAP_CFG_REQ:
ng_l2cap_process_cfg_req(con, hdr->ident);
break;
case NG_L2CAP_CFG_RSP:
ng_l2cap_process_cfg_rsp(con, hdr->ident);
break;
case NG_L2CAP_DISCON_REQ:
ng_l2cap_process_discon_req(con, hdr->ident);
break;
case NG_L2CAP_DISCON_RSP:
ng_l2cap_process_discon_rsp(con, hdr->ident);
break;
case NG_L2CAP_ECHO_REQ:
ng_l2cap_process_echo_req(con, hdr->ident);
break;
case NG_L2CAP_ECHO_RSP:
ng_l2cap_process_echo_rsp(con, hdr->ident);
break;
case NG_L2CAP_INFO_REQ:
ng_l2cap_process_info_req(con, hdr->ident);
break;
case NG_L2CAP_INFO_RSP:
ng_l2cap_process_info_rsp(con, hdr->ident);
break;
default:
NG_L2CAP_ERR(
"%s: %s - unknown L2CAP signaling command, code=%#x, ident=%d, length=%d\n",
__func__, NG_NODE_NAME(l2cap->node),
hdr->code, hdr->ident, hdr->length);
send_l2cap_reject(con, hdr->ident,
NG_L2CAP_REJ_NOT_UNDERSTOOD, 0, 0, 0);
NG_FREE_M(con->rx_pkt);
break;
}
con->rx_pkt = m;
}
return (0);
}
static int
ng_l2cap_process_lesignal_cmd(ng_l2cap_con_p con)
{
ng_l2cap_p l2cap = con->l2cap;
ng_l2cap_cmd_hdr_t *hdr = NULL;
struct mbuf *m = NULL;
while (con->rx_pkt != NULL) {
if (con->rx_pkt->m_pkthdr.len < sizeof(*hdr)) {
NG_L2CAP_ERR(
"%s: %s - invalid L2CAP signaling command. Packet too small, len=%d\n",
__func__, NG_NODE_NAME(l2cap->node),
con->rx_pkt->m_pkthdr.len);
NG_FREE_M(con->rx_pkt);
return (EMSGSIZE);
}
NG_L2CAP_M_PULLUP(con->rx_pkt, sizeof(*hdr));
if (con->rx_pkt == NULL)
return (ENOBUFS);
hdr = mtod(con->rx_pkt, ng_l2cap_cmd_hdr_t *);
hdr->length = le16toh(hdr->length);
m_adj(con->rx_pkt, sizeof(*hdr));
if (con->rx_pkt->m_pkthdr.len < hdr->length) {
NG_L2CAP_ERR(
"%s: %s - invalid L2CAP signaling command, code=%#x, ident=%d. " \
"Invalid command length=%d, m_pkthdr.len=%d\n",
__func__, NG_NODE_NAME(l2cap->node),
hdr->code, hdr->ident, hdr->length,
con->rx_pkt->m_pkthdr.len);
NG_FREE_M(con->rx_pkt);
return (EMSGSIZE);
}
if (con->rx_pkt->m_pkthdr.len > hdr->length)
m = m_split(con->rx_pkt, hdr->length, M_NOWAIT);
else
m = NULL;
switch (hdr->code) {
case NG_L2CAP_CMD_REJ:
ng_l2cap_process_cmd_rej(con, hdr->ident);
break;
case NG_L2CAP_CMD_PARAM_UPDATE_REQUEST:
ng_l2cap_process_cmd_urq(con, hdr->ident);
break;
case NG_L2CAP_CMD_PARAM_UPDATE_RESPONSE:
ng_l2cap_process_cmd_urs(con, hdr->ident);
break;
default:
NG_L2CAP_ERR(
"%s: %s - unknown L2CAP signaling command, code=%#x, ident=%d, length=%d\n",
__func__, NG_NODE_NAME(l2cap->node),
hdr->code, hdr->ident, hdr->length);
send_l2cap_reject(con, hdr->ident,
NG_L2CAP_REJ_NOT_UNDERSTOOD, 0, 0, 0);
NG_FREE_M(con->rx_pkt);
break;
}
con->rx_pkt = m;
}
return (0);
}
static int ng_l2cap_process_cmd_urq(ng_l2cap_con_p con, uint8_t ident)
{
send_l2cap_param_urs(con, ident, NG_L2CAP_UPDATE_PARAM_ACCEPT);
NG_FREE_M(con->rx_pkt);
return 0;
}
static int ng_l2cap_process_cmd_urs(ng_l2cap_con_p con, uint8_t ident)
{
NG_FREE_M(con->rx_pkt);
return 0;
}
static int
ng_l2cap_process_cmd_rej(ng_l2cap_con_p con, u_int8_t ident)
{
ng_l2cap_p l2cap = con->l2cap;
ng_l2cap_cmd_rej_cp *cp = NULL;
ng_l2cap_cmd_p cmd = NULL;
NG_L2CAP_M_PULLUP(con->rx_pkt, sizeof(*cp));
if (con->rx_pkt == NULL)
return (ENOBUFS);
cp = mtod(con->rx_pkt, ng_l2cap_cmd_rej_cp *);
cp->reason = le16toh(cp->reason);
cmd = ng_l2cap_cmd_by_ident(con, ident);
if (cmd != NULL) {
if (ng_l2cap_command_untimeout(cmd) != 0) {
NG_FREE_M(con->rx_pkt);
return (ETIMEDOUT);
}
ng_l2cap_unlink_cmd(cmd);
switch (cmd->code) {
case NG_L2CAP_CON_REQ:
ng_l2cap_l2ca_con_rsp(cmd->ch,cmd->token,cp->reason,0);
ng_l2cap_free_chan(cmd->ch);
break;
case NG_L2CAP_CFG_REQ:
ng_l2cap_l2ca_cfg_rsp(cmd->ch, cmd->token, cp->reason);
break;
case NG_L2CAP_DISCON_REQ:
ng_l2cap_l2ca_discon_rsp(cmd->ch,cmd->token,cp->reason);
ng_l2cap_free_chan(cmd->ch);
break;
case NG_L2CAP_ECHO_REQ:
ng_l2cap_l2ca_ping_rsp(cmd->con, cmd->token,
cp->reason, NULL);
break;
case NG_L2CAP_INFO_REQ:
ng_l2cap_l2ca_get_info_rsp(cmd->con, cmd->token,
cp->reason, NULL);
break;
default:
NG_L2CAP_ALERT(
"%s: %s - unexpected L2CAP_CommandRej. Unexpected L2CAP command opcode=%d\n",
__func__, NG_NODE_NAME(l2cap->node), cmd->code);
break;
}
ng_l2cap_free_cmd(cmd);
} else
NG_L2CAP_ERR(
"%s: %s - unexpected L2CAP_CommandRej command. " \
"Requested ident does not exist, ident=%d\n",
__func__, NG_NODE_NAME(l2cap->node), ident);
NG_FREE_M(con->rx_pkt);
return (0);
}
static int
ng_l2cap_process_con_req(ng_l2cap_con_p con, u_int8_t ident)
{
ng_l2cap_p l2cap = con->l2cap;
struct mbuf *m = con->rx_pkt;
ng_l2cap_con_req_cp *cp = NULL;
ng_l2cap_chan_p ch = NULL;
int error = 0;
u_int16_t dcid, psm;
int idtype;
NG_L2CAP_M_PULLUP(m, sizeof(*cp));
if (m == NULL)
return (ENOBUFS);
cp = mtod(m, ng_l2cap_con_req_cp *);
psm = le16toh(cp->psm);
dcid = le16toh(cp->scid);
NG_FREE_M(m);
con->rx_pkt = NULL;
if(dcid == NG_L2CAP_ATT_CID)
idtype = NG_L2CAP_L2CA_IDTYPE_ATT;
else if(dcid == NG_L2CAP_SMP_CID)
idtype = NG_L2CAP_L2CA_IDTYPE_SMP;
else if( con->linktype != NG_HCI_LINK_ACL)
idtype = NG_L2CAP_L2CA_IDTYPE_LE;
else
idtype = NG_L2CAP_L2CA_IDTYPE_BREDR;
ch = ng_l2cap_new_chan(l2cap, con, psm, idtype);
if (ch == NULL)
return (send_l2cap_con_rej(con, ident, 0, dcid,
NG_L2CAP_NO_RESOURCES));
ch->dcid = dcid;
ch->ident = ident;
ch->state = NG_L2CAP_W4_L2CA_CON_RSP;
error = ng_l2cap_l2ca_con_ind(ch);
if (error != 0) {
send_l2cap_con_rej(con, ident, ch->scid, dcid,
(error == ENOMEM)? NG_L2CAP_NO_RESOURCES :
NG_L2CAP_PSM_NOT_SUPPORTED);
ng_l2cap_free_chan(ch);
}
return (error);
}
static int
ng_l2cap_process_con_rsp(ng_l2cap_con_p con, u_int8_t ident)
{
ng_l2cap_p l2cap = con->l2cap;
struct mbuf *m = con->rx_pkt;
ng_l2cap_con_rsp_cp *cp = NULL;
ng_l2cap_cmd_p cmd = NULL;
u_int16_t scid, dcid, result, status;
int error = 0;
NG_L2CAP_M_PULLUP(m, sizeof(*cp));
if (m == NULL)
return (ENOBUFS);
cp = mtod(m, ng_l2cap_con_rsp_cp *);
dcid = le16toh(cp->dcid);
scid = le16toh(cp->scid);
result = le16toh(cp->result);
status = le16toh(cp->status);
NG_FREE_M(m);
con->rx_pkt = NULL;
cmd = ng_l2cap_cmd_by_ident(con, ident);
if (cmd == NULL) {
NG_L2CAP_ERR(
"%s: %s - unexpected L2CAP_ConnectRsp command. ident=%d, con_handle=%d\n",
__func__, NG_NODE_NAME(l2cap->node), ident,
con->con_handle);
return (ENOENT);
}
if (cmd->ch->state != NG_L2CAP_W4_L2CAP_CON_RSP) {
NG_L2CAP_ERR(
"%s: %s - unexpected L2CAP_ConnectRsp. " \
"Invalid channel state, cid=%d, state=%d\n",
__func__, NG_NODE_NAME(l2cap->node), scid,
cmd->ch->state);
goto reject;
}
if (cmd->ch->scid != scid) {
NG_L2CAP_ERR(
"%s: %s - unexpected L2CAP_ConnectRsp. Channel IDs do not match, scid=%d(%d)\n",
__func__, NG_NODE_NAME(l2cap->node), cmd->ch->scid,
scid);
goto reject;
}
if ((error = ng_l2cap_command_untimeout(cmd)) != 0)
return (error);
if (result == NG_L2CAP_PENDING) {
cmd->ch->dcid = dcid;
ng_l2cap_command_timeout(cmd, bluetooth_l2cap_ertx_timeout());
error = ng_l2cap_l2ca_con_rsp(cmd->ch, cmd->token,
result, status);
if (error != 0)
ng_l2cap_free_chan(cmd->ch);
} else {
ng_l2cap_unlink_cmd(cmd);
if (result == NG_L2CAP_SUCCESS) {
cmd->ch->dcid = dcid;
cmd->ch->state = ((cmd->ch->scid == NG_L2CAP_ATT_CID)||
(cmd->ch->scid == NG_L2CAP_SMP_CID))
?
NG_L2CAP_OPEN : NG_L2CAP_CONFIG;
} else
NG_L2CAP_INFO(
"%s: %s - failed to open L2CAP channel, result=%d, status=%d\n",
__func__, NG_NODE_NAME(l2cap->node), result,
status);
error = ng_l2cap_l2ca_con_rsp(cmd->ch, cmd->token,
result, status);
if (error != 0 || result != NG_L2CAP_SUCCESS)
ng_l2cap_free_chan(cmd->ch);
ng_l2cap_free_cmd(cmd);
}
return (error);
reject:
send_l2cap_reject(con, ident, NG_L2CAP_REJ_INVALID_CID, 0, scid, dcid);
return (0);
}
static int
ng_l2cap_process_cfg_req(ng_l2cap_con_p con, u_int8_t ident)
{
ng_l2cap_p l2cap = con->l2cap;
struct mbuf *m = con->rx_pkt;
ng_l2cap_cfg_req_cp *cp = NULL;
ng_l2cap_chan_p ch = NULL;
u_int16_t dcid, respond, result;
ng_l2cap_cfg_opt_t hdr;
ng_l2cap_cfg_opt_val_t val;
int off, error = 0;
con->rx_pkt = NULL;
NG_L2CAP_M_PULLUP(m, sizeof(*cp));
if (m == NULL)
return (ENOBUFS);
cp = mtod(m, ng_l2cap_cfg_req_cp *);
dcid = le16toh(cp->dcid);
respond = NG_L2CAP_OPT_CFLAG(le16toh(cp->flags));
m_adj(m, sizeof(*cp));
ch = ng_l2cap_chan_by_scid(l2cap, dcid, NG_L2CAP_L2CA_IDTYPE_BREDR);
if (ch == NULL) {
NG_L2CAP_ERR(
"%s: %s - unexpected L2CAP_ConfigReq command. " \
"Channel does not exist, cid=%d\n",
__func__, NG_NODE_NAME(l2cap->node), dcid);
goto reject;
}
if (ch->state != NG_L2CAP_CONFIG && ch->state != NG_L2CAP_OPEN) {
NG_L2CAP_ERR(
"%s: %s - unexpected L2CAP_ConfigReq. " \
"Invalid channel state, cid=%d, state=%d\n",
__func__, NG_NODE_NAME(l2cap->node), dcid, ch->state);
goto reject;
}
if (ch->state == NG_L2CAP_OPEN) {
ch->cfg_state = 0;
ch->state = NG_L2CAP_CONFIG;
}
for (result = 0, off = 0; ; ) {
error = get_next_l2cap_opt(m, &off, &hdr, &val);
if (error == 0) {
NG_FREE_M(m);
break;
} else if (error > 0) {
switch (hdr.type) {
case NG_L2CAP_OPT_MTU:
ch->omtu = val.mtu;
break;
case NG_L2CAP_OPT_FLUSH_TIMO:
ch->flush_timo = val.flush_timo;
break;
case NG_L2CAP_OPT_QOS:
bcopy(&val.flow, &ch->iflow, sizeof(ch->iflow));
break;
default:
break;
}
} else {
respond = 1;
if (error == -3) {
m_adj(m, off - sizeof(hdr));
m->m_pkthdr.len = sizeof(hdr) + hdr.length;
result = NG_L2CAP_UNKNOWN_OPTION;
} else {
NG_FREE_M(m);
result = NG_L2CAP_REJECT;
}
break;
}
}
if (respond) {
error = send_l2cap_cfg_rsp(con, ident, ch->dcid, result, m);
if (error != 0) {
ng_l2cap_l2ca_discon_ind(ch);
ng_l2cap_free_chan(ch);
}
} else {
ch->ident = ident;
error = ng_l2cap_l2ca_cfg_ind(ch);
if (error != 0)
ng_l2cap_free_chan(ch);
}
return (error);
reject:
NG_FREE_M(m);
send_l2cap_reject(con, ident, NG_L2CAP_REJ_INVALID_CID, 0, 0, dcid);
return (0);
}
static int
ng_l2cap_process_cfg_rsp(ng_l2cap_con_p con, u_int8_t ident)
{
ng_l2cap_p l2cap = con->l2cap;
struct mbuf *m = con->rx_pkt;
ng_l2cap_cfg_rsp_cp *cp = NULL;
ng_l2cap_cmd_p cmd = NULL;
u_int16_t scid, cflag, result;
ng_l2cap_cfg_opt_t hdr;
ng_l2cap_cfg_opt_val_t val;
int off, error = 0;
con->rx_pkt = NULL;
NG_L2CAP_M_PULLUP(m, sizeof(*cp));
if (m == NULL)
return (ENOBUFS);
cp = mtod(m, ng_l2cap_cfg_rsp_cp *);
scid = le16toh(cp->scid);
cflag = NG_L2CAP_OPT_CFLAG(le16toh(cp->flags));
result = le16toh(cp->result);
m_adj(m, sizeof(*cp));
cmd = ng_l2cap_cmd_by_ident(con, ident);
if (cmd == NULL) {
NG_L2CAP_ERR(
"%s: %s - unexpected L2CAP_ConfigRsp command. ident=%d, con_handle=%d\n",
__func__, NG_NODE_NAME(l2cap->node), ident,
con->con_handle);
NG_FREE_M(m);
return (ENOENT);
}
if (cmd->ch->scid != scid) {
NG_L2CAP_ERR(
"%s: %s - unexpected L2CAP_ConfigRsp. " \
"Channel ID does not match, scid=%d(%d)\n",
__func__, NG_NODE_NAME(l2cap->node), cmd->ch->scid,
scid);
goto reject;
}
if (cmd->ch->state != NG_L2CAP_CONFIG) {
NG_L2CAP_ERR(
"%s: %s - unexpected L2CAP_ConfigRsp. " \
"Invalid channel state, scid=%d, state=%d\n",
__func__, NG_NODE_NAME(l2cap->node), cmd->ch->scid,
cmd->ch->state);
goto reject;
}
if ((error = ng_l2cap_command_untimeout(cmd)) != 0) {
NG_FREE_M(m);
return (error);
}
for (off = 0; ; ) {
error = get_next_l2cap_opt(m, &off, &hdr, &val);
if (error == 0)
break;
else if (error > 0) {
switch (hdr.type) {
case NG_L2CAP_OPT_MTU:
cmd->ch->imtu = val.mtu;
break;
case NG_L2CAP_OPT_FLUSH_TIMO:
cmd->ch->flush_timo = val.flush_timo;
break;
case NG_L2CAP_OPT_QOS:
bcopy(&val.flow, &cmd->ch->oflow,
sizeof(cmd->ch->oflow));
break;
default:
break;
}
} else {
NG_L2CAP_ALERT(
"%s: %s - failed to parse configuration options, error=%d\n",
__func__, NG_NODE_NAME(l2cap->node), error);
result = NG_L2CAP_UNKNOWN;
cflag = 0;
break;
}
}
NG_FREE_M(m);
if (cflag)
ng_l2cap_command_timeout(cmd, bluetooth_l2cap_rtx_timeout());
else {
ng_l2cap_unlink_cmd(cmd);
error = ng_l2cap_l2ca_cfg_rsp(cmd->ch, cmd->token, result);
if (error != 0) {
NG_L2CAP_ERR(
"%s: %s - failed to send L2CA_Config response, error=%d\n",
__func__, NG_NODE_NAME(l2cap->node), error);
ng_l2cap_free_chan(cmd->ch);
}
ng_l2cap_free_cmd(cmd);
}
return (error);
reject:
NG_FREE_M(m);
send_l2cap_reject(con, ident, NG_L2CAP_REJ_INVALID_CID, 0, scid, 0);
return (0);
}
static int
ng_l2cap_process_discon_req(ng_l2cap_con_p con, u_int8_t ident)
{
ng_l2cap_p l2cap = con->l2cap;
ng_l2cap_discon_req_cp *cp = NULL;
ng_l2cap_chan_p ch = NULL;
ng_l2cap_cmd_p cmd = NULL;
u_int16_t scid, dcid;
NG_L2CAP_M_PULLUP(con->rx_pkt, sizeof(*cp));
if (con->rx_pkt == NULL)
return (ENOBUFS);
cp = mtod(con->rx_pkt, ng_l2cap_discon_req_cp *);
dcid = le16toh(cp->dcid);
scid = le16toh(cp->scid);
NG_FREE_M(con->rx_pkt);
ch = ng_l2cap_chan_by_scid(l2cap, dcid, NG_L2CAP_L2CA_IDTYPE_BREDR);
if (ch == NULL) {
NG_L2CAP_ERR(
"%s: %s - unexpected L2CAP_DisconnectReq message. " \
"Channel does not exist, cid=%d\n",
__func__, NG_NODE_NAME(l2cap->node), dcid);
goto reject;
}
if (ch->state != NG_L2CAP_OPEN && ch->state != NG_L2CAP_CONFIG &&
ch->state != NG_L2CAP_W4_L2CAP_DISCON_RSP) {
NG_L2CAP_ERR(
"%s: %s - unexpected L2CAP_DisconnectReq. " \
"Invalid channel state, cid=%d, state=%d\n",
__func__, NG_NODE_NAME(l2cap->node), dcid, ch->state);
goto reject;
}
if (ch->dcid != scid || ch->scid != dcid) {
NG_L2CAP_ERR(
"%s: %s - unexpected L2CAP_DisconnectReq. " \
"Channel IDs does not match, channel: scid=%d, dcid=%d, " \
"request: scid=%d, dcid=%d\n",
__func__, NG_NODE_NAME(l2cap->node), ch->scid, ch->dcid,
scid, dcid);
goto reject;
}
if (ch->state != NG_L2CAP_W4_L2CAP_DISCON_RSP) {
ng_l2cap_l2ca_discon_ind(ch);
ng_l2cap_free_chan(ch);
}
cmd = ng_l2cap_new_cmd(con, NULL, ident, NG_L2CAP_DISCON_RSP, 0);
if (cmd == NULL)
return (ENOMEM);
_ng_l2cap_discon_rsp(cmd->aux, ident, dcid, scid);
if (cmd->aux == NULL) {
ng_l2cap_free_cmd(cmd);
return (ENOBUFS);
}
ng_l2cap_link_cmd(con, cmd);
ng_l2cap_lp_deliver(con);
return (0);
reject:
send_l2cap_reject(con, ident, NG_L2CAP_REJ_INVALID_CID, 0, scid, dcid);
return (0);
}
static int
ng_l2cap_process_discon_rsp(ng_l2cap_con_p con, u_int8_t ident)
{
ng_l2cap_p l2cap = con->l2cap;
ng_l2cap_discon_rsp_cp *cp = NULL;
ng_l2cap_cmd_p cmd = NULL;
u_int16_t scid, dcid;
int error = 0;
NG_L2CAP_M_PULLUP(con->rx_pkt, sizeof(*cp));
if (con->rx_pkt == NULL)
return (ENOBUFS);
cp = mtod(con->rx_pkt, ng_l2cap_discon_rsp_cp *);
dcid = le16toh(cp->dcid);
scid = le16toh(cp->scid);
NG_FREE_M(con->rx_pkt);
cmd = ng_l2cap_cmd_by_ident(con, ident);
if (cmd == NULL) {
NG_L2CAP_ERR(
"%s: %s - unexpected L2CAP_DisconnectRsp command. ident=%d, con_handle=%d\n",
__func__, NG_NODE_NAME(l2cap->node), ident,
con->con_handle);
goto out;
}
if (cmd->ch->state != NG_L2CAP_W4_L2CAP_DISCON_RSP) {
NG_L2CAP_ERR(
"%s: %s - unexpected L2CAP_DisconnectRsp. " \
"Invalid channel state, cid=%d, state=%d\n",
__func__, NG_NODE_NAME(l2cap->node), scid,
cmd->ch->state);
goto out;
}
if (cmd->ch->scid != scid || cmd->ch->dcid != dcid) {
NG_L2CAP_ERR(
"%s: %s - unexpected L2CAP_DisconnectRsp. " \
"Channel IDs do not match, scid=%d(%d), dcid=%d(%d)\n",
__func__, NG_NODE_NAME(l2cap->node), cmd->ch->scid,
scid, cmd->ch->dcid, dcid);
goto out;
}
if ((error = ng_l2cap_command_untimeout(cmd)) != 0)
goto out;
error = ng_l2cap_l2ca_discon_rsp(cmd->ch, cmd->token, NG_L2CAP_SUCCESS);
ng_l2cap_free_chan(cmd->ch);
out:
return (error);
}
static int
ng_l2cap_process_echo_req(ng_l2cap_con_p con, u_int8_t ident)
{
ng_l2cap_p l2cap = con->l2cap;
ng_l2cap_cmd_hdr_t *hdr = NULL;
ng_l2cap_cmd_p cmd = NULL;
con->rx_pkt = ng_l2cap_prepend(con->rx_pkt, sizeof(*hdr));
if (con->rx_pkt == NULL) {
NG_L2CAP_ALERT(
"%s: %s - ng_l2cap_prepend() failed, size=%zd\n",
__func__, NG_NODE_NAME(l2cap->node), sizeof(*hdr));
return (ENOBUFS);
}
hdr = mtod(con->rx_pkt, ng_l2cap_cmd_hdr_t *);
hdr->code = NG_L2CAP_ECHO_RSP;
hdr->ident = ident;
hdr->length = htole16(con->rx_pkt->m_pkthdr.len - sizeof(*hdr));
cmd = ng_l2cap_new_cmd(con, NULL, ident, NG_L2CAP_ECHO_RSP, 0);
if (cmd == NULL) {
NG_FREE_M(con->rx_pkt);
return (ENOBUFS);
}
cmd->aux = con->rx_pkt;
con->rx_pkt = NULL;
ng_l2cap_link_cmd(con, cmd);
ng_l2cap_lp_deliver(con);
return (0);
}
static int
ng_l2cap_process_echo_rsp(ng_l2cap_con_p con, u_int8_t ident)
{
ng_l2cap_p l2cap = con->l2cap;
ng_l2cap_cmd_p cmd = NULL;
int error = 0;
cmd = ng_l2cap_cmd_by_ident(con, ident);
if (cmd != NULL) {
if ((error = ng_l2cap_command_untimeout(cmd)) != 0) {
NG_FREE_M(con->rx_pkt);
return (error);
}
ng_l2cap_unlink_cmd(cmd);
error = ng_l2cap_l2ca_ping_rsp(cmd->con, cmd->token,
NG_L2CAP_SUCCESS, con->rx_pkt);
ng_l2cap_free_cmd(cmd);
con->rx_pkt = NULL;
} else {
NG_L2CAP_ERR(
"%s: %s - unexpected L2CAP_EchoRsp command. " \
"Requested ident does not exist, ident=%d\n",
__func__, NG_NODE_NAME(l2cap->node), ident);
NG_FREE_M(con->rx_pkt);
}
return (error);
}
static int
ng_l2cap_process_info_req(ng_l2cap_con_p con, u_int8_t ident)
{
ng_l2cap_p l2cap = con->l2cap;
ng_l2cap_cmd_p cmd = NULL;
u_int16_t type;
NG_L2CAP_M_PULLUP(con->rx_pkt, sizeof(ng_l2cap_info_req_cp));
if (con->rx_pkt == NULL)
return (ENOBUFS);
type = le16toh(mtod(con->rx_pkt, ng_l2cap_info_req_cp *)->type);
NG_FREE_M(con->rx_pkt);
cmd = ng_l2cap_new_cmd(con, NULL, ident, NG_L2CAP_INFO_RSP, 0);
if (cmd == NULL)
return (ENOMEM);
switch (type) {
case NG_L2CAP_CONNLESS_MTU:
_ng_l2cap_info_rsp(cmd->aux, ident, NG_L2CAP_CONNLESS_MTU,
NG_L2CAP_SUCCESS, NG_L2CAP_MTU_DEFAULT);
break;
default:
_ng_l2cap_info_rsp(cmd->aux, ident, type,
NG_L2CAP_NOT_SUPPORTED, 0);
break;
}
if (cmd->aux == NULL) {
ng_l2cap_free_cmd(cmd);
return (ENOBUFS);
}
ng_l2cap_link_cmd(con, cmd);
ng_l2cap_lp_deliver(con);
return (0);
}
static int
ng_l2cap_process_info_rsp(ng_l2cap_con_p con, u_int8_t ident)
{
ng_l2cap_p l2cap = con->l2cap;
ng_l2cap_info_rsp_cp *cp = NULL;
ng_l2cap_cmd_p cmd = NULL;
int error = 0;
NG_L2CAP_M_PULLUP(con->rx_pkt, sizeof(*cp));
if (con->rx_pkt == NULL)
return (ENOBUFS);
cp = mtod(con->rx_pkt, ng_l2cap_info_rsp_cp *);
cp->type = le16toh(cp->type);
cp->result = le16toh(cp->result);
m_adj(con->rx_pkt, sizeof(*cp));
cmd = ng_l2cap_cmd_by_ident(con, ident);
if (cmd == NULL) {
NG_L2CAP_ERR(
"%s: %s - unexpected L2CAP_InfoRsp command. " \
"Requested ident does not exist, ident=%d\n",
__func__, NG_NODE_NAME(l2cap->node), ident);
NG_FREE_M(con->rx_pkt);
return (ENOENT);
}
if ((error = ng_l2cap_command_untimeout(cmd)) != 0) {
NG_FREE_M(con->rx_pkt);
return (error);
}
ng_l2cap_unlink_cmd(cmd);
if (cp->result == NG_L2CAP_SUCCESS) {
switch (cp->type) {
case NG_L2CAP_CONNLESS_MTU:
if (con->rx_pkt->m_pkthdr.len == sizeof(u_int16_t))
*mtod(con->rx_pkt, u_int16_t *) =
le16toh(*mtod(con->rx_pkt,u_int16_t *));
else {
cp->result = NG_L2CAP_UNKNOWN;
NG_L2CAP_ERR(
"%s: %s - invalid L2CAP_InfoRsp command. " \
"Bad connectionless MTU parameter, len=%d\n",
__func__, NG_NODE_NAME(l2cap->node),
con->rx_pkt->m_pkthdr.len);
}
break;
default:
NG_L2CAP_WARN(
"%s: %s - invalid L2CAP_InfoRsp command. Unknown info type=%d\n",
__func__, NG_NODE_NAME(l2cap->node), cp->type);
break;
}
}
error = ng_l2cap_l2ca_get_info_rsp(cmd->con, cmd->token,
cp->result, con->rx_pkt);
ng_l2cap_free_cmd(cmd);
con->rx_pkt = NULL;
return (error);
}
static int
send_l2cap_reject(ng_l2cap_con_p con, u_int8_t ident, u_int16_t reason,
u_int16_t mtu, u_int16_t scid, u_int16_t dcid)
{
ng_l2cap_cmd_p cmd = NULL;
cmd = ng_l2cap_new_cmd(con, NULL, ident, NG_L2CAP_CMD_REJ, 0);
if (cmd == NULL)
return (ENOMEM);
_ng_l2cap_cmd_rej(cmd->aux, cmd->ident, reason, mtu, scid, dcid);
if (cmd->aux == NULL) {
ng_l2cap_free_cmd(cmd);
return (ENOBUFS);
}
ng_l2cap_link_cmd(con, cmd);
ng_l2cap_lp_deliver(con);
return (0);
}
static int
send_l2cap_con_rej(ng_l2cap_con_p con, u_int8_t ident, u_int16_t scid,
u_int16_t dcid, u_int16_t result)
{
ng_l2cap_cmd_p cmd = NULL;
cmd = ng_l2cap_new_cmd(con, NULL, ident, NG_L2CAP_CON_RSP, 0);
if (cmd == NULL)
return (ENOMEM);
_ng_l2cap_con_rsp(cmd->aux, cmd->ident, scid, dcid, result, 0);
if (cmd->aux == NULL) {
ng_l2cap_free_cmd(cmd);
return (ENOBUFS);
}
ng_l2cap_link_cmd(con, cmd);
ng_l2cap_lp_deliver(con);
return (0);
}
static int
send_l2cap_cfg_rsp(ng_l2cap_con_p con, u_int8_t ident, u_int16_t scid,
u_int16_t result, struct mbuf *opt)
{
ng_l2cap_cmd_p cmd = NULL;
cmd = ng_l2cap_new_cmd(con, NULL, ident, NG_L2CAP_CFG_RSP, 0);
if (cmd == NULL) {
NG_FREE_M(opt);
return (ENOMEM);
}
_ng_l2cap_cfg_rsp(cmd->aux, cmd->ident, scid, 0, result, opt);
if (cmd->aux == NULL) {
ng_l2cap_free_cmd(cmd);
return (ENOBUFS);
}
ng_l2cap_link_cmd(con, cmd);
ng_l2cap_lp_deliver(con);
return (0);
}
static int
send_l2cap_param_urs(ng_l2cap_con_p con, u_int8_t ident,
u_int16_t result)
{
ng_l2cap_cmd_p cmd = NULL;
cmd = ng_l2cap_new_cmd(con, NULL, ident,
NG_L2CAP_CMD_PARAM_UPDATE_RESPONSE,
0);
if (cmd == NULL) {
return (ENOMEM);
}
_ng_l2cap_cmd_urs(cmd->aux, cmd->ident, result);
if (cmd->aux == NULL) {
ng_l2cap_free_cmd(cmd);
return (ENOBUFS);
}
ng_l2cap_link_cmd(con, cmd);
ng_l2cap_lp_deliver(con);
return (0);
}
static int
get_next_l2cap_opt(struct mbuf *m, int *off, ng_l2cap_cfg_opt_p hdr,
ng_l2cap_cfg_opt_val_p val)
{
int hint, len = m->m_pkthdr.len - (*off);
if (len == 0)
return (0);
if (len < 0 || len < sizeof(*hdr))
return (-1);
m_copydata(m, *off, sizeof(*hdr), (caddr_t) hdr);
*off += sizeof(*hdr);
len -= sizeof(*hdr);
hint = NG_L2CAP_OPT_HINT(hdr->type);
hdr->type &= NG_L2CAP_OPT_HINT_MASK;
switch (hdr->type) {
case NG_L2CAP_OPT_MTU:
if (hdr->length != NG_L2CAP_OPT_MTU_SIZE || len < hdr->length)
return (-2);
m_copydata(m, *off, NG_L2CAP_OPT_MTU_SIZE, (caddr_t) val);
val->mtu = le16toh(val->mtu);
*off += NG_L2CAP_OPT_MTU_SIZE;
break;
case NG_L2CAP_OPT_FLUSH_TIMO:
if (hdr->length != NG_L2CAP_OPT_FLUSH_TIMO_SIZE ||
len < hdr->length)
return (-2);
m_copydata(m, *off, NG_L2CAP_OPT_FLUSH_TIMO_SIZE, (caddr_t)val);
val->flush_timo = le16toh(val->flush_timo);
*off += NG_L2CAP_OPT_FLUSH_TIMO_SIZE;
break;
case NG_L2CAP_OPT_QOS:
if (hdr->length != NG_L2CAP_OPT_QOS_SIZE || len < hdr->length)
return (-2);
m_copydata(m, *off, NG_L2CAP_OPT_QOS_SIZE, (caddr_t) val);
val->flow.token_rate = le32toh(val->flow.token_rate);
val->flow.token_bucket_size =
le32toh(val->flow.token_bucket_size);
val->flow.peak_bandwidth = le32toh(val->flow.peak_bandwidth);
val->flow.latency = le32toh(val->flow.latency);
val->flow.delay_variation = le32toh(val->flow.delay_variation);
*off += NG_L2CAP_OPT_QOS_SIZE;
break;
default:
if (hint)
*off += hdr->length;
else
return (-3);
break;
}
return (1);
}