/*
 * l2cap_stub.c -- minimal L2CAP upper-API stub for the rfcomm_harness KLD.
 *
 * The production RFCOMM code (sys/netbt/rfcomm_session.c, rfcomm_dlc.c,
 * rfcomm_upper.c) is compiled VERBATIM into the module.  It only talks to
 * the L2CAP layer through the ten functions below; we implement them with
 * just enough behaviour (channel record keeping, OM TU, send = log+free)
 * for the real state machine to run unmodified, exactly as l2cap_upper.c
 * would drive it.
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/mbuf.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>

/*
 * The real malloc type lives in bt_proto.c which we do not compile in.
 */
MALLOC_DEFINE(M_BLUETOOTH, "Bluetooth", "Bluetooth system memory");

/*
 * Real struct l2cap_channel (from netbt/l2cap.h) -- the RFCOMM code only
 * stores the pointer, we fill in the few members that matter.  The
 * harness fires btproto callbacks through chan->lc_proto/chan->lc_upper
 * exactly the way sys/netbt/l2cap_upper.c does.
 */
#define MAXCHAN 8
static struct l2cap_channel stub_chans[MAXCHAN];
static int stub_nchan;

struct l2cap_channel *harness_first_session_channel(void);

struct l2cap_channel *
harness_first_session_channel(void)
{
	if (stub_nchan < 1)
		return NULL;
	return &stub_chans[0];	/* rfcomm_session_alloc() attaches first */
}

struct l2cap_channel *harness_last_channel(void);

struct l2cap_channel *
harness_last_channel(void)
{
	if (stub_nchan < 1)
		return NULL;
	return &stub_chans[stub_nchan - 1];	/* most recent attach */
}

int
l2cap_attach(struct l2cap_channel **handle, const struct btproto *proto,
    void *upper)
{
	struct l2cap_channel *chan;

	if (stub_nchan >= MAXCHAN)
		return ENOMEM;

	chan = &stub_chans[stub_nchan++];
	memset(chan, 0, sizeof(*chan));
	chan->lc_proto = proto;
	chan->lc_upper = upper;
	chan->lc_omtu = 1024;		/* > RFCOMM_MTU_MIN + 6 */
	chan->lc_state = L2CAP_OPEN;	/* pretend link is up */

	*handle = chan;
	return 0;
}

int
l2cap_detach(struct l2cap_channel **handle)
{
	*handle = NULL;
	return 0;
}

int
l2cap_bind(struct l2cap_channel *chan, struct sockaddr_bt *addr)
{
	return 0;
}

int
l2cap_listen(struct l2cap_channel *chan)
{
	return 0;
}

int
l2cap_connect(struct l2cap_channel *chan, struct sockaddr_bt *dest)
{
	/* pretend the L2CAP connect request went out */
	return 0;
}

int
l2cap_disconnect(struct l2cap_channel *chan, int linger)
{
	return 0;
}

int
l2cap_send(struct l2cap_channel *chan, struct mbuf *m)
{
	kprintf("[l2cap-stub] send: %d bytes (first octet %#02x)\n",
	    m->m_pkthdr.len, mtod(m, uint8_t *)[0]);
	m_freem(m);
	return 0;
}

int
l2cap_setopt(struct l2cap_channel *chan, int opt, void *addr)
{
	/* SO_L2CAP_LM: pretend mode change is instantly complete */
	return 0;
}

int
l2cap_getopt(struct l2cap_channel *chan, int opt, void *addr)
{
	if (opt == SO_L2CAP_OMTU) {
		*(uint16_t *)addr = chan->lc_omtu;
		return sizeof(uint16_t);
	}
	return 0;
}

int
l2cap_sockaddr(struct l2cap_channel *chan, struct sockaddr_bt *addr)
{
	memset(addr, 0, sizeof(*addr));
	addr->bt_len = sizeof(*addr);
	addr->bt_family = AF_BLUETOOTH;
	addr->bt_psm = L2CAP_PSM_RFCOMM;
	addr->bt_bdaddr.b[0] = 0x11;	/* "us" */
	return 0;
}

int
l2cap_peeraddr(struct l2cap_channel *chan, struct sockaddr_bt *addr)
{
	memset(addr, 0, sizeof(*addr));
	addr->bt_len = sizeof(*addr);
	addr->bt_family = AF_BLUETOOTH;
	addr->bt_psm = L2CAP_PSM_RFCOMM;
	addr->bt_bdaddr.b[0] = 0x22;	/* "them" */
	return 0;
}
