#include "lib/rplan.h"
+
+/** Cache entry header */
+struct entry_h {
+ uint32_t time; /**< The time of inception. */
+ uint32_t ttl; /**< TTL at inception moment. */
+ uint8_t rank; /**< See enum kr_rank */
+ uint8_t flags; /**< TODO */
+ uint8_t data[];
+};
+
+
+struct key {
+ const knot_dname_t *dname; /**< corresponding dname (points within qry->sname) */
+ uint8_t name_len; /**< current length of the name in buf */
+ uint8_t buf[KR_CACHE_KEY_MAXLEN];
+};
+
+static int closest_NS(struct kr_cache *cache, struct key *k);
+
+/** TODO */
+static knot_db_val_t key_exact_type(struct key *k, uint16_t ktype)
+{
+ k->buf[k->name_len + 1] = 0; /* make sure different names can never match */
+ k->buf[k->name_len + 2] = 'E'; /* tag for exact name+type matches */
+ memcpy(k->buf + k->name_len + 3, &ktype, 2);
+ /* key == dname_lf + '0' + 'E' + RRTYPE */
+ return (knot_db_val_t){ k->buf + 1, k->name_len + 4 };
+}
+
int read_lmdb(struct kr_cache *cache, const struct kr_query *qry) {
- uint8_t keybuf[KNOT_DNAME_MAXLEN + 100]; // TODO
- int ret = knot_dname_lf(keybuf, qry->sname, NULL);
+ struct key k_storage, *k = &k_storage;
+ int ret = knot_dname_lf(k->buf, qry->sname, NULL);
if (ret) {
return kr_error(ret);
}
- uint8_t kname_len = keybuf[0]; /**< current length of the name in keybuf */
- keybuf[kname_len + 1] = 0; /* make sure different names can never match */
+ k->name_len = k->buf[0];
/** 1. check if the name exists in the cache, not considering wildcards
- * 1a. exact name+type match
+ * 1a. exact name+type match (can be negative answer in insecure zones)
*/
uint16_t ktype = qry->stype;
if (ktype == KNOT_RRTYPE_CNAME || ktype == KNOT_RRTYPE_DNAME) {
ktype = KNOT_RRTYPE_NS;
}
- memcpy(keybuf + kname_len + 2, &ktype, 2);
- /* dname_lf + 0 + RRTYPE */
- knot_db_val_t key = { keybuf + 1, kname_len + 3 };
- knot_db_val_t val = { NULL, 0 };
+ knot_db_val_t key = key_exact_type(k, ktype);
+ knot_db_val_t val = { };
ret = cache_op(cache, read, &key, &val, 1);
switch (ret) {
- case 0:
+ case 0: {
+ if (val.len < sizeof(struct entry_h)) {
+ assert(false); // TODO: correct length, recovery, etc.
+ return kr_error(EILSEQ);
+ }
+ const struct entry_h *eh = val.data;
// FIXME: check time and rank (and type if stype == xNAME),
// return result if OK, otherwise fall through to break;
+ // insecure zones might have a negative-answer packet here
+ }
case (-abs(ENOENT)):
break;
default:
return kr_error(ret);
}
- /** 1b. otherwise, find the longest prefix NS/xNAME (with OK time+rank).
- * We store xNAME at NS type to lower the number of searches.
- * CNAME is only considered for equal name, of course.
- * We also store NSEC* parameters at NS type; probably the latest two will be kept.
+ /** 1b. otherwise, find the longest prefix NS/xNAME (with OK time+rank). [...] */
+ k->dname = qry->sname;
+ ret = closest_NS(cache, k);
+ if (ret != kr_ok()) {
+ return ret;
+ }
+
+ /* Note: up to here we can run on any cache backend,
+ * without touching the code. */
+
+ /* FIXME:
+ * - Update the notion of current zone cut accordingly.
+ * - find NSEC* parameters
+ * - insecure zone -> return (nothing more to find)
*/
- const knot_dname_t *cut = qry->sname;
+
+ /** 2. closest (provable) encloser.
+ * iterate over all NSEC* chain parameters
+ */
+ while (true) { //for (int i_nsecp = 0; i
+ int nsec = 1;
+ switch (nsec) {
+ case 1: {
+ /* find a previous-or-equal name+NSEC in cache covering
+ * the QNAME, checking TTL etc. */
+
+ //nsec_leq()
+ /* we basically need dname_lf with two bytes added
+ * on a correct place within the name (the cut) */
+ int ret = knot_dname_lf(k->buf, qry->sname, NULL);
+ if (ret) {
+ return kr_error(ret);
+ }
+ uint8_t *begin = k->buf + k->name_len + 1; /* one byte after zone's zero */
+ uint8_t *end = k->buf + k->buf[0] - 1; /* we don't need final zero */
+ memmove(begin + 2, begin, end - begin);
+ begin[0] = 0;
+ begin[1] = '1'; /* tag for NSEC1 */
+ knot_db_val_t key = { k->buf + 1, k->buf[0] + 1 };
+ knot_db_val_t val = { };
+ /* key == zone's dname_lf + 0 + '1' + dname_lf of the name
+ * within the zone without the final 0 */
+ ret = cache_op(cache, read_leq, &key, &val);
+ // FIXME: check that it covers the name
+ // - check validity, etc.
+
+ break;
+ }
+ case 3:
+ //FIXME
+ break;
+ default:
+ assert(false);
+ }
+ }
+
+ return kr_ok();
+}
+
+
+
+
+/** Find the longest prefix NS/xNAME (with OK time+rank).
+ * We store xNAME at NS type to lower the number of searches.
+ * CNAME is only considered for equal name, of course.
+ * We also store NSEC* parameters at NS type; probably the latest two will be kept.
+ */
+static int closest_NS(struct kr_cache *cache, struct key *k)
+{
bool exact_match = true;
+ // LATER(optim): if stype is NS, we check the same value again
do {
- ktype = KNOT_RRTYPE_NS;
- memcpy(keybuf + kname_len + 2, &ktype, 2);
-
- key = { keybuf + 1, kname_len + 3 };
- val = { NULL, 0 };
- ret = cache_op(cache, read, &key, &val, 1);
+ knot_db_val_t key = key_exact_type(k, KNOT_RRTYPE_NS);
+ knot_db_val_t val = { };
+ int ret = cache_op(cache, read, &key, &val, 1);
switch (ret) {
- case 0:
+ case 0: {
// FIXME: check time and rank, and more complex stuff
- ktype = exact_match ? KNOT_RRTYPE_CNAME : KNOT_RRTYPE_DNAME;
+ uint16_t ktype = exact_match ? KNOT_RRTYPE_CNAME : KNOT_RRTYPE_DNAME;
+ // return kr_ok();
+ }
case (-abs(ENOENT)):
break;
default:
/* remove one more label */
exact_match = false;
- if (cut[0] == 0) {
+ if (k->dname[0] == 0) {
// FIXME: missing root NS
}
- kname_len -= (cut[0] + 1);
- cut += (cut[0] + 1);
- keybuf[kname_len + 1] = 0
+ k->name_len -= (k->dname[0] + 1);
+ k->dname += (k->dname[0] + 1);
+ k->buf[k->name_len + 1] = 0;
} while (true);
-cut_found:
-
- return kr_ok();
}
-
-
* - non-NULL .rw is always active
*/
struct {
- bool ro_active;
+ bool ro_active, ro_curs_active;
MDB_txn *ro, *rw;
+ MDB_cursor *ro_curs;
} txn;
};
if (env->txn.ro && env->txn.ro_active) {
mdb_txn_reset(env->txn.ro);
env->txn.ro_active = false;
+ env->txn.ro_curs_active = false;
}
int ret = txn_get_noresize(env, 0/*RW*/, &env->txn.rw);
if (ret == MDB_SUCCESS) {
} else if (env->txn.ro && env->txn.ro_active) {
mdb_txn_reset(env->txn.ro);
env->txn.ro_active = false;
+ env->txn.ro_curs_active = false;
}
return ret;
}
+/** Obtain a read-only cursor (and a read-only transaction). */
+static int txn_curs_get(struct lmdb_env *env, MDB_cursor **curs)
+{
+ assert(env && curs);
+ if (env->txn.ro_curs_active) {
+ goto success;
+ }
+ /* Only in a read-only txn; TODO: it's a bit messy/coupled */
+ if (env->txn.rw) {
+ int ret = cdb_sync(env);
+ if (ret) return ret;
+ }
+ MDB_txn *txn = NULL;
+ int ret = txn_get(env, &txn, true);
+ if (ret) return ret;
+
+ if (env->txn.ro_curs) {
+ ret = mdb_cursor_renew(txn, env->txn.ro_curs);
+ } else {
+ ret = mdb_cursor_open(txn, env->dbi, &env->txn.ro_curs);
+ }
+ if (ret) return ret;
+success:
+ assert(env->txn.ro_curs_active && env->txn.ro && env->txn.ro_active
+ && !env->txn.rw);
+ *curs = env->txn.ro_curs;
+ assert(*curs);
+ return kr_ok();
+}
+
+static void free_txn_ro(struct lmdb_env *env)
+{
+ if (env->txn.ro) {
+ mdb_txn_abort(env->txn.ro);
+ env->txn.ro = NULL;
+ }
+ if (env->txn.ro_curs) {
+ mdb_cursor_close(env->txn.ro_curs);
+ env->txn.ro_curs = NULL;
+ }
+}
+
/*! \brief Close the database. */
static void cdb_close_env(struct lmdb_env *env)
{
/* Get rid of any transactions. */
cdb_sync(env);
- if (env->txn.ro) {
- mdb_txn_abort(env->txn.ro);
- env->txn.ro = NULL;
- }
+ free_txn_ro(env);
mdb_env_sync(env->env, 1);
mdb_dbi_close(env->env, env->dbi);
/* We are about to switch to a different file, so end all txns, to be sure. */
(void) cdb_sync(db);
- if (env->txn.ro) {
- mdb_txn_abort(env->txn.ro);
- env->txn.ro = NULL;
- }
+ free_txn_ro(db);
/* Since there is no guarantee that there will be free
* pages to hold whole dirtied db for transaction-safe clear,
return ret < 0 ? ret : results;
}
+static int cdb_read_leq(knot_db_t *env, knot_db_val_t *key, knot_db_val_t *val)
+{
+ assert(env && key && key->data && val);
+ MDB_cursor *curs = NULL;
+ int ret = txn_curs_get(env, &curs);
+ if (ret) return ret;
+
+ MDB_val key2_m = val_knot2mdb(*key);
+ MDB_val val2_m = { };
+ ret = mdb_cursor_get(curs, &key2_m, &val2_m, MDB_SET_RANGE);
+ if (ret) return lmdb_error(ret);
+ /* test for equality //:unlikely */
+ if (key2_m.mv_size == key->len
+ && memcmp(key2_m.mv_data, key->data, key->len) == 0) {
+ ret = 0; /* equality */
+ goto success;
+ }
+ /* we must be greater than key; do one step to smaller */
+ ret = mdb_cursor_get(curs, &key2_m, &val2_m, MDB_PREV);
+ if (ret) return lmdb_error(ret);
+ ret = 1;
+success:
+ /* finalize the output */
+ *key = val_mdb2knot(key2_m);
+ *val = val_mdb2knot(val2_m);
+ return ret;
+}
+
+
const struct kr_cdb_api *kr_cdb_lmdb(void)
{
static const struct kr_cdb_api api = {
"lmdb",
cdb_init, cdb_deinit, cdb_count, cdb_clear, cdb_sync,
cdb_readv, cdb_writev, cdb_remove,
- cdb_match, cdb_prune
+ cdb_match, cdb_prune,
+ cdb_read_leq
};
return &api;