1    | /***************************************
2    |   $Revision: 1.9 $
3    | 
4    |   Radix tree (rx).  rx_node.c - functions to operate on nodes of the tree
5    |   (creation/deletion).
6    | 
7    |   Status: NOT REVUED, TESTED, INCOMPLETE
8    | 
9    |   Design and implementation by: Marek Bukowy
10   | 
11   |   ******************/ /******************
12   |   Copyright (c) 1999                              RIPE NCC
13   |  
14   |   All Rights Reserved
15   |   
16   |   Permission to use, copy, modify, and distribute this software and its
17   |   documentation for any purpose and without fee is hereby granted,
18   |   provided that the above copyright notice appear in all copies and that
19   |   both that copyright notice and this permission notice appear in
20   |   supporting documentation, and that the name of the author not be
21   |   used in advertising or publicity pertaining to distribution of the
22   |   software without specific, written prior permission.
23   |   
24   |   THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
25   |   ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS; IN NO EVENT SHALL
26   |   AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
27   |   DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
28   |   AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
29   |   OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
30   |   ***************************************/
31   | 
32   | #include <erroutines.h>
33   | #include <rxroutines.h>
34   | #include <memwrap.h>
35   | #include <stubs.h>
36   | #include <glib.h>
37   | 
38   | #include <comparisons.h> 
39   | 
40   | /***************************************************************************/
41   | /*++++++++++++++++
42   |   rx_creat_node = create a new data node 
43   |   (empty{glue} nodes get created automatically).
44   | 
45   |   Takes a pointer to the (already allocated) data leaf to be included 
46   |   in the list of data nodes (presumably empty as the node is only now being
47   |   created).
48   |   
49   |   Requires a stack of nodes created in CREAT mode (with glue nodes, 
50   |   until deep enough and the last node being non-glue).
51   |   
52   |   MT notes: requires the tree to be locked.
53   |   
54   |   Returns: RX_OK or error code.
55   | 
56   |   +++++++++++++++++*/
57   | static
58   | er_ret_t
59   | rx_creat_node (
60   | 	       ip_prefix_t   *newpref,  /*+ prefix of the node to be added +*/
61   | 	       rx_tree_t     *tree,     /*+ tree the new node goes to +*/
62   | 	       rx_dataleaf_t *dataleaf, /*+ dataleaf to attach at this node+*/
63   | 	       rx_nodcpy_t   stack[],   /*+ stack==array of node_copies +*/
64   | 	       int           stackdepth /*+ length of the stack +*/
65   | 	     )
66   | {
67   |   rx_node_t *newnode, *curnode, *memnode, *gluenode;
68   |   int chk_bit, dif_bit, link, curpos;
69   |   char buf[1024];
70   |   er_ret_t err;
71   | 
72   |   // assume no such node yet. Will die if there is one.
73   |    
74   |   // calloc, because parent/child keys and child ptrs are not always set.
75   | 
76   |   if( (err=wr_calloc( (void **) & newnode, 1, sizeof(rx_node_t))) != UT_OK) {
77   |     return err; 
78   |   }
79   |   
80   |   // increment the number of nodes in the tree
81   |   tree -> num_nodes ++;
82   |   
83   |   newnode -> prefix = *newpref;
84   |   
85   |   // attach the leaf to a (presumably empty?! hence NULL) list...
86   |   newnode->leaves_ptr = g_list_prepend(NULL, dataleaf);
87   |   newnode->glue = 0;
88   | 
89   |   // OK, so take a look at the tree
90   | 
91   |   if ( tree -> num_nodes == 1 ) { 
92   |     // The tree was empty. Create a new top node.
93   |     
94   |     tree -> top_ptr = newnode;
95   |     ER_dbg_va(FAC_RX, ASP_RX_NODCRE_BOT, "Created as the top node");
96   |     return RX_OK;
97   |   }
98   | 
99   |   // OK, there is at least one node in the tree. Take a look at the stack.
100  | 
101  |   //    we've got a real node there (not a glue), but we may be too deep.
102  |   //   (it's not a glue, because glues have always two children.
103  |   //    we had to go that deep because from a glue alone one doesn't know 
104  |   //    what it glues)
105  |   // GO UP.
106  |   // take the first differing bit from comparing 
107  |   // the new and the found nodes' prefixes. 
108  |   // (not deeper than the shorter of the two)
109  |   
110  |   curpos =   stackdepth-1;
111  |   curnode =  & stack[curpos].cpy;
112  | 
113  |   chk_bit = smaller(curnode->prefix.bits, newpref->bits );
114  |   
115  |   for(dif_bit = 0; dif_bit < chk_bit; dif_bit++) {
116  |     // break the loop when the first different bit is found
117  | 
118  |     if( IP_addr_bit_get( & curnode->prefix.ip, dif_bit) 
119  | 	!=  IP_addr_bit_get( & newpref->ip, dif_bit) ) {
120  |       break;
121  |     }
122  |   }
123  |  
124  |   ER_dbg_va(FAC_RX, ASP_RX_NODCRE_DET, 
125  | 	    "cur = %d, new = %d, chk_bit = %d, dif_bit = %d", 
126  | 	    curnode->prefix.bits, newpref->bits, chk_bit, dif_bit );
127  |   
128  |   if(dif_bit == IP_sizebits(newpref->ip.space)) die; // it mustn't happen!!!
129  |  
130  |   // go up to that level (watch the head of the tree!)
131  |   
132  |   while( curpos > 0 && stack[curpos-1].cpy.prefix.bits >= dif_bit) {
133  |     curpos--;
134  |     ER_dbg_va(FAC_RX, ASP_RX_NODCRE_DET, 
135  | 	      "up to level %d", curpos );
136  |   }
137  |   
138  |   /*
139  |     if the bit lenghts of the node, new prefix and the diffbit are equal
140  |     {
141  |     YOU'VE GOT THE NODE where the new one will be attached.
142  |     Either it has data (and will be moved accordingly), 
143  |     or is a glue (and will be turned into a regular node).
144  |     }
145  |   */
146  |   
147  |   curnode =  & stack[curpos].cpy;
148  |   
149  |   // RAM: set a pointer to the real node in memory
150  |   memnode = stack[curpos].srcptr;
151  |         
152  |   if(    dif_bit == newpref->bits 
153  | 	 && dif_bit == curnode->prefix.bits ) {
154  | 
155  |     // such node already exists, nothing to change in the tree!!!
156  |     // this should be checked before calling this function, so..
157  |       
158  |     die;
159  |   }
160  |   /*
161  |     else  ** the branch ends here; we must create a new node... **
162  |     {
163  |     OK, how is the new node's prefix length w.r.t the dif_bit ? 
164  |     longer  -> make it a child of the node found
165  |     shorter -> make it the parent of the node found and take its place
166  |     equal   -> make a glue node the parent of both 
167  |     }
168  |     
169  |     WHEN ATTACHING THE NODE, VALUES FROM THE STACK ARE USED,
170  |     TO PREVENT EXCESSIVE LOOKUPS AGAIN.
171  |     
172  |   */
173  |   else {
174  |     
175  |     // **** attach it.
176  |     if( ER_is_traced(FAC_RX, ASP_RX_NODCRE_DET) ) {
177  |       rx_nod_print(curnode, buf, 1024);
178  |       ER_dbg_va(FAC_RX, ASP_RX_NODCRE_DET, "Looking at node %s", buf);
179  |     }
180  |     
181  |     if( curnode -> prefix.bits == dif_bit ) {
182  |       
183  |       // attach here as a child of the node found      
184  |       link = IP_addr_bit_get( &newpref->ip, dif_bit );
185  |       
186  |       ER_dbg_va(FAC_RX, ASP_RX_NODCRE_BOT, "attaching as child %d", link);
187  |       
188  |       if( memnode -> child_ptr[link] != NULL ) {
189  | 	die;
190  |       }
191  |       
192  |       memnode -> child_ptr[link] = newnode;
193  |       newnode -> parent_ptr = memnode;
194  |     }
195  |     else if ( newpref->bits == dif_bit ) {
196  |       // make it the parent of the node found and take its place,
197  |       // moving it down.
198  | 
199  |       // set the link from the NEW node to the OLD one (different than before)
200  | 
201  |       link = IP_addr_bit_get( &curnode->prefix.ip, dif_bit );
202  |       
203  |       ER_dbg_va(FAC_RX, ASP_RX_NODCRE_BOT, "shifting down as child %d", link);
204  | 
205  |       // PARENT<->NEW LINKS
206  |       // see if the node was the top_node
207  |       if (curnode -> parent_ptr == NULL) {
208  | 	//  update tree struct 
209  | 	tree -> top_ptr = newnode;
210  |       } else {    
211  | 	// no - fix the child link at the parent.
212  | 	// at the link where it was attached
213  | 	int link = (curnode->parent_ptr->child_ptr[1] == memnode);
214  | 	memnode -> parent_ptr -> child_ptr[link] = newnode;
215  |       }
216  |       memnode -> parent_ptr = newnode;
217  | 
218  |       // NEW<->CHILD LINKS
219  |       newnode -> parent_ptr = curnode->parent_ptr;
220  |       newnode -> child_ptr[link] = memnode;
221  |     }
222  |     else {
223  |       // create a glue and shift the curnode below the glue,
224  |       // then attach the new node at the glue
225  |       
226  |       // calloc, because parent/child keys are not set.
227  | 
228  |       if( (err=wr_calloc( (void **)& gluenode, 1, sizeof(rx_node_t))) != UT_OK) {
229  | 	return err; // die;
230  |       }
231  |       tree -> num_nodes ++;
232  | 
233  |       ER_dbg_va(FAC_RX, ASP_RX_NODCRE_BOT, "created glue node at %p", gluenode);
234  | 
235  |       gluenode -> prefix.bits = dif_bit;
236  | 
237  |       // fill in the address. The glue node should get the prefix
238  |       // shorter by one than the shorter of the two prefixes that are glued
239  |       // (difbit)
240  |       //
241  |       
242  |       gluenode -> prefix.ip = newpref->ip;
243  |       gluenode -> prefix.bits = dif_bit;
244  |       
245  |       // the ip in this prefix is probably incorrect. Fix it.
246  |       IP_pref_bit_fix(  & gluenode -> prefix );
247  |       
248  |       gluenode -> leaves_ptr = NULL;
249  |       gluenode -> glue = 1;
250  | 
251  |       // 1. Fix the link to and from the parent to the gluenode.
252  | 
253  |       gluenode -> parent_ptr = curnode->parent_ptr;
254  |       if (gluenode->parent_ptr == NULL) {
255  | 	tree -> top_ptr = gluenode;
256  |       } 
257  |       else {
258  | 	// fix the child link in the parent. 
259  | 	// if it was at 1, then let fix the link 1, 0 otherwise
260  | 	
261  | 	link = (curnode->parent_ptr->child_ptr[1] == memnode);
262  |       
263  | 	memnode->parent_ptr->child_ptr[link] = gluenode;
264  |       }
265  | 
266  |       // 2. Fix the links between gluenode and the OLD node
267  | 
268  |       link = IP_addr_bit_get( &newpref->ip, dif_bit );
269  | 
270  |       gluenode -> child_ptr[ ! link ] = memnode;
271  |       memnode->parent_ptr = gluenode;
272  | 
273  |       // 3. Fix the links between gluenode and the NEW node
274  |       
275  |       gluenode -> child_ptr[ link ] = newnode;
276  |       newnode -> parent_ptr = gluenode;
277  |     }
278  |     return RX_OK;
279  |   }
280  |   die;
281  |   return -1; //this is just to calm down the compiler
282  | }
283  | 
284  | 
285  | /***************************************************************************/
286  | /*+ hook for g_list_foreach to free a list element +*/
287  | 
288  | void
289  | rx_free_list_element(void *cpy, void *trash)
290  | {
291  |   wr_free(cpy);
292  | }
293  | 
294  | /***************************************************************************/
295  | /*+++++++++++++++++++
296  | 
297  |   General function to operate on dataleaves attached to a single node
298  |   (create / modify / delete).
299  |   
300  |   searches tree, finds and creates (modifies/deletes) a node,
301  |   copies modified nodes to disk using rx_sql_node_set (not yet implemented).
302  |   Updates memory rollback info.
303  |   
304  |   Currently only creation is implemented.
305  | 
306  |   Add a dataleaf at node defined by prefix. Create a new node if it doesn't 
307  |   exist yet.
308  | 
309  |   MT notes: requires the tree to be locked.
310  |   
311  |   Returns: RX_OK or error code.
312  | 
313  |   Errors from:
314  |   rx_bin_search,
315  |   memory alloc routines.
316  |   
317  |   - no such node (if not in create mode)
318  |   
319  |   - too many nodes found (strange).
320  |   
321  |   +++++++++++++++++*/
322  |   
323  | er_ret_t
324  | RX_bin_node (
325  | 	     rx_oper_mt   mode,       /*+ MODE={cre|mod|del} +*/
326  | 	     ip_prefix_t *newpref,    /*+ prefix of the node +*/
327  | 	     rx_tree_t	*tree,        /*+ pointer to the tree structure +*/
328  | 	     rx_dataleaf_t *dataleaf  /*+ dataleaf to attach at the node +*/
329  | 	     )
330  |      
331  | {
332  |   GList *nodlist = NULL;
333  |   int nodesfound, stackdepth;
334  |   int glue;
335  |   rx_nodcpy_t *curcpy;
336  |   rx_node_t *curnode;
337  |   rx_nodcpy_t *stack;
338  |   er_ret_t err;
339  |   char bbf[IP_PREFSTR_MAX];
340  | 
341  | 
342  |   if( ER_is_traced( FAC_RX, ASP_RX_NODCRE_BOT)) {
343  |     IP_pref_b2a( newpref , bbf, IP_PREFSTR_MAX);
344  |     ER_dbg_va(FAC_RX, ASP_RX_NODCRE_BOT,
345  | 	      "rx_bin_node: new %s in spc %d /fam %d /reg %d", 
346  | 	      bbf, tree->space, tree->family, tree->reg_id);
347  |   }
348  | 
349  |   // first check: are we using the correct tree ???
350  |   if( tree->space != newpref->ip.space ) {
351  |     /* trying to insert a prefix of space %d into a tree of space %d\n",
352  | 	   tree->space,
353  | 	   newpref->ip.space);
354  |     */
355  |     die;
356  |   }
357  | 
358  |   assert( newpref->bits <= IP_sizebits(tree->space) );
359  | 
360  |   // fix the prefix, to make sure all insignificant bits are 0
361  |   IP_pref_bit_fix( newpref );
362  |   
363  |   if( (err=wr_malloc( (void **) &stack, 
364  | 	   sizeof(rx_nodcpy_t) * IP_sizebits(tree->space))) != UT_OK) {
365  |     return err; //die;
366  |   }
367  |   
368  |   if( (err=rx_build_stack(stack, &stackdepth, 
369  | 		     tree, newpref, RX_STK_CREAT) != RX_OK )) {
370  |     return err; //die
371  |   }
372  |   
373  |   //   rx_stk_print(stack, stackdepth);
374  |   
375  |   // perform a search on the stack. The result is a list, and it must
376  |   // be properly deleted after use!!
377  | 
378  |   if( rx_nod_search(RX_SRCH_CREAT, 0, 0, 
379  | 		    tree, newpref, stack, stackdepth, 
380  | 		    &nodlist, RX_ANS_ALL) != RX_OK ) {
381  |     return err; // die;
382  |   }
383  | 
384  |   
385  |   // count number of nodes in the answer 
386  |   nodesfound = g_list_length (nodlist);
387  |   
388  |   switch( nodesfound ) {
389  |   case 0:
390  |     /* no such node (yet). See what we're up to.
391  |        if( mode==cre )  create, else - program error, die */
392  | 
393  |     if( mode != RX_OPER_CRE) {
394  |       die;
395  |     }
396  |     
397  |     /*  C R E A T I O N */
398  |     ER_dbg_va(FAC_RX, ASP_RX_NODCRE_BOT, 
399  | 	      "Creating a new node ");
400  |     rx_creat_node(  newpref, tree, dataleaf, stack, stackdepth );
401  |     break;
402  |   case 1: /* found */
403  |     switch( mode ) {
404  |     case RX_OPER_CRE:
405  |       //  attach the data at the node that was found;
406  | 
407  |       curcpy = g_list_nth_data(nodlist, 0);
408  | 
409  |       curnode = curcpy->srcptr;
410  |       // was it glue ?
411  |       glue = curnode->glue;
412  |             
413  |       curnode->leaves_ptr = g_list_prepend(curnode->leaves_ptr, dataleaf);
414  |       // not it's not a glue anymore
415  |       curnode->glue = 0;
416  | 
417  |       ER_dbg_va(FAC_RX, ASP_RX_NODCRE_BOT, "Appended data to a %s node",
418  | 		glue ? "glue" : "data");
419  |       
420  |       break;
421  |     case RX_OPER_MOD:
422  |       // put new data in;
423  |       break;
424  |     case RX_OPER_DEL:
425  |       break;
426  |       // fine, check the number of children:
427  |       //  0 - just delete,
428  |       //  1 - copy the child's link to parent. then delete
429  |       //  2 - turn into a glue
430  |     }
431  |     break;
432  |   default:
433  |     /* too many nodes found! from an exact/exact-less-1 search.
434  |        this cannot happen. Call Ghostbusters now.
435  |      */
436  |     die;
437  |   }
438  | 
439  |   g_list_foreach(nodlist, rx_free_list_element, NULL);
440  |   
441  |   wr_free(stack);
442  |   return RX_OK;
443  | }
444  | 
445  | /***************************************************************************/
446  | /*+++++++++++++++
447  |   performs the actual update for inetnums (possibly composed of many prefixes).
448  |   Decomposes the ranges into prefixes and then falls back to rx_bin_node
449  |   to perform changes at the nodes.
450  |   
451  |   Requires/returns - practically the same as rx_bin_node.
452  | ++++++++++++++++*/
453  | 
454  | er_ret_t
455  | RX_inum_node( rx_oper_mt mode,       /*+ MODE={cre|mod|del} +*/
456  | 	      ip_range_t *rang,      /*+ range of IP addresses +*/
457  | 	      rx_tree_t *tree,       /*+ pointer to the tree structure +*/
458  | 	      rx_dataleaf_t *leafptr /*+ dataleaf to attach at the node +*/
459  | 	      )     
460  | {
461  |   int i, prefcount;
462  |   GList *preflist = NULL;
463  |   char buf[IP_RANGSTR_MAX];
464  | 
465  |   if( ER_is_traced( FAC_RX, ASP_RX_NODCRE_BOT)) {
466  |     IP_rang_b2a(rang, buf, IP_RANGSTR_MAX );
467  |     ER_dbg_va(FAC_RX, ASP_RX_NODCRE_BOT, 
468  | 	      "rx_inum_node: adding %s", buf);
469  |   }
470  | 
471  |   // decompose, put links to the data leaf into every prefix
472  |   // that makes up this range.
473  |   IP_rang_decomp(rang, &preflist);
474  |   
475  |   // see if there is more than 1 prefix, set the composed flag
476  |   prefcount = g_list_length(preflist);
477  |   leafptr->composed = (prefcount > 1) ;
478  |   
479  |   leafptr->iprange = *rang;
480  |   
481  |   for(i=0; i < prefcount; i++) {
482  |     ip_prefix_t *mypref = g_list_nth_data(preflist, i);
483  |     
484  |     RX_bin_node(mode, mypref, tree, leafptr);
485  |   }
486  |     
487  |   // free the storage from decomposition
488  |   g_list_foreach(preflist, rx_free_list_element, NULL);
489  |   g_list_free(preflist);
490  | 
491  |   return RX_OK;
492  | }
493  | 
494  | 
495  | /***************************************************************************/
496  | /*+++++++++++++++
497  |   translates ranges/prefixes into binary prefixes.
498  |   finds tree, locks it.
499  |   initiates memory rollback structure (???)
500  |   
501  |   builds a dataleaf and puts into the node(s), 
502  |   calling rx_bin_node for every prefix.
503  |   
504  |   checks rollback condition and (possibly) rolls back ???
505  |   
506  |   MT-note: locks/unlocks the tree.
507  |   
508  |   Possible errors
509  |   - all errors from:
510  |     ip_asc_2_bin,
511  |     rx_get_tree,
512  |     rx_bin_node,
513  |     wr_free
514  |   
515  | +++++++++++++++++*/
516  | er_ret_t
517  | RX_asc_node ( rx_oper_mt mode,       /*+ MODE={cre|mod|del} +*/
518  | 	      char *rangstr,         /*+ string prefix/range/IP +*/
519  | 	      rx_regid_t reg_id,     /*+ id of the registry +*/
520  | 	      ip_space_t spc_id,     /*+ type of space (ipv4/ipv6) +*/
521  | 	      rx_fam_t   fam_id,     /*+ family of objects (route/inetnum) +*/
522  | 	      void *data             /*+ pointer to the payload +*/
523  | 	      )     
524  | 
525  | {
526  |  
527  |   /*
528  |    For creation of a new node:
529  | 
530  |      READ-LOCK THE FOREST 
531  | 
532  |      get the root tree for this space (rx_get_tree)
533  |      got it ? good. No ? error!!!
534  | 
535  |      Check if any of the prefixes spans more than one subtree...
536  |      Check if they all exist already..
537  |  
538  |      if any is missing
539  |      then
540  |          WRITE-LOCK THE FOREST
541  |      fi
542  | 
543  |      for all missing subtrees
544  |          create missing trees
545  |      rof
546  | 
547  |      UNLOCK THE FOREST
548  | 
549  |      **now start writing the data:**
550  | 
551  |      put *data* records in memory and sql table
552  | 
553  |      for all matchind [sub]trees (in order of the list)
554  | 	 WRITE-LOCK the in-memory [sub]tree
555  | 	 WRITE-LOCK the sql-table for it
556  | 	 
557  | 	 for(all prefixes in memory that match this tree)
558  | 	     create a node in the tree pointing to the data
559  | 	 rof
560  | 	 UNLOCK the tree
561  |      rof
562  | 
563  | 
564  | */
565  |   
566  | 
567  |   ip_range_t myrang;
568  |   ip_prefix_t mypref;
569  |   rx_dataleaf_t *leafptr;
570  |   rx_tree_t *mytree;
571  |   int rang_ok;
572  | 
573  |   if( RX_get_tree ( &mytree, reg_id, spc_id, fam_id) != RX_OK ) {
574  |     die;
575  |   }
576  |   
577  |   ER_dbg_va(FAC_RX, ASP_RX_NODCRE_BOT, 
578  | 	    "rx_asc_node: inserting object  %s", rangstr);
579  |   
580  |   // set the data leaf values
581  |   if( wr_calloc( (void **)& leafptr, sizeof(rx_dataleaf_t), 1) 
582  |       != UT_OK) {
583  |     die;
584  |   }
585  |   
586  |   leafptr->data_ptr = data;
587  | 
588  |   switch( fam_id )
589  |     {
590  |     case RX_FAM_IN:
591  |       rang_ok = 1;
592  |       
593  |       if( IP_rang_e2b(&myrang, rangstr) == IP_OK ) {
594  | 	// that's nice. everything is set.
595  |       } else {
596  | 	// see if's a valid IP, maybe it's an IPv4 classful range
597  | 	if( IP_addr_e2b( &myrang.begin, rangstr ) == IP_OK ) {
598  | 	  if( IP_rang_classful( &myrang , &myrang.begin ) != IP_OK ) {
599  | 	    rang_ok = 0;
600  | 	  }
601  | 	}
602  | 	else {
603  | 	  // sorry. we don't accept that.	      
604  | 	  rang_ok = 0;
605  | 	}
606  |       }
607  |       
608  |       if( rang_ok == 1 ) {
609  | 	return RX_inum_node( mode, &myrang, mytree, leafptr );
610  |       }
611  |       // else: fall through to the end of the function. (unrecognized arg)
612  | 
613  |       break;
614  | 
615  |     case RX_FAM_RT:
616  |       if( IP_pref_e2b(&mypref, rangstr) == IP_OK ) {
617  | 	return RX_bin_node(RX_OPER_CRE, &mypref, mytree, leafptr);
618  |       }
619  |     }
620  |   
621  |   ER_dbg_va(FAC_RX, ASP_RX_NODCRE_BOT, 
622  | 	    "can't understand the key, discarding the OBJECT.");
623  |   wr_free(data);
624  |   wr_free(leafptr);
625  |   
626  |   return RX_BADKEY;
627  | }
628  |