/***************************************
  $Revision: 1.21 $

  Radix tree (rx).  rx_search.c - functions to search nodes of the tree

  Status: NOT REVUED, TESTED, INCOMPLETE

  Design and implementation by: Marek Bukowy

  ******************/ /******************
  Copyright (c) 1999                              RIPE NCC
 
  All Rights Reserved
  
  Permission to use, copy, modify, and distribute this software and its
  documentation for any purpose and without fee is hereby granted,
  provided that the above copyright notice appear in all copies and that
  both that copyright notice and this permission notice appear in
  supporting documentation, and that the name of the author not be
  used in advertising or publicity pertaining to distribution of the
  software without specific, written prior permission.
  
  THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
  ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS; IN NO EVENT SHALL
  AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
  DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
  AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  ***************************************/


#include <erroutines.h>
#include <rxroutines.h>
#include <stubs.h>

#include "iproutines.h"

/***************************************************************************/

/*++++++++++++++
Descends the given tree following the last prefix bit to get [past]
the node with the given prefix.
It fills up a stack of COPIES of nodes, including glue nodes.

Then it also sets the number of elements on the stack: 
set maxdepth to the position where a next one would be written
( = last + 1, or number of nodes pushed)

   The dmodes:
           
   RX_STK_QUERY_NOGLUE = (search exact/less spec) stop when 
                         * the current prefix length >= newprefix length 
                         * the current prefix does not match anymore
                         * do not add glue nodes
                        
   RX_STK_QUERY_ALLNOD = as above, except that the glue and data nodes are
                         treated equally (i.e. glue nodes are not skipped)

   RX_STK_CREAT        = descend until the next non-glue node past the one found
                         in exact mode (for creation)

++++++++++++++*/

er_ret_t
rx_build_stack(rx_nodcpy_t    stack[], 
               int            *maxdepth, 
               rx_tree_t      *tree, 
               ip_prefix_t    *newpref,
               rx_stk_mt      dmode
               )
{
  register rx_node_t *curnode;
  register int link, quit_now=0;
  char bbf[IP_PREFSTR_MAX];

  if( ER_is_traced( FAC_RX, ASP_RX_STKBLD_GEN)) {
    IP_pref_b2a( newpref , bbf, IP_PREFSTR_MAX);
    ER_dbg_va(FAC_RX, ASP_RX_STKBLD_GEN, 
            "rx_build_stack: searching for %s in mode %d", bbf, dmode);
  }

  *maxdepth = 0;
  
  if ( tree -> num_nodes == 0) { 
    // The tree was empty. 
    return RX_OK;
  }
  
  curnode = tree->top_ptr;
  // this works for RAM, for SQL one would have to call a 'getsqlnode' here
  
  // OK, there is at least one node. Descend the tree 
  // as long as the correct bit length is not exceeded
  // or a glue is being found (take the last non-glue node then) 
  // or you run out of nodes in the direction of descending
  
  do {
    // check at the current node, where the one we look for would fit
    // (the second argument of IP_addr_bit_get starts with 0,
    // so this effectively looks at the bit next to the last significant bit
    // of the current node
    
    link = IP_addr_bit_get( & newpref->ip, curnode->prefix.bits );    
    
    // check conditions for leaving the loop    
    if(curnode->child_ptr[link] == NULL) {
      // end of branch. quit after adding the current node to the stack
      // (or before - subject to bit test in QUERY mode)
      quit_now = 1;
    }
    else {
    /* check the node. 
       BIG DIFFERENCE between the modes:
       in CREAT we don't mind the stack to go too deep, 
       in QUERY it can lead to false answers
       (e.g. a /24 is found for a /23 query). 

       So this must be "peeled off the stack" later in the search routine,
       if both types of stack are to work properly with query searches.

       ONE MORE THING: in LESS SPECIFIC searches a QUERY stack MUST BE USED,
       because only this one actually compares the prefixes. 
       This has no effect on the exact search, but will cause less-spec
       to return an object that does not contain the search term.
    */


      if( curnode->prefix.bits > newpref->bits ) {
        // deep enough.
        quit_now = 2;
      }

      if(dmode == RX_STK_CREAT && curnode->glue) {
        // mode: creation. 
        // Cancel quitting if glue -- in CREAT mode the stack building 
        // should stop at the next real (non-glue) node.
        // ("next" meaning following link #0)
        quit_now = 0;
      }
    }
    
    /* now that the conditions for leaving the loop after the node is
       added on the stack, see if we shouldn't leave the loop BEFOREHAND */
    
    /* In query mode, we should quit as soon as we see a mismatch */

    if(dmode != RX_STK_CREAT
       && 0 != IP_addr_cmp(&curnode->prefix.ip, &newpref->ip, 
                           curnode->prefix.bits) ) {
        //QUIT NOW! (do not add this node)
      quit_now = 64;
    }

    // push the current node on the stack. RAM only.
    // 
    // (unless quit_now is 64 which means do NOT copy the current node.
    //
    // In CREAT and QUERY_ALLNOD modes, push everything. 
    // In QUERY_NOGLUE mode, only non-glues.
      
    if( quit_now < 64 
        && (dmode != RX_STK_QUERY_NOGLUE || curnode->glue == 0 )) {
      memcpy( & stack[*maxdepth].cpy, curnode, sizeof(rx_node_t));
      stack[*maxdepth].srcptr = curnode;
      stack[*maxdepth].srckey = SQ_NOKEY;
      stack[*maxdepth].tree = tree;
      (*maxdepth)++;
    }
    
    // make debug info.
   
    if( ER_is_traced( FAC_RX, ASP_RX_STKBLD_DET)) {
      IP_pref_b2a( & curnode->prefix , bbf, IP_PREFSTR_MAX );
      ER_dbg_va(FAC_RX, ASP_RX_STKBLD_DET,
                "rx_build_stack: %s%d at %s%s (stk len: %d)",
                quit_now ? "stop/" : "link ",  
                quit_now ? quit_now : link,
                bbf, ( curnode->glue ) ? " ++glue++" : "",
                *maxdepth  );
    }
    
       curnode = curnode -> child_ptr[link];

  } while( !quit_now ); 

  return RX_OK;
}

