/*
 * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * See the COPYRIGHT file distributed with this work for additional
 * information regarding copyright ownership.
 */

/*! \file */

#include <config.h>

#include <atf-c.h>

#include <stdbool.h>
#include <stdio.h>
#include <inttypes.h>
#include <unistd.h>

#if defined(OPENSSL) || defined(PKCS11CRYPTO)

#include <isc/base64.h>
#include <isc/buffer.h>
#include <isc/util.h>

#include <dns/name.h>
#include <dns/fixedname.h>
#include <dns/keytable.h>
#include <dns/nta.h>
#include <dns/rdataclass.h>
#include <dns/rdatastruct.h>
#include <dns/rootns.h>
#include <dns/view.h>

#include <dst/dst.h>

#include "dnstest.h"

dns_keytable_t *keytable = NULL;
dns_ntatable_t *ntatable = NULL;

static const char *keystr1 = "BQEAAAABok+vaUC9neRv8yeT/FEGgN7svR8s7VBUVSBd8NsAiV8AlaAg O5FHar3JQd95i/puZos6Vi6at9/JBbN8qVmO2AuiXxVqfxMKxIcy+LEB 0Vw4NaSJ3N3uaVREso6aTSs98H/25MjcwLOr7SFfXA7bGhZatLtYY/xu kp6Km5hMfkE=";
static const dns_keytag_t keytag1 = 30591;

static const char *keystr2 = "BQEAAAABwuHz9Cem0BJ0JQTO7C/a3McR6hMaufljs1dfG/inaJpYv7vH XTrAOm/MeKp+/x6eT4QLru0KoZkvZJnqTI8JyaFTw2OM/ItBfh/hL2lm Cft2O7n3MfeqYtvjPnY7dWghYW4sVfH7VVEGm958o9nfi79532Qeklxh x8pXWdeAaRU=";

static dns_view_t *view = NULL;

/*
 * Test utilities.  In general, these assume input parameters are valid
 * (checking with ATF_REQUIRE_EQ, thus aborting if not) and unlikely run time
 * errors (such as memory allocation failure) won't happen.  This helps keep
 * the test code concise.
 */

/*
 * Utility to convert C-string to dns_name_t.  Return a pointer to
 * static data, and so is not thread safe.
 */
static dns_name_t *
str2name(const char *namestr) {
	static dns_fixedname_t fname;
	static dns_name_t *name;
	static isc_buffer_t namebuf;
	void *deconst_namestr;

	name = dns_fixedname_initname(&fname);
	DE_CONST(namestr, deconst_namestr); /* OK, since we don't modify it */
	isc_buffer_init(&namebuf, deconst_namestr, strlen(deconst_namestr));
	isc_buffer_add(&namebuf, strlen(namestr));
	ATF_REQUIRE_EQ(dns_name_fromtext(name, &namebuf, dns_rootname, 0,
					 NULL), ISC_R_SUCCESS);

	return (name);
}

static void
create_key(uint16_t flags, uint8_t proto, uint8_t alg,
	   const char *keynamestr, const char *keystr, dst_key_t **target)
{
	dns_rdata_dnskey_t keystruct;
	unsigned char keydata[4096];
	isc_buffer_t keydatabuf;
	unsigned char rrdata[4096];
	isc_buffer_t rrdatabuf;
	isc_region_t r;
	const dns_rdataclass_t rdclass = dns_rdataclass_in; /* for brevity */

	keystruct.common.rdclass = rdclass;
	keystruct.common.rdtype = dns_rdatatype_dnskey;
	keystruct.mctx = NULL;
	ISC_LINK_INIT(&keystruct.common, link);
	keystruct.flags = flags;
	keystruct.protocol = proto;
	keystruct.algorithm = alg;

	isc_buffer_init(&keydatabuf, keydata, sizeof(keydata));
	isc_buffer_init(&rrdatabuf, rrdata, sizeof(rrdata));
	ATF_REQUIRE_EQ(isc_base64_decodestring(keystr, &keydatabuf),
		       ISC_R_SUCCESS);
	isc_buffer_usedregion(&keydatabuf, &r);
	keystruct.datalen = r.length;
	keystruct.data = r.base;
	ATF_REQUIRE_EQ(dns_rdata_fromstruct(NULL, keystruct.common.rdclass,
					    keystruct.common.rdtype,
					    &keystruct, &rrdatabuf),
		       ISC_R_SUCCESS);

	ATF_REQUIRE_EQ(dst_key_fromdns(str2name(keynamestr), rdclass,
				       &rrdatabuf, mctx, target),
		       ISC_R_SUCCESS);
}

