/* 
    nwcalls.c - Connection oriented calls
    Copyright (C) 1999, 2000  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:

	0.00  1998, December 1		Petr Vandrovec <vandrove@vc.cvut.cz>
		Initial release.

	1.00  1999, November 20		Petr Vandrovec <vandrove@vc.cvut.cz>
		Added copyright and license.

	1.01  2000, January 16		Petr Vandrovec <vandrove@vc.cvut.cz>
		Changes for 32bit uids.
		
	1.02  2000, May 1		Petr Vandrovec <vandrove@vc.cvut.cz>
		Added NWGetFileServerUTCTime.

	1.03  2000, May 7		Petr Vandrovec <vandrove@vc.cvut.cz>
		NWGetFileServerUTCTime moved to nwtime.c.

	1.04  2000, May 27		Petr Vandrovec <vandrove@vc.cvut.cz>
		Make NWParsePath return ERR_NULL_POINTER if path==NULL.

	1.05  2000, June 1		Petr Vandrovec <vandrove@vc.cvut.cz>
		Added NWGetBinderyAccessLevel.

        1.06  2001, January 7		Patrick Pollet <patrick.pollet@insa-lyon.fr>
		Make NWCCConnGetInfo ERR_NULL_POINTER if buffer==NULL.
		Added answer to NWCC_INFO_TREE_NAME in NWCCConnGetInfo.
		Added NWCCOpenConnByName (only IPX transport supported).

        1.07  2001, January 8		Patrick Pollet <patrick.pollet@insa-lyon.fr>
		Added disk retrictions API calls.

        1.08  2001, January 9		Patrick Pollet <patrick.pollet@insa-lyon.fr>
		Added semaphores API calls.

        1.09  2001, January 10		Patrick Pollet <patrick.pollet@insa-lyon.fr>
		Added NWCCGetConnAddress,NWCCGetConnAddressLength,
		Fixed NWGetVolumeNumber prototype in nwcalls.h and added it here.

	1.10  2001, February 11		Petr Vandrovec <vandrove@vc.cvut.cz>
		Added NWScanSemaphoresByConn.

	1.11  2001, February 25		Petr Vandrovec <vandrove@vc.cvut.cz>
		Added __NWExecuteGlobal code, implemented NW{Set,Get}BroadcastsMode for
		permanent connections.

	1.12  2001, February 25		Patrick Pollet <Patrick.Pollet@cipcinsa.insa-lyon.fr>
		Added NWIsObjectInSet.

	1.13  2001, March 7		Petr Vandrovec <vandrove@vc.cvut.cz>
		Fixed meaning of iterHandle parameter in
		NWScanVolDiskRestrictions/NWScanVolDiskRestrictions2.

	1.14  2001, May 31		Petr Vandrovec <vandrove@vc.cvut.cz>
		Include strings.h, do not use IPX in IPX-less config, hack
		around missing MSG_DONTWAIT.

	1.15  2001, June 26		Petr Vandrovec <vandrove@vc.cvut.cz>
		Added missing #include <stddef.h>. Discovered by Daniel Greenberg <daneli@umich.edu>

	1.16  2001, December 30		Petr Vandrovec <vandrove@vc.cvut.cz>
		Forget about NWCC_TRAN_TYPE_* crap and use NT_* everywhere.

	1.17  2002, January 20		Petr Vandrovec <vandrove@vc.cvut.cz>
		Move NWCC_INFO_MOUNT_POINT code from nwclient.c here.

	1.18  2002, February 4		Petr Vandrovec <vandrove@vc.cvut.cz>
		Handle correctly kernels reporting volume number > 255.

	1.19  2002, May 3		Petr Vandrovec <vandrove@vc.cvut.cz>
		Move NWCCOpenConnByName to resolve.c.

 */

#include "config.h"

#include <errno.h>
#include <string.h>
#include <strings.h>
#include <stdlib.h>
#include <stddef.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/un.h>
#include <sys/uio.h>
#include <sys/time.h>

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

NWCCODE NWCallsInit(void* dummy1, void* dummy2) {
	(void)dummy1;
	(void)dummy2;
	return 0;
}

NWCCODE NWRequest(NWCONN_HANDLE conn, nuint function, 
	nuint numRq, const NW_FRAGMENT* rq, 
	nuint numRp, NW_FRAGMENT* rp) {
	long result;
	nuint i;
	size_t rest;
	unsigned char* ptr;

	ncp_init_request(conn);
	if (function & NCPC_SUBFUNCTION) {
		ncp_add_word_hl(conn, 0);
		ncp_add_byte(conn, NCPC_SUBFN(function));
		conn->has_subfunction = 1;
	}
	for (i=numRq; i--; rq++) {
		ncp_add_mem(conn, rq->fragAddr.ro, rq->fragSize);
	}
	result = ncp_request(conn, NCPC_FN(function));
	if (result) {
		ncp_unlock_conn(conn);
		return result;
	}
	rest = conn->ncp_reply_size;
	ptr = ncp_reply_data(conn, 0);
	for (i=numRp; i--; rp++) {
		size_t spc = rp->fragSize;
		if (spc > rest) {
			memcpy(rp->fragAddr.rw, ptr, rest);
			/* Do we want to modify fragList ? */
			/* I think so because of we want to propagate */
			/* reply length back to client */
			/* maybe we could do "return conn->ncp_reply_size" */
			/* instead of "return 0". */
			rp->fragSize = rest;
			rest = 0;
		} else {
			memcpy(rp->fragAddr.rw, ptr, spc);
			rest -= spc;
		}
	}
	ncp_unlock_conn(conn);
	return 0;
}
	
/* NWRequestSimple: Simple request, 1 request fragment, max. 1 reply fragment
 * !!! check carefully whether you call this function with rp->fragAddress ?= NULL !!!
 * conn = connection handle
 * function = NCP function (0x00 - 0xFF), subfunction 0x00xx - 0xFFxx, sfn flag 0x1xxxx
 * rq = pointer to request, if rqlen == 0 then address can be NULL
 * rqlen = request length, if rqlen == 0, rq can be NULL
 * rp = pointer to reply fragment
 *	on error: return nonzero code, unlock conn
 *	on success: return 0;
 *			if rp == NULL: throw away reply
 *			if rp != NULL && rp->fragAddress == NULL: fill rp with pointer
 *					to data and with size, do NOT unlock conn(!)
 *		if rp != NULL && rp->fragAddress != NULL: fill buffer pointed by
 *					rp->fragAddress with reply up to rp->fragSize
 *					rp->fragSize is reply length (can be greater than
 *					fragSize on input), unlock conn
 */
NWCCODE NWRequestSimple(NWCONN_HANDLE conn, nuint function,
	const void* rq, size_t rqlen, NW_FRAGMENT* rp) {
	long result;
	
	if ((rp && rp->fragSize && !rp->fragAddr.rw) || (rqlen && !rq))
		return NWE_PARAM_INVALID;
	ncp_init_request(conn);
	if (function & NCPC_SUBFUNCTION) {
		ncp_add_word_hl(conn, rqlen+1);
		ncp_add_byte(conn, NCPC_SUBFN(function));
	}
	if (rqlen) 
		ncp_add_mem(conn, rq, rqlen);
	result = ncp_request(conn, NCPC_FN(function));
	if (result) {
		ncp_unlock_conn(conn);
		return result;
	}
	if (rp) {
		size_t rest;
		void* ptr;

		rest = conn->ncp_reply_size;
		ptr = ncp_reply_data(conn, 0);
		if (rp->fragAddr.rw) {
			size_t maxsize = rp->fragSize;
			if (maxsize > rest)
				maxsize = rest;
			rp->fragSize = rest;
			memcpy(rp->fragAddr.rw, ptr, maxsize);
		} else {
			rp->fragAddr.rw = ptr;
			rp->fragSize = rest;
			return 0;
		}
	}
	ncp_unlock_conn(conn);
	return 0;
}