/***************************************************************************/
/*+++++++++
   helper for the nod_search routine: 

   allocates a new node copy struct, copy the struct and add to nodlist
++++++++++*/

static
er_ret_t
rx_nod_append( GList **nodlist, rx_nodcpy_t *element) 
{
  rx_nodcpy_t *newcpy;
  er_ret_t err;
  
  if( (err=wr_calloc( (void **) & newcpy, 1, sizeof(rx_nodcpy_t))) != UT_OK) {
    return err; //    die;
  }
  memcpy(newcpy, element, sizeof(rx_nodcpy_t));        
  (*nodlist) = g_list_prepend( *nodlist, newcpy );

  return RX_OK;
}




/***************************************************************************/

/*+++++++++++
  helper for MORE specific lookup in rx_nod_search 

  adds a node to the list of answers.
+++++++++++*/

static
er_ret_t
rx_walk_hook_addnode(rx_node_t *node, int level, int nodecounter, 
                     void *userptr)
{   
  rx_nodcpy_t nodcpy;
  hook_addnode_userdat_t *userdat = userptr;
  

  // do not append glue nodes
  if( node->glue == 1 ) return RX_OK;
  
  // in RAM mode, do not copy the node.
  //  memcpy( &nodcpy.cpy, node, sizeof(rx_node_t));
  /* XXX to avoid warnings from workshop: but slows things down! */
  memset( &nodcpy.cpy, 0, sizeof(rx_node_t));

  nodcpy.srcptr = node;
  nodcpy.srckey = SQ_NOKEY;
  nodcpy.tree = userdat->tree;
  
  return rx_nod_append( userdat->nodlist, &nodcpy);
}


/***************************************************************************/

/*+++++++++++
  helper for DBLS lookup in rx_nod_search 

  adds a node to the list of answers.
+++++++++++*/

static
er_ret_t
rx_walk_hook_adddoubles(rx_node_t *node, int level, int nodecounter, 
                        void *userptr)
{
  rx_nodcpy_t nodcpy;
  hook_addnode_userdat_t  *userdat = userptr;
  int leaves = g_list_length(node->leaves_ptr);
  char buf[1024];
  
  // do not append glue nodes
  if( node->glue == 1 ) return RX_OK;

 
  // add only nodes with more than 1 dataleaf
  if( leaves < 2 ) return RX_OK;

  if( ER_is_traced( FAC_RX, ASP_RX_SRCH_DET)) {
    rx_nod_print(node, buf, 1024);
    ER_dbg_va(FAC_RX, ASP_RX_SRCH_DET,
              "rx_walk_hook_adddoubles: %30s, %d leaves", buf, leaves);
  }

  //  memcpy( &nodcpy.cpy, node, sizeof(rx_node_t));
  nodcpy.srcptr = node;
  nodcpy.srckey = SQ_NOKEY;
  nodcpy.tree = userdat->tree;
  
  return rx_nod_append( userdat->nodlist, &nodcpy);
}