/* Common setup: create a keytable and ntatable to test with a few keys */
static void
create_tables() {
	isc_result_t result;
	dst_key_t *key = NULL;
	isc_stdtime_t now;

	result = dns_test_makeview("view", &view);
	ATF_REQUIRE_EQ(result, ISC_R_SUCCESS);

	ATF_REQUIRE_EQ(dns_keytable_create(mctx, &keytable), ISC_R_SUCCESS);
	ATF_REQUIRE_EQ(dns_ntatable_create(view, taskmgr, timermgr,
					   &ntatable), ISC_R_SUCCESS);

	/* Add a normal key */
	create_key(257, 3, 5, "example.com", keystr1, &key);
	ATF_REQUIRE_EQ(dns_keytable_add(keytable, false, &key),
		       ISC_R_SUCCESS);

	/* Add a null key */
	ATF_REQUIRE_EQ(dns_keytable_marksecure(keytable,
					       str2name("null.example")),
		       ISC_R_SUCCESS);

	/* Add a negative trust anchor, duration 1 hour */
	isc_stdtime_get(&now);
	ATF_REQUIRE_EQ(dns_ntatable_add(ntatable,
					str2name("insecure.example"),
					false, now, 3600),
		       ISC_R_SUCCESS);
}

static void
destroy_tables() {
	if (ntatable != NULL)
		dns_ntatable_detach(&ntatable);
	if (keytable != NULL)
		dns_keytable_detach(&keytable);

	dns_view_detach(&view);
}

/*
 * Individual unit tests
 */

ATF_TC(add);
ATF_TC_HEAD(add, tc) {
	atf_tc_set_md_var(tc, "descr", "add keys to the keytable");
}
ATF_TC_BODY(add, tc) {
	dst_key_t *key = NULL;
	dns_keynode_t *keynode = NULL;
	dns_keynode_t *next_keynode = NULL;
	dns_keynode_t *null_keynode = NULL;

	UNUSED(tc);

	ATF_REQUIRE_EQ(dns_test_begin(NULL, true), ISC_R_SUCCESS);
	create_tables();

	/*
	 * Get the keynode for the example.com key.  There's no other key for
	 * the name, so nextkeynode() should return NOTFOUND.
	 */
	ATF_REQUIRE_EQ(dns_keytable_find(keytable, str2name("example.com"),
					 &keynode), ISC_R_SUCCESS);
	ATF_REQUIRE_EQ(dns_keytable_nextkeynode(keytable, keynode,
						&next_keynode), ISC_R_NOTFOUND);

	/*
	 * Try to add the same key.  This should have no effect, so
	 * nextkeynode() should still return NOTFOUND.
	 */
	create_key(257, 3, 5, "example.com", keystr1, &key);
	ATF_REQUIRE_EQ(dns_keytable_add(keytable, false, &key),
		       ISC_R_SUCCESS);
	ATF_REQUIRE_EQ(dns_keytable_nextkeynode(keytable, keynode,
						&next_keynode), ISC_R_NOTFOUND);

	/* Add another key (different keydata) */
	dns_keytable_detachkeynode(keytable, &keynode);
	create_key(257, 3, 5, "example.com", keystr2, &key);
	ATF_REQUIRE_EQ(dns_keytable_add(keytable, false, &key),
		       ISC_R_SUCCESS);
	ATF_REQUIRE_EQ(dns_keytable_find(keytable, str2name("example.com"),
					 &keynode), ISC_R_SUCCESS);
	ATF_REQUIRE_EQ(dns_keytable_nextkeynode(keytable, keynode,
						&next_keynode), ISC_R_SUCCESS);
	dns_keytable_detachkeynode(keytable, &next_keynode);

	/*
	 * Add a normal key to a name that has a null key.  The null key node
	 * will be updated with the normal key.
	 */
	dns_keytable_detachkeynode(keytable, &keynode);
	ATF_REQUIRE_EQ(dns_keytable_find(keytable, str2name("null.example"),
					 &null_keynode), ISC_R_SUCCESS);
	create_key(257, 3, 5, "null.example", keystr2, &key);
	ATF_REQUIRE_EQ(dns_keytable_add(keytable, false, &key),
		       ISC_R_SUCCESS);
	ATF_REQUIRE_EQ(dns_keytable_find(keytable, str2name("null.example"),
					 &keynode), ISC_R_SUCCESS);
	ATF_REQUIRE_EQ(keynode, null_keynode); /* should be the same node */
	ATF_REQUIRE(dns_keynode_key(keynode) != NULL); /* now have a key */
	dns_keytable_detachkeynode(keytable, &null_keynode);

	/*
	 * Try to add a null key to a name that already has a key.  It's
	 * effectively no-op, so the same key node is still there, with no
	 * no next node.
	 * (Note: this and above checks confirm that if a name has a null key
	 * that's the only key for the name).
	 */
	ATF_REQUIRE_EQ(dns_keytable_marksecure(keytable,
					       str2name("null.example")),
		       ISC_R_SUCCESS);
	ATF_REQUIRE_EQ(dns_keytable_find(keytable, str2name("null.example"),
					 &null_keynode), ISC_R_SUCCESS);
	ATF_REQUIRE_EQ(keynode, null_keynode);
	ATF_REQUIRE(dns_keynode_key(keynode) != NULL);
	ATF_REQUIRE_EQ(dns_keytable_nextkeynode(keytable, keynode,
						&next_keynode), ISC_R_NOTFOUND);
	dns_keytable_detachkeynode(keytable, &null_keynode);

	dns_keytable_detachkeynode(keytable, &keynode);
	destroy_tables();
	dns_test_end();
}

