/*
 * Copyright (c) 1999 by Sun Microsystems, Inc.
 * All rights reserved.
 */

#pragma ident   "@(#)SLPFindSrvTypes.c	1.1	99/04/02 SMI"

#include <syslog.h>
#include <slp-internal.h>

static SLPBoolean UnpackSrvTypesReply(slp_handle_impl_t *, char *,
					SLPSrvTypeCallback, void *,
					void **, int *);
static SLPError slp_packSrvTypeRqst(slp_handle_impl_t *, const char *);
static char *collate_types(char *, void **, int *, int);
static char *build_types_list(void *);
static void collect_types(void *, VISIT, int, void *);

SLPError SLPFindSrvTypes(SLPHandle hSLP, const char *pcNamingAuthority,
				const char *pcScopeList,
				SLPSrvTypeCallback callback, void *pvUser) {
	SLPError err;

	if (!hSLP || !pcNamingAuthority || !pcScopeList ||
	    !*pcScopeList || !callback) {
		return (SLP_PARAMETER_BAD);
	}

	if ((strlen(pcNamingAuthority) > SLP_MAX_STRINGLEN) ||
	    (strlen(pcScopeList) > SLP_MAX_STRINGLEN)) {
	    return (SLP_PARAMETER_BAD);
	}

	if ((err = slp_start_call(hSLP)) != SLP_OK)
		return (err);

	/* format params into msgBuf */
	err = slp_packSrvTypeRqst(hSLP, pcNamingAuthority);

	if (err == SLP_OK)
		err = slp_ua_common(hSLP, pcScopeList,
				    (SLPGenericAppCB *) callback, pvUser,
				    (SLPMsgReplyCB *) UnpackSrvTypesReply);

	if (err != SLP_OK)
		slp_end_call(hSLP);

	return (err);
}

static SLPBoolean UnpackSrvTypesReply(slp_handle_impl_t *hp, char *reply,
					SLPSrvTypeCallback cb, void *cookie,
					void **collator, int *numResults) {
	char *pcSrvTypes;
	SLPError errCode;
	unsigned short protoErrCode;
	size_t off, len;
	int maxResults = slp_get_maxResults();
	SLPBoolean cont = SLP_TRUE;

	if (!reply) {
		/* no more results */
		if (!hp->async) {
		    pcSrvTypes = build_types_list(*collator);
		}

		if (!hp->async && pcSrvTypes) {
		    /* synchronous case */
		    cb(hp, pcSrvTypes, SLP_OK, cookie);
		    free(pcSrvTypes);
		}
		cb(hp, NULL, SLP_LAST_CALL, cookie);
		return (SLP_FALSE);
	}

	/* parse reply into params */
	len = slp_get_length(reply);
	off = SLP_HDRLEN + slp_get_langlen(reply);
	/* error code */
	if (slp_get_sht(reply, len, &off, &protoErrCode) != SLP_OK)
		return (SLP_TRUE);
	/* internal errors should have been filtered out by the net code */
	if ((errCode = slp_map_err(protoErrCode)) != SLP_OK) {
		return (cb(hp, NULL, errCode, cookie));
	}

	/* types string */
	if (slp_get_string(reply, len, &off, &pcSrvTypes) != SLP_OK)
		return (SLP_TRUE);

	/* collate the types for sync behavior */
	if (!hp->async) {
	    pcSrvTypes = collate_types(pcSrvTypes, collator,
					numResults, maxResults);
	    if (!pcSrvTypes)
		return (SLP_TRUE);
	} else {
	    /* async; invoke cb */
	    cont = cb((SLPHandle) hp, pcSrvTypes, errCode, cookie);
	}

	/* cleanup */
	free(pcSrvTypes);

	/* check maxResults */
	if (!hp->internal_call && *numResults == maxResults) {
		return (SLP_FALSE);
	}

	return (cont);
}

static SLPError slp_packSrvTypeRqst(slp_handle_impl_t *hp, const char *na) {
	SLPError err;
	size_t len, nalen, msgLen, tmplen;
	int all_nas;
	slp_msg_t *msg = &(hp->msg);

	/*
	 * Allocate iovec for the message. A SrvTypeRqst is layed out thus:
	 *  0: header
	 *  1: prlist length
	 *  2: prlist (filled in later by networking code)
	 *  3: na
	 *  4: scopes length
	 *  5: scopes (filled in later by networking code)
	 */
	if (!(msg->iov = calloc(6, sizeof (*(msg->iov))))) {
		slp_err(LOG_CRIT, 0, "slp_packSrvTypeRqst", "out of memory");
		return (SLP_MEMORY_ALLOC_FAILED);
	}
	msg->iovlen = 6;

	/* calculate msg length */
	all_nas = strcmp(na, "*") == 0 ? 1 : 0;
	if (all_nas) {
		nalen = 0;
	} else {
		nalen = strlen(na);
	}
	nalen += 2;

	msgLen = 2 +	/* prlist length */
	    nalen +	/* NA string */
	    2;		/* Scope string length */

	if (!(msg->msg = calloc(1, msgLen))) {
		free(msg->iov);
		slp_err(LOG_CRIT, 0, "slp_packSrvTypeRqst", "out of memory");
		return (SLP_MEMORY_ALLOC_FAILED);
	}

	/* set pointer to PR list and scope list length spaces */
	msg->prlistlen.iov_base = msg->msg;
	msg->prlistlen.iov_len = 2;
	msg->iov[1].iov_base = msg->msg;
	msg->iov[1].iov_len = 2;

	msg->scopeslen.iov_base = msg->msg + 2;
	msg->scopeslen.iov_len = 2;
	msg->iov[4].iov_base = msg->msg + 2;
	msg->iov[4].iov_len = 2;

	/* set up the scopes and prlist pointers into iov */
	msg->prlist = &(msg->iov[2]);
	msg->scopes = &(msg->iov[5]);

	len = 4;

	/* set up NA string in iovec */
	msg->iov[3].iov_base = msg->msg + len;
	tmplen = len;

	if (all_nas) {
		err = slp_add_sht(msg->msg, msgLen, 0xffff, &len);
	} else {
		err = slp_add_string(msg->msg, msgLen, na, &len);
	}
	msg->iov[3].iov_len = len - tmplen;

	hp->fid = SRVTYPERQST;
	if (err == SLP_OK) {
		return (SLP_OK);
	}

	/* else error */
	free(msg->iov);
	free(msg->msg);

	return (err);
}

/*
 * Using the collator, determines which types in the types list
 * have already been recieved, and composes a new list of the remaining
 * (unique) types. If there are no unique types, returns NULL;
 * types is destructively modified.
 */
static char *collate_types(char *types, void **collator,
				int *numResults, int maxResults) {
	char *p, *s, **res, *utypes = NULL;

	/* walk through the types list */
	p = types;
	for (s = types; p && *numResults != maxResults; s = p) {
		p = slp_utf_strchr(s, ',');
		if (p)
			*p++ = 0;
		if (!(s = strdup(s))) {
		    free(types);
		    if (utypes) free(utypes);
		    slp_err(LOG_CRIT, 0, "collate_types", "out of memory");
		    return (NULL);
		}
		/* search the tree for this type */
		res = slp_tsearch((void *) s, collator,
			(int (*)(const void *, const void *)) slp_strcasecmp);
		if (*res == s) {
			/* first time we've encountered this type */
			slp_add2list(s, &utypes, SLP_FALSE);
			(*numResults)++;
		} else {
			/* else  already in tree */
			free(s);
		}
	}
	free(types);
	return (utypes);
}

/*
 * This is used after all types have been collated into the tree.
 * It walks through the tree, composing a list from all the types in
 * the tree, and freeing each node of the tree as it goes.
 * Returns the list, or NULL if the tree is empty.
 */
/* the walk action function: */
/*ARGSUSED*/
static void collect_types(void *node, VISIT order, int level, void *cookie) {
	char **types = (char **)cookie;

	if (order == endorder || order == leaf) {
		char *t = *(char **)node;
		slp_add2list(t, types, SLP_FALSE);
		free(t);
		free(node);
	}
}

/* the walk driver: */
static char *build_types_list(void *collator) {
	char *types = NULL;

	if (!collator)
		return (NULL);
	slp_twalk(collator, collect_types, 0, (void *) &types);
	return (types);
}