/***************************************************************************/
er_ret_t
rx_nod_search (
               rx_srch_mt  search_mode,
               int         par_a,
               int         par_b,
               /* see rx_asc_search() for explanation */
               rx_tree_t  *tree,           // tree ptr
               ip_prefix_t  *prefix,          // binary prefix

               rx_nodcpy_t stack[],         // stack==array of node_copies
               int         stackcount,      // number of element on the stack,
                                            // can come from a creat stack!

               GList       **nodlist,       // answers go here
               int         max_count        // max # of answers
               )                        
     /*
        searches the stack for a given prefix, finds *nodes* in the stack 
        and appends *copies of the nodes* to the nodlist;

        finds
        0 or 1 nodes for exact search
        0 or 1 nodes for exless (0 if no less specific node found)
        any number (incl. 0) for {more|less}^n-m specific 
     
       returns errcode.

       
     */
{
  char buf[1024];
  int sps = stackcount-1;       // stack position.
  int depthcounter=0;
  er_ret_t err=RX_OK;
  int i;
  hook_addnode_userdat_t datstr;
  er_ret_t (*hook_function)();  // pointer to the walk_hook function
                                // (see MORE spec lookup)

  /* structure for carrying data to walk_tree hook functions, used only
     in MORE, DBLS and RANG search modes 
  */
  datstr.nodlist = nodlist;
  datstr.tree    = tree;
  datstr.prefix  = prefix;
    
  
  if( ER_is_traced( FAC_RX, ASP_RX_SRCH_BOT)) {
    IP_pref_b2a( prefix , buf, IP_PREFSTR_MAX);
    ER_dbg_va(FAC_RX, ASP_RX_SRCH_BOT,
              "rx_nod_search: searching for %s in mode %d", buf, search_mode);
  }

  /* in non-CREAT modes, glue nodes are skipped anyway. 
     (they should normally not be there if the stack was created in
     the STK_QUERY mode, but it's possible to use a CREAT stack too).

     It's also possible that the stack is too deep.
     So, truncate the stack to the last non-glue node 
     of the length <= search term.
  */
  
  if( search_mode != RX_SRCH_CREAT && search_mode != RX_SRCH_RANG) {
    while( sps >= 0 
           && ( 
               stack[sps].cpy.prefix.bits > prefix->bits           // too deep
               || ( search_mode != RX_SRCH_MORE && search_mode != RX_SRCH_DBLS 
                    && stack[sps].cpy.glue == 1 )                  // is glue
               || ( search_mode == RX_SRCH_LESS
                    && stack[sps].cpy.prefix.bits == prefix->bits )// too deep
               )
           ) {
      
      if( ER_is_traced( FAC_RX, ASP_RX_SRCH_DET)) {
        rx_nod_print( & stack[sps].cpy , buf, IP_PREFSTR_MAX);
        ER_dbg_va(FAC_RX, ASP_RX_SRCH_DET,
                  "rx_nod_search: peeling off %d: %s", sps, buf);
      }
      sps--;
    }
  }
  
  // nothing left on the stack. Sorry.
  // we allow that for more spec search -- this means
  // that the search term is a shorter prefix than the one
  // in the top node. Possibly it's 0/0 which is valid for more spec search.

  if( search_mode != RX_SRCH_MORE && search_mode != RX_SRCH_DBLS 
      && sps < 0 ) {       
    return RX_OK;
  }
      
  switch(search_mode) {
  case RX_SRCH_EXACT:
  case RX_SRCH_CREAT:
    // go up the tree (stack) and exit when the proper prefix is found.
    // For RX_SRCH_EXACT skip glue nodes, for RX_SRCH_CREAT take all.
    // They may contain a valid prefix, so watch out.

    while(sps >= 0) {

      if( ER_is_traced( FAC_RX, ASP_RX_SRCH_DET)) {
        rx_nod_print(& stack[sps].cpy, buf, 1024);
        ER_dbg_va(FAC_RX, ASP_RX_SRCH_DET,
                  "rx_nod_search: position %d: %s", sps, buf);
      }
      
      if ( search_mode == RX_SRCH_EXACT 
           && stack[sps].cpy.glue ) {
        die;
      }
      
      if ( memcmp( & stack[sps].cpy.prefix, 
                   prefix, 
                   sizeof(ip_prefix_t)) == 0 ) {
        // FOUND!!
        // add to the nodlist.

        if( (err=rx_nod_append( nodlist, & stack[sps])) != RX_OK ) {
          return err;
        }

        ER_dbg_va(FAC_RX, ASP_RX_SRCH_BOT, "rx_nod_search: found!");
        break;
      }
      sps--;
    }
    break;

  case RX_SRCH_EXLESS:
    // just fetch the last element off the stack (if any). 
    // Must be non-glue for EXLESS.

    if( sps >= 0 ) {
      rx_nod_append( nodlist, & stack[sps]); 
    }

    // else : nothing found.
    // For EXLESS: check if the stack contains only non-glue nodes.
    // If it contains a glue, it means it was created in the CREAT mode,
    // which renders the above algorithm absolutely useless. Then crash,
    // this is a programmer's error.

    while( sps >= 0 ) {
      if( stack[sps].cpy.glue ) {
        die;
      }
      sps--;
    }

    break;

  case RX_SRCH_LESS:
    while( sps >= 0 && depthcounter < par_a ) {
      if( stack[sps].cpy.glue == 0 ) {
        rx_nod_append( nodlist, & stack[sps]); 
        depthcounter++;
      }
      sps--;
    }
    break;

  case RX_SRCH_MORE:
  case RX_SRCH_DBLS:   // special (debug?) mode : find nodes with multiple
                       // data leaves. Much like more specific, except that
                       // most nodes will be skipped.
                       // The difference is in calling another hook function
    hook_function = ( search_mode == RX_SRCH_MORE )  
      ? rx_walk_hook_addnode
      : rx_walk_hook_adddoubles;
    
    // the result of a more spec search should NOT contain the object exactly
    // matching the query, even if it exists in the database. So two walks are 
    // performed, one for each child (if it exists). 
    // MEMORY IMPLEMENTATION ONLY FOR THE MOMENT

    // COVER THE CASE 0.0.0.0/0 
    // or any other prefix that the tree might be set to represent,
    // but there is no actual object for it (not even glue)

    if( sps < 0 )  {                  // start from the top node if contained 
      if( prefix->bits >= tree->prefix.bits ) {
        
        rx_walk_tree( tree->top_ptr, hook_function,
                      RX_WALK_REVERS | RX_WALK_SKPGLU, // skip glue nodes while counting
                      par_a, // display this many levels 
                      0, 0, &datstr, &err);
        if( err != RX_OK ) {
          return err;
        }
      }
    }            
    else { 
      for( i = 1; i >= 0; i--) {
        if( stack[sps].cpy.child_ptr[i] != NULL ) {
          if( ER_is_traced( FAC_RX, ASP_RX_SRCH_DET)) {
            IP_pref_b2a(& stack[sps].cpy.child_ptr[i]->prefix, buf, 1023);
          }

          if( 0 == IP_addr_cmp( & stack[sps].cpy.child_ptr[i]->prefix.ip, 
                                & prefix->ip, 
                                prefix->bits) ) {
            
            ER_dbg_va(FAC_RX, ASP_RX_SRCH_DET,
                      "rx_nod_search: digging child %d: %s", i, buf);
            
            rx_walk_tree( stack[sps].cpy.child_ptr[i],  hook_function, 
                          RX_WALK_REVERS | RX_WALK_SKPGLU, // skip glue nodes while counting
                          par_a, // display this many levels 
                          0, 0, &datstr, &err);
            if( err != RX_OK ) {
              return err;
            }
          }
          else {
            ER_dbg_va(FAC_RX, ASP_RX_SRCH_DET,
                      "rx_nod_search: prefix mismatch with child %d: %s", 
                      i, buf);
          }
        }
      }
    }
    break;

  case RX_SRCH_RANG:
    /* OK, start from the node at the end of the stack (exless match including
       glue nodes) then

       
       if its prefix length is 
         longer -> OK, it stopped too far, go up
         OK -> found! descend from here as long as the prefixes are in range
         shorter -> apparently there is even no such glue node. come back down
                    one step
                   
    */
    
    i = sps;               // go up the tree (down the stack) 
                           // until too far (one node too much, after >= )
    while( i >= 0 && stack[i].cpy.prefix.bits >= prefix->bits ) {
      i--;
    }
    
    // look where you are:
    
    if( i < 0 )          // it was the top object, but its prefix was too long
      i=0;               // take the top object as the base
    else
      i++;               // went one too much, now come back one step
    
    
    rx_walk_tree( stack[i].srcptr,  rx_walk_hook_addnode, 
                  RX_WALK_PRFLEN, // skip glue nodes while counting
                  par_a, // display up to this max length
                  0, 0, &datstr, &err);
    if( err != RX_OK ) {
      return err;
    }
    
    break;    

    // return RX_NOYETI;
    //not implemented
    //    die; 
  default:
    die; // are you nuts??
  }

  return err;

}