/* FIXME: Mark socket O_NONBLOCK instead */
#ifndef MSG_DONTWAIT
#define MSG_DONTWAIT 0
#endif
static int timedRecv(int fd, void* buff, size_t len, int timeout) {
	struct timeval tv;

	tv.tv_sec = timeout / 1000000;
	tv.tv_usec = timeout % 1000000;
	do {
		fd_set rdset;
		int rd;

		do {
			FD_ZERO(&rdset);
			FD_SET(fd, &rdset);
			rd = select(fd + 1, &rdset, NULL, NULL, &tv);
		} while (rd == -1 && errno == EINTR);
		if (rd == -1)
			break;
		if (!FD_ISSET(fd, &rdset))
			break;
		rd = recv(fd, buff, len, MSG_DONTWAIT);
		if (rd == 0)
			break;
		if (rd > 0) {
			size_t rd2 = rd;

			if (rd2 > len)
				break;
			(unsigned char*)buff += rd2;
			len -= rd2;
			if (!len)
				return 0;
		}
	} while (1);
	return -1;
}

static int timedRecvMsg(int fd, struct iovec* v, size_t vnum, int timeout) {
	struct timeval tv;

	tv.tv_sec = timeout / 1000000;
	tv.tv_usec = timeout % 1000000;
	do {
		fd_set rdset;
		int rd;
		struct msghdr m;

		do {
			FD_ZERO(&rdset);
			FD_SET(fd, &rdset);
			rd = select(fd + 1, &rdset, NULL, NULL, &tv);
		} while (rd == -1 && errno == EINTR);
		if (rd == -1)
			break;
		if (!FD_ISSET(fd, &rdset))
			break;
		m.msg_name = NULL;
		m.msg_namelen = 0;
		m.msg_iov = v;
		m.msg_iovlen = vnum;
		m.msg_control = NULL;
		m.msg_controllen = 0;
		m.msg_flags = 0;
		rd = recvmsg(fd, &m, MSG_DONTWAIT);
		if (rd == 0)
			break;
		if (rd > 0) {
			size_t rd2 = rd;
			while (rd2 >= v->iov_len) {
				rd2 -= v++->iov_len;
				if (!--vnum) {
					if (rd2)
						return -1;
					return 0;
				}
			}
			(char*)v->iov_base += rd2;
			v->iov_len -= rd2;
		}
	} while (1);
	return -1;
}

static NWCCODE __NWExecuteGlobal(NWCONN_HANDLE conn, nuint function,
		nuint numRq, const NW_FRAGMENT* rq,
		nuint numRp, const NW_FRAGMENT* rp,
		size_t* rplen) {
	NWCCODE err = NCPLIB_NCPD_DEAD;
	int fd;
	struct msghdr msg;
	struct iovec iov[17];
	struct iovec* i;
	int sndlen;
	int rd;
	int retry = 0;
	size_t exprpl;
	size_t maxrpl;
	unsigned char rqb[20];
	nuint j;
	size_t rdu;

	if (rplen)
		*rplen = 0;

	if ((numRq > 16) || (numRp > 17)) {
		return NWE_REQ_TOO_MANY_REQ_FRAGS;
	}
	ncp_lock_conn(conn);
retryLoop:;	
	fd = conn->global_fd;
	if (fd == -1) {
		int fsfd;
		struct stat stb;
		struct sockaddr_un sun;
		socklen_t sunlen;

		fsfd = ncp_get_fid(conn);
		if (fsfd == -1) {
			goto errquit;
		}
		if (fstat(fsfd, &stb)) {
			goto errquit;
		}
		fd = socket(PF_UNIX, SOCK_STREAM, 0);
		if (fd < 0) {
			goto errquit;
		}
		sunlen = offsetof(struct sockaddr_un, sun_path) + sprintf(sun.sun_path, "%cncpfs.permanent.mount.%lu", 0,
				(unsigned long)stb.st_dev) + 1;
		sun.sun_family = AF_UNIX;
		if (connect(fd, (struct sockaddr*)&sun, sunlen)) {
			close(fd);
			goto errquit;
		}
		conn->global_fd = fd;
	}
	msg.msg_name = NULL;
	msg.msg_namelen = 0;
	msg.msg_iov = iov;
	msg.msg_iovlen = numRq + 1;
	msg.msg_control = NULL;
	msg.msg_controllen = 0;
	msg.msg_flags = 0;
	iov[0].iov_base = rqb;
	iov[0].iov_len = 20;
	sndlen = 20;
	i = iov + 1;
	{
		nuint numr = numRq;
		const NW_FRAGMENT* nwf = rq;
		
		while (numr--) {
			i->iov_base = nwf->fragAddr.rw;
			i->iov_len = nwf->fragSize;
			sndlen += nwf->fragSize;
			i++;
			rq++;
		}
		numr = numRp;
		nwf = rp;
		maxrpl = 12;
		while (numr--) {
			maxrpl += nwf++->fragSize;
		}
	}
	DSET_LH(rqb, 0, NCPI_REQUEST_SIGNATURE);
	DSET_LH(rqb, 4, sndlen);
	DSET_LH(rqb, 8, 1);
	DSET_LH(rqb, 12, maxrpl);
	DSET_LH(rqb, 16, function);
	rd = sendmsg(fd, &msg, MSG_NOSIGNAL);
	if (rd != sndlen) {
		close(fd);
		conn->global_fd = -1;
		if (retry) {
			goto errquit;
		}
		retry = 1;
		goto retryLoop;
	}
	rd = timedRecv(fd, rqb, 12, 30000000);
	if (rd < 0) {
		goto abort;
	}
	if (DVAL_LH(rqb, 0) != NCPI_REPLY_SIGNATURE) {
		goto abort;
	}
	exprpl = DVAL_LH(rqb, 4);
	if (exprpl < 12) {
		goto abort;
	}
	exprpl -= 12;
	rdu = exprpl;
	i = iov;
	for (j = 0; j < numRp; j++) {
		size_t r = rp[j].fragSize;
		if (r > rdu)
			r = rdu;
		if (r) {
			i->iov_base = rp[j].fragAddr.rw;
			i->iov_len = r;
			rdu -= r;
			i++;
		}
	}
	if (i != iov) {
		if (timedRecvMsg(fd, iov, i-iov, 10000000)) {
			goto abort;
		}
	}
	if (rplen) {
		*rplen = exprpl - rdu;
	}
	if (rdu) {
		timedRecv(fd, conn->packet, rdu, 10000000);
	}
	err = DVAL_LH(rqb, 8);
quit:;	
	ncp_unlock_conn(conn);
	return err;
abort:;
	close(fd);
	conn->global_fd = -1;
errquit:;
	goto quit;
}

NWCCODE NWGetObjectName(NWCONN_HANDLE conn, NWObjectID ID, char* name, NWObjectType* type) {
	long result;
	struct ncp_bindery_object spc;

	result = ncp_get_bindery_object_name(conn, ID, &spc);
	if (result)
		return result;
	if (name) strncpy(name, spc.object_name, NCP_BINDERY_NAME_LEN);
	if (type) *type = spc.object_type;
	return 0;
}

NWCCODE NWGetObjectID(NWCONN_HANDLE conn, const char* name, NWObjectType type, NWObjectID* ID) {
	long result;
	struct ncp_bindery_object spc;
	
	result = ncp_get_bindery_object_id(conn, type, name, &spc);
	if (result)
		return result;
	if (ID)
		*ID = spc.object_id;
	return 0;
}

NWCCODE NWGetBinderyAccessLevel(NWCONN_HANDLE conn,
		nuint8* level, NWObjectID* id) {
	nuint8 reply[16];
	NW_FRAGMENT rp;
	NWCCODE err;
	
	rp.fragAddr.rw = reply;
	rp.fragSize = sizeof(reply);
	
	err = NWRequestSimple(conn, NCPC_SFN(0x17, 0x46), NULL, 0, &rp);
	if (err)
		return err;
	if (rp.fragSize < 5)
		return NWE_INVALID_NCP_PACKET_LENGTH;
	if (level)
		*level = BVAL(reply, 0);
	if (id)
		*id = DVAL_HL(reply, 1);
	return 0;
} 

NWCCODE NWGetNSEntryInfo(NWCONN_HANDLE conn, nuint dirHandle, const char* path, 
	nuint srcNS, nuint dstNS, nuint16 attr, nuint32 rim, NW_ENTRY_INFO* info) {
	unsigned char buff[1024];
	int rslt;

	rslt = ncp_path_to_NW_format(path, buff, sizeof(buff));
	if (rslt < 0)
		return -rslt;
	rslt = ncp_obtain_file_or_subdir_info2(conn, srcNS, dstNS, attr, rim, 
		dirHandle?0:0xFF, 0, dirHandle, buff, rslt, info);
	return rslt;
}

