Models accepting elements not declared explicitly

Some time ago, Dominique in one of his blog posts described an idea of models accepting unknown parameters. Instead of declaring each element separately, we could use such construct:

'Sshd::MatchElement' => {
accept => {
match_not =>  '/Match/i' ,
type => 'leaf',
value_type => 'string'
}
}


Since today morning, I was working on this feature and it is now available for testing, in slightly modified version. With my modifications, you can write a model like this:

[
          {
            'read_config' => [
                               {
                                 'file' => 'kismet.conf',
                                 'backend' => 'ComplexIni',
                                 'config_dir' => '/home/krzych/configmodel/test'
                               }
                             ],
            'name' => 'Kismet',
            'accept' => [
                           {
                            'match' => 'alert|defaultchannels',
                            'type' => 'list',
                            'cargo' => {
                                          'value_type' => 'string',
                                          'type' => 'leaf'
                                     },
                        },
                            {
                            'match' => '.*',
                            'type' => 'leaf',
                            'value_type' => 'string'
                        }
                        ],
            'element' => [
                           'version',
                           {
                             'value_type' => 'uniline',
                             'summary' => 'Version string',
                             'mandatory' => 1,
                             'type' => 'leaf'
                           },
                           'servername',
                           {
                             'value_type' => 'uniline',
                             'summary' => 'Server name',
                             'mandatory' => 1,
                             'type' => 'leaf',
                             'description' => 'Specifies the server name'
                           },
                           'networkmanagersleep',
                           {
                             'value_type' => 'enum',
                             'summary' => 'Try to put NM to sleep when launching Kismet',
                             'type' => 'leaf',
                             'description' => 'This feature requires  DBus support!',
                             'choice' => [
                                           'true',
                                           'false'
                                         ]
                           },
                           'source',
                           {
                             'cargo' => {
                                          'value_type' => 'string',
                                          'type' => 'leaf'
                                        },
                             'type' => 'list'
                           }
                         ]
          }
];

I decided to make accept parameter similar to element – so user can declare more than one rule. Also, the match field behaves in the same way like in element – instead of writing ‚/channel/’ one has to write ‚channel’. This may be perceived as a limitation, but I wanted to be consistent with existing code.
In this example, there are two accept rules. The first defines that all elements with ‚alert’ or ‚defaultchannels’ in name will be treated as lists – second one specifies that all remaining elements are to be treated like strings. Notice, that the element parameter is still preset – it has precedence over accept. So, the model will parse all items defined in element and then try to load the rest using accept.
Now you can use this model to edit regular Kismet configuration file:

Editing kismet config file
Editing kismet config file

As you may see, this model has a small problem – there are config variables incorrectly recognized as lists (e.g. alertbacklog). It’s easy to fix, one only needs to modify regexp in accept declaration. I made this mistake on purpose – to show that one needs to define regexps carefully (or maybe it would be good idea to autodetect a type?)

How it works?

The most important part of this feature lies in Node.pm file:

    #Load elements matched by accept parameter
    if(defined $self->{model}{accept}){
        foreach my $acc (@{$self->{model}{accept}}){
            my $mr = eval {qr/$acc->{match}/; };
            my $nvh = dclone $acc;
            delete $nvh->{match};
            #Now, $h contains all elements not yet parsed
            foreach my $elt (keys %$h){
                if ($elt =~ $mr){
                    #add element...
                    $self->{model}{element}{$elt} = $nvh;
                    $self->create_element($elt);
                    #add to element list...
                    push @{$self->{model}{element_list}}, $elt;
                    $self->{model}{experience}{$elt} = 'beginner';
                    $self->{model}{summary}{$elt} = '';
                    $self->{model}{level}{$elt} = 'normal';
                    $self->{model}{description}{$elt} = '';
                    $self->{model}{status}{$elt} = 'standard';
                    #Setup 'original' values. These are extracted by reset_element_property.
                    #Without these two lines, aforementioned method would set experience and level for
                    #elements added here to 'undef' - rendering model uneditable.
                    $self->{config_model}{model}{$self->config_class_name}{level}{$elt} = 'normal';
                    $self->{config_model}{model}{$self->config_class_name}{experience}{$elt} = 'beginner';
                    #load value
                    #TODO: annotations
                    my $obj = $self->fetch_element($elt,'master', not $check) ;
                    $obj ->load_data(delete $h->{$elt}) ;
                }
            }
        }
    }

As you can see, accept rules are parsed one by one – when a match is found, element is added to the model and data is then loaded. Since this is a fresh code written today, it may has a lot of problems, but is a good base to extend this feature.
P.S. Dominique, I knew that I should make some testcases and then ask you abut further work, but I couldn’t resist the urge to code 🙂

One thought on “Models accepting elements not declared explicitly

  1. Very good start 🙂
    Here are a few nitpicks:
    – eval is evil (well, most often). „my $mr = eval {qr/$acc->{match}/; };” is better written as „my $mr = qw/$acc->{match}/ ;. You may also want to match the *whole* parameter coming from the file with „my $mr = qw/^$acc->{match}$/ ;”
    – avoid using too many variables with acronyms or cryptic names except in very short blocks. Make sure that the acronym is very obvious to understand. (I also use this trick and often regret it several months later)
    – line 1351-1355, you may want to fill these values with ||= to avoid clobbering values coming from the model
    All the best
    PS: lol 😀

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *