/*
 * Copyright (c) 1999, Boris Popov
 * 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.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *    This product includes software developed by Boris Popov.
 * 4. Neither the name of the author nor the names of any co-contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * 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.
 *
 * Routines to prepare request and fetch reply
 *
 * $FreeBSD: src/sys/netncp/ncp_rq.c,v 1.1 1999/10/02 04:06:17 bp Exp $
 */ 
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/errno.h>
#include <sys/mbuf.h>
#include <sys/uio.h>

#include <netncp/ncp.h>
#include <netncp/ncp_conn.h>
#include <netncp/ncp_rq.h>
#include <netncp/ncp_subr.h>
#include <netncp/ncp_ncp.h>
#include <netncp/ncp_nls.h>

static struct mbuf* m_getm(struct mbuf *top, int len);

int
ncp_rq_head(struct ncp_rq *rqp, u_int32_t ptype, u_int8_t fn,struct proc *p,
    struct ucred *cred)
{
	struct mbuf *m;
	struct ncp_rqhdr *rq;
	struct ncp_bursthdr *brq;
	caddr_t pstart;

	bzero(rqp, sizeof(*rqp));
	rqp->p = p;
	rqp->cred = cred;
	m = m_gethdr(M_WAIT, MT_DATA);
	if (m == NULL) 
		return ENOBUFS;		/* if M_WAIT ? */
	m->m_pkthdr.rcvif = NULL;
	rqp->rq = rqp->mrq = m;
	rqp->rp = NULL;
	switch(ptype) {
	    case NCP_PACKET_BURST:
		MH_ALIGN(m, sizeof(*brq) + 24);
		m->m_len = sizeof(*brq);
		brq = mtod(m, struct ncp_bursthdr *);
		brq->bh_type = ptype;
		brq->bh_streamtype = 0x2;
		pstart = (caddr_t)brq;
		break;
	    default:
		MH_ALIGN(m, sizeof(*rq) + 2);	/* possible len field in some functions */
		m->m_len = sizeof(*rq);
		rq = mtod(m, struct ncp_rqhdr *);
		rq->type = ptype;
		rq->seq = 0;	/* filled later */
		rq->fn = fn;
		pstart = (caddr_t)rq;
		break;
	}
	rqp->bpos = pstart + m->m_len;
	return 0;
}

int
ncp_rq_done(struct ncp_rq *rqp) {
	m_freem(rqp->rq);
	rqp->rq=NULL;
	if (rqp->rp) m_freem(rqp->rp);
	rqp->rp=NULL;
	return (0);
}

static struct mbuf*
m_getm(struct mbuf *top, int len) {
	int rest = len, mlen;
	struct mbuf *m=0,*mp;
	
	mp = top;
	while (rest > 0) {
/*	NCPSDEBUG("%d\n",rest);*/
		m = m_get(M_WAIT, MT_DATA);
		if (rest > MINCLSIZE) {
			MCLGET(m,M_WAIT);
			mlen = ( (m->m_flags & M_EXT) == 0) ? MLEN : MCLBYTES;
		} else { 
			mlen = MLEN;
		}
		m->m_len = 0/*min(mlen,rest)*/;
		mp->m_next = m;
		mp = m;
		rest -= mlen;
	}
	return top;
}

/*
 * Routines to fill the request
 */
static caddr_t ncp_mchecksize(struct ncp_rq *rqp, int size);
#define	NCP_RQADD(t)	((t*)(ncp_mchecksize(rqp,sizeof(t))))

caddr_t
ncp_mchecksize(struct ncp_rq *rqp, int size) {
	caddr_t bpos1;

	if (size>MLEN)
		panic("ncp_mchecksize\n");
	if (M_TRAILINGSPACE(rqp->mrq)<(size)) {
		struct mbuf *m;
		m = m_get(M_WAIT, MT_DATA);
		m->m_len = 0;
		rqp->bpos = mtod(m, caddr_t);
		rqp->mrq->m_next = m;
		rqp->mrq = m;
	}
	rqp->mrq->m_len += size;
	bpos1 = rqp->bpos;
	rqp->bpos += size;
	return bpos1;
}

void
ncp_rq_byte(struct ncp_rq *rqp,u_int8_t x) {
	*NCP_RQADD(u_int8_t)=x;
}

void
ncp_rq_word_hl(struct ncp_rq *rqp, u_int16_t x) {
	setwbe(NCP_RQADD(u_int16_t), 0, x);
}

void
ncp_rq_word_lh(struct ncp_rq *rqp, u_int16_t x) {
	setwle(NCP_RQADD(u_int16_t), 0, x);
}

void
ncp_rq_dword_lh(struct ncp_rq *rqp, u_int32_t x) {
	setdle(NCP_RQADD(u_int32_t), 0, x);
}

void
ncp_rq_pathstring(struct ncp_rq *rqp, int size, char *name, struct ncp_nlstables *nt) {
	struct mbuf *m;
	int cplen;

	ncp_rq_byte(rqp, size);
	m = rqp->mrq;
	cplen = min(size, M_TRAILINGSPACE(m));
	if (cplen) {
		ncp_pathcopy(name, rqp->bpos, cplen, nt);
		size -= cplen;
		name += cplen;
		m->m_len += cplen;
	}
	if (size) {
		m_getm(m, size);
		while (size > 0){
			m = m->m_next;
			cplen = min(size, M_TRAILINGSPACE(m));
			ncp_pathcopy(name, mtod(m, caddr_t) + m->m_len, cplen, nt);
			size -= cplen;
			name += cplen;
			m->m_len += cplen;
		}
	}
	rqp->bpos = mtod(m,caddr_t) + m->m_len;
	rqp->mrq = m;
	return;
}

int
ncp_rq_putanymem(struct ncp_rq *rqp, caddr_t source, int size, int type) {
	struct mbuf *m;
	int cplen, error;

	m = rqp->mrq;
	cplen = min(size, M_TRAILINGSPACE(m));
	if (cplen) {
		if (type==1) {
			error = copyin(source, rqp->bpos, cplen);
			if (error) return error;
		} else
			bcopy(source, rqp->bpos, cplen);
		size -= cplen;
		source += cplen;
		m->m_len += cplen;
	}
	if (size) {
		m_getm(m, size);
		while (size > 0){
			m = m->m_next;
			cplen = min(size, M_TRAILINGSPACE(m));
			if (type==1) {
				error = copyin(source, mtod(m, caddr_t) + m->m_len, cplen);
				if (error) return error;
			} else
				bcopy(source, mtod(m, caddr_t) + m->m_len, cplen);
			size -= cplen;
			source += cplen;
			m->m_len += cplen;
		}
	}
	rqp->bpos = mtod(m,caddr_t) + m->m_len;
	rqp->mrq = m;
	return 0;
}

int
ncp_rq_mbuf(struct ncp_rq *rqp, struct mbuf *m, int size) {

	rqp->mrq->m_next = m;
	m->m_next = NULL;
	if (size != M_COPYALL) m->m_len = size;
	rqp->bpos = mtod(m,caddr_t) + m->m_len;
	rqp->mrq = m;
	return 0;
}

void 
ncp_rq_pstring(struct ncp_rq *rqp, char *s) {
	int len = strlen(s);
	if (len > 255) {
		nwfs_printf("string too long: %s\n", s);
		len = 255;
	}
	ncp_rq_byte(rqp, len);
	ncp_rq_mem(rqp, s, len);
	return;
}

void 
ncp_rq_dbase_path(struct ncp_rq *rqp, u_int8_t vol_num, u_int32_t dir_base,
                    int namelen, u_char *path, struct ncp_nlstables *nt)
{
	int complen;

	ncp_rq_byte(rqp, vol_num);
	ncp_rq_dword(rqp, dir_base);
	ncp_rq_byte(rqp, 1);	/* with dirbase */
	if (path != NULL && path[0]) {
		if (namelen < 0) {
			namelen = *path++;
			ncp_rq_byte(rqp, namelen);
			for(; namelen; namelen--) {
				complen = *path++;
				ncp_rq_byte(rqp, complen);
				ncp_rq_mem(rqp, path, complen);
				path += complen;
			}
		} else {
			ncp_rq_byte(rqp, 1);	/* 1 component */
			ncp_rq_pathstring(rqp, namelen, path, nt);
		}
	} else {
		ncp_rq_byte(rqp, 0);
		ncp_rq_byte(rqp, 0);
	}
}
/*
 * fetch reply routines
 */
#define ncp_mspaceleft	(mtod(rqp->mrp,caddr_t)+rqp->mrp->m_len-rqp->bpos)

u_int8_t
ncp_rp_byte(struct ncp_rq *rqp) {
	if (rqp->mrp == NULL) return 0;
	if (ncp_mspaceleft < 1) {
		rqp->mrp = rqp->mrp->m_next;
		if (rqp->mrp == NULL) return 0;
		rqp->bpos = mtod(rqp->mrp, caddr_t);
	}
	rqp->bpos += 1;
	return rqp->bpos[-1];
}

u_int16_t
ncp_rp_word_lh(struct ncp_rq *rqp) {
	caddr_t prev = rqp->bpos;
	u_int16_t t;

	if (rqp->mrp == NULL) return 0;
	if (ncp_mspaceleft >= 2) {
		rqp->bpos += 2;
		return getwle(prev,0);
	}
	t = *((u_int8_t*)(rqp->bpos));
	rqp->mrp = rqp->mrp->m_next;
	if (rqp->mrp == NULL) return 0;
	((u_int8_t *)&t)[1] = *((u_int8_t*)(rqp->bpos = mtod(rqp->mrp, caddr_t)));
	rqp->bpos += 2;
	return t;
}

u_int16_t
ncp_rp_word_hl(struct ncp_rq *rqp) {
	return (ntohs(ncp_rp_word_lh(rqp)));
}

u_int32_t
ncp_rp_dword_hl(struct ncp_rq *rqp) {
	int togo, rest;
	caddr_t prev = rqp->bpos;
	u_int32_t t;

	if (rqp->mrp == NULL) return 0;
	rest = ncp_mspaceleft;
	if (rest >= 4) {
		rqp->bpos += 4;
		return getdbe(prev,0);
	}
	togo = 0;
	while (rest--) {
		((u_int8_t *)&t)[togo++] = *((u_int8_t*)(prev++));
	}
	rqp->mrp = rqp->mrp->m_next;
	if (rqp->mrp == NULL) return 0;
	prev = mtod(rqp->mrp, caddr_t);
	rqp->bpos = prev + 4 - togo;	/* XXX possible low than togo bytes in next mbuf */
	while (togo < 4) {
		((u_int8_t *)&t)[togo++] = *((u_int8_t*)(prev++));
	}
	return getdbe(&t,0);
}

u_int32_t
ncp_rp_dword_lh(struct ncp_rq *rqp) {
	int rest, togo;
	caddr_t prev = rqp->bpos;
	u_int32_t t;

	if (rqp->mrp == NULL) return 0;
	rest = ncp_mspaceleft;
	if (rest >= 4) {
		rqp->bpos += 4;
		return getdle(prev,0);
	}
	togo = 0;
	while (rest--) {
		((u_int8_t *)&t)[togo++] = *((u_int8_t*)(prev++));
	}
	rqp->mrp = rqp->mrp->m_next;
	if (rqp->mrp == NULL) return 0;
	prev = mtod(rqp->mrp, caddr_t);
	rqp->bpos = prev + 4 - togo;	/* XXX possible low than togo bytes in next mbuf */
	while (togo < 4) {
		((u_int8_t *)&t)[togo++] = *((u_int8_t*)(prev++));
	}
	return getdle(&t,0);
}
void
ncp_rp_mem(struct ncp_rq *rqp,caddr_t target, int size) {
	register struct mbuf *m=rqp->mrp;
	register unsigned count;
	
	while (size > 0) {
		if (m==0) {	/* should be panic */
			printf("ncp_rp_mem: incomplete copy\n");
			return;
		}
		count = mtod(m,caddr_t)+m->m_len-rqp->bpos;
		if (count == 0) {
			m=m->m_next;
			rqp->bpos=mtod(m,caddr_t);
			continue;
		}
		count = min(count,size);
		bcopy(rqp->bpos, target, count);
		size -= count;
		target += count;
		rqp->bpos += count;
	}
	rqp->mrp=m;
	return;
}

int
ncp_rp_usermem(struct ncp_rq *rqp,caddr_t target, int size) {
	register struct mbuf *m=rqp->mrp;
	register unsigned count;
	int error;
	
	while (size>0) {
		if (m==0) {	/* should be panic */
			printf("ncp_rp_mem: incomplete copy\n");
			return EFAULT;
		}
		count = mtod(m,caddr_t)+m->m_len-rqp->bpos;
		if (count == 0) {
			m=m->m_next;
			rqp->bpos=mtod(m,caddr_t);
			continue;
		}
		count = min(count,size);
		error=copyout(rqp->bpos, target, count);
		if (error) return error;
		size -= count;
		target += count;
		rqp->bpos += count;
	}
	rqp->mrp=m;
	return 0;
}

struct mbuf*
ncp_rp_mbuf(struct ncp_rq *rqp, int size) {
	register struct mbuf *m=rqp->mrp, *rm;
	register unsigned count;
	
	rm = m_copym(m, rqp->bpos - mtod(m,caddr_t), size, M_WAIT);
	while (size > 0) {
		if (m == 0) {
			printf("ncp_rp_mbuf: can't advance\n");
			return rm;
		}
		count = mtod(m,caddr_t)+ m->m_len - rqp->bpos;
		if (count == 0) {
			m = m->m_next;
			rqp->bpos = mtod(m, caddr_t);
			continue;
		}
		count = min(count, size);
		size -= count;
		rqp->bpos += count;
	}
	rqp->mrp=m;
	return rm;
}

int
nwfs_mbuftouio(mrep, uiop, siz, dpos)
	struct mbuf **mrep;
	register struct uio *uiop;
	int siz;
	caddr_t *dpos;
{
	register char *mbufcp, *uiocp;
	register int xfer, left, len;
	register struct mbuf *mp;
	long uiosiz;
	int error = 0;

	mp = *mrep;
	if (!mp) return 0;
	mbufcp = *dpos;
	len = mtod(mp, caddr_t)+mp->m_len-mbufcp;
	while (siz > 0) {
		if (uiop->uio_iovcnt <= 0 || uiop->uio_iov == NULL)
			return (EFBIG);
		left = uiop->uio_iov->iov_len;
		uiocp = uiop->uio_iov->iov_base;
		if (left > siz)
			left = siz;
		uiosiz = left;
		while (left > 0) {
			while (len == 0) {
				mp = mp->m_next;
				if (mp == NULL)
					return (EBADRPC);
				mbufcp = mtod(mp, caddr_t);
				len = mp->m_len;
			}
			xfer = (left > len) ? len : left;
#ifdef notdef
			/* Not Yet.. */
			if (uiop->uio_iov->iov_op != NULL)
				(*(uiop->uio_iov->iov_op))
				(mbufcp, uiocp, xfer);
			else
#endif
			if (uiop->uio_segflg == UIO_SYSSPACE)
				bcopy(mbufcp, uiocp, xfer);
			else
				copyout(mbufcp, uiocp, xfer);
			left -= xfer;
			len -= xfer;
			mbufcp += xfer;
			uiocp += xfer;
			uiop->uio_offset += xfer;
			uiop->uio_resid -= xfer;
		}
		if (uiop->uio_iov->iov_len <= siz) {
			uiop->uio_iovcnt--;
			uiop->uio_iov++;
		} else {
			uiop->uio_iov->iov_base += uiosiz;
			uiop->uio_iov->iov_len -= uiosiz;
		}
		siz -= uiosiz;
	}
	*dpos = mbufcp;
	*mrep = mp;
	return (error);
}
/*
 * copies a uio scatter/gather list to an mbuf chain.
 * NOTE: can ony handle iovcnt == 1
 */
int
nwfs_uiotombuf(uiop, mq, siz, bpos)
	register struct uio *uiop;
	struct mbuf **mq;
	int siz;
	caddr_t *bpos;
{
	register char *uiocp;
	register struct mbuf *mp, *mp2;
	register int xfer, left, mlen;
	int uiosiz, clflg;

#ifdef DIAGNOSTIC
	if (uiop->uio_iovcnt != 1)
		panic("nfsm_uiotombuf: iovcnt != 1");
#endif

	if (siz > MLEN)		/* or should it >= MCLBYTES ?? */
		clflg = 1;
	else
		clflg = 0;
	mp = mp2 = *mq;
	while (siz > 0) {
		left = uiop->uio_iov->iov_len;
		uiocp = uiop->uio_iov->iov_base;
		if (left > siz)
			left = siz;
		uiosiz = left;
		while (left > 0) {
			mlen = M_TRAILINGSPACE(mp);
			if (mlen == 0) {
				MGET(mp, M_WAIT, MT_DATA);
				if (clflg)
					MCLGET(mp, M_WAIT);
				mp->m_len = 0;
				mp2->m_next = mp;
				mp2 = mp;
				mlen = M_TRAILINGSPACE(mp);
			}
			xfer = (left > mlen) ? mlen : left;
#ifdef notdef
			/* Not Yet.. */
			if (uiop->uio_iov->iov_op != NULL)
				(*(uiop->uio_iov->iov_op))
				(uiocp, mtod(mp, caddr_t)+mp->m_len, xfer);
			else
#endif
			if (uiop->uio_segflg == UIO_SYSSPACE)
				bcopy(uiocp, mtod(mp, caddr_t)+mp->m_len, xfer);
			else
				copyin(uiocp, mtod(mp, caddr_t)+mp->m_len, xfer);
			mp->m_len += xfer;
			left -= xfer;
			uiocp += xfer;
			uiop->uio_offset += xfer;
			uiop->uio_resid -= xfer;
		}
		uiop->uio_iov->iov_base += uiosiz;
		uiop->uio_iov->iov_len -= uiosiz;
		siz -= uiosiz;
	}
	*bpos = mtod(mp, caddr_t)+mp->m_len;
	*mq = mp;
	return (0);
}