NWCCODE NWParsePath(const char* path, char* server, NWCONN_HANDLE* conn, 
	char* volume, char* volpath) {
	long result;
	NWCONN_HANDLE cn;
	struct NWCCRootEntry rent;
	char tmp[1000];
	char* ptr;

	if (!path)
		return ERR_NULL_POINTER;

	result = ncp_open_mount(path, &cn);
	if (result) {
		if (volume)
			*volume = 0;
		if (volpath)
			strcpy(volpath, path);
		if (conn)
			*conn = NULL;
		if (server)
			*server = 0;
		return 0;
	}
	result = NWCCGetConnInfo(cn, NWCC_INFO_ROOT_ENTRY, sizeof(rent), &rent);
	if (result) {
		ncp_close(cn);
		return NWE_REQUESTER_FAILURE;
	}
	if (rent.volume > 255) {
		*tmp = 0;
	} else {
		result = ncp_ns_get_full_name(cn, NW_NS_DOS, NW_NS_DOS, 1,
			rent.volume, rent.dirEnt, NULL, 0, tmp, sizeof(tmp));
		if (result) {
			ncp_close(cn);
			return result;
		}
	}
	ptr = strchr(tmp, ':');
	if (ptr) {
		if (volume) {
			memcpy(volume, tmp, ptr-tmp);
			volume[ptr-tmp]=0;
		}
		if (volpath)
			strcpy(volpath, ptr+1);
	} else {
		if (volume)
			*volume = 0;
		if (volpath)
			strcpy(volpath, tmp);
	}
	if (server) {
		if (NWGetFileServerName(cn, server)) {
			*server = 0;
		}
	}
	if (conn)
		*conn = cn;
	else
		ncp_close(cn);
	return 0;
}

NWCCODE NWDownFileServer(NWCONN_HANDLE conn, nuint force) {
	nuint8 flag = force?0:0xFF;

	return NWRequestSimple(conn, NCPC_SFN(0x17, 0xD3), &flag, sizeof(flag), NULL);
}

NWCCODE NWCloseBindery(NWCONN_HANDLE conn) {
	return NWRequestSimple(conn, NCPC_SFN(0x17, 0x44), NULL, 0, NULL);
}

NWCCODE NWOpenBindery(NWCONN_HANDLE conn) {
	return NWRequestSimple(conn, NCPC_SFN(0x17, 0x45), NULL, 0, NULL);
}

NWCCODE NWDisableTTS(NWCONN_HANDLE conn) {
	return NWRequestSimple(conn, NCPC_SFN(0x17, 0xCF), NULL, 0, NULL);
}

NWCCODE NWEnableTTS(NWCONN_HANDLE conn) {
	return NWRequestSimple(conn, NCPC_SFN(0x17, 0xD0), NULL, 0, NULL);
}

NWCCODE NWDisableFileServerLogin(NWCONN_HANDLE conn) {
	return NWRequestSimple(conn, NCPC_SFN(0x17, 0xCB), NULL, 0, NULL);
}

NWCCODE NWEnableFileServerLogin(NWCONN_HANDLE conn) {
	return NWRequestSimple(conn, NCPC_SFN(0x17, 0xCC), NULL, 0, NULL);
}

static NWCCODE NWSMExec(NWCONN_HANDLE conn, int scode, 
		const nuint8* rqd, const char* txt, const char* txt2, 
	nuint32* rpd) {
	NW_FRAGMENT rq[4];
	NW_FRAGMENT rp;
	static const char here20[20] = { 0 };
	nuint8 rpl[24];
	nuint8 hdr[3];
	NWCCODE err;

	if (!rqd) rq[1].fragAddr.ro = here20; else rq[1].fragAddr.ro = rqd;
	rq[1].fragSize = 20;
	rq[0].fragAddr.ro = hdr;
	rq[0].fragSize = sizeof(hdr);
	rq[2].fragAddr.ro = txt;
	rq[2].fragSize = strlen(txt) + 1;
	rq[3].fragAddr.ro = txt2;
	rq[3].fragSize = txt2?(strlen(txt2) + 1):0;
	WSET_HL(hdr, 0, rq[3].fragSize + rq[2].fragSize + 1 + 20);
	BSET(hdr, 2, scode);
	rp.fragAddr.rw = rpl;
	rp.fragSize = sizeof(rpl);
	err = NWRequest(conn, 0x83, 4, rq, 1, &rp);
	if (err)
		return err;
	if (rp.fragSize < 4)
		return 0x897E;	/* buffer mismatch */
	err = DVAL_LH(rpl, 0);
	if (err)
		return err;
	if (rpd) {
		if (rp.fragSize < 24)
			return 0x897E;
		*rpd = DVAL_LH(rpl, 20);
	}
	return err;
}

NWCCODE NWSMLoadNLM(NWCONN_HANDLE conn, const char* cmd) {
	return NWSMExec(conn, 0x01, NULL, cmd, NULL, NULL);
}

NWCCODE NWSMUnloadNLM(NWCONN_HANDLE conn, const char* cmd) {
	return NWSMExec(conn, 0x02, NULL, cmd, NULL, NULL);
}

NWCCODE NWSMMountVolume(NWCONN_HANDLE conn, const char* vol, nuint32* volnum) {
	return NWSMExec(conn, 0x03, NULL, vol, NULL, volnum);
}

NWCCODE NWSMDismountVolumeByName(NWCONN_HANDLE conn, const char* vol) {
	return NWSMExec(conn, 0x04, NULL, vol, NULL, NULL);
}

NWCCODE NWSMDismountVolumeByNumber(NWCONN_HANDLE conn, nuint32 vol) {
	char volname[NCP_VOLNAME_LEN + 1];
	NWCCODE err;

	err = ncp_get_volume_name(conn, vol, volname, sizeof(volname));
	if (err)
		return err;
	return NWSMDismountVolumeByName(conn, volname);
}

NWCCODE NWSMSetDynamicCmdIntValue(NWCONN_HANDLE conn, const char* set,
		nuint32 value) {
	unsigned char hdr[20];

	memset(hdr, 0, sizeof(hdr));
	DSET_LH(hdr, 0, 1);
	DSET_LH(hdr, 4, value);
	return NWSMExec(conn, 0x06, hdr, set, NULL, NULL);
}

NWCCODE NWSMSetDynamicCmdStrValue(NWCONN_HANDLE conn, const char* set,
		const char* value) {
	return NWSMExec(conn, 0x06, NULL, set, value, NULL);
}

NWCCODE NWSMExecuteNCFFile(NWCONN_HANDLE conn, const char* cmd) {
	return NWSMExec(conn, 0x07, NULL, cmd, NULL, NULL);
}

/* Pre 3.11 NetWare */
static NWCCODE NWClearConnectionNumberV0(NWCONN_HANDLE conn, NWCONN_NUM connNumber) {
	nuint8 connN;

	if (connNumber >= 256)
		return NWE_CONN_NUM_INVALID;
	connN = connNumber;
	return NWRequestSimple(conn, NCPC_SFN(0x17, 0xD2), &connN, 1, NULL);
}

/* Post (including) 3.11 NetWare... */
static NWCCODE NWClearConnectionNumberV1(NWCONN_HANDLE conn, NWCONN_NUM connNumber) {
	nuint8 connN[4];

	DSET_LH(connN, 0, connNumber);
	return NWRequestSimple(conn, NCPC_SFN(0x17, 0xFE), &connN, 4, NULL);
}

NWCCODE NWClearConnectionNumber(NWCONN_HANDLE conn, NWCONN_NUM connNumber) {
	NWCCODE err;

	err = NWClearConnectionNumberV1(conn, connNumber);
	if (err == NWE_NCP_NOT_SUPPORTED) {
		err = NWClearConnectionNumberV0(conn, connNumber);
	}
	return err;
}

/* -------------------------------- */
/* Get object connection list
 * name must not be NULL (otherwise core)
 * name must be shorter than 256 chars (otherwise NWE_SERVER_FAILURE)
 * conns can be NULL
 * numConns can be NULL
 * returned numConns can be greater than maxConns, but connections
   above maxConns are lost
 * that's all? */
