root/sys/netgraph/bluetooth/l2cap/ng_l2cap_evnt.c
/*
 * ng_l2cap_evnt.c
 */

/*-
 * SPDX-License-Identifier: BSD-2-Clause
 *
 * Copyright (c) Maksim Yevmenkin <m_evmenkin@yahoo.com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * $Id: ng_l2cap_evnt.c,v 1.5 2003/09/08 19:11:45 max Exp $
 */

#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>

/******************************************************************************
 ******************************************************************************
 **                    L2CAP events processing module
 ******************************************************************************
 ******************************************************************************/

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);

/*
 * Receive L2CAP packet. First get L2CAP header and verify packet. Than
 * get destination channel and process packet.
 */

int
ng_l2cap_receive(ng_l2cap_con_p con)
{
        ng_l2cap_p       l2cap = con->l2cap;
        ng_l2cap_hdr_t  *hdr = NULL;
        int              error = 0;

        /* Check packet */
        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;
        }

        /* Get L2CAP header */
        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);

        /* Check payload size */
        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;
        }

        /* Process packet */
        switch (hdr->dcid) {
        case NG_L2CAP_SIGNAL_CID: /* L2CAP command */
                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: /* Connectionless packet */
                error = ng_l2cap_l2ca_clt_receive(con);
                break;

        default: /* Data packet */
                error = ng_l2cap_l2ca_receive(con);
                break;
        }

        return (error);
drop:
        NG_FREE_M(con->rx_pkt);

        return (error);
} /* ng_l2cap_receive */

/*
 * Process L2CAP signaling command. We already know that destination channel ID
 * is 0x1 that means we have received signaling command from peer's L2CAP layer.
 * So get command header, decode and process it.
 *
 * XXX do we need to check signaling MTU here?
 */

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) {
                /* Verify packet length */
                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);
                }

                /* Get signaling command */
                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));

                /* Verify command length */
                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);
                }

                /* Get the command, save the rest (if any) */
                if (con->rx_pkt->m_pkthdr.len > hdr->length)
                        m = m_split(con->rx_pkt, hdr->length, M_NOWAIT);
                else
                        m = NULL;

                /* Process command */
                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_CommandRej. Do not really care 
                         * about the result
                         */

                        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);
} /* ng_l2cap_process_signal_cmd */
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) {
                /* Verify packet length */
                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);
                }

                /* Get signaling command */
                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));

                /* Verify command length */
                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);
                }

                /* Get the command, save the rest (if any) */
                if (con->rx_pkt->m_pkthdr.len > hdr->length)
                        m = m_split(con->rx_pkt, hdr->length, M_NOWAIT);
                else
                        m = NULL;

                /* Process command */
                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_CommandRej. Do not really care 
                         * about the result
                         */

                        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);
} /* ng_l2cap_process_signal_cmd */
/*Update Paramater Request*/
static int ng_l2cap_process_cmd_urq(ng_l2cap_con_p con, uint8_t ident)
{
        /* We do not implement parameter negotiation for now. */
        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)
{
        /* We only support master side yet .*/
        //send_l2cap_reject(con,ident ... );

        NG_FREE_M(con->rx_pkt);
        return 0;
}

/*
 * Process L2CAP_CommandRej command
 */

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;

        /* Get command parameters */
        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);

        /* Check if we have pending command descriptor */
        cmd = ng_l2cap_cmd_by_ident(con, ident);
        if (cmd != NULL) {
                /* If command timeout already happened then ignore reject */
                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); /* XXX free channel */
                        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);
} /* ng_l2cap_process_cmd_rej */

/*
 * Process L2CAP_ConnectReq command
 */

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;

        /* Get command parameters */
        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;

        /*
         * Create new channel and send L2CA_ConnectInd notification 
         * to the upper layer protocol.
         */

        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));

        /* Update channel IDs */
        ch->dcid = dcid;

        /* Sent L2CA_ConnectInd notification to the upper layer */
        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);
} /* ng_l2cap_process_con_req */