ATF_TC(delete);
ATF_TC_HEAD(delete, tc) {
	atf_tc_set_md_var(tc, "descr", "delete keys from the keytable");
}
ATF_TC_BODY(delete, tc) {
	UNUSED(tc);

	ATF_REQUIRE_EQ(dns_test_begin(NULL, true), ISC_R_SUCCESS);
	create_tables();

	/* dns_keytable_delete requires exact match */
	ATF_REQUIRE_EQ(dns_keytable_delete(keytable, str2name("example.org")),
		       ISC_R_NOTFOUND);
	ATF_REQUIRE_EQ(dns_keytable_delete(keytable, str2name("s.example.com")),
		       ISC_R_NOTFOUND);
	ATF_REQUIRE_EQ(dns_keytable_delete(keytable, str2name("example.com")),
		       ISC_R_SUCCESS);

	/* works also for nodes with a null key */
	ATF_REQUIRE_EQ(dns_keytable_delete(keytable, str2name("null.example")),
		       ISC_R_SUCCESS);

	/* or a negative trust anchor */
	ATF_REQUIRE_EQ(dns_ntatable_delete(ntatable,
					   str2name("insecure.example")),
		       ISC_R_SUCCESS);

	destroy_tables();
	dns_test_end();
}

ATF_TC(deletekeynode);
ATF_TC_HEAD(deletekeynode, tc) {
	atf_tc_set_md_var(tc, "descr", "delete key nodes from the keytable");
}
ATF_TC_BODY(deletekeynode, tc) {
	dst_key_t *key = NULL;

	UNUSED(tc);

	ATF_REQUIRE_EQ(dns_test_begin(NULL, true), ISC_R_SUCCESS);
	create_tables();

	/* key name doesn't match */
	create_key(257, 3, 5, "example.org", keystr1, &key);
	ATF_REQUIRE_EQ(dns_keytable_deletekeynode(keytable, key),
		       ISC_R_NOTFOUND);
	dst_key_free(&key);

	/* subdomain match is the same as no match */
	create_key(257, 3, 5, "sub.example.com", keystr1, &key);
	ATF_REQUIRE_EQ(dns_keytable_deletekeynode(keytable, key),
		       ISC_R_NOTFOUND);
	dst_key_free(&key);

	/* name matches but key doesn't match (resulting in PARTIALMATCH) */
	create_key(257, 3, 5, "example.com", keystr2, &key);
	ATF_REQUIRE_EQ(dns_keytable_deletekeynode(keytable, key),
		       DNS_R_PARTIALMATCH);
	dst_key_free(&key);

	/*
	 * exact match.  after deleting the node the internal rbt node will be
	 * empty, and any delete or deletekeynode attempt should result in
	 * NOTFOUND.
	 */
	create_key(257, 3, 5, "example.com", keystr1, &key);
	ATF_REQUIRE_EQ(dns_keytable_deletekeynode(keytable, key),
		       ISC_R_SUCCESS);
	ATF_REQUIRE_EQ(dns_keytable_deletekeynode(keytable, key),
		       ISC_R_NOTFOUND);
	ATF_REQUIRE_EQ(dns_keytable_delete(keytable, str2name("example.com")),
		       ISC_R_NOTFOUND);
	dst_key_free(&key);

	/*
	 * A null key node for a name is not deleted when searched by key;
	 * it must be deleted by dns_keytable_delete()
	 */
	create_key(257, 3, 5, "null.example", keystr1, &key);
	ATF_REQUIRE_EQ(dns_keytable_deletekeynode(keytable, key),
		       DNS_R_PARTIALMATCH);
	ATF_REQUIRE_EQ(dns_keytable_delete(keytable, dst_key_name(key)),
		       ISC_R_SUCCESS);
	dst_key_free(&key);

	destroy_tables();
	dns_test_end();
}

ATF_TC(find);
ATF_TC_HEAD(find, tc) {
	atf_tc_set_md_var(tc, "descr", "check find-variant operations");
}
ATF_TC_BODY(find, tc) {
	dns_keynode_t *keynode = NULL;
	dns_fixedname_t fname;
	dns_name_t *name;

	UNUSED(tc);

	ATF_REQUIRE_EQ(dns_test_begin(NULL, true), ISC_R_SUCCESS);
	create_tables();

	/*
	 * dns_keytable_find() requires exact name match.  It matches node
	 * that has a null key, too.
	 */
	ATF_REQUIRE_EQ(dns_keytable_find(keytable, str2name("example.org"),
					 &keynode), ISC_R_NOTFOUND);
	ATF_REQUIRE_EQ(dns_keytable_find(keytable, str2name("sub.example.com"),
					 &keynode), ISC_R_NOTFOUND);
	ATF_REQUIRE_EQ(dns_keytable_find(keytable, str2name("example.com"),
					 &keynode), ISC_R_SUCCESS);
	dns_keytable_detachkeynode(keytable, &keynode);
	ATF_REQUIRE_EQ(dns_keytable_find(keytable, str2name("null.example"),
					 &keynode), ISC_R_SUCCESS);
	ATF_REQUIRE_EQ(dns_keynode_key(keynode), NULL);
	dns_keytable_detachkeynode(keytable, &keynode);

	/*
	 * dns_keytable_finddeepestmatch() allows partial match.  Also match
	 * nodes with a null key.
	 */
	name = dns_fixedname_initname(&fname);
	ATF_REQUIRE_EQ(dns_keytable_finddeepestmatch(keytable,
						     str2name("example.com"),
						     name), ISC_R_SUCCESS);
	ATF_REQUIRE_EQ(dns_name_equal(name, str2name("example.com")), true);
	ATF_REQUIRE_EQ(dns_keytable_finddeepestmatch(keytable,
						     str2name("s.example.com"),
						     name), ISC_R_SUCCESS);
	ATF_REQUIRE_EQ(dns_name_equal(name, str2name("example.com")), true);
	ATF_REQUIRE_EQ(dns_keytable_finddeepestmatch(keytable,
						     str2name("example.org"),
						     name), ISC_R_NOTFOUND);
	ATF_REQUIRE_EQ(dns_keytable_finddeepestmatch(keytable,
						     str2name("null.example"),
						     name), ISC_R_SUCCESS);
	ATF_REQUIRE_EQ(dns_name_equal(name, str2name("null.example")),
		       true);

	/*
	 * dns_keytable_findkeynode() requires exact name, algorithm, keytag
	 * match.  If algorithm or keytag doesn't match, should result in
	 * PARTIALMATCH.  Same for a node with a null key.
	 */
	ATF_REQUIRE_EQ(dns_keytable_findkeynode(keytable,
						str2name("example.org"),
						5, keytag1, &keynode),
		       ISC_R_NOTFOUND);
	ATF_REQUIRE_EQ(dns_keytable_findkeynode(keytable,
						str2name("sub.example.com"),
						5, keytag1, &keynode),
		       ISC_R_NOTFOUND);
	ATF_REQUIRE_EQ(dns_keytable_findkeynode(keytable,
						str2name("example.com"),
						4, keytag1, &keynode),
		       DNS_R_PARTIALMATCH); /* different algorithm */
	ATF_REQUIRE_EQ(dns_keytable_findkeynode(keytable,
						str2name("example.com"),
						5, keytag1 + 1, &keynode),
		       DNS_R_PARTIALMATCH); /* different keytag */
	ATF_REQUIRE_EQ(dns_keytable_findkeynode(keytable,
						str2name("null.example"),
						5, 0, &keynode),
		       DNS_R_PARTIALMATCH); /* null key */
	ATF_REQUIRE_EQ(dns_keytable_findkeynode(keytable,
						str2name("example.com"),
						5, keytag1, &keynode),
		       ISC_R_SUCCESS); /* complete match */
	dns_keytable_detachkeynode(keytable, &keynode);

	destroy_tables();
	dns_test_end();
}

ATF_TC(issecuredomain);
ATF_TC_HEAD(issecuredomain, tc) {
	atf_tc_set_md_var(tc, "descr", "check issecuredomain()");
}
ATF_TC_BODY(issecuredomain, tc) {
	bool issecure;
	const char **n;
	const char *names[] = {"example.com", "sub.example.com",
			       "null.example", "sub.null.example", NULL};

	UNUSED(tc);
	ATF_REQUIRE_EQ(dns_test_begin(NULL, true), ISC_R_SUCCESS);
	create_tables();

	/*
	 * Domains that are an exact or partial match of a key name are
	 * considered secure.  It's the case even if the key is null
	 * (validation will then fail, but that's actually the intended effect
	 * of installing a null key).
	 */
	for (n = names; *n != NULL; n++) {
		ATF_REQUIRE_EQ(dns_keytable_issecuredomain(keytable,
							   str2name(*n),
							   NULL,
							   &issecure),
			       ISC_R_SUCCESS);
		ATF_REQUIRE_EQ(issecure, true);
	}

	/*
	 * If the key table has no entry (not even a null one) for a domain or
	 * any of its ancestors, that domain is considered insecure.
	 */
	ATF_REQUIRE_EQ(dns_keytable_issecuredomain(keytable,
						   str2name("example.org"),
						   NULL,
						   &issecure),
		       ISC_R_SUCCESS);
	ATF_REQUIRE_EQ(issecure, false);

	destroy_tables();
	dns_test_end();
}

ATF_TC(dump);
ATF_TC_HEAD(dump, tc) {
	atf_tc_set_md_var(tc, "descr", "check dns_keytable_dump()");
}
ATF_TC_BODY(dump, tc) {
	UNUSED(tc);

	ATF_REQUIRE_EQ(dns_test_begin(NULL, true), ISC_R_SUCCESS);
	create_tables();

	/*
	 * Right now, we only confirm the dump attempt doesn't cause disruption
	 * (so we don't check the dump content).
	 */
	ATF_REQUIRE_EQ(dns_keytable_dump(keytable, stdout), ISC_R_SUCCESS);

	destroy_tables();
	dns_test_end();
}

ATF_TC(nta);
ATF_TC_HEAD(nta, tc) {
	atf_tc_set_md_var(tc, "descr", "check negative trust anchors");
}
ATF_TC_BODY(nta, tc) {
	isc_result_t result;
	dst_key_t *key = NULL;
	bool issecure, covered;
	dns_view_t *myview = NULL;
	isc_stdtime_t now;

	UNUSED(tc);

	result = dns_test_begin(NULL, true);
	ATF_REQUIRE_EQ(result, ISC_R_SUCCESS);

	result = dns_test_makeview("view", &myview);
	ATF_REQUIRE_EQ(result, ISC_R_SUCCESS);

	result = isc_task_create(taskmgr, 0, &myview->task);
	ATF_REQUIRE_EQ(result, ISC_R_SUCCESS);

	result = dns_view_initsecroots(myview, mctx);
	ATF_REQUIRE_EQ(result, ISC_R_SUCCESS);
	result = dns_view_getsecroots(myview, &keytable);
	ATF_REQUIRE_EQ(result, ISC_R_SUCCESS);

	result = dns_view_initntatable(myview, taskmgr, timermgr);
	ATF_REQUIRE_EQ(result, ISC_R_SUCCESS);
	result = dns_view_getntatable(myview, &ntatable);
	ATF_REQUIRE_EQ(result, ISC_R_SUCCESS);

	create_key(257, 3, 5, "example", keystr1, &key);
	result = dns_keytable_add(keytable, false, &key);
	ATF_REQUIRE_EQ(result, ISC_R_SUCCESS);

	isc_stdtime_get(&now);
	result = dns_ntatable_add(ntatable,
				  str2name("insecure.example"),
				  false, now, 1);
	ATF_REQUIRE_EQ(result, ISC_R_SUCCESS);

	/* Should be secure */
	result = dns_view_issecuredomain(myview,
					 str2name("test.secure.example"),
					 now, true, &issecure);
	ATF_CHECK_EQ(result, ISC_R_SUCCESS);
	ATF_CHECK(issecure);

	/* Should not be secure */
	result = dns_view_issecuredomain(myview,
					 str2name("test.insecure.example"),
					 now, true, &issecure);
	ATF_CHECK_EQ(result, ISC_R_SUCCESS);
	ATF_CHECK(!issecure);

	/* NTA covered */
	covered = dns_view_ntacovers(myview, now, str2name("insecure.example"),
				     dns_rootname);
	ATF_CHECK(covered);

	/* Not NTA covered */
	covered = dns_view_ntacovers(myview, now, str2name("secure.example"),
				     dns_rootname);
	ATF_CHECK(!covered);

	/* As of now + 2, the NTA should be clear */
	result = dns_view_issecuredomain(myview,
					 str2name("test.insecure.example"),
					 now + 2, true, &issecure);
	ATF_CHECK_EQ(result, ISC_R_SUCCESS);
	ATF_CHECK(issecure);

	/* Now check deletion */
	result = dns_view_issecuredomain(myview, str2name("test.new.example"),
					 now, true, &issecure);
	ATF_CHECK_EQ(result, ISC_R_SUCCESS);
	ATF_CHECK(issecure);

	result = dns_ntatable_add(ntatable, str2name("new.example"),
				  false, now, 3600);
	ATF_REQUIRE_EQ(result, ISC_R_SUCCESS);

	result = dns_view_issecuredomain(myview, str2name("test.new.example"),
					 now, true, &issecure);
	ATF_CHECK_EQ(result, ISC_R_SUCCESS);
	ATF_CHECK(!issecure);

	result = dns_ntatable_delete(ntatable, str2name("new.example"));
	ATF_REQUIRE_EQ(result, ISC_R_SUCCESS);

	result = dns_view_issecuredomain(myview, str2name("test.new.example"),
					 now, true, &issecure);
	ATF_CHECK_EQ(result, ISC_R_SUCCESS);
	ATF_CHECK(issecure);

	/* Clean up */
	dns_ntatable_detach(&ntatable);
	dns_keytable_detach(&keytable);
	dns_view_detach(&myview);

	dns_test_end();
}

#else
#include <isc/util.h>

ATF_TC(untested);
ATF_TC_HEAD(untested, tc) {
	atf_tc_set_md_var(tc, "descr", "skipping keytable test");
}
ATF_TC_BODY(untested, tc) {
	UNUSED(tc);
	atf_tc_skip("DNSSEC not available");
}
#endif

/*
 * Main
 */
ATF_TP_ADD_TCS(tp) {
#if defined(OPENSSL) || defined(PKCS11CRYPTO)
	ATF_TP_ADD_TC(tp, add);
	ATF_TP_ADD_TC(tp, delete);
	ATF_TP_ADD_TC(tp, deletekeynode);
	ATF_TP_ADD_TC(tp, find);
	ATF_TP_ADD_TC(tp, issecuredomain);
	ATF_TP_ADD_TC(tp, dump);
	ATF_TP_ADD_TC(tp, nta);
#else
	ATF_TP_ADD_TC(tp, untested);
#endif

	return (atf_no_error());
}