static NWCCODE NCPGetObjectConnectionList215(NWCONN_HANDLE conn,
		const char* name, nuint16 type, size_t* numConns,
		NWCONN_NUM* conns, size_t maxConns) {
	nuint8 rq_array[2+1];
	nuint8 rp_array[1+256];
	NW_FRAGMENT rq[2];
	NW_FRAGMENT rp;
	size_t strl;
	NWCCODE err;
	size_t nconns;
		
	strl = strlen(name);
	if (strl > 255)
		return NWE_SERVER_FAILURE;
	WSET_HL(rq_array, 0, type);
	BSET(rq_array, 2, strl);
	rq[0].fragAddr.ro = rq_array;
	rq[0].fragSize = 3;
	rq[1].fragAddr.ro = name;
	rq[1].fragSize = strl;
	rp.fragAddr.rw = rp_array;
	rp.fragSize = sizeof(rp_array);
	err = NWRequest(conn, NCPC_SFN(0x17, 0x15), 2, rq, 1, &rp);
	if (err)
		return err;
	if (rp.fragSize < 1) {
		/* at least one byte must be returned, do not you think? */
		return NWE_INVALID_NCP_PACKET_LENGTH;
	}
	nconns = BVAL(rp_array, 0);
	if (rp.fragSize <= nconns) {
		/* reply shorter than returned values... */
		return NWE_INVALID_NCP_PACKET_LENGTH;
	}
	if (maxConns > nconns)
		maxConns = nconns;
	if (conns) {
		size_t i;
		
		for (i = 1; i <= maxConns; i++) {
			*conns++ = BVAL(rp_array, i);
		}
	}
	if (numConns)
		*numConns = nconns;
	return 0;
}

static NWCCODE NCPGetObjectConnectionList(NWCONN_HANDLE conn, NWCONN_NUM first,
		const char* name, nuint16 type, size_t* numConns,
		NWCONN_NUM* conns, size_t maxConns) {
	nuint8 rq_array[4+2+1];
	nuint8 rp_array[1+1024];
	NW_FRAGMENT rq[2];
	NW_FRAGMENT rp;
	size_t strl;
	NWCCODE err;
	size_t nconns;
		
	strl = strlen(name);
	if (strl > 255)
		return NWE_SERVER_FAILURE;
	DSET_LH(rq_array, 0, first);
	WSET_HL(rq_array, 4, type);
	BSET(rq_array, 6, strl);
	rq[0].fragAddr.ro = rq_array;
	rq[0].fragSize = 7;
	rq[1].fragAddr.ro = name;
	rq[1].fragSize = strl;
	rp.fragAddr.rw = rp_array;
	rp.fragSize = sizeof(rp_array);
	err = NWRequest(conn, NCPC_SFN(0x17, 0x1B), 2, rq, 1, &rp);
	if (err)
		return err;
	if (rp.fragSize < 1) {
		return NWE_INVALID_NCP_PACKET_LENGTH;
	}
	nconns = BVAL(rp_array, 0);
	if (rp.fragSize < nconns * 4 + 1) {
		return NWE_INVALID_NCP_PACKET_LENGTH;
	}
	if (maxConns > nconns)
		maxConns = nconns;
	if (conns) {
		size_t i;
		
		for (i = 0; i < maxConns; i++) {
			*conns++ = DVAL_LH(rp_array, 1 + i * 4);
		}
	}
	if (numConns)
		*numConns = nconns;
	return 0;
}

static NWCCODE NCPGetConnListFromObject(NWCONN_HANDLE conn, NWObjectID objID,
		NWCONN_NUM firstConn, size_t* conns, NWCONN_NUM* connlist) {
	nuint8 rq_array[4+4];
	nuint8 rp_array[2+4*125]; /* 125 is magic NWServer constant... */
				  /* ncphdr+2+4*125 is just below 512... maybe? */
	NW_FRAGMENT rq;
	NW_FRAGMENT rp;
	NWCCODE err;
	size_t nconns;
		
	DSET_HL(rq_array, 0, objID);
	DSET_LH(rq_array, 4, firstConn);
	rq.fragAddr.ro = rq_array;
	rq.fragSize = 8;
	rp.fragAddr.rw = rp_array;
	rp.fragSize = sizeof(rp_array);
	err = NWRequest(conn, NCPC_SFN(0x17, 0x1F), 1, &rq, 1, &rp);
	if (err)
		return err;
	if (rp.fragSize < 2) {
		return NWE_INVALID_NCP_PACKET_LENGTH;
	}
	nconns = WVAL_LH(rp_array, 0);
	if (rp.fragSize < nconns * 4 + 2) {
		return NWE_INVALID_NCP_PACKET_LENGTH;
	}
	if (nconns > 125)
		nconns = 125; /* ugh... Houston, we have a problem... */
	if (connlist) {
		size_t i;
		
		for (i = 0; i < nconns; i++) {
			*connlist++ = DVAL_LH(rp_array, 2 + i * 4);
		}
	}
	if (conns)
		*conns = nconns;
	return 0;
}

NWCCODE NWGetObjectConnectionNumbers(NWCONN_HANDLE conn, const char* name,
		nuint16 type, size_t* numConns, NWCONN_NUM* conns,
		size_t maxConns) {
	size_t returned;
	size_t complete;
	NWCCODE err;
	
	err = NCPGetObjectConnectionList(conn, 0, name, type, &returned, 
			conns, maxConns);
	if (err == NWE_NCP_NOT_SUPPORTED) {		
		return NCPGetObjectConnectionList215(conn, name, type, 
			numConns, conns, maxConns);
	}
	if (err)
		return err;
	complete = 0;
	/* TODO: do it better... */
	while (!err && (returned == 255) && (maxConns > 255) && conns) {
		conns += returned;
		maxConns -= returned;
		complete += returned;
		returned = 0;
		err = NCPGetObjectConnectionList(conn, conns[-1], name,
			type, &returned, conns, maxConns);
	}
	if (numConns)
		*numConns = complete + returned;
	return 0;	
}

NWCCODE NWGetConnListFromObject(NWCONN_HANDLE conn, NWObjectID objID,
		NWCONN_NUM firstConn, size_t* conns, NWCONN_NUM* connlist) {
	char name[NCP_BINDERY_NAME_LEN+1];
	nuint16 type;
	NWCCODE err;
	
	err = NCPGetConnListFromObject(conn, objID, firstConn, conns, connlist);
	if (err != NWE_NCP_NOT_SUPPORTED)
		return err;
	err = NWGetObjectName(conn, objID, name, &type);
	if (err)
		return err;
	if (firstConn) {
		if (conns) *conns = 0;
	} else
		err = NWGetObjectConnectionNumbers(conn, name, type, conns, connlist, 125);
	return err;
}

/* caller must not modify connArray during run ! */
static NWCCODE NCPSendBroadcastMessageOld(NWCONN_HANDLE conn, const char* message,
		size_t conns, NWCONN_NUM* connArray, nuint8* deliveryStatus) {
	nuint8 rq_array[1+255+1];
	nuint8 rp_array[1+255];
	size_t strl;
	NWCCODE err;
	size_t nconns;
	size_t rconns;
	size_t cnt;
	NWCONN_NUM* tmp;
	
	strl = strlen(message);
	if (strl > 58)
		strl = 58;
	if (conns > 255)
		return NWE_SERVER_FAILURE;
	if (!conns)
		return 0;
	nconns = 1;
	tmp = connArray;
	for (cnt = conns; cnt; cnt--) {
		NWCONN_NUM r = *tmp++;
		if (r < 256)
			BSET(rq_array, nconns++, r);
	}
	BSET(rq_array, 0, nconns-1);
	BSET(rq_array, nconns, strl);

	if (nconns != 1) {
		NW_FRAGMENT rq[2];
		NW_FRAGMENT rp;
		
		/* something to do... */
		rq[0].fragAddr.ro = rq_array;
		rq[0].fragSize = nconns + 1;
		rq[1].fragAddr.ro = message;
		rq[1].fragSize = strl;
		rp.fragAddr.rw = rp_array;
		rp.fragSize = sizeof(rp_array);
		err = NWRequest(conn, NCPC_SFN(0x15, 0x00), 2, rq, 1, &rp);
		if (err)
			return err;
		if (rp.fragSize < 1) {
			return NWE_INVALID_NCP_PACKET_LENGTH;
		}
		rconns = BVAL(rp_array, 0);
		if (rp.fragSize < rconns + 1) {
			return NWE_INVALID_NCP_PACKET_LENGTH;
		}
		if (rconns != nconns)
			return NWE_INVALID_NCP_PACKET_LENGTH;
	}
	if (deliveryStatus) {
		size_t r = 1;
		for (; conns--;) {
			if (*connArray++ < 256)
				*deliveryStatus++ = BVAL(rp_array, r++);
			else
				*deliveryStatus++ = 0x01;
		}
	}
	return 0;
}