/*
 * Process L2CAP_ConnectRsp command
 */

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;

        /* Get command parameters */
        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;

        /* Check if we have pending command descriptor */
        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);
        }

        /* Verify channel state, if invalid - do nothing */
        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;
        }

        /* Verify CIDs and send reject if does not match */
        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;
        }

        /*
         * Looks good. We got confirmation from our peer. Now process
         * it. First disable RTX timer. Then check the result and send 
         * notification to the upper layer. If command timeout already
         * happened then ignore response.
         */

        if ((error = ng_l2cap_command_untimeout(cmd)) != 0)
                return (error);

        if (result == NG_L2CAP_PENDING) {
                /*
                 * Our peer wants more time to complete connection. We shall 
                 * start ERTX timer and wait. Keep command in the list.
                 */

                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) {
                        /*
                         * Channel is open. Complete command and move to CONFIG
                         * state. Since we have sent positive confirmation we 
                         * expect to receive L2CA_Config request from the upper
                         * layer protocol.
                         */

                        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
                        /* There was an error, so close the channel */
                        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);

                /* XXX do we have to remove the channel on error? */
                if (error != 0 || result != NG_L2CAP_SUCCESS)
                        ng_l2cap_free_chan(cmd->ch);

                ng_l2cap_free_cmd(cmd);
        }

        return (error);

reject:
        /* Send reject. Do not really care about the result */
        send_l2cap_reject(con, ident, NG_L2CAP_REJ_INVALID_CID, 0, scid, dcid);

        return (0);
} /* ng_l2cap_process_con_rsp */

/*
 * Process L2CAP_ConfigReq command
 */

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;

        /* Get command parameters */
        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));

        /* Check if we have this channel and it is in valid state */
        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;
        }

        /* Verify channel state */
        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) { /* Re-configuration */
                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) { /* We done with this packet */
                        NG_FREE_M(m);
                        break;
                } else if (error > 0) { /* Got option */
                        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: /* Ignore unknown hint option */
                                break;
                        }
                } else { /* Oops, something is wrong */
                        respond = 1;

                        if (error == -3) {
                                /*
                                 * Adjust mbuf so we can get to the start
                                 * of the first option we did not like.
                                 */

                                m_adj(m, off - sizeof(hdr));
                                m->m_pkthdr.len = sizeof(hdr) + hdr.length;

                                result = NG_L2CAP_UNKNOWN_OPTION;
                        } else {
                                /* XXX FIXME Send other reject codes? */
                                NG_FREE_M(m);
                                result = NG_L2CAP_REJECT;
                        }

                        break;
                }
        }

        /*
         * Now check and see if we have to respond. If everything was OK then 
         * respond contain "C flag" and (if set) we will respond with empty 
         * packet and will wait for more options. 
         * 
         * Other case is that we did not like peer's options and will respond 
         * with L2CAP_Config response command with Reject error code. 
         * 
         * When "respond == 0" than we have received all options and we will 
         * sent L2CA_ConfigInd event to the upper layer protocol.
         */

        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 {
                /* Send L2CA_ConfigInd event to the upper layer protocol */
                ch->ident = ident;
                error = ng_l2cap_l2ca_cfg_ind(ch);
                if (error != 0)
                        ng_l2cap_free_chan(ch);
        }

        return (error);

reject:
        /* Send reject. Do not really care about the result */
        NG_FREE_M(m);

        send_l2cap_reject(con, ident, NG_L2CAP_REJ_INVALID_CID, 0, 0, dcid);

        return (0);
} /* ng_l2cap_process_cfg_req */

/*
 * Process L2CAP_ConfigRsp command
 */

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;

        /* Get command parameters */
        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));

        /* Check if we have this command */
        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);
        }

        /* Verify CIDs and send reject if does not match */
        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;
        }

        /* Verify channel state and reject if invalid */
        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;
        }

        /*
         * Looks like it is our response, so process it. First parse options,
         * then verify C flag. If it is set then we shall expect more 
         * configuration options from the peer and we will wait. Otherwise we 
         * have received all options and we will send L2CA_ConfigRsp event to
         * the upper layer protocol. If command timeout already happened then
         * ignore response.
         */

        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) /* We done with this packet */
                        break;
                else if (error > 0) { /* Got option */
                        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: /* Ignore unknown hint option */
                                break;
                        }
                } else {
                        /*
                         * XXX FIXME What to do here?
                         *
                         * This is really BAD :( options packet was broken, or 
                         * peer sent us option that we did not understand. Let 
                         * upper layer know and do not wait for more options.
                         */

                        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) /* Restart timer and wait for more options */
                ng_l2cap_command_timeout(cmd, bluetooth_l2cap_rtx_timeout());
        else {
                ng_l2cap_unlink_cmd(cmd);

                /* Send L2CA_Config response to the upper layer protocol */
                error = ng_l2cap_l2ca_cfg_rsp(cmd->ch, cmd->token, result);
                if (error != 0) {
                        /*
                         * XXX FIXME what to do here? we were not able to send
                         * response to the upper layer protocol, so for now 
                         * just close the channel. Send L2CAP_Disconnect to 
                         * remote peer?
                         */

                        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:
        /* Send reject. Do not really care about the result */
        NG_FREE_M(m);

        send_l2cap_reject(con, ident, NG_L2CAP_REJ_INVALID_CID, 0, scid, 0);

        return (0);
} /* ng_l2cap_process_cfg_rsp */

/*
 * Process L2CAP_DisconnectReq command
 */

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;

        /* Get command parameters */
        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);

        /* Check if we have this channel and it is in valid state */
        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;
        }

        /* XXX Verify channel state and reject if invalid -- is that true? */
        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;
        }

        /* Match destination channel ID */
        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;
        }

        /*
         * Looks good, so notify upper layer protocol that channel is about 
         * to be disconnected and send L2CA_DisconnectInd message. Then respond
         * with L2CAP_DisconnectRsp.
         */

        if (ch->state != NG_L2CAP_W4_L2CAP_DISCON_RSP) {
                ng_l2cap_l2ca_discon_ind(ch); /* do not care about result */
                ng_l2cap_free_chan(ch);
        }

        /* Send L2CAP_DisconnectRsp */
        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);
        }

        /* Link command to the queue */
        ng_l2cap_link_cmd(con, cmd);
        ng_l2cap_lp_deliver(con);

        return (0);