/*****************************************************************************/
/*+
   this is a specialised find function to find nodes in the list of 
   answer structs that point to the given data leaf.
   This is used to avoid reporting data leaves more than once
   (eg. because the node is composed (inetnum) 
   
+*/

static
GList *
rx_find_leaf(GList *anslist, rx_dataleaf_t *leafptr)
{
  GList *item;
  
  for(item = g_list_first(anslist);
      item != NULL;
      item = g_list_next(item)) {
    if( ((rx_datref_t *)(item->data))->leafptr == leafptr) {
      return item;
    }
  }
  
  return NULL; 
}

/*****************************************************************************/
/*+++++++++++++
  builds a stack for this prefix, finds *nodes* in the stack 
  and appends *copies of the data leaves* to the LL of answers;
  
  sorts by SQL object keys and uniq's the data
  
  finds:
  0 or 1 nodes for exact search
  0 or 1 nodes for exless (0 if no less specific node found)
  any number (incl. 0) for {more|less}-n specific 
  
  then copies the nodes/dataleaves to the answer structs and appends them
  to the given LL. So, effectively, the number of answers can be
  anything from 0 to infinity, because objects may be duplicate 
  even at the same node.
  
  returns errcode.
  
  algorithm:
  
  builds stack[MAXBIT (==128)];
  
  if( more/less-depth && par_a == 0)
  
  run rx_nod_search, then 
  
  if(more spec) rx_nod_walk(maxdepth=n, append_to_LL() );
  if(less spec) do { append(LL, stack[i]) } while(i-- && n--);
  otherwise just set LL
  
  
  The routine provides _at_least_ max_count answers. 
  It will *try* to stop after max_count as soon as possible 
  - but it's the higher level routine that should do the final cut.
+++++++++++++++*/

er_ret_t
RX_bin_search (
               rx_srch_mt  search_mode,
               int         par_a,
               int         par_b,
               rx_tree_t  *tree,           // tree ptr
               ip_prefix_t *prefix,         // binary prefix
               GList       **datleaves,    // data leaves go here
               int         max_count 
               )
   
{
  char buf[256];
  rx_nodcpy_t  stack[128];
  int i, k;
  int stkcnt, resnum = 0, maxleaves;
  GList  *nodlist = NULL, *nitem;
  GList  *complist = NULL;
  rx_node_t *curnode;
  rx_nodcpy_t *curcpy;
  rx_datref_t *datref;
  rx_stk_mt     dmode;

  // more specific node search may start from a glue node, 
  // for all others the stack should not contain glues.

  dmode = ( search_mode == RX_SRCH_MORE 
            || search_mode == RX_SRCH_DBLS
            || search_mode == RX_SRCH_RANG ) 
    ? RX_STK_QUERY_ALLNOD
    : RX_STK_QUERY_NOGLUE;
  
  rx_build_stack(stack, &stkcnt, tree, prefix, dmode);

  rx_nod_search( search_mode, par_a, par_b, tree, prefix, 
                 stack, stkcnt, &nodlist, 1000);
  
  ER_dbg_va(FAC_RX, ASP_RX_SRCH_BOT, "RX_bin_search: processing nodes");

  for( nitem = g_list_first(nodlist);
       nitem != NULL;
       nitem = g_list_next(nitem)) {    
    
    resnum++;
    curcpy = nitem->data;
    
    /*
      if memory mode includes RAM:
      * do not expect copies of nodes in the list received from bin_search.
      * iterate through data leaves with g_list_nth_data.
      */
    
    curnode = curcpy->srcptr;
    
    //    rx_nod_print( curnode, buf, 1024 );
    
    maxleaves = g_list_length(curnode->leaves_ptr);
    //    fprintf(stderr,"###node %d, %d dataleaves attached:", i, maxleaves);

    // iterate through dataleafs attached to this node
    for(k=0; k<maxleaves; k++) {
      rx_dataleaf_t *leafptr = g_list_nth_data(curnode->leaves_ptr, k);

      // check the conditions to add the leaf:

      // 1. never add the same leaf twice (can occur for composed inetnums)
      // 2. never add composed inetnum for exact prefix search
      //    (but do for exact range search...) - must be solved in upper layer.

      // add only if not yet on the list, i.e if it's composed then check,
      // otherwise just add
      if( tree->family == RX_FAM_IN && leafptr->composed == 1 ) {
        GList *item;
        int already_there = 0;
        
        for(item = g_list_first(complist);
            item != NULL;
            item = g_list_next(item)) {
          if( item->data == leafptr ) {
            already_there = 1;
          }
        }
        
        if( already_there == 1 ) {
          continue;
        }
        else {
          g_list_prepend(complist, leafptr);
          // and now safely add the answer
        }
      }
      
      if( wr_calloc( (void **) &datref, sizeof(rx_datref_t), 1) != UT_OK) {
        die;
      }
      datref->leafptr = leafptr;
      
      *datleaves = g_list_prepend(*datleaves, datref);
    }
  }

  g_list_foreach(nodlist, rx_free_list_element, NULL);
  g_list_free(nodlist);

  // the payload of complist are pointers or (cast) integers included 
  // in the list elements. no need to free them, just the list
  g_list_free(complist);

  ER_dbg_va(FAC_RX, ASP_RX_SRCH_BOT,
            "RX_bin_search: found %d nodes", resnum);
    
  
  /* the LL of answers (*datleaves) contains pointers to answer structs, 
     that SHOULD BE NORMALIZED HERE (==with no redundant entries)
  */

return RX_OK;
}