static NWCCODE NCPSendBroadcastMessage(NWCONN_HANDLE conn, const char* message,
		size_t conns, NWCONN_NUM* connArray, nuint8* deliveryStatus) {
#define __MAX_PER_REQUEST 512
	nuint8 rq_array[2+4*__MAX_PER_REQUEST+1];
	nuint8 rp_array[2+4*__MAX_PER_REQUEST];
	NW_FRAGMENT rq[2];
	NW_FRAGMENT rp;
	size_t strl;
	NWCCODE err;
	size_t nconns;
	size_t cnt;
		
	strl = strlen(message);
	if (strl > 255)
		strl = 255;
	if (conns > __MAX_PER_REQUEST)
		return NWE_SERVER_FAILURE;
	if (!conns)
		return 0;
	WSET_LH(rq_array, 0, conns);
	nconns = 2;
	for (cnt = conns; cnt; cnt--) {
		DSET_LH(rq_array, nconns, *connArray++);
		nconns += 4;
	}
	BSET(rq_array, nconns, strl);
	rq[0].fragAddr.ro = rq_array;
	rq[0].fragSize = nconns + 1;
	rq[1].fragAddr.ro = message;
	rq[1].fragSize = strl;
	rp.fragAddr.rw = rp_array;
	rp.fragSize = sizeof(rp_array);
	err = NWRequest(conn, NCPC_SFN(0x15, 0x0A), 2, rq, 1, &rp);
	if (err)
		return err;
	if (rp.fragSize < 2) {
		return NWE_INVALID_NCP_PACKET_LENGTH;
	}
	nconns = WVAL_LH(rp_array, 0);
	if (rp.fragSize < nconns * 4 + 2) {
		return NWE_INVALID_NCP_PACKET_LENGTH;
	}
	if (nconns != conns)
		return NWE_INVALID_NCP_PACKET_LENGTH;
	if (deliveryStatus) {
		size_t i;
		
		for (i = 0; i < nconns; i++) {
			*deliveryStatus++ = DVAL_LH(rp_array, 2 + i * 4);
		}
	}
	return 0;
#undef __MAX_PER_REQUEST	
}

NWCCODE NWSendBroadcastMessage(NWCONN_HANDLE conn, const char* message,
		size_t conns, NWCONN_NUM* connArray, nuint8* deliveryStatus) {
	NWCCODE err;
	
	err = NCPSendBroadcastMessage(conn, message, conns, connArray,
		deliveryStatus);
	if (err != NWE_NCP_NOT_SUPPORTED)
		return err;
	return NCPSendBroadcastMessageOld(conn, message, conns, connArray,
		deliveryStatus);
}

NWCCODE __NWReadFileServerInfo(NWCONN_HANDLE conn) {
	NWCCODE err;
	struct ncp_file_server_info_2 target;

	if (conn->serverInfo.valid)
		return 0;
	err = ncp_get_file_server_information_2(conn, &target, sizeof(target));
	if (err) {
		return err;
	}
	ncpt_mutex_lock(&conn->serverInfo.mutex);
	if (!conn->serverInfo.valid) {
		char* sn;
		conn->serverInfo.version.major = target.FileServiceVersion;
		conn->serverInfo.version.minor = target.FileServiceSubVersion;
		conn->serverInfo.version.revision = target.Revision;
		conn->serverInfo.ncp64bit = target._64BitOffsetsSupportedFlag != 0;
		sn = strdup(target.ServerName);
		if (sn) {
			if (conn->serverInfo.serverName)
				free(conn->serverInfo.serverName);
			conn->serverInfo.serverName = sn;
		} else
			err = ENOMEM;
		if (!err)
			conn->serverInfo.valid = 1;
	}
	ncpt_mutex_unlock(&conn->serverInfo.mutex);
	return err;
}

static NWCCODE NWGetFileServerNameInt(NWCONN_HANDLE conn, size_t maxlen, char* name) {
	NWCCODE err;

	err = __NWReadFileServerInfo(conn);
	if (err)
		return err;
	if (name) {
		size_t len = strlen(conn->serverInfo.serverName) + 1;
		if (len > maxlen)
			return NWE_BUFFER_OVERFLOW;
		memcpy(name, conn->serverInfo.serverName, len);
	}
	return 0;	
}

NWCCODE NWGetFileServerName(NWCONN_HANDLE conn, char* name) {
	return NWGetFileServerNameInt(conn, NCP_BINDERY_NAME_LEN+1, name);
}

NWCCODE NWGetFileServerVersion(NWCONN_HANDLE conn, u_int16_t* version) {
	NWCCODE err;

	err = __NWReadFileServerInfo(conn);
	if (err)
		return err;
	if (version) {
		*version = (conn->serverInfo.version.major << 8) 
			 | conn->serverInfo.version.minor;
	}
	return 0;
}

NWCCODE NWGetConnectionNumber(NWCONN_HANDLE conn, NWCONN_NUM* number) {
	int err = ncp_get_conn_number(conn);
	if (err < 0)
		return NWE_REQUESTER_FAILURE;
	*number = err;
	return 0;
}

NWCCODE ncp_put_req_size_unsigned(void* buffer, size_t reqlen, unsigned /* long */ long val) {
	switch (reqlen) {
		case 1:	if (val & ~0xFF)
				return E2BIG;
			*(u_int8_t*)buffer = val;
			return 0;
		case 2:	if (val & ~0xFFFF)
				return E2BIG;
			*(u_int16_t*)buffer = val;
			return 0;
		case 4:	/* if (val & ~0xFFFFFFFFUL)
				return E2BIG;
			*/
			*(u_int32_t*)buffer = val;
			return 0;
	}
	return NWE_PARAM_INVALID;
}

/* should be in ndslib.h ? */
#define NWE_BIND_NO_SUCH_PROP NWE_NCP_NOT_SUPPORTED

