/*
    ncpext.c
    Copyright (C) 2001  Petr Vandrovec

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

    Revision history:

	1.00  2001, January 27		Petr Vandrovec <vandrove@vc.cvut.cz>
		Initial version.

 */

#include "config.h"

#include <ncp/nwcalls.h>
#include <ncp/nwnet.h>
#include "ncplib_i.h"
#include "ncpcode.h"

#include <string.h>
#include <stdlib.h>

#define ncp_array_size(x) (sizeof(x)/sizeof((x)[0]))

NWCCODE
NWScanNCPExtensions(
		NWCONN_HANDLE conn,
		nuint32* iterHandle,
		char* name,
		nuint8* majorVersion,
		nuint8* minorVersion,
		nuint8* revision,
		nuint8 queryData[32]) {
	NWCCODE err;
	
	if (!iterHandle) {
		return NWE_PARAM_INVALID;
	}
	ncp_init_request_s(conn, 0);
	ncp_add_dword_lh(conn, *iterHandle);
	err = ncp_request(conn, 36);
	if (err) {
		ncp_unlock_conn(conn);
		return err;
	}
	if (conn->ncp_reply_size < 72) {
		ncp_unlock_conn(conn);
		return NWE_INVALID_NCP_PACKET_LENGTH;
	}
	*iterHandle = ncp_reply_dword_lh(conn, 0);
	if (majorVersion)
		*majorVersion = ncp_reply_byte(conn, 4);
	if (minorVersion)
		*minorVersion = ncp_reply_byte(conn, 5);
	if (revision)
		*revision = ncp_reply_byte(conn, 6);
	if (queryData)
		memcpy(queryData, ncp_reply_data(conn, 40), 32);
	if (name) {
		size_t namelen;
		
		namelen = ncp_reply_byte(conn, 7);
		if (namelen >= MAX_NCP_EXTENSION_NAME_BYTES) {
			ncp_unlock_conn(conn);
			return NWE_BUFFER_OVERFLOW;
		}
		memcpy(name, ncp_reply_data(conn, 8), namelen);
		name[namelen] = 0;
	}
	ncp_unlock_conn(conn);
	return 0;
}

NWCCODE
NWGetNumberNCPExtensions(
		NWCONN_HANDLE conn,
		nuint* exts) {
	NWCCODE err;
	u_int32_t num;
	NW_FRAGMENT rp;
	u_int32_t iterHandle;
	
	rp.fragAddr.rw = &num;
	rp.fragSize = sizeof(num);
	err = NWRequestSimple(conn, NCPC_SFN(36,3), NULL, 0, &rp);
	if (err) {
		if (err != NWE_NCP_NOT_SUPPORTED) {
			return err;
		}
		iterHandle = ~0;
		num = 0;
		while ((err = NWScanNCPExtensions(conn, &iterHandle, NULL, NULL, NULL, NULL, NULL)) == 0) {
			num++;
		}
		if (err != NWE_SERVER_FAILURE)
			return err;
	} else {
		if (rp.fragSize < 4)
			return NWE_INVALID_NCP_PACKET_LENGTH;
	}
	if (exts)
		*exts = num;
	return 0;	
}

static NWCCODE
NWFragLen(size_t *total,
	  nuint cnt,
          const NW_FRAGMENT* frag)
{
	size_t len;
	
	if (cnt && !frag) {
		return ERR_NULL_POINTER;
	}
	len = 0;
	while (cnt--) {
		len += frag->fragSize;
		frag++;
	}
	*total = len;
	return 0;
}

typedef struct {
	nuint8* current;
	size_t space;
	int currentActive;
	nuint fragCount;
	NW_FRAGMENT* fragList;
} NWFragger;

static void
NWPushFragStart(NWFragger*   fragger,
                nuint        fragCount,
		NW_FRAGMENT* fragList)
{
	fragger->currentActive = 0;
	fragger->fragCount = fragCount;
	fragger->fragList = fragList;
}