/**************************************************************************/
/*+++++++++++
    this routine goes through the list of prefixes and performs a bin_search
   on each of them; attaches the results to datlist.
   Then, frees the prefix list.
+++++++++++*/
static
er_ret_t
rx_preflist_search (
                    rx_srch_mt search_mode, 
                    int par_a,
                    int par_b,
                    rx_tree_t  *mytree,
                    GList    **preflist,
                    GList    **datlist
                    )

{ 
  char   prefstr[IP_PREFSTR_MAX];
  GList   *qitem;
  ip_prefix_t *querypref;
  er_ret_t err;
  
  for( qitem = g_list_first(*preflist);
       qitem != NULL;
       qitem = g_list_next(qitem)) {
    
    querypref = qitem->data;
    
    if( IP_pref_b2a( querypref, prefstr, IP_PREFSTR_MAX) != IP_OK ) {
      die;
    }
    ER_dbg_va(FAC_RX, ASP_RX_SRCH_BOT,
              "rx_preflist_search: mode %d (par %d) for %s", 
              search_mode, par_a, prefstr);
    
    if (mytree->num_nodes > 0) {
      err = RX_bin_search( search_mode, par_a, par_b, mytree, querypref, 
                   datlist, RX_ANS_ALL);
      if( err != RX_OK ) {
        return err;
      }
    }
  }
  
  g_list_foreach(*preflist, rx_free_list_element, NULL);
  g_list_free(*preflist);
  *preflist = NULL;
  return RX_OK;
}

  
/**************************************************************************/
/*+++++++++++++++
  translates a query into a binary prefix (or prefixes, if range).
  for registry+space (or if they are zero, for all
  registries/spaces)
  finds tree 
  calls RX_bin_search (returning node copies).
  will not put duplicate entries (composed inetnums).
  returns some sort of error code :-) 
  
  Cuts the number of answers from RX_bin_search down to max_count,
  but since some of the answers may have been "normalized" in the
  underlying functions (multiple occurences removed), 
  the result is _at_most_ max_count.
  
  appends to a given list of data blocks (not nodes!)
  
  returns RX_OK or a code from an underlying function
++++++++++++*/

er_ret_t
RX_asc_search ( 
               rx_srch_mt search_mode, 
               /*+ MODE={exact|exless|less-depth|more-depth|more-bit}
                 exless == our default search (exact or less-1 spec.)
                 more-bit == more specific bit length searches:
                 inclusive, exclusive, bit length, range of bit lengths...
                 All of them can be expressed using a range of lengths.
                 That means TWO integer parameters must be also given.
                 +*/
               int par_a,
               int par_b,
               /*+ the semantic of these parameters (par_a,par_b) is:
                  - for more-bit: the length range [preflen - par_a], 
                  - for less/more-depth: par_a is the depth
                  (1-2-3... means so many levels,
                  RX_ALL_DEPTHS means all levels)
                  - for exact/exless: both are ignored.

                  par_b is ignored, it's there for historical/future reasons
               +*/
               
               char *key,          /*+ search term: (string) prefix/range/IP +*/
               int   reg_id,
               ip_space_t spc_id,  /*+ space id, one of IPv4 IPv6. +*/
               rx_fam_t   fam_id,  /*+ RX_FAM_RT or RX_FAM_IN +*/
               GList **anslist,    /*+ answers go here, please +*/
               int    max_count    /*+ max # of answers. RX_ALLANS == unlimited +*/
               )
     