NWCCODE NWCCGetConnInfo(NWCONN_HANDLE conn, nuint info, size_t len,
		void* buffer) {
	if (!buffer) /* just in case */
		return ERR_NULL_POINTER;

	switch (info) {
		case NWCC_INFO_CONN_NUMBER:
			if (len == sizeof(NWCONN_NUM))
				return NWGetConnectionNumber(conn, (NWCONN_NUM*)buffer);
			{
				NWCONN_NUM cn;
				NWCCODE err;
				
				err = NWGetConnectionNumber(conn, &cn);
				if (err)
					return err;
				return ncp_put_req_size_unsigned(buffer, len, cn);
			}
		case NWCC_INFO_USER_ID:
			{
				if (!conn->user_id_valid) {
					NWCCODE err;
					struct ncp_bindery_object obj;
					NWCONN_NUM cn;

					err = NWGetConnectionNumber(conn, &cn);
					if (err)
						return err;
					err = ncp_get_stations_logged_info(conn, cn, &obj, NULL);
					if (err)
						return err;
					conn->user_id = obj.object_id;
					conn->user_id_valid = 1;
				}
				if (len != sizeof(NWObjectID))
					return NWE_BUFFER_OVERFLOW;
				*(NWObjectID*)buffer = conn->user_id;
				return 0;
			}
		case NWCC_INFO_SERVER_NAME:
			return NWGetFileServerNameInt(conn, len, buffer);
		case NWCC_INFO_MAX_PACKET_SIZE:
			return ncp_put_req_size_unsigned(buffer, len, conn->i.buffer_size);
		case NWCC_INFO_SERVER_VERSION:
			{
				NWCCODE err;

				err = __NWReadFileServerInfo(conn);
				if (err)
					return err;
				if (len != sizeof(NWCCVersion))
					return NWE_BUFFER_OVERFLOW;
				((NWCCVersion*)buffer)->major = conn->serverInfo.version.major;
				((NWCCVersion*)buffer)->minor = conn->serverInfo.version.minor;
				((NWCCVersion*)buffer)->revision = conn->serverInfo.version.revision;
#if 0
				printf ("NWCC_INFO_SERVER_VERSION %d %d %d %d %s\n", conn->serverInfo.valid,
					conn->serverInfo.version.major, conn->serverInfo.version.minor,
					conn->serverInfo.version.revision, conn->serverInfo.serverName);
#endif

			}
			return 0;
		case NWCC_INFO_TRAN_ADDR:
			{
				NWCCTranAddr* p = buffer;
				if (len < sizeof(NWCCTranAddr))
					return NWE_BUFFER_OVERFLOW;
				switch (conn->addr.any.sa_family) {
#ifdef CONFIG_NATIVE_IP
					case AF_INET:
						if (p->len < 6)
							return NWE_BUFFER_OVERFLOW;
						p->len = 6;
						p->type = NT_UDP;
						memcpy(((char*)(p->buffer))+2, &conn->addr.inet.sin_addr.s_addr, 4);
						memcpy(p->buffer, &conn->addr.inet.sin_port, 2);
						break;
#endif
#ifdef CONFIG_NATIVE_IPX
					case AF_IPX:
						if (p->len < 12)
							return NWE_BUFFER_OVERFLOW;
						p->len = 12;
						p->type = NT_IPX;
						memcpy(p->buffer, &conn->addr.ipx.sipx_network, 4);
						memcpy(((char*)(p->buffer))+4, conn->addr.ipx.sipx_node, 6);
						memcpy(((char*)(p->buffer))+10, &conn->addr.ipx.sipx_port, 2);
						break;
#endif
					default:
						return NWE_REQUESTER_FAILURE;
				}				
			}
			return 0;
		case NWCC_INFO_USER_NAME:
			{
				const char* user = "";
				size_t usrlen;
				NWCCODE err;
				struct ncp_bindery_object obj;

				if (!conn->user) {
					err = ncp_get_stations_logged_info(conn, conn->i.connection, &obj, NULL);
					if (!err)
						conn->user = strdup(obj.object_name);
				}
				if (conn->user)
					user = conn->user;
				usrlen = strlen(user) + 1;
				if (usrlen > len)
					return NWE_BUFFER_OVERFLOW;
				memcpy(buffer, user, usrlen);
			}
			return 0;
		case NWCC_INFO_MOUNT_UID:
			{
				uid_t tmpuid;
				NWCCODE err;

				if (ncp_get_conn_type(conn) != NCP_CONN_PERMANENT)
					return NWE_REQUESTER_FAILURE;
				if (len == sizeof(uid_t))
					return ncp_get_mount_uid(conn->mount_fid,
						buffer);
				err = ncp_get_mount_uid(conn->mount_fid,
					&tmpuid);
				if (err)
					return err;
				return ncp_put_req_size_unsigned(buffer, len, tmpuid);
			}
		case NWCC_INFO_SECURITY:
			{
				unsigned int val = 0;
				
				if (ncp_get_sign_wanted(conn))
					val |= NWCC_SECUR_LEVEL_SIGN_HEADERS;
				if (ncp_get_sign_active(conn))
					val |= NWCC_SECUR_SIGNING_IN_USE;
				return ncp_put_req_size_unsigned(buffer, len, val);
			}
		case NWCC_INFO_BCAST_STATE:
			{
				unsigned char rpb[4];
				NW_FRAGMENT rp;
				size_t tln;
				NWCCODE err;

				rp.fragAddr.rw = rpb;
				rp.fragSize = sizeof(rpb);
				
				err = __NWExecuteGlobal(conn, NCPI_OP_GETBCASTSTATE, 0, NULL, 1, &rp, &tln);
				if (err != NCPLIB_NCPD_DEAD) {
					if (err)
						return err;
					if (tln < 4)
						return NWE_INVALID_NCP_PACKET_LENGTH;
					return ncp_put_req_size_unsigned(buffer, len, DVAL_LH(rpb, 0));
				}
				if (conn->bcast_state == NWCC_BCAST_PERMIT_UNKNOWN)
					return NWE_REQUESTER_FAILURE;
				return ncp_put_req_size_unsigned(buffer, len, conn->bcast_state);
			}
		case NWCC_INFO_TREE_NAME:
#ifdef NDS_SUPPORT
			{

				char tn[MAX_TREE_NAME_CHARS+1];
				size_t treelen;

				if (!NWIsDSServer(conn, tn))
					return NWE_BIND_NO_SUCH_PROP;
				if (buffer) {
					char* p;
					p = strchr(tn, '\0') - 1;
					while ((p >= tn) && (*p == '_'))
						p--;
					treelen = p - tn + 1;
					tn[treelen] = 0;
					if (treelen >= len)
						return NWE_BUFFER_OVERFLOW;
					strcpy(buffer,tn);
				}
			}
			return 0;
#else
			return NWE_BIND_NO_SUCH_PROP;
#endif
		case NWCC_INFO_ROOT_ENTRY:
			{
				if (ncp_get_conn_type(conn) != NCP_CONN_PERMANENT)
					return NWE_REQUESTER_FAILURE;
				if (len < sizeof(struct NWCCRootEntry))
					return NWE_BUFFER_OVERFLOW;
				((struct NWCCRootEntry*)buffer)->volume = conn->i.volume_number;
				((struct NWCCRootEntry*)buffer)->dirEnt = conn->i.directory_id;
			}
			return 0;
		case NWCC_INFO_MOUNT_POINT:
			{
				size_t need_len;
				
				if (ncp_get_conn_type(conn) != NCP_CONN_PERMANENT)
					return NWE_REQUESTER_FAILURE;
				if (!conn->mount_point)
					return NWE_REQUESTER_FAILURE;
				need_len = strlen(conn->mount_point) + 1;
				if (len < need_len)
					return NWE_BUFFER_OVERFLOW;
				memcpy(buffer, conn->mount_point, need_len);
			}
			return 0;
		default:
			return NWE_INVALID_LEVEL;
	}
}

NWCCODE NWCCOpenConnByAddr(const NWCCTranAddr* addr, nuint openState,
		nuint reserved, NWCONN_HANDLE* conn) {
	nuint32 t;
	union ncp_sockaddr server;
	const nuint8* buffer;
	enum NET_ADDRESS_TYPE addrType;
	
	buffer = addr->buffer;
	if (!buffer)
		return NWE_PARAM_INVALID; /* ERR_NULL_POINTER */
	t = addr->type;
#ifdef CONFIG_NATIVE_IPX
	if (t == NWCC_TRAN_TYPE_IPX_old || t == NT_IPX) {
		if (addr->len < 12)
			return NWE_BUFFER_OVERFLOW;
		server.ipx.sipx_family = AF_IPX;
		memcpy(&server.ipx.sipx_network, buffer, 4);
		memcpy( server.ipx.sipx_node, buffer+4, 6);
		memcpy(&server.ipx.sipx_port, buffer+4+6, 2);
		server.ipx.sipx_type = 0x11;	/* NCP/IPX */
		addrType = NT_IPX;
	} else 
#endif
#ifdef CONFIG_NATIVE_IP
	if (t == NT_UDP || t == NT_TCP) {
		if (addr->len < 6)
			return NWE_BUFFER_OVERFLOW;
		server.inet.sin_family = AF_INET;
		memcpy(&server.inet.sin_addr.s_addr, buffer+2, 4);
		memcpy(&server.inet.sin_port, buffer, 2);
		addrType = t;
	} else
#endif
		return NWE_UNSUPPORTED_TRAN_TYPE;
	return NWCCOpenConnBySockAddr(&server.any, addrType, openState,
		reserved, conn);
}

NWCCODE NWCCCloseConn(NWCONN_HANDLE conn) {
	return ncp_close(conn);
}


/******************************************************** PP*/

NWCCODE NWScanVolDiskRestrictions(
		NWCONN_HANDLE	conn,
		NWVOL_NUM	volNum,
		nuint32*	sequence,
		NWVolumeRestrictions * volInfo) {
/* old API ??? by blocks of 12
   picke them up by blocks of 16 by return them by 12 */
	NWVOL_RESTRICTIONS myBuffer16;
	NWCCODE err;

	if (!volInfo)
		return ERR_NULL_POINTER;
	err = NWScanVolDiskRestrictions2(conn, volNum, sequence, &myBuffer16);
	if (err)
		return err;
	if (myBuffer16.numberOfEntries > 12)
		myBuffer16.numberOfEntries = 12;
	volInfo->numberOfEntries = myBuffer16.numberOfEntries;
	if (myBuffer16.numberOfEntries) 
		memcpy(volInfo->resInfo, myBuffer16.resInfo, myBuffer16.numberOfEntries * sizeof(NWOBJ_REST));
	return 0;
}