static NWCCODE
NWPushFragPush(NWFragger*    fragger,
               const nuint8* data,
	       size_t        len)
{
	while (len) {
		if (!fragger->currentActive) {
			if (fragger->fragCount == 0) {
				return NWE_BUFFER_OVERFLOW;
			}
			fragger->current = fragger->fragList->fragAddr.rw;
			fragger->space = fragger->fragList->fragSize;
			fragger->currentActive = 1;
		}
		if (len >= fragger->space) {
			memcpy(fragger->current, data, fragger->space);
			len -= fragger->space;
			data += fragger->space;
			fragger->fragList++;
			fragger->fragCount--;
			fragger->currentActive = 0;
		} else {
			memcpy(fragger->current, data, len);
			fragger->current += len;
			fragger->space -= len;
			return 0;
		}
	}
	return 0;
}

static void
NWPushFragFinish(NWFragger*    fragger)
{
	if (fragger->currentActive) {
		fragger->fragList->fragSize -= fragger->space;
		fragger->fragList++;
		fragger->fragCount--;
	}
	while (fragger->fragCount--) {
		fragger->fragList->fragSize = 0;
		fragger->fragList++;
	}
}

NWCCODE
NWFragNCPExtensionRequest(
		NWCONN_HANDLE	conn,
		nuint32		NCPExtensionID,
		nuint		reqFragCount,
		NW_FRAGMENT*	reqFragList,
		nuint		replyFragCount,
		NW_FRAGMENT*	replyFragList)
{
	NWCCODE err;
	size_t reqLen;
	size_t rpLen;
	u_int16_t ver;

	err = NWFragLen(&reqLen, reqFragCount, reqFragList);
	if (err) {
		return err;
	}
	err = NWFragLen(&rpLen, replyFragCount, replyFragList);
	if (err) {
		return err;
	}
	err = NWGetFileServerVersion(conn, &ver);
	if (err) {
		return err;
	}
	if (reqLen < 523 && rpLen < 530 && (rpLen < 100 || ver > 0x030B)) {
		NW_FRAGMENT* rq;
		NW_FRAGMENT rp[2];
		u_int8_t rqhdr[8];
		u_int8_t rphdr[2];
		u_int8_t rpbuf[530];
		NWFragger fragger;
		
		rq = malloc(sizeof(NW_FRAGMENT) * (reqFragCount + 1));
		if (!rq) {
			return ERR_NOT_ENOUGH_MEMORY;
		}
		memcpy(rq + 1, reqFragList, sizeof(NW_FRAGMENT) * reqFragCount);
		rq->fragAddress = rqhdr;
		rq->fragSize = 8;

		WSET_HL(rqhdr, 0, reqLen + 6);
		DSET_LH(rqhdr, 2, NCPExtensionID);
		WSET_LH(rqhdr, 6, rpLen);
		
		rp[0].fragAddress = rphdr;
		rp[0].fragSize = 2;
		rp[1].fragAddress = rpbuf;
		rp[1].fragSize = 530;
		
		err = NWRequest(conn, 37, reqFragCount + 1, rq, 2, rp);
		free(rq);
		if (err) {
			return err;
		}

		NWPushFragStart(&fragger, replyFragCount, replyFragList);
		NWPushFragPush(&fragger, rpbuf, rp[1].fragSize);
		NWPushFragFinish(&fragger);
		return 0;
	} else {
		NW_FRAGMENT rq[2];
		u_int8_t rqhdr[20];
		u_int8_t rqbuf[519];
		u_int8_t rphdr[10];
		u_int8_t rpbuf[524];
		size_t flen;
		const u_int8_t* dptr;
		size_t dlen;
		NWFragger fragger;
		
		WSET_HL(rqhdr,  0, 0xFFFF);
		DSET_LH(rqhdr,  2, NCPExtensionID);
		DSET_LH(rqhdr,  6, 0);
//		WSET_LH(rqhdr, 10, 0);	//472
		DSET_LH(rqhdr, 12, reqLen);
		DSET_LH(rqhdr, 16, rpLen);
		
		rq[0].fragAddress = rqhdr;
		rq[0].fragSize = 20;
		
		flen = 511;

		if (reqLen) {
			dptr = reqFragList->fragAddr.ro;
			dlen = reqFragList->fragSize;
			reqFragList++;
		} else {
			dptr = NULL;
			dlen = 0;
		}
		do {
			NW_FRAGMENT rp[3];

			rq[1].fragSize = 0;
			if (reqLen) {
				if (dlen >= flen) {
					rq[1].fragAddr.ro = dptr;
					rq[1].fragSize = flen;
					dptr += flen;
					dlen -= flen;
					reqLen -= flen;
					if (dlen == 0 && reqLen) {
						dptr = reqFragList->fragAddress;
						dlen = reqFragList->fragSize;
						reqFragList++;
					}
				} else if (dlen == reqLen) {
					rq[1].fragAddr.ro = dptr;
					rq[1].fragSize = dlen;
					reqLen = 0;
				} else {
					u_int8_t* pptr;

					rq[1].fragAddress = pptr = rqbuf;
					rq[1].fragSize = 0;
					while (flen && reqLen) {
						if (dlen > flen) {
							memcpy(pptr, dptr, flen);
							dptr += flen;
							dlen -= flen;
							rq[1].fragSize += flen;
							reqLen -= flen;
							break;
						} else {
							memcpy(pptr, dptr, dlen);
							pptr += dlen;
							flen -= dlen;
							rq[1].fragSize += dlen;
							reqLen -= dlen;
							dptr = reqFragList->fragAddress;
							dlen = reqFragList->fragSize;
							reqFragList++;
						}
					}
				}
			}
			WSET_LH(rqhdr, 10, rq[1].fragSize);

			rp[0].fragAddress = rphdr;
			rp[0].fragSize = 10;
			rp[1].fragAddress = rpbuf;
			rp[1].fragSize = sizeof(rpbuf);

			err = NWRequest(conn, 37, 2, rq, 3, rp);
			if (err) {
				return err;
			}
			DSET_LH(rqhdr, 6, DVAL_LH(rphdr, 0));
			rq[0].fragSize = 12;
			flen = 519;
		} while (reqLen);

		{
			size_t tlen = DVAL_LH(rphdr, 6);
			if (rpLen > tlen) {
				rpLen = tlen;
			}
		}
		
		NWPushFragStart(&fragger, replyFragCount, replyFragList);
		while (rpLen) {
			size_t fragLen;

			fragLen = WVAL_LH(rphdr, 4);
			
			if (fragLen <= rpLen) {
				rpLen -= fragLen;
			} else {
				fragLen = rpLen;
				rpLen = 0;
			}
			err = NWPushFragPush(&fragger, rpbuf, fragLen);
			if (err) {
				return err;
			}
			if (rpLen) {
				NW_FRAGMENT rp[2];

				rp[0].fragAddress = rphdr;
				rp[0].fragSize = 6;
				rp[1].fragAddress = rpbuf;
				rp[1].fragSize = sizeof(rpbuf);

				err = NWRequest(conn, 37, 2, rq, 3, rp);
				if (err) {
					return err;
				}
				DSET_LH(rqhdr, 6, DVAL_LH(rphdr, 0));
			}
		}
		NWPushFragFinish(&fragger);
	}
	return 0; 
}

NWCCODE
NWNCPExtensionRequest(
		NWCONN_HANDLE	conn,
		nuint32		NCPExtensionID,
		const void*	requestData,
		size_t		requestDataLen,
		void*		replyData,
		size_t*		replyDataLen)
{
	NW_FRAGMENT rq;
	NW_FRAGMENT rp;
	NWCCODE err;

	rq.fragAddr.ro = requestData;
	rq.fragSize = requestDataLen;
	rp.fragAddr.rw = replyData;
	rp.fragSize = *replyDataLen;

	err = NWFragNCPExtensionRequest(conn, NCPExtensionID, 1, &rq, 1, &rp);
	if (!err) {
		*replyDataLen = rp.fragSize;
	}
	return err;
}