{
  GList    *datlist = NULL, *preflist = NULL, *ditem;
  ip_prefix_t testpref;
  char   prefstr[IP_PREFSTR_MAX];
  int prefcount = 0;
  rx_tree_t  *mytree;
  er_ret_t err; 
  ip_range_t myrang;
  int is_exless_inetnum = 0, is_exact_range_inetnum = 0;
  ip_rangesize_t  min_span, span;

 
  ER_dbg_va(FAC_RX, ASP_RX_SRCH_BOT,
            "rx_asc_search: mode %d (par %d) for %s", 
            search_mode, par_a, key);
  
  if( (err = RX_get_tree ( &mytree, reg_id, spc_id, fam_id)) != RX_OK ) {
    return err;
  }  
  
  /* 
     the behaviour for a default inetnum (range) query is: 
       do an exact match; 
       if it fails, do an exless match on the encompassing prefix
     for routes(prefixes):
       do an exless match
     
     So if it's the default search mode on an inetnum tree,
     and the key is a range, 
     then an exact search is performed on one of the composing prefixes.

     Then the resulting data leaves are checked for exact matching with 
     the range queried for.
     Any dataleaves that do not match are discarded, and if none are left,
     the procedure falls back to searching for the encompassing prefix.
     (calculated in the smart_conv routine).

     Whatever way, the EXLESS search on inetnum tree should
     return the shortest range that was found. 
     (or range*s* if of equal length). That is done at the end.
  */
  
  /* EXACT search of a route tree for a composed range makes no sense */

  if( fam_id == RX_FAM_RT && search_mode == RX_SRCH_EXACT 
      && IP_rang_a2b(&myrang, key) == IP_OK ) {
    IP_rang_decomp(&myrang, &preflist);
    prefcount = g_list_length(preflist);

    if( prefcount > 1 ) {
      // abort search
      return RX_OK;
    }
  }
  
  if( fam_id == RX_FAM_IN ) {
    if (search_mode == RX_SRCH_EXLESS ) { 
      /* save a flag for later, will be needed when processing the results */
      is_exless_inetnum = 1;
    }
    
    if( (search_mode == RX_SRCH_EXLESS || search_mode == RX_SRCH_EXACT) 
        && IP_rang_a2b(&myrang, key) == IP_OK ) {
      /* 
         perform just one exact search on one of the composing prefixes
         - the object must be found if it's in the database 
      */
      
      if (search_mode == RX_SRCH_EXACT ) { 
        /* save a flag for later, will be needed when processing the results */
        is_exact_range_inetnum = 1;
      }
      
      IP_rang_decomp(&myrang, &preflist);
      
      ER_dbg_va(FAC_RX, ASP_RX_SRCH_BOT,
                "rx_asc_search: quick decomp exact search");
      
      err = RX_bin_search( RX_SRCH_EXACT, 0, 0, mytree, 
                           preflist->data,
                           &datlist, RX_ANS_ALL);
      if( err != RX_OK ) {
        return err;
      }
      
      g_list_foreach(preflist, rx_free_list_element, NULL);
      g_list_free(preflist);
      preflist = NULL;
      
      /* now check the results */
      
      ditem = g_list_first(datlist);
      while( ditem != NULL ) {
        rx_dataleaf_t *lptr = ( (rx_datref_t *)ditem->data)->leafptr;
        
        if( memcmp(&lptr->iprange, &myrang, sizeof(ip_range_t)) == 0) {
          // found! leave it, remove others
          ditem = g_list_next(ditem);
        }
        else {

          ER_dbg_va(FAC_RX, ASP_RX_SRCH_DET,
                    "rx_asc_search: ex/rang/in: range mismatch, discarded");

          // mismatch. remove that one and start over (Aarggh!)
          datlist = g_list_remove(datlist, ditem->data);
          wr_free(ditem->data);
          ditem = g_list_first(datlist);
        }
      }
    }
  }

  /* now let's see if anything is left */
  
  if( g_list_length(datlist) > 0 ) {

    /* YES! we hit an inetnum object. */    
    ER_dbg_va(FAC_RX, ASP_RX_SRCH_BOT,
              "rx_asc_search: exact match found for %s", key);

    // do not treat it as exless anymore, it was exact and matched.
    is_exless_inetnum = 0;
  }
  else if( is_exact_range_inetnum != 1 ) {

    /* nothing found.

       Either it's not a range (and so wasn't even tried)
       or the mode was different from EXLESS and EXACT.
       Any way, we will now perform the search on anything that 
       the query can be converted to.

    */

    if( ( err = IP_smart_conv(key, 0, (search_mode == RX_SRCH_EXLESS),
                              &preflist, IP_EXPN)) != IP_OK ) {
      return err;
    }
    
    prefcount = g_list_length(preflist);
    
    ER_dbg_va(FAC_RX, ASP_RX_SRCH_BOT,
              "rx_asc_search:  query translated into %d prefix subqueries", 
              prefcount);

    // if there is only one prefix, store it for possible use later

    testpref = *( (ip_prefix_t *) g_list_first(preflist) -> data);
    
    // now go through the list of prefixes and perform the searches
    // rx_preflist_search deallocates the prefixes after use.
    
    err = rx_preflist_search (search_mode, par_a, par_b, 
                              mytree, &preflist, &datlist);
    if( err != RX_OK ) {
      return err;
    }
  }

  // find the smallest range span

  if( is_exless_inetnum == 1 ) { 
    min_span = 0xffffffff;

    // go through the list and find the shortest range.    
    for(ditem = g_list_first(datlist);
        ditem != NULL;
        ditem = g_list_next(ditem)) {
      rx_datref_t *refptr = (rx_datref_t *) (ditem->data);

      span = IP_rang_span(refptr->leafptr->iprange);
      
      if( span < min_span ) {
        min_span = span;
      }
    }

    ER_dbg_va(FAC_RX, ASP_RX_SRCH_DET,
              "rx_asc_search: minimal span is %d", min_span);
  }
  
  // Add the dataleaf copies to the list of answers.
  
  for(ditem = g_list_first(datlist);
      ditem != NULL;
      ditem = g_list_next(ditem)) {
    er_ret_t err;
    rx_datref_t *refptr = (rx_datref_t *) (ditem->data);
    rx_datcpy_t *datcpy;
    void *dataptr;
    
    // For exless_inet_range search, discard all 
    // except the one(s) of the smallest size.    
    if( is_exless_inetnum == 1 
        && (span = IP_rang_span(refptr->leafptr->iprange)) != min_span ) {

      ER_dbg_va(FAC_RX, ASP_RX_SRCH_DET,
                "rx_asc_search: discarded object with span %d", span);
      continue;
    }
    
    // if this is an EXACT search on inetnums specified with a prefix,
    // (i.e., one prefix was returned from the conversion)
    // then check if the range in the object is equivalent to that prefix

    if( search_mode == RX_SRCH_EXACT 
        && fam_id == RX_FAM_IN 
        && prefcount == 1 ) {
      ip_range_t  testrang;

      IP_pref_2_rang( &testrang, &testpref );
      
      if( memcmp( & refptr->leafptr->iprange, 
                  &testrang, sizeof(ip_range_t)) != 0) {

        ER_dbg_va(FAC_RX, ASP_RX_SRCH_DET,
                  "rx_asc_search: discarded an object from exact/inetnum/prefix search");

        continue;
      }
    }
    

    // OK, so we ACCEPT this result. Copy it.

    if( (err=wr_calloc( (void **)& datcpy, 1, sizeof(rx_datcpy_t))) != UT_OK) {
      return err; //    die;
    }
    
    datcpy->leafcpy = *(refptr->leafptr);
    
    // copy the immediate data too. Set the ptr.
    
    if( (err=wr_calloc( (void **) & dataptr, 1, refptr->leafptr->data_len)) 
        != UT_OK) {
      return err; //    die;
    }
    memcpy(dataptr, refptr->leafptr->data_ptr, refptr->leafptr->data_len);
    
    datcpy->leafcpy.data_ptr = dataptr;
    
    *anslist = g_list_prepend(*anslist, datcpy);
  }    
  
 g_list_foreach(datlist, rx_free_list_element, NULL);
 g_list_free(datlist);
 
 return RX_OK;

}

