]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
add edit lists and tests
authorAlan T. DeKok <aland@freeradius.org>
Thu, 11 Nov 2021 22:13:05 +0000 (17:13 -0500)
committerAlan T. DeKok <aland@freeradius.org>
Fri, 12 Nov 2021 15:42:59 +0000 (10:42 -0500)
so that we can edit lists "in place", and then undo the edits
if anything goes wrong.

src/lib/util/all.mk
src/lib/util/edit.c [new file with mode: 0644]
src/lib/util/edit.h [new file with mode: 0644]
src/lib/util/edit_tests.c [new file with mode: 0644]
src/lib/util/edit_tests.mk [new file with mode: 0644]
src/lib/util/libfreeradius-util.mk

index 9d3ca0e56ca1ff76e56a2e8ff266ab86d2a2e059..56fdab7acd6c85c2d6944ba59d1a9e48c640e091 100644 (file)
@@ -4,6 +4,7 @@ SUBMAKEFILES := \
        dbuff_tests.mk \
        dcursor_tests.mk \
        dlist_tests.mk \
+       edit_tests.mk \
        heap_tests.mk \
        hmac_tests.mk \
        libfreeradius-util.mk \
diff --git a/src/lib/util/edit.c b/src/lib/util/edit.c
new file mode 100644 (file)
index 0000000..fc9d88f
--- /dev/null
@@ -0,0 +1,434 @@
+/*
+ *   This library is free software; you can redistribute it and/or
+ *   modify it under the terms of the GNU Lesser General Public
+ *   License as published by the Free Software Foundation; either
+ *   version 2.1 of the License, or (at your option) any later version.
+ *
+ *   This library 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
+ *   Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+/**
+ * $Id$
+ *
+ * @file src/lib/util/edit.c
+ * @brief Functions to edit pair lists, and track undo operations
+ *
+ * @copyright 2021 Network RADIUS SAS (legal@networkradius.com)
+ */
+
+RCSID("$Id$")
+
+#include <freeradius-devel/util/value.h>
+#include "edit.h"
+
+/** Track a series of edits.
+ *
+ */
+struct fr_edit_list_s {
+       /*
+        *      List of edits to be made, in order.
+        */
+       fr_dlist_head_t list;
+
+       /*
+        *      VPs which were inserted, and then over-written by a
+        *      later edit.
+        */
+       fr_pair_list_t  deleted_pairs;
+};
+
+/** Track one particular edit.
+ */
+typedef struct {
+       fr_edit_op_t    op;             //!< edit operation to perform
+       fr_dlist_t      entry;          //!< linked list of edits
+
+       fr_pair_t       *vp;            //!< pair edited, deleted, or inserted
+
+       union {
+               fr_value_box_t  data;   //!< original data
+               fr_pair_list_t  children;  //!< original child list,
+                                          //!< for T_OP_SET of structural entries
+
+               struct {
+                       fr_pair_list_t  *list; //!< parent list
+                       fr_pair_t       *ref;   //!< reference pair for delete, insert before/after
+               };
+       };
+} fr_edit_t;
+
+
+/** Undo one particular edit.
+ */
+static int edit_undo(fr_edit_t *e)
+{
+       fr_pair_t *vp = e->vp;
+#ifndef NDEBUG
+       int rcode;
+#endif
+
+       switch (e->op) {
+       case FR_EDIT_INVALID:
+               return -1;
+
+       case FR_EDIT_VALUE:
+               if (fr_type_is_leaf(vp->vp_type)) {
+                       if (!fr_type_is_fixed_size(vp->vp_type)) fr_value_box_clear(&vp->data);
+                       fr_value_box_copy_shallow(NULL, &vp->data, &e->data);
+               } else {
+                       fr_assert(fr_type_is_structural(vp->vp_type));
+
+                       fr_pair_list_free(&vp->vp_group);
+                       fr_pair_list_append(&vp->vp_group, &e->children);
+               }
+               break;
+
+       case FR_EDIT_DELETE:
+#ifndef NDEBUG
+               rcode =
+#endif
+               fr_pair_insert_after(e->list, e->ref, vp);
+               fr_assert(rcode == 0);
+               break;
+
+       case FR_EDIT_INSERT_BEFORE:
+       case FR_EDIT_INSERT_AFTER:
+               /*
+                *      We can free the VP here, as any edits to its'
+                *      children MUST come after the creation of the
+                *      VP.  And any deletion of VPs after this one
+                *      must come after this VP was created.
+                */
+               fr_pair_delete(e->list, vp);
+               talloc_free(vp);
+               break;
+       }
+
+       return 0;
+}
+
+/** Abort the entries in an edit list.
+ *
+ *  After this call, the input list(s) are unchanged from before any
+ *  edits were made.
+ *
+ *  the caller still has to call talloc_free(el);
+ */
+void fr_edit_list_abort(fr_edit_list_t *el)
+{
+       fr_edit_t *e;
+
+       if (!el) return;
+
+       /*
+        *      Undo edits in reverse order, as later edits depend on
+        *      earlier ones.  We don't have multiple edits of the
+        *      same VP, but we can create a VP, and then later edit
+        *      its children.
+        */
+       while ((e = fr_dlist_pop_tail(&el->list)) != NULL) {
+               edit_undo(e);
+       };
+}
+
+/** Record one particular edit
+ *
+ *  For INSERT / DELETE, this function will also insert / delete the
+ *  VP.
+ *
+ *  For VALUE changes, this function must be called BEFORE the value
+ *  is changed.  Once this function has been called, it is then safe
+ *  to edit the value in place.
+ *
+ *  Note that VALUE changes for structural types are allowed ONLY when
+ *  using T_OP_SET, which over-writes previous values.  For every
+ *  other modification to structural types, we MUST instead call
+ *  insert / delete on the vp_group.
+ */
+int fr_edit_list_record(fr_edit_list_t *el, fr_edit_op_t op, fr_pair_t *vp, fr_pair_list_t *list, fr_pair_t *ref)
+{
+       fr_edit_t *e;
+
+       if (!el) return 0;
+
+       /*
+        *      Search for previous edits.
+        *
+        *      @todo - if we're modifying values of a child VP, and
+        *      it's parent is marked as INSERT, then we don't need to
+        *      record FR_EDIT_VALUE changes to the children.  It's
+        *      not yet clear how best to track this.
+        */
+       for (e = fr_dlist_head(&el->list);
+            e != NULL;
+            e = fr_dlist_next(&el->list, e)) {
+               fr_assert(e->vp != NULL);
+
+               if (e->vp != vp) continue;
+
+               switch (op) {
+               case FR_EDIT_INVALID:
+                       return -1;
+
+                       /*
+                        *      We're editing a previous edit.
+                        *      There's no need to record anything
+                        *      new, as we've already recorded the
+                        *      original value.
+                        *
+                        *      Note that we can insert a pair and
+                        *      then edit it.  The undo list only
+                        *      saves the insert, as the later edit is
+                        *      irrelevant.  If we're undoing, we
+                        *      simply delete the new attribute which
+                        *      was inserted.
+                        */
+               case FR_EDIT_VALUE:
+                       /*
+                        *      If we delete a pair, we can't later
+                        *      edit it.  That indicates serious
+                        *      issues with the code.
+                        */
+                       fr_assert(e->op != FR_EDIT_DELETE);
+
+                       /*
+                        *      We're over-writing (again) the
+                        *      children of a structural type.
+                        *
+                        *      Save the attributes we added in the
+                        *      previous T_OP_SET operation.  Some
+                        *      other edit may be changing them, so we
+                        *      don't want to delete the attributes
+                        *      until we're done.
+                        */
+                       if (fr_type_is_structural(vp->vp_type) && !fr_pair_list_empty(&vp->vp_group)) {
+                               fr_pair_list_append(&el->deleted_pairs, &vp->vp_group);
+                       }
+                       return 0;
+
+                       /*
+                        *      We're inserting a new pair.
+                        *
+                        *      We can't have previously edited this
+                        *      pair (inserted, deleted, or updated
+                        *      the value), as the pair is new!
+                        */
+               case FR_EDIT_INSERT_BEFORE:
+               case FR_EDIT_INSERT_AFTER:
+                       fr_assert(0);
+                       return -1;
+
+                       /*
+                        *      We're being asked to delete something
+                        *      we previously inserted, or previously
+                        *      edited.
+                        */
+               case FR_EDIT_DELETE:
+                       /*
+                        *      We can't delete something which was
+                        *      already deleted.
+                        */
+                       fr_assert(e->op != FR_EDIT_DELETE);
+
+                       /*
+                        *      We had previously inserted it.  So
+                        *      just delete the insert operation, and
+                        *      delete the VP from the list.
+                        *
+                        *      Other edits may refer to children of
+                        *      this pair.  So we don't free the VP
+                        *      immediately, but instead reparent it
+                        *      to the edit list.  So that when the
+                        *      edit list is freed, the VP will be
+                        *      freed.
+                        */
+                       if ((e->op == FR_EDIT_INSERT_BEFORE) ||
+                           (e->op == FR_EDIT_INSERT_AFTER)) {
+                               fr_assert(e->list == list);
+
+                               fr_pair_remove(list, vp);
+                               fr_pair_append(&el->deleted_pairs, vp);
+
+                               fr_dlist_remove(&el->list, e);
+                               talloc_free(e);
+                               return 0;
+                       }
+
+                       /*
+                        *      We had previously changed the value,
+                        *      but now we're going to delete it.
+                        *
+                        *      Since it had previously existed, we
+                        *      have to reset its value to the
+                        *      original one, and then track the
+                        *      deletion.
+                        */
+                       fr_assert(e->op == FR_EDIT_VALUE);
+                       edit_undo(e);
+                       e->op = FR_EDIT_DELETE;
+                       goto delete;
+               }
+       } /* loop over existing edits */
+
+       /*
+        *      No edit for this pair exists.  Create a new edit entry.
+        */
+       e = talloc_zero(el, fr_edit_t);
+       if (!e) return -1;
+
+       e->op = op;
+       e->vp = vp;
+
+       switch (op) {
+       case FR_EDIT_INVALID:
+               talloc_free(e);
+               return -1;
+
+       case FR_EDIT_VALUE:
+               fr_assert(list == NULL);
+               fr_assert(ref == NULL);
+
+               if (fr_type_is_leaf(vp->vp_type)) {
+                       fr_value_box_copy_shallow(NULL, &e->data, &vp->data);
+                       if (!fr_type_is_fixed_size(vp->vp_type)) fr_value_box_memdup_shallow(&vp->data, vp->data.enumv,
+                                                                                            e->data.vb_octets, e->data.vb_length,
+                                                                                            e->data.tainted);
+               } else {
+                       fr_assert(fr_type_is_structural(vp->vp_type));
+
+                       fr_pair_list_init(&e->children);
+                       fr_pair_list_append(&e->children, &vp->vp_group);
+               }
+               break;
+
+       case FR_EDIT_INSERT_BEFORE:
+               fr_assert(list != NULL);
+
+               /*
+                *      There's no need to record "prev".  On undo, we
+                *      just delete this pair from the list.
+                */
+               e->list = list;
+               fr_pair_insert_before(list, ref, vp);
+               break;
+
+       case FR_EDIT_INSERT_AFTER:
+               fr_assert(list != NULL);
+
+               /*
+                *      There's no need to record "prev".  On undo, we
+                *      just delete this pair from the list.
+                */
+               e->list = list;
+               fr_pair_insert_after(list, ref, vp);
+               break;
+
+       case FR_EDIT_DELETE:
+       delete:
+               fr_assert(list != NULL);
+               fr_assert(ref == NULL);
+
+               e->list = list;
+               e->ref = fr_pair_list_prev(list, vp);
+
+               fr_pair_remove(list, vp);
+               break;
+       }
+
+       fr_dlist_insert_tail(&el->list, e);
+       return 0;
+}
+
+/** Finalize the edits when we destroy the edit list.
+ *
+ *  Which in large part means freeing the VPs which have been deleted,
+ *  and then deleting the edit list.
+ */
+static int _edit_list_destructor(fr_edit_list_t *el)
+{
+       fr_edit_t *e;
+
+       if (!el) return 0;
+
+       for (e = fr_dlist_head(&el->list);
+            e != NULL;
+            e = fr_dlist_next(&el->list, e)) {
+               switch (e->op) {
+               case FR_EDIT_INVALID:
+                       fr_assert(0);
+                       break;
+
+               case FR_EDIT_INSERT_BEFORE:
+               case FR_EDIT_INSERT_AFTER:
+                       break;
+
+               case FR_EDIT_DELETE:
+                       fr_assert(e->vp != NULL);
+                       talloc_free(e->vp);
+                       break;
+
+               case FR_EDIT_VALUE:
+                       if (fr_type_is_leaf(e->vp->vp_type)) {
+                               fr_value_box_clear(&e->data);
+                       } else {
+                               fr_assert(fr_type_is_structural(e->vp->vp_type));
+                               fr_pair_list_free(&e->children);
+                       }
+                       break;
+               }
+       }
+
+       fr_pair_list_free(&el->deleted_pairs);
+
+       talloc_free(el);
+
+       return 0;
+}
+
+fr_edit_list_t *fr_edit_list_alloc(TALLOC_CTX *ctx)
+{
+       fr_edit_list_t *el;
+
+       el = talloc_zero(ctx, fr_edit_list_t);
+       if (!el) return NULL;
+
+       fr_dlist_init(&el->list, fr_edit_t, entry);
+
+       fr_pair_list_init(&el->deleted_pairs);
+
+       talloc_set_destructor(el, _edit_list_destructor);
+
+       return el;
+}
+
+/** Notes
+ *
+ *  Unlike "update" sections, edits are _not_ hierarchical.  If we're
+ *  editing values a list, then the list has to exist.  If we're
+ *  inserting pairs in a list, then we find the lowest existing pair,
+ *  and add pairs there.
+ *
+ *  The functions tmpl_extents_find() and tmpl_extents_build_to_leaf()
+ *  should help us figure out where the VPs exist or not.
+ *
+ *  The overall "update" algorithm is now:
+ *
+ *     alloc(edit list)
+ *
+ *     foreach entry in the things to do
+ *             expand LHS if needed to local TMPL
+ *             expand RHS if needed to local box / cursor / TMPL
+ *
+ *             use LHS/RHS cursors to find VPs
+ *             edit VPs, recording edits
+ *
+ *     free temporary map
+ *     talloc_free(edit list)
+ */
diff --git a/src/lib/util/edit.h b/src/lib/util/edit.h
new file mode 100644 (file)
index 0000000..afb79c3
--- /dev/null
@@ -0,0 +1,52 @@
+#pragma once
+/*
+ *  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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+/**
+ * $Id$
+ *
+ * @file lib/util/edit.h
+ * @brief Structures and prototypes for editing lists.
+ *
+ * @copyright 2021 Network RADIUS SAS (legal@networkradius.com)
+ */
+RCSIDH(map_h, "$Id$")
+
+#include <freeradius-devel/util/pair.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef enum {
+       FR_EDIT_INVALID = 0,
+       FR_EDIT_DELETE,                 //!< delete a VP
+       FR_EDIT_VALUE,                  //!< edit a VP in place
+       FR_EDIT_INSERT_BEFORE,          //!< insert a VP into a list, before another one
+       FR_EDIT_INSERT_AFTER,           //!< insert a VP into a list, after another one.
+} fr_edit_op_t;
+
+typedef struct fr_edit_list_s fr_edit_list_t;
+
+fr_edit_list_t *fr_edit_list_alloc(TALLOC_CTX *ctx);
+
+void fr_edit_list_abort(fr_edit_list_t *el);
+
+int fr_edit_list_record(fr_edit_list_t *el, fr_edit_op_t op, fr_pair_t *vp, fr_pair_list_t *list, fr_pair_t *prev);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/src/lib/util/edit_tests.c b/src/lib/util/edit_tests.c
new file mode 100644 (file)
index 0000000..0eb4f58
--- /dev/null
@@ -0,0 +1,459 @@
+/*
+ *   This library is free software; you can redistribute it and/or
+ *   modify it under the terms of the GNU Lesser General Public
+ *   License as published by the Free Software Foundation; either
+ *   version 2.1 of the License, or (at your option) any later version.
+ *
+ *   This library 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
+ *   Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public
+ *   License along with this library; if not, write to the Free Software
+ *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+/** Tests for a AVP manipulation and search API.
+ *
+ * @file src/lib/util/edit_tests.c
+ * @author Alan DeKok (aland@networkradius.com)
+ * @copyright 2021 Network RADIUS SAS (legal@networkradius.com)
+ */
+
+/**
+ *     The 'TEST_INIT' macro provided by 'acutest.h' allowing to register a function to be called
+ *     before call the unit tests. Therefore, It calls the function ALL THE TIME causing an overhead.
+ *     That is why we are initializing test_init() by "__attribute__((constructor));" reducing the
+ *     test execution by 50% of the time.
+ */
+#define USE_CONSTRUCTOR
+
+/*
+ * It should be declared before include the "acutest.h"
+ */
+#ifdef USE_CONSTRUCTOR
+static void test_init(void) __attribute__((constructor));
+#else
+static void test_init(void);
+#      define TEST_INIT  test_init()
+#endif
+
+#include <freeradius-devel/util/acutest.h>
+#include <freeradius-devel/util/acutest_helpers.h>
+#include <freeradius-devel/util/pair_test_helpers.h>
+
+#include <freeradius-devel/util/conf.h>
+#include <freeradius-devel/util/dict.h>
+#include <freeradius-devel/util/edit.h>
+
+#ifdef HAVE_GPERFTOOLS_PROFILER_H
+#  include <gperftools/profiler.h>
+#endif
+
+static TALLOC_CTX       *autofree;
+static fr_pair_list_t   test_pairs;
+static fr_dict_t       *test_dict;
+
+/** Global initialisation
+ */
+static void test_init(void)
+{
+       autofree = talloc_autofree_context();
+       if (!autofree) {
+       error:
+               fr_perror("edit_tests");
+               fr_exit_now(EXIT_FAILURE);
+       }
+
+       /*
+        *      Mismatch between the binary and the libraries it depends on
+        */
+       if (fr_check_lib_magic(RADIUSD_MAGIC_NUMBER) < 0) goto error;
+
+       if (fr_dict_test_init(autofree, &test_dict, NULL) < 0) goto error;
+
+       /* Initialize the "test_pairs" list */
+       fr_pair_list_init(&test_pairs);
+
+       if (fr_pair_test_list_alloc(autofree, &test_pairs, NULL) < 0) goto error;
+}
+
+static void add_pairs(fr_pair_list_t *local_pairs)
+{
+       int count;
+
+       fr_pair_list_init(local_pairs);
+
+       fr_pair_append(local_pairs, fr_pair_afrom_da(autofree, fr_dict_attr_test_uint32));
+       fr_pair_append(local_pairs, fr_pair_afrom_da(autofree, fr_dict_attr_test_octets));
+       fr_pair_append(local_pairs, fr_pair_afrom_da(autofree, fr_dict_attr_test_tlv));
+
+       count = fr_pair_list_len(local_pairs);
+       TEST_CASE("Expected (count == 3)");
+       TEST_CHECK(count == 3);
+}
+
+static void expect3(fr_pair_list_t *local_pairs)
+{
+       int count;
+       fr_pair_t *vp;
+
+       count = fr_pair_list_len(local_pairs);
+       TEST_CASE("Expected (count == 3) after undoing the edits");
+       TEST_CHECK(count == 3);
+
+       vp = fr_pair_list_head(local_pairs);
+       TEST_CHECK(vp->da == fr_dict_attr_test_uint32);
+
+       vp = fr_pair_list_tail(local_pairs);
+       TEST_CHECK(vp->da == fr_dict_attr_test_tlv);
+
+       fr_pair_list_free(local_pairs);
+}
+
+/*
+ *     Tests functions
+ */
+static void test_pair_delete_head(void)
+{
+       fr_pair_t       *vp;
+       fr_pair_list_t  local_pairs;
+       size_t          count;
+       fr_edit_list_t  *el;
+       int             rcode;
+
+       TEST_CASE("Add 3 pairs and delete the first one");
+
+       add_pairs(&local_pairs);
+
+       vp = fr_pair_list_head(&local_pairs);
+
+       el = fr_edit_list_alloc(NULL);
+       TEST_CHECK(el != NULL);
+
+       rcode = fr_edit_list_record(el, FR_EDIT_DELETE, vp, &local_pairs, NULL);
+       TEST_CHECK(rcode == 0);
+
+       talloc_free(el);
+
+       count = fr_pair_list_len(&local_pairs);
+       TEST_CASE("Expected (count == 2) after deleting the head");
+       TEST_CHECK(count == 2);
+
+       vp = fr_pair_list_head(&local_pairs);
+       TEST_CASE("head is now what was the second pair");
+       TEST_CHECK(vp->da == fr_dict_attr_test_octets);
+
+       fr_pair_list_free(&local_pairs);
+}
+
+static void test_pair_delete_head_abort(void)
+{
+       fr_pair_t       *vp;
+       fr_pair_list_t  local_pairs;
+       size_t          count;
+       fr_edit_list_t  *el;
+       int             rcode;
+
+       TEST_CASE("Add 3 pairs and delete the first one");
+
+       add_pairs(&local_pairs);
+
+       vp = fr_pair_list_head(&local_pairs);
+
+       el = fr_edit_list_alloc(NULL);
+       TEST_CHECK(el != NULL);
+
+       rcode = fr_edit_list_record(el, FR_EDIT_DELETE, vp, &local_pairs, NULL);
+       TEST_CHECK(rcode == 0);
+
+       count = fr_pair_list_len(&local_pairs);
+       TEST_CASE("Expected (count == 2) after deleting the head");
+       TEST_CHECK(count == 2);
+
+       /*
+        *      Abort the edit
+        */
+       fr_edit_list_abort(el);
+
+       talloc_free(el);
+
+       expect3(&local_pairs);
+}
+
+static void test_pair_delete_middle(void)
+{
+       fr_pair_t       *vp;
+       fr_pair_list_t  local_pairs;
+       size_t          count;
+       fr_edit_list_t  *el;
+       int             rcode;
+
+       TEST_CASE("Add 3 pairs and delete the middle one");
+
+       add_pairs(&local_pairs);
+
+       vp = fr_pair_list_head(&local_pairs);
+       vp = fr_pair_list_next(&local_pairs, vp);
+       TEST_CHECK(vp != NULL);
+
+       el = fr_edit_list_alloc(NULL);
+       TEST_CHECK(el != NULL);
+
+       rcode = fr_edit_list_record(el, FR_EDIT_DELETE, vp, &local_pairs, NULL);
+       TEST_CHECK(rcode == 0);
+
+       talloc_free(el);
+
+       /* let's count */
+       count = fr_pair_list_len(&local_pairs);
+       TEST_CASE("Expected (count == 2) after deleting the middle");
+       TEST_CHECK(count == 2);
+
+       vp = fr_pair_list_head(&local_pairs);
+       TEST_CHECK(vp->da == fr_dict_attr_test_uint32);
+
+       vp = fr_pair_list_tail(&local_pairs);
+       TEST_CHECK(vp->da == fr_dict_attr_test_tlv);
+
+       fr_pair_list_free(&local_pairs);
+}
+
+static void test_pair_delete_middle_abort(void)
+{
+       fr_pair_t       *vp, *middle;
+       fr_pair_list_t  local_pairs;
+       size_t          count;
+       fr_edit_list_t  *el;
+       int             rcode;
+
+       TEST_CASE("Add 3 pairs and delete the middle one, then abort");
+
+       add_pairs(&local_pairs);
+
+       vp = fr_pair_list_head(&local_pairs);
+       middle = fr_pair_list_next(&local_pairs, vp);
+       TEST_CHECK(middle != NULL);
+       TEST_CHECK(middle->da == fr_dict_attr_test_octets);
+
+       el = fr_edit_list_alloc(NULL);
+       TEST_CHECK(el != NULL);
+
+       rcode = fr_edit_list_record(el, FR_EDIT_DELETE, middle, &local_pairs, NULL);
+       TEST_CHECK(rcode == 0);
+
+       count = fr_pair_list_len(&local_pairs);
+       TEST_CASE("Expected (count == 2) after deleting the middle");
+       TEST_CHECK(count == 2);
+
+       vp = fr_pair_list_head(&local_pairs);
+       TEST_CHECK(vp->da == fr_dict_attr_test_uint32);
+
+       vp = fr_pair_list_tail(&local_pairs);
+       TEST_CHECK(vp->da == fr_dict_attr_test_tlv);
+
+       /*
+        *      Abort the edit
+        */
+       fr_edit_list_abort(el);
+
+       talloc_free(el);
+
+       expect3(&local_pairs);
+}
+
+static void test_pair_delete_multiple(void)
+{
+       fr_pair_t       *vp;
+       fr_pair_list_t  local_pairs;
+       size_t          count;
+       fr_edit_list_t  *el;
+       int             rcode;
+
+       TEST_CASE("Add 3 pairs and delete the last 2");
+
+       add_pairs(&local_pairs);
+
+       vp = fr_pair_list_head(&local_pairs);
+       vp = fr_pair_list_next(&local_pairs, vp);
+       TEST_CHECK(vp != NULL);
+
+       el = fr_edit_list_alloc(NULL);
+       TEST_CHECK(el != NULL);
+
+       rcode = fr_edit_list_record(el, FR_EDIT_DELETE, vp, &local_pairs, NULL); /* middle */
+       TEST_CHECK(rcode == 0);
+
+       vp = fr_pair_list_tail(&local_pairs);
+       TEST_CHECK(vp != NULL);
+
+       rcode = fr_edit_list_record(el, FR_EDIT_DELETE, vp, &local_pairs, NULL); /* tail */
+       TEST_CHECK(rcode == 0);
+
+       talloc_free(el);
+
+       count = fr_pair_list_len(&local_pairs);
+       TEST_CASE("Expected (count == 1) after deleting the last 2");
+       TEST_CHECK(count == 1);
+
+       vp = fr_pair_list_head(&local_pairs);
+       TEST_CHECK(vp->da == fr_dict_attr_test_uint32);
+
+       vp = fr_pair_list_tail(&local_pairs);
+       TEST_CHECK(vp->da == fr_dict_attr_test_uint32); /* head == tail */
+
+       fr_pair_list_free(&local_pairs);
+}
+
+static void test_pair_delete_multiple_abort(void)
+{
+       fr_pair_t       *vp;
+       fr_pair_list_t  local_pairs;
+       size_t          count;
+       fr_edit_list_t  *el;
+       int             rcode;
+
+       TEST_CASE("Add 3 pairs and delete the last two, then abort");
+
+       add_pairs(&local_pairs);
+
+       vp = fr_pair_list_head(&local_pairs);
+       vp = fr_pair_list_next(&local_pairs, vp);
+       TEST_CHECK(vp->da == fr_dict_attr_test_octets);
+
+       el = fr_edit_list_alloc(NULL);
+       TEST_CHECK(el != NULL);
+
+       rcode = fr_edit_list_record(el, FR_EDIT_DELETE, vp, &local_pairs, NULL); /* middle */
+       TEST_CHECK(rcode == 0);
+
+       vp = fr_pair_list_tail(&local_pairs);
+       TEST_CHECK(vp != NULL);
+
+       rcode = fr_edit_list_record(el, FR_EDIT_DELETE, vp, &local_pairs, NULL); /* tail */
+       TEST_CHECK(rcode == 0);
+
+       count = fr_pair_list_len(&local_pairs);
+       TEST_CASE("Expected (count == 1) after deleting the last 2");
+       TEST_CHECK(count == 1);
+
+       vp = fr_pair_list_head(&local_pairs);
+       TEST_CHECK(vp->da == fr_dict_attr_test_uint32);
+
+       vp = fr_pair_list_tail(&local_pairs);
+       TEST_CHECK(vp->da == fr_dict_attr_test_uint32);
+
+       /*
+        *      Abort the edit
+        */
+       fr_edit_list_abort(el);
+
+       talloc_free(el);
+
+       expect3(&local_pairs);
+}
+
+
+static void test_pair_edit_value(void)
+{
+       fr_pair_t       *vp;
+       fr_pair_list_t  local_pairs;
+       fr_edit_list_t  *el;
+       int             rcode;
+
+       TEST_CASE("Add 3 pairs and change the value of the first one");
+
+       add_pairs(&local_pairs);
+
+       vp = fr_pair_list_head(&local_pairs);
+       TEST_CHECK(vp != NULL);
+
+       el = fr_edit_list_alloc(NULL);
+       TEST_CHECK(el != NULL);
+
+       rcode = fr_edit_list_record(el, FR_EDIT_VALUE, vp, NULL, NULL);
+       TEST_CHECK(rcode == 0);
+
+       TEST_CHECK(vp->vp_uint32 == 0);
+
+       vp->vp_uint32 = 1;
+       TEST_CHECK(vp->vp_uint32 == 1);
+
+       talloc_free(el);
+
+       vp = fr_pair_list_head(&local_pairs);
+       TEST_CHECK(vp->da == fr_dict_attr_test_uint32);
+       TEST_CHECK(vp->vp_uint32 == 1);
+
+       expect3(&local_pairs);
+}
+
+static void test_pair_edit_value_abort(void)
+{
+       fr_pair_t       *vp;
+       fr_pair_list_t  local_pairs;
+       fr_edit_list_t  *el;
+       int             rcode;
+
+       TEST_CASE("Add 3 pairs and change the value of the first one, then abort");
+
+       add_pairs(&local_pairs);
+
+       vp = fr_pair_list_head(&local_pairs);
+       TEST_CHECK(vp != NULL);
+
+       el = fr_edit_list_alloc(NULL);
+       TEST_CHECK(el != NULL);
+
+       rcode = fr_edit_list_record(el, FR_EDIT_VALUE, vp, NULL, NULL);
+       TEST_CHECK(rcode == 0);
+
+       TEST_CHECK(vp->vp_uint32 == 0);
+
+       vp->vp_uint32 = 1;
+       TEST_CHECK(vp->vp_uint32 == 1);
+
+       /*
+        *      Abort the edit
+        */
+       fr_edit_list_abort(el);
+
+       talloc_free(el);
+
+       vp = fr_pair_list_head(&local_pairs);
+       TEST_CHECK(vp->da == fr_dict_attr_test_uint32);
+       TEST_CHECK(vp->vp_uint32 == 0);
+
+       expect3(&local_pairs);
+}
+
+TEST_LIST = {
+       /*
+        *      Deletion.
+        */
+       { "pair_delete_head",                   test_pair_delete_head },
+       { "pair_delete_head_abort",             test_pair_delete_head_abort },
+
+       { "pair_delete_middle",                 test_pair_delete_middle },
+       { "pair_delete_middle_abort",           test_pair_delete_middle_abort },
+
+       { "pair_delete_multiple",               test_pair_delete_multiple },
+       { "pair_delete_multiple_abort",         test_pair_delete_multiple_abort },
+
+       /*
+        *      Insert before
+        */
+
+       /*
+        *      Insert after
+        */
+
+       /*
+        *      Value modification
+        */
+       { "pair_edit_value",                    test_pair_edit_value },
+       { "pair_edit_value_abort",              test_pair_edit_value_abort },
+
+       { NULL }
+};
diff --git a/src/lib/util/edit_tests.mk b/src/lib/util/edit_tests.mk
new file mode 100644 (file)
index 0000000..be5d4e4
--- /dev/null
@@ -0,0 +1,7 @@
+TARGET         := edit_tests
+
+SOURCES                := edit_tests.c
+
+TGT_LDLIBS     := $(LIBS) $(GPERFTOOLS_LIBS)
+TGT_LDFLAGS    := $(LDFLAGS) $(GPERFTOOLS_LDFLAGS)
+TGT_PREREQS    := libfreeradius-util.a
index e7cf935fcb957df054f73a96068cd13467bda5b0..b2ca3303dab8dc55c38793321a2d9135a2d481b5 100644 (file)
@@ -25,6 +25,7 @@ SOURCES               := \
                   dict_validate.c \
                   dl.c \
                   dns.c \
+                  edit.c \
                   event.c \
                   ext.c \
                   fifo.c \