p11_kit_remote_serve_tokens (const char **tokens,
			     size_t n_tokens,
			     CK_FUNCTION_LIST *provider,
			     int in_fd,
			     int out_fd)
{
	p11_virtual *lower = NULL;
	p11_virtual *filter = NULL;
	CK_FUNCTION_LIST *proxy = NULL;
	CK_FUNCTION_LIST **modules = NULL;
	CK_FUNCTION_LIST *provider_modules[2] = { NULL, NULL };
	CK_FUNCTION_LIST *module;
	p11_dict *filters = NULL;
	p11_dictiter filters_iter;
	p11_array *filtered = NULL;
	P11KitIter *iter;
	P11KitUri **uris = NULL;
	void *value;
	int ret = 1;
	int error = 0;
	size_t i;

	return_val_if_fail (tokens != NULL, 2);
	return_val_if_fail (n_tokens > 0, 2);
	return_val_if_fail (in_fd >= 0, 2);
	return_val_if_fail (out_fd >= 0, 2);

	uris = calloc (n_tokens, sizeof (P11KitUri *));
	if (uris == NULL) {
		error = ENOMEM;
		goto out;
	}

	for (i = 0; i < n_tokens; i++) {
		uris[i] = p11_kit_uri_new ();
		if (uris[i] == NULL) {
			error = ENOMEM;
			goto out;
		}
		if (p11_kit_uri_parse (tokens[i], P11_KIT_URI_FOR_TOKEN, uris[i]) !=
		    P11_KIT_URI_OK) {
			error = EINVAL;
			goto out;
		}
	}

	if (provider) {
		if (p11_kit_module_initialize (provider) != CKR_OK) {
			error = EINVAL;
			goto out;
		}
		provider_modules[0] = provider;
		modules = provider_modules;
	} else {
		modules = p11_kit_modules_load_and_initialize (0);
		if (modules == NULL) {
			error = EINVAL;
			goto out;
		}
	}

	filters = p11_dict_new (p11_dict_direct_hash, p11_dict_direct_equal,
				NULL, p11_filter_release);
	if (filters == NULL) {
		error = ENOMEM;
		goto out;
	}

	iter = p11_kit_iter_new (NULL,
				 P11_KIT_ITER_WITH_TOKENS |
				 P11_KIT_ITER_WITHOUT_OBJECTS);
	if (iter == NULL) {
		error = ENOMEM;
		goto out;
	}

	p11_kit_iter_begin (iter, modules);
	while (p11_kit_iter_next (iter) == CKR_OK) {
		CK_TOKEN_INFO *token;

		assert (p11_kit_iter_get_kind (iter) == P11_KIT_ITER_KIND_TOKEN);

		token = p11_kit_iter_get_token (iter);

		/* Check if the token is the one we want to export */
		for (i = 0; i < n_tokens; i++)
			if (p11_kit_uri_match_token_info (uris[i], token))
				break;
		if (i == n_tokens)
			continue;

		module = p11_kit_iter_get_module (iter);
		assert (module != NULL);

		/* Create a virtual module that provides only the
		 * specified tokens */
		filter = p11_dict_get (filters, module);
		if (filter == NULL) {
			lower = calloc (1, sizeof (p11_virtual));
			if (lower == NULL) {
				error = ENOMEM;
				goto out;
			}
			p11_virtual_init (lower, &p11_virtual_base, module, NULL);
			filter = p11_filter_subclass (lower, NULL);
			if (filter == NULL) {
				error = EINVAL;
				p11_message_err (error, "couldn't subclass filter");
				goto out;
			}
			if (!p11_dict_set (filters, module, filter)) {
				error = EINVAL;
				p11_message_err (error, "couldn't register filter");
				goto out;
			}
		}

		for (i = 0; i < n_tokens; i++) {
			if (p11_kit_uri_match_token_info (uris[i], token)) {
				token_set_write_protected (token, uris[i]);
				p11_filter_allow_token (filter, token);
			}
		}
	}
	p11_kit_iter_free (iter);

	filtered = p11_array_new ((p11_destroyer)module_unwrap);
	p11_dict_iterate (filters, &filters_iter);
	while (p11_dict_next (&filters_iter, NULL, &value)) {
		module = p11_virtual_wrap ((p11_virtual *)value,
					   (p11_destroyer)p11_virtual_uninit);
		if (module == NULL) {
			error = EINVAL;
			p11_message_err (error, "couldn't wrap filter module");
			goto out;
		}
		if (!p11_array_push (filtered, module)) {
			error = ENOMEM;
			goto out;
		}
	}

	/* NULL terminate the array */
	if (!p11_array_push (filtered, NULL)) {
		error = ENOMEM;
		goto out;
	}

	/* Need to finalize the modules that we initialized for iteration */
	p11_kit_modules_finalize (modules);

	if (p11_proxy_module_create (&proxy, (CK_FUNCTION_LIST **)filtered->elem)
	    != CKR_OK) {
		error = EINVAL;
		p11_message_err (error, "couldn't create a proxy module");
		goto out;
	}

	ret = p11_kit_remote_serve_module (proxy, in_fd, out_fd);

 out:
	if (filtered != NULL)
		p11_array_free (filtered);
	if (filters != NULL)
		p11_dict_free (filters);
	if (modules != provider_modules)
		p11_kit_modules_release (modules);
	if (error != 0)
		errno = error;
	if (uris) {
		for (i = 0; i < n_tokens; i++)
			p11_kit_uri_free (uris[i]);
		free (uris);
	}

	return ret;
}