NWCCODE NWScanVolDiskRestrictions2(
		NWCONN_HANDLE	conn,
		NWVOL_NUM	volNum,
		nuint32*	sequence,
		NWVOL_RESTRICTIONS * volInfo) {
/* by blocks of 16 */
	nuint8 rq[1+4];
	nuint8 rp_b[sizeof (NWVOL_RESTRICTIONS)];
	NW_FRAGMENT rp;
	NWCCODE err;
	unsigned int tmp;
	unsigned int i;

	if (!sequence || !volInfo)
		return ERR_NULL_POINTER;

	rp.fragAddr.rw = rp_b;
	rp.fragSize = sizeof(rp_b);

	BSET(rq,0,volNum);
	DSET_LH(rq,1,*sequence);

	err = NWRequestSimple(conn, NCPC_SFN(22,32), &rq, 5, &rp);
	if (err)
		return err;
	if (rp.fragSize < 1)
		return NWE_INVALID_NCP_PACKET_LENGTH;
	tmp = BVAL(rp_b,0);
	/* tmp =0 means no more entries, done */
	if (tmp > 16)
		return NWE_INVALID_NCP_PACKET_LENGTH; /* or a better code ??? */
	if (rp.fragSize < 1+tmp*8)
		return NWE_INVALID_NCP_PACKET_LENGTH;

	volInfo->numberOfEntries = tmp;
	for (i = 0; i < tmp; i++) {
		volInfo->resInfo[i].objectID = DVAL_HL(rp_b, 1+i*8);
		volInfo->resInfo[i].restriction = DVAL_LH(rp_b, 1+i*8+4);
	}
	for (; i < 16; i++) {
		volInfo->resInfo[i].objectID = 0;
		volInfo->resInfo[i].restriction = 0;
	}
	return 0;
}


NWCCODE NWRemoveObjectDiskRestrictions(
		NWCONN_HANDLE	conn,
		NWVOL_NUM	volNum,
		NWObjectID	objectID) {
	nuint8 rq[5];

	BSET(rq, 0, volNum);
	DSET_HL(rq, 1, objectID);
	return NWRequestSimple(conn, NCPC_SFN(22,34), &rq, 5, NULL);
}

NWCCODE NWSetObjectVolSpaceLimit(
		NWCONN_HANDLE	conn,
		NWVOL_NUM	volNum,
		NWObjectID	objectID,
		nuint32		restriction) {
	nuint8 rq[1+4+4];

	BSET(rq,0,volNum);
	DSET_HL(rq,1,objectID);
	DSET_LH(rq,5,restriction);
	return NWRequestSimple(conn, NCPC_SFN(22,33), &rq, 1+4+4, NULL);
}

NWCCODE NWGetObjDiskRestrictions(
		NWCONN_HANDLE	conn,
		NWVOL_NUM	volNum,
		NWObjectID	objectID,
		nuint32*	restriction,
		nuint32*	inUse) {
	nuint8 rq[1+4];
	nuint8 rp_b[4+4];
	NW_FRAGMENT rp;
	NWCCODE err;

	rp.fragAddr.rw = rp_b;
	rp.fragSize = sizeof(rp_b);

	BSET(rq, 0, volNum);
	DSET_HL(rq, 1, objectID);

	err = NWRequestSimple(conn, NCPC_SFN(22,41), &rq, 5, &rp);
	if (err)
		return err;
	if (rp.fragSize < 8)
		return NWE_INVALID_NCP_PACKET_LENGTH;
	if (restriction)
		*restriction = DVAL_LH(rp_b, 0);
	if (inUse)
		*inUse = DVAL_LH(rp_b, 4);
	return 0;
}

/*****************************************************************************/
NWCCODE __NWEnableBroadcasts(NWCONN_HANDLE conn) {
	return NWRequestSimple(conn, NCPC_SFN(21,3), NULL, 0, NULL);
}

NWCCODE __NWDisableBroadcasts(NWCONN_HANDLE conn) {
	return NWRequestSimple(conn, NCPC_SFN(21,2), NULL, 0, NULL);
}

NWCCODE NWGetBroadcastMode(NWCONN_HANDLE conn, nuint16* mode) {
	return NWCCGetConnInfo(conn, NWCC_INFO_BCAST_STATE, sizeof(*mode), mode);
}

NWCCODE NWSetBroadcastMode(NWCONN_HANDLE conn, nuint16 mode) {
	NWCCODE err;
	nuint8 rqb[4];
	NW_FRAGMENT rq;

	switch (mode) {
		case NWCC_BCAST_PERMIT_ALL:
		case NWCC_BCAST_PERMIT_SYSTEM:
		case NWCC_BCAST_PERMIT_NONE:
		case NWCC_BCAST_PERMIT_POLL:
			break;
		default:
			return NWE_PARAM_INVALID;
	}
	DSET_LH(rqb, 0, mode);
	rq.fragAddr.ro = rqb;
	rq.fragSize = sizeof(rqb);
	
	err = __NWExecuteGlobal(conn, NCPI_OP_SETBCASTSTATE, 1, &rq, 0, NULL, NULL);
	if (err != NCPLIB_NCPD_DEAD) {
		return err;
	}
	switch (mode) {
		case NWCC_BCAST_PERMIT_ALL:
			err = __NWEnableBroadcasts(conn); 
			break;
		case NWCC_BCAST_PERMIT_SYSTEM:
		case NWCC_BCAST_PERMIT_NONE:
			err = __NWDisableBroadcasts(conn);
			break;
		case NWCC_BCAST_PERMIT_POLL:
			err = __NWDisableBroadcasts(conn);
			break;
		default:
			err = NWE_PARAM_INVALID;
	}
	if (err)
		return err;
	conn->bcast_state = mode;
	return 0;
}

NWCCODE NWEnableBroadcasts(NWCONN_HANDLE conn) {
	return NWSetBroadcastMode(conn, NWCC_BCAST_PERMIT_ALL);
}

NWCCODE NWDisableBroadcasts(NWCONN_HANDLE conn) {
	return NWSetBroadcastMode(conn, NWCC_BCAST_PERMIT_NONE);
}

/********************************************************************************/


NWCCODE NWSignalSemaphore(
		NWCONN_HANDLE	conn,
		nuint32		semHandle) {
	NWCCODE err;

	ncp_init_request(conn);
	ncp_add_byte(conn, 3);	/* sub_func */
	ncp_add_dword_lh(conn, semHandle);
	err = ncp_request(conn, 111);
	ncp_unlock_conn(conn);
	return err;
}

NWCCODE NWCloseSemaphore(
		NWCONN_HANDLE	conn,
		nuint32		semHandle) {
	NWCCODE err;

	ncp_init_request(conn);
	ncp_add_byte(conn, 4);	/* sub_func */
	ncp_add_dword_lh(conn, semHandle);
	err = ncp_request(conn, 111);
	/* this NCP returns openCount and shareCount , but no way to return them */
	ncp_unlock_conn(conn);
	return err;
}

NWCCODE NWOpenSemaphore(
		NWCONN_HANDLE	conn,
		const char*	semName,
		nint16		initValue,
		nuint32*	semHandle,
		nuint16*	semOpenCount){
	NWCCODE err;
	size_t len;
	nuint8 nameBuf[512];

	if (!semName)
		return ERR_NULL_POINTER;
	if (!semHandle)
		return ERR_NULL_POINTER;

	len = strlen(semName);
	if (len > 255)
		len = 255;

	memset(nameBuf, 0, sizeof(nameBuf));
	memcpy(nameBuf, semName, len);
	ncp_init_request(conn);
	ncp_add_byte(conn, 0);	/* sub func 0 */
	ncp_add_byte(conn, initValue);
	ncp_add_byte(conn, len);
	ncp_add_mem(conn, nameBuf, sizeof(nameBuf));
	/* the string must be exactly 512 bytes
	   else server complains about a NCP packet incomplete */
	err = ncp_request(conn, 111);
	if (err) {
		ncp_unlock_conn(conn);
		return err;
	}

	*semHandle = ncp_reply_dword_lh(conn, 0);
	if (semOpenCount)
		*semOpenCount = ncp_reply_byte(conn, 4); /* NCP returns byte not word? */
	ncp_unlock_conn(conn);
	return 0;
}

NWCCODE NWExamineSemaphore(
		NWCONN_HANDLE	conn,
		nuint32		semHandle,
		nint16*		semValue,
		nuint16*	semOpenCount) {
	NWCCODE err;

	ncp_init_request(conn);
	ncp_add_byte(conn, 1);	/* sub_func */
	ncp_add_dword_lh(conn, semHandle);
	err = ncp_request(conn, 111);
	if (!err) {
		if (semValue) {
			*semValue = ncp_reply_byte(conn, 0); /* NCP returns byte not word? */
		}
		if (semOpenCount){
			*semOpenCount = ncp_reply_byte(conn, 1); /* NCP returns byte not word? */
		}
	}
	ncp_unlock_conn(conn);
	return err;
}

NWCCODE NWWaitOnSemaphore(
		NWCONN_HANDLE	conn,
		nuint32		semHandle,
		nuint16		timeOutValue) {
	NWCCODE err;

	ncp_init_request(conn);
	ncp_add_byte(conn, 2);	/* sub_func */
	ncp_add_dword_lh(conn, semHandle);
	ncp_add_word_lh(conn, timeOutValue);
	err = ncp_request(conn, 111);
	ncp_unlock_conn(conn);
	return err;
}

NWCCODE NWCancelWait(
		NWCONN_HANDLE	conn) {
	return NWRequestSimple(conn, NCPC_FN(112), NULL, 0, NULL);
}

NWCCODE NWGetVolumeNumber (
		NWCONN_HANDLE	conn,
		const char*	volumeName,
		NWVOL_NUM*	volumeNumber) {
	NWCCODE err;
	int v;

	/* nothing fancy but make code portable from MSWIN_PLAT to UNIX_PLAT ;-) */
	if (!volumeName)
		return ERR_NULL_POINTER;
	if (!volumeNumber)
		return ERR_NULL_POINTER;
	err = ncp_get_volume_number(conn, volumeName, &v);
	if (err)
		return err;
	*volumeNumber = v;
	return 0;
}



static NWCCODE __NWCCGetConnAddressInt(	/* internal */
		NWCONN_HANDLE	conn,
		nuint32*	bufLen,	/* return both */
		NWCCTranAddr*	tranAddr) {
	NWCCODE err;
	nuint8 myBuffer[32];
	NWCCTranAddr myTranAddr;

	myTranAddr.buffer = myBuffer;
	myTranAddr.len = sizeof(myBuffer);

	err = NWCCGetConnInfo(conn, NWCC_INFO_TRAN_ADDR, sizeof(myTranAddr), &myTranAddr);
	if (err)
		return err;
	if (bufLen)
		*bufLen = myTranAddr.len;
	if (tranAddr) {
		tranAddr->type = myTranAddr.type;
		tranAddr->len = myTranAddr.len;
		if (tranAddr->buffer)
			memcpy(tranAddr->buffer, myTranAddr.buffer, myTranAddr.len);
	}
	return 0;
}

NWCCODE NWCCGetConnAddress(
		NWCONN_HANDLE	conn,
		nuint32		bufLen,
		NWCCTranAddr*	tranAddr) {

	NWCCODE err;
	nuint32 aux;

	err = __NWCCGetConnAddressInt(conn, &aux, NULL);
	if (err)
		return err;
	if (aux > bufLen)
		return NWE_BUFFER_OVERFLOW;
	return __NWCCGetConnAddressInt(conn, NULL, tranAddr);
}

NWCCODE NWCCGetConnAddressLength(
		NWCONN_HANDLE	conn,
		nuint32*	bufLen) {
	return __NWCCGetConnAddressInt(conn, bufLen, NULL);
}

static NWCCODE __ncp_semaphore_fetch(CONN_SEMAPHORE *semaphore, CONN_SEMAPHORES *semaphores) {
	size_t nm;
	u_int8_t* base = semaphores->records + semaphores->curOffset;
	u_int8_t* end = semaphores->records + sizeof(semaphores->records);
	

	if (base + 7 > end) {
		return NWE_INVALID_NCP_PACKET_LENGTH;
	}
	nm = BVAL(base, 7);
	if (base + 7 + nm > end) {
		return NWE_INVALID_NCP_PACKET_LENGTH;
	}
	if (nm >= sizeof(semaphore->semaphoreName)) {
		return NWE_BUFFER_OVERFLOW;
	}
	semaphore->openCount = WVAL_LH(base, 0);
	semaphore->semaphoreValue = WVAL_LH(base, 2);
	semaphore->taskNumber = WVAL_LH(base, 4);
	memcpy(semaphore->semaphoreName, base + 7, nm);
	semaphore->semaphoreName[nm] = 0;
	semaphores->curOffset += 7 + nm;
	semaphores->curRecord++;
	return 0;
}

NWCCODE
NWScanSemaphoresByConn(
		NWCONN_HANDLE	conn,
		NWCONN_NUM	connNum,
		u_int16_t*	iterHandle,
		CONN_SEMAPHORE*	semaphore,
		CONN_SEMAPHORES* semaphores) {
	NWCCODE result;
	u_int16_t ih;
	
	if (!iterHandle || !semaphores) {
		return NWE_PARAM_INVALID;
	}
	ih = *iterHandle;
	if (!ih) {
		semaphores->nextRequest = 0;
		semaphores->numRecords = 0;
		semaphores->curRecord = 0;
	} else if (ih < semaphores->numRecords) {
		if (!semaphore)
			return NWE_PARAM_INVALID;
		if (semaphores->curRecord != ih)
			return NWE_PARAM_INVALID;
		result = __ncp_semaphore_fetch(semaphore, semaphores);
		if (result)
			goto abrt;
		ih = semaphores->curRecord;
		goto finish;
	} else if (semaphores->nextRequest == 0) {
		return NWE_REQUESTER_FAILURE;
	}
	ncp_init_request_s(conn, 241);
	ncp_add_word_lh(conn, connNum);
	ncp_add_word_lh(conn, semaphores->nextRequest);
	result = ncp_request(conn, 23);
	if (result) {
		ncp_unlock_conn(conn);
		goto abrt;
	}
	if (conn->ncp_reply_size < 4) {
		ncp_unlock_conn(conn);
		result = NWE_INVALID_NCP_PACKET_LENGTH;
		goto abrt;
	}
	semaphores->nextRequest = ncp_reply_word_lh(conn, 0);
	semaphores->numRecords = ncp_reply_word_lh(conn, 2);
	if (semaphores->numRecords == 0) {
		ncp_unlock_conn(conn);
		result = NWE_REQUESTER_FAILURE;
		goto abrt;
	}
	{
		size_t tocopy;
		
		tocopy = conn->ncp_reply_size - 4;
		if (tocopy > sizeof(semaphores->records)) {
			tocopy = sizeof(semaphores->records);
		}
		memcpy(semaphores->records, ncp_reply_data(conn, 4), tocopy);
	}
	ncp_unlock_conn(conn);
	semaphores->curRecord = 0;
	semaphores->curOffset = 0;
	if (semaphores->numRecords && semaphore) {
		result = __ncp_semaphore_fetch(semaphore, semaphores);
		if (result)
			goto abrt;
		ih = 1;
	} else if (semaphores->numRecords) {
		ih = semaphores->numRecords;
	} else {
		semaphores->nextRequest = 0;
		ih = 0xFFFF;
	}
finish:;
	if (ih >= semaphores->numRecords && semaphores->nextRequest == 0) {
		ih = 0xFFFF;
	}
	*iterHandle = ih;
	return 0;
abrt:;
	semaphores->nextRequest = 0;
	semaphores->numRecords = 0;
	*iterHandle = 0xFFFF;
	return result;
}

NWCCODE
NWIsObjectInSet(NWCONN_HANDLE conn,
		const char* object_name,
		NWObjectType object_type,
		const char* property_name,
		const char* member_name,
		NWObjectType member_type) {
	NWCCODE result;

	ncp_init_request_s(conn, 67);
	ncp_add_word_hl(conn, object_type);
	ncp_add_pstring(conn, object_name);
	ncp_add_pstring(conn, property_name);
	ncp_add_word_hl(conn, member_type);
	ncp_add_pstring(conn, member_name);
	result = ncp_request(conn, 23);
	ncp_unlock_conn(conn);
	return result;
}