reject:
        /* Send reject. Do not really care about the result */
        send_l2cap_reject(con, ident, NG_L2CAP_REJ_INVALID_CID, 0, scid, dcid);

        return (0);
} /* ng_l2cap_process_discon_req */

/*
 * Process L2CAP_DisconnectRsp command
 */

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;

        /* Get command parameters */
        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);

        /* Check if we have pending command descriptor */
        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;
        }

        /* Verify channel state, do nothing if invalid */
        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;
        }

        /* Verify CIDs and send reject if does not match */
        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;
        }

        /*
         * Looks like we have successfully disconnected channel, so notify 
         * upper layer. If command timeout already happened then ignore
         * response.
         */

        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); /* this will free commands too */
out:
        return (error);
} /* ng_l2cap_process_discon_rsp */

/*
 * Process L2CAP_EchoReq command
 */

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);
        }

        /* Attach data and link command to the queue */
        cmd->aux = con->rx_pkt;
        con->rx_pkt = NULL;
        ng_l2cap_link_cmd(con, cmd);
        ng_l2cap_lp_deliver(con);

        return (0);
} /* ng_l2cap_process_echo_req */

/*
 * Process L2CAP_EchoRsp command
 */

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;

        /* Check if we have this command */
        cmd = ng_l2cap_cmd_by_ident(con, ident);
        if (cmd != NULL) {
                /* If command timeout already happened then ignore response */
                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);
} /* ng_l2cap_process_echo_rsp */

/*
 * Process L2CAP_InfoReq command
 */

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;

        /* Get command parameters */
        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);
        }

        /* Link command to the queue */
        ng_l2cap_link_cmd(con, cmd);
        ng_l2cap_lp_deliver(con);

        return (0);
} /* ng_l2cap_process_info_req */

/*
 * Process L2CAP_InfoRsp command
 */

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;

        /* Get command parameters */
        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));

        /* Check if we have pending command descriptor */
        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 command timeout already happened then ignore response */
        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; /* XXX */

                                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);
} /* ng_l2cap_process_info_rsp */

/*
 * Send L2CAP reject
 */

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);
        }

        /* Link command to the queue */
        ng_l2cap_link_cmd(con, cmd);
        ng_l2cap_lp_deliver(con);

        return (0);
} /* send_l2cap_reject */

/*
 * Send L2CAP connection reject
 */

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);
        }

        /* Link command to the queue */
        ng_l2cap_link_cmd(con, cmd);
        ng_l2cap_lp_deliver(con);

        return (0);
} /* send_l2cap_con_rej */

/*
 * Send L2CAP config response
 */

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);
        }

        /* Link command to the queue */
        ng_l2cap_link_cmd(con, cmd);
        ng_l2cap_lp_deliver(con);

        return (0);
} /* send_l2cap_cfg_rsp */

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);
        }

        /* Link command to the queue */
        ng_l2cap_link_cmd(con, cmd);
        ng_l2cap_lp_deliver(con);

        return (0);
} /* send_l2cap_cfg_rsp */

/*
 * Get next L2CAP configuration option
 *
 * Return codes:
 *  0   no option
 *  1   we have got option
 * -1   header too short
 * -2   bad option value or length
 * -3   unknown option
 */

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);
} /* get_next_l2cap_opt */