xref: /freebsd/crypto/openssl/util/perl/OpenSSL/Ordinals.pm (revision e0c4386e7e71d93b0edc0c8fa156263fc4a8b0b6)
1*e0c4386eSCy Schubert#! /usr/bin/env perl
2*e0c4386eSCy Schubert# Copyright 2018-2023 The OpenSSL Project Authors. All Rights Reserved.
3*e0c4386eSCy Schubert#
4*e0c4386eSCy Schubert# Licensed under the Apache License 2.0 (the "License").  You may not use
5*e0c4386eSCy Schubert# this file except in compliance with the License.  You can obtain a copy
6*e0c4386eSCy Schubert# in the file LICENSE in the source distribution or at
7*e0c4386eSCy Schubert# https://www.openssl.org/source/license.html
8*e0c4386eSCy Schubert
9*e0c4386eSCy Schubertpackage OpenSSL::Ordinals;
10*e0c4386eSCy Schubert
11*e0c4386eSCy Schubertuse strict;
12*e0c4386eSCy Schubertuse warnings;
13*e0c4386eSCy Schubertuse Carp;
14*e0c4386eSCy Schubertuse Scalar::Util qw(blessed);
15*e0c4386eSCy Schubertuse OpenSSL::Util;
16*e0c4386eSCy Schubert
17*e0c4386eSCy Schubertuse constant {
18*e0c4386eSCy Schubert    # "magic" filters, see the filters at the end of the file
19*e0c4386eSCy Schubert    F_NAME      => 1,
20*e0c4386eSCy Schubert    F_NUMBER    => 2,
21*e0c4386eSCy Schubert};
22*e0c4386eSCy Schubert
23*e0c4386eSCy Schubert=head1 NAME
24*e0c4386eSCy Schubert
25*e0c4386eSCy SchubertOpenSSL::Ordinals - a private module to read and walk through ordinals
26*e0c4386eSCy Schubert
27*e0c4386eSCy Schubert=head1 SYNOPSIS
28*e0c4386eSCy Schubert
29*e0c4386eSCy Schubert  use OpenSSL::Ordinals;
30*e0c4386eSCy Schubert
31*e0c4386eSCy Schubert  my $ordinals = OpenSSL::Ordinals->new(from => "foo.num");
32*e0c4386eSCy Schubert  # or alternatively
33*e0c4386eSCy Schubert  my $ordinals = OpenSSL::Ordinals->new();
34*e0c4386eSCy Schubert  $ordinals->load("foo.num");
35*e0c4386eSCy Schubert
36*e0c4386eSCy Schubert  foreach ($ordinals->items(comparator => by_name()) {
37*e0c4386eSCy Schubert    print $_->name(), "\n";
38*e0c4386eSCy Schubert  }
39*e0c4386eSCy Schubert
40*e0c4386eSCy Schubert=head1 DESCRIPTION
41*e0c4386eSCy Schubert
42*e0c4386eSCy SchubertThis is a OpenSSL private module to load an ordinals (F<.num>) file and
43*e0c4386eSCy Schubertwrite out the data you want, sorted and filtered according to your rules.
44*e0c4386eSCy Schubert
45*e0c4386eSCy SchubertAn ordinals file is a file that enumerates all the symbols that a shared
46*e0c4386eSCy Schubertlibrary or loadable module must export.  Each of them have a unique
47*e0c4386eSCy Schubertassigned number as well as other attributes to indicate if they only exist
48*e0c4386eSCy Schuberton a subset of the supported platforms, or if they are specific to certain
49*e0c4386eSCy Schubertfeatures.
50*e0c4386eSCy Schubert
51*e0c4386eSCy SchubertThe unique numbers each symbol gets assigned needs to be maintained for a
52*e0c4386eSCy Schubertshared library or module to stay compatible with previous versions on
53*e0c4386eSCy Schubertplatforms that maintain a transfer vector indexed by position rather than
54*e0c4386eSCy Schubertby name.  They also help keep information on certain symbols that are
55*e0c4386eSCy Schubertaliases for others for certain platforms, or that have different forms
56*e0c4386eSCy Schuberton different platforms.
57*e0c4386eSCy Schubert
58*e0c4386eSCy Schubert=head2 Main methods
59*e0c4386eSCy Schubert
60*e0c4386eSCy Schubert=over  4
61*e0c4386eSCy Schubert
62*e0c4386eSCy Schubert=cut
63*e0c4386eSCy Schubert
64*e0c4386eSCy Schubert=item B<new> I<%options>
65*e0c4386eSCy Schubert
66*e0c4386eSCy SchubertCreates a new instance of the C<OpenSSL::Ordinals> class.  It takes options
67*e0c4386eSCy Schubertin keyed pair form, i.e. a series of C<< key => value >> pairs.  Available
68*e0c4386eSCy Schubertoptions are:
69*e0c4386eSCy Schubert
70*e0c4386eSCy Schubert=over 4
71*e0c4386eSCy Schubert
72*e0c4386eSCy Schubert=item B<< from => FILENAME >>
73*e0c4386eSCy Schubert
74*e0c4386eSCy SchubertNot only create a new instance, but immediately load it with data from the
75*e0c4386eSCy Schubertordinals file FILENAME.
76*e0c4386eSCy Schubert
77*e0c4386eSCy Schubert=back
78*e0c4386eSCy Schubert
79*e0c4386eSCy Schubert=cut
80*e0c4386eSCy Schubert
81*e0c4386eSCy Schubertsub new {
82*e0c4386eSCy Schubert    my $class = shift;
83*e0c4386eSCy Schubert    my %opts = @_;
84*e0c4386eSCy Schubert
85*e0c4386eSCy Schubert    my $instance = {
86*e0c4386eSCy Schubert        filename        => undef, # File name registered when loading
87*e0c4386eSCy Schubert        loaded_maxnum   => 0,     # Highest allocated item number when loading
88*e0c4386eSCy Schubert        loaded_contents => [],    # Loaded items, if loading there was
89*e0c4386eSCy Schubert        maxassigned     => 0,     # Current highest assigned item number
90*e0c4386eSCy Schubert        maxnum          => 0,     # Current highest allocated item number
91*e0c4386eSCy Schubert        contents        => [],    # Items, indexed by number
92*e0c4386eSCy Schubert        name2num        => {},    # Name to number dictionary
93*e0c4386eSCy Schubert        aliases         => {},    # Aliases cache.
94*e0c4386eSCy Schubert        stats           => {},    # Statistics, see 'sub validate'
95*e0c4386eSCy Schubert        debug           => $opts{debug},
96*e0c4386eSCy Schubert    };
97*e0c4386eSCy Schubert    bless $instance, $class;
98*e0c4386eSCy Schubert
99*e0c4386eSCy Schubert    $instance->set_version($opts{version});
100*e0c4386eSCy Schubert    $instance->load($opts{from}) if defined($opts{from});
101*e0c4386eSCy Schubert
102*e0c4386eSCy Schubert    return $instance;
103*e0c4386eSCy Schubert}
104*e0c4386eSCy Schubert
105*e0c4386eSCy Schubert=item B<< $ordinals->load FILENAME >>
106*e0c4386eSCy Schubert
107*e0c4386eSCy SchubertLoads the data from FILENAME into the instance.  Any previously loaded data
108*e0c4386eSCy Schubertis dropped.
109*e0c4386eSCy Schubert
110*e0c4386eSCy SchubertTwo internal databases are created.  One database is simply a copy of the file
111*e0c4386eSCy Schubertcontents and is treated as read-only.  The other database is an exact copy of
112*e0c4386eSCy Schubertthe first, but is treated as a work database, i.e. it can be modified and added
113*e0c4386eSCy Schubertto.
114*e0c4386eSCy Schubert
115*e0c4386eSCy Schubert=cut
116*e0c4386eSCy Schubert
117*e0c4386eSCy Schubertsub load {
118*e0c4386eSCy Schubert    my $self = shift;
119*e0c4386eSCy Schubert    my $filename = shift;
120*e0c4386eSCy Schubert
121*e0c4386eSCy Schubert    croak "Undefined filename" unless defined($filename);
122*e0c4386eSCy Schubert
123*e0c4386eSCy Schubert    my @tmp_contents = ();
124*e0c4386eSCy Schubert    my %tmp_name2num = ();
125*e0c4386eSCy Schubert    my $max_assigned = 0;
126*e0c4386eSCy Schubert    my $max_num = 0;
127*e0c4386eSCy Schubert    open F, '<', $filename or croak "Unable to open $filename";
128*e0c4386eSCy Schubert    while (<F>) {
129*e0c4386eSCy Schubert        s|\R$||;                # Better chomp
130*e0c4386eSCy Schubert        s|#.*||;
131*e0c4386eSCy Schubert        next if /^\s*$/;
132*e0c4386eSCy Schubert
133*e0c4386eSCy Schubert        my $item = OpenSSL::Ordinals::Item->new(source => $filename, from => $_);
134*e0c4386eSCy Schubert
135*e0c4386eSCy Schubert        my $num = $item->number();
136*e0c4386eSCy Schubert        if ($num eq '?') {
137*e0c4386eSCy Schubert            $num = ++$max_num;
138*e0c4386eSCy Schubert        } elsif ($num eq '?+') {
139*e0c4386eSCy Schubert            $num = $max_num;
140*e0c4386eSCy Schubert        } else {
141*e0c4386eSCy Schubert            croak "Disordered ordinals, number sequence restarted"
142*e0c4386eSCy Schubert                if $max_num > $max_assigned && $num < $max_num;
143*e0c4386eSCy Schubert            croak "Disordered ordinals, $num < $max_num"
144*e0c4386eSCy Schubert                if $num < $max_num;
145*e0c4386eSCy Schubert            $max_assigned = $max_num = $num;
146*e0c4386eSCy Schubert        }
147*e0c4386eSCy Schubert
148*e0c4386eSCy Schubert        $item->intnum($num);
149*e0c4386eSCy Schubert        push @{$tmp_contents[$num]}, $item;
150*e0c4386eSCy Schubert        $tmp_name2num{$item->name()} = $num;
151*e0c4386eSCy Schubert    }
152*e0c4386eSCy Schubert    close F;
153*e0c4386eSCy Schubert
154*e0c4386eSCy Schubert    $self->{contents} = [ @tmp_contents ];
155*e0c4386eSCy Schubert    $self->{name2num} = { %tmp_name2num };
156*e0c4386eSCy Schubert    $self->{maxassigned} = $max_assigned;
157*e0c4386eSCy Schubert    $self->{maxnum} = $max_num;
158*e0c4386eSCy Schubert    $self->{filename} = $filename;
159*e0c4386eSCy Schubert
160*e0c4386eSCy Schubert    # Make a deep copy, allowing {contents} to be an independent work array
161*e0c4386eSCy Schubert    foreach my $i (1..$max_num) {
162*e0c4386eSCy Schubert        if ($tmp_contents[$i]) {
163*e0c4386eSCy Schubert            $self->{loaded_contents}->[$i] =
164*e0c4386eSCy Schubert                [ map { OpenSSL::Ordinals::Item->new($_) }
165*e0c4386eSCy Schubert                  @{$tmp_contents[$i]} ];
166*e0c4386eSCy Schubert        }
167*e0c4386eSCy Schubert    }
168*e0c4386eSCy Schubert    $self->{loaded_maxnum} = $max_num;
169*e0c4386eSCy Schubert    return 1;
170*e0c4386eSCy Schubert}
171*e0c4386eSCy Schubert
172*e0c4386eSCy Schubert=item B<< $ordinals->renumber >>
173*e0c4386eSCy Schubert
174*e0c4386eSCy SchubertRenumber any item that doesn't have an assigned number yet.
175*e0c4386eSCy Schubert
176*e0c4386eSCy Schubert=cut
177*e0c4386eSCy Schubert
178*e0c4386eSCy Schubertsub renumber {
179*e0c4386eSCy Schubert    my $self = shift;
180*e0c4386eSCy Schubert
181*e0c4386eSCy Schubert    my $max_assigned = 0;
182*e0c4386eSCy Schubert    foreach ($self->items(sort => by_number())) {
183*e0c4386eSCy Schubert        $_->number($_->intnum()) if $_->number() =~ m|^\?|;
184*e0c4386eSCy Schubert        if ($max_assigned < $_->number()) {
185*e0c4386eSCy Schubert            $max_assigned = $_->number();
186*e0c4386eSCy Schubert        }
187*e0c4386eSCy Schubert    }
188*e0c4386eSCy Schubert    $self->{maxassigned} = $max_assigned;
189*e0c4386eSCy Schubert}
190*e0c4386eSCy Schubert
191*e0c4386eSCy Schubert=item B<< $ordinals->rewrite >>
192*e0c4386eSCy Schubert
193*e0c4386eSCy Schubert=item B<< $ordinals->rewrite >>, I<%options>
194*e0c4386eSCy Schubert
195*e0c4386eSCy SchubertIf an ordinals file has been loaded, it gets rewritten with the data from
196*e0c4386eSCy Schubertthe current work database.
197*e0c4386eSCy Schubert
198*e0c4386eSCy SchubertIf there are more arguments, they are used as I<%options> with the
199*e0c4386eSCy Schubertsame semantics as for B<< $ordinals->items >> described below, apart
200*e0c4386eSCy Schubertfrom B<sort>, which is forbidden here.
201*e0c4386eSCy Schubert
202*e0c4386eSCy Schubert=cut
203*e0c4386eSCy Schubert
204*e0c4386eSCy Schubertsub rewrite {
205*e0c4386eSCy Schubert    my $self = shift;
206*e0c4386eSCy Schubert    my %opts = @_;
207*e0c4386eSCy Schubert
208*e0c4386eSCy Schubert    $self->write($self->{filename}, %opts);
209*e0c4386eSCy Schubert}
210*e0c4386eSCy Schubert
211*e0c4386eSCy Schubert=item B<< $ordinals->write FILENAME >>
212*e0c4386eSCy Schubert
213*e0c4386eSCy Schubert=item B<< $ordinals->write FILENAME >>, I<%options>
214*e0c4386eSCy Schubert
215*e0c4386eSCy SchubertWrites the current work database data to the ordinals file FILENAME.
216*e0c4386eSCy SchubertThis also validates the data, see B<< $ordinals->validate >> below.
217*e0c4386eSCy Schubert
218*e0c4386eSCy SchubertIf there are more arguments, they are used as I<%options> with the
219*e0c4386eSCy Schubertsame semantics as for B<< $ordinals->items >> described next, apart
220*e0c4386eSCy Schubertfrom B<sort>, which is forbidden here.
221*e0c4386eSCy Schubert
222*e0c4386eSCy Schubert=cut
223*e0c4386eSCy Schubert
224*e0c4386eSCy Schubertsub write {
225*e0c4386eSCy Schubert    my $self = shift;
226*e0c4386eSCy Schubert    my $filename = shift;
227*e0c4386eSCy Schubert    my %opts = @_;
228*e0c4386eSCy Schubert
229*e0c4386eSCy Schubert    croak "Undefined filename" unless defined($filename);
230*e0c4386eSCy Schubert    croak "The 'sort' option is not allowed" if $opts{sort};
231*e0c4386eSCy Schubert
232*e0c4386eSCy Schubert    $self->validate();
233*e0c4386eSCy Schubert
234*e0c4386eSCy Schubert    open F, '>', $filename or croak "Unable to open $filename";
235*e0c4386eSCy Schubert    foreach ($self->items(%opts, sort => by_number())) {
236*e0c4386eSCy Schubert        print F $_->to_string(),"\n";
237*e0c4386eSCy Schubert    }
238*e0c4386eSCy Schubert    close F;
239*e0c4386eSCy Schubert    $self->{filename} = $filename;
240*e0c4386eSCy Schubert    $self->{loaded_maxnum} = $self->{maxnum};
241*e0c4386eSCy Schubert    return 1;
242*e0c4386eSCy Schubert}
243*e0c4386eSCy Schubert
244*e0c4386eSCy Schubert=item B<< $ordinals->items >> I<%options>
245*e0c4386eSCy Schubert
246*e0c4386eSCy SchubertReturns a list of items according to a set of criteria.  The criteria is
247*e0c4386eSCy Schubertgiven in form keyed pair form, i.e. a series of C<< key => value >> pairs.
248*e0c4386eSCy SchubertAvailable options are:
249*e0c4386eSCy Schubert
250*e0c4386eSCy Schubert=over 4
251*e0c4386eSCy Schubert
252*e0c4386eSCy Schubert=item B<< sort => SORTFUNCTION >>
253*e0c4386eSCy Schubert
254*e0c4386eSCy SchubertSORTFUNCTION is a reference to a function that takes two arguments, which
255*e0c4386eSCy Schubertcorrespond to the classic C<$a> and C<$b> that are available in a C<sort>
256*e0c4386eSCy Schubertblock.
257*e0c4386eSCy Schubert
258*e0c4386eSCy Schubert=item B<< filter => FILTERFUNCTION >>
259*e0c4386eSCy Schubert
260*e0c4386eSCy SchubertFILTERFUNCTION is a reference to a function that takes one argument, which
261*e0c4386eSCy Schubertis every OpenSSL::Ordinals::Item element available.
262*e0c4386eSCy Schubert
263*e0c4386eSCy Schubert=back
264*e0c4386eSCy Schubert
265*e0c4386eSCy Schubert=cut
266*e0c4386eSCy Schubert
267*e0c4386eSCy Schubertsub items {
268*e0c4386eSCy Schubert    my $self = shift;
269*e0c4386eSCy Schubert    my %opts = @_;
270*e0c4386eSCy Schubert
271*e0c4386eSCy Schubert    my $comparator = $opts{sort};
272*e0c4386eSCy Schubert    my $filter = $opts{filter} // sub { 1; };
273*e0c4386eSCy Schubert
274*e0c4386eSCy Schubert    my @l = undef;
275*e0c4386eSCy Schubert    if (ref($filter) eq 'ARRAY') {
276*e0c4386eSCy Schubert        # run a "magic" filter
277*e0c4386eSCy Schubert        if    ($filter->[0] == F_NUMBER) {
278*e0c4386eSCy Schubert            my $index = $filter->[1];
279*e0c4386eSCy Schubert            @l = $index ? @{$self->{contents}->[$index] // []} : ();
280*e0c4386eSCy Schubert        } elsif ($filter->[0] == F_NAME) {
281*e0c4386eSCy Schubert            my $index = $self->{name2num}->{$filter->[1]};
282*e0c4386eSCy Schubert            @l = $index ? @{$self->{contents}->[$index] // []} : ();
283*e0c4386eSCy Schubert        } else {
284*e0c4386eSCy Schubert            croak __PACKAGE__."->items called with invalid filter";
285*e0c4386eSCy Schubert        }
286*e0c4386eSCy Schubert    } elsif (ref($filter) eq 'CODE') {
287*e0c4386eSCy Schubert        @l = grep { $filter->($_) }
288*e0c4386eSCy Schubert            map { @{$_ // []} }
289*e0c4386eSCy Schubert            @{$self->{contents}};
290*e0c4386eSCy Schubert    } else {
291*e0c4386eSCy Schubert        croak __PACKAGE__."->items called with invalid filter";
292*e0c4386eSCy Schubert    }
293*e0c4386eSCy Schubert
294*e0c4386eSCy Schubert    return sort { $comparator->($a, $b); } @l
295*e0c4386eSCy Schubert        if (defined $comparator);
296*e0c4386eSCy Schubert    return @l;
297*e0c4386eSCy Schubert}
298*e0c4386eSCy Schubert
299*e0c4386eSCy Schubert# Put an array of items back into the object after having checked consistency
300*e0c4386eSCy Schubert# If there are exactly two items:
301*e0c4386eSCy Schubert# - They MUST have the same number
302*e0c4386eSCy Schubert# - They MUST have the same version
303*e0c4386eSCy Schubert# - For platforms, both MUST hold the same ones, but with opposite values
304*e0c4386eSCy Schubert# - For features, both MUST hold the same ones.
305*e0c4386eSCy Schubert# - They MUST NOT have identical name, type, numeral, version, platforms, and features
306*e0c4386eSCy Schubert# If there's just one item, just put it in the slot of its number
307*e0c4386eSCy Schubert# In all other cases, something is wrong
308*e0c4386eSCy Schubertsub _putback {
309*e0c4386eSCy Schubert    my $self = shift;
310*e0c4386eSCy Schubert    my @items = @_;
311*e0c4386eSCy Schubert
312*e0c4386eSCy Schubert    if (scalar @items < 1 || scalar @items > 2) {
313*e0c4386eSCy Schubert        croak "Wrong number of items: ", scalar @items, "\n ",
314*e0c4386eSCy Schubert            join("\n ", map { $_->{source}.": ".$_->name() } @items), "\n";
315*e0c4386eSCy Schubert    }
316*e0c4386eSCy Schubert    if (scalar @items == 2) {
317*e0c4386eSCy Schubert        # Collect some data
318*e0c4386eSCy Schubert        my %numbers = ();
319*e0c4386eSCy Schubert        my %versions = ();
320*e0c4386eSCy Schubert        my %features = ();
321*e0c4386eSCy Schubert        foreach (@items) {
322*e0c4386eSCy Schubert            $numbers{$_->intnum()} = 1;
323*e0c4386eSCy Schubert            $versions{$_->version()} = 1;
324*e0c4386eSCy Schubert            foreach ($_->features()) {
325*e0c4386eSCy Schubert                $features{$_}++;
326*e0c4386eSCy Schubert            }
327*e0c4386eSCy Schubert        }
328*e0c4386eSCy Schubert
329*e0c4386eSCy Schubert        # Check that all items we're trying to put back have the same number
330*e0c4386eSCy Schubert        croak "Items don't have the same numeral: ",
331*e0c4386eSCy Schubert            join(", ", map { $_->name()." => ".$_->intnum() } @items), "\n"
332*e0c4386eSCy Schubert            if (scalar keys %numbers > 1);
333*e0c4386eSCy Schubert        croak "Items don't have the same version: ",
334*e0c4386eSCy Schubert            join(", ", map { $_->name()." => ".$_->version() } @items), "\n"
335*e0c4386eSCy Schubert            if (scalar keys %versions > 1);
336*e0c4386eSCy Schubert
337*e0c4386eSCy Schubert        # Check that both items run with the same features
338*e0c4386eSCy Schubert        foreach (@items) {
339*e0c4386eSCy Schubert        }
340*e0c4386eSCy Schubert        foreach (keys %features) {
341*e0c4386eSCy Schubert            delete $features{$_} if $features{$_} == 2;
342*e0c4386eSCy Schubert        }
343*e0c4386eSCy Schubert        croak "Features not in common between ",
344*e0c4386eSCy Schubert            $items[0]->name(), " and ", $items[1]->name(), ":",
345*e0c4386eSCy Schubert            join(", ", sort keys %features), "\n"
346*e0c4386eSCy Schubert            if %features;
347*e0c4386eSCy Schubert
348*e0c4386eSCy Schubert        # Check for in addition identical name, type, and platforms
349*e0c4386eSCy Schubert        croak "Duplicate entries for ".$items[0]->name()." from ".
350*e0c4386eSCy Schubert            $items[0]->source()." and ".$items[1]->source()."\n"
351*e0c4386eSCy Schubert            if $items[0]->name() eq $items[1]->name()
352*e0c4386eSCy Schubert            && $items[0]->type() eq $items[1]->type()
353*e0c4386eSCy Schubert            && $items[0]->platforms() eq $items[1]->platforms();
354*e0c4386eSCy Schubert
355*e0c4386eSCy Schubert        # Check that all platforms exist in both items, and have opposite values
356*e0c4386eSCy Schubert        my @platforms = ( { $items[0]->platforms() },
357*e0c4386eSCy Schubert                          { $items[1]->platforms() } );
358*e0c4386eSCy Schubert        foreach my $platform (keys %{$platforms[0]}) {
359*e0c4386eSCy Schubert            if (exists $platforms[1]->{$platform}) {
360*e0c4386eSCy Schubert                if ($platforms[0]->{$platform} != !$platforms[1]->{$platform}) {
361*e0c4386eSCy Schubert                    croak "Platforms aren't opposite: ",
362*e0c4386eSCy Schubert                        join(", ",
363*e0c4386eSCy Schubert                             map { my %tmp_h = $_->platforms();
364*e0c4386eSCy Schubert                                   $_->name().":".$platform
365*e0c4386eSCy Schubert                                       ." => "
366*e0c4386eSCy Schubert                                       .$tmp_h{$platform} } @items),
367*e0c4386eSCy Schubert                        "\n";
368*e0c4386eSCy Schubert                }
369*e0c4386eSCy Schubert
370*e0c4386eSCy Schubert                # We're done with these
371*e0c4386eSCy Schubert                delete $platforms[0]->{$platform};
372*e0c4386eSCy Schubert                delete $platforms[1]->{$platform};
373*e0c4386eSCy Schubert            }
374*e0c4386eSCy Schubert        }
375*e0c4386eSCy Schubert        # If there are any remaining platforms, something's wrong
376*e0c4386eSCy Schubert        if (%{$platforms[0]} || %{$platforms[0]}) {
377*e0c4386eSCy Schubert            croak "There are platforms not in common between ",
378*e0c4386eSCy Schubert                $items[0]->name(), " and ", $items[1]->name(), "\n";
379*e0c4386eSCy Schubert        }
380*e0c4386eSCy Schubert    }
381*e0c4386eSCy Schubert    $self->{contents}->[$items[0]->intnum()] = [ @items ];
382*e0c4386eSCy Schubert}
383*e0c4386eSCy Schubert
384*e0c4386eSCy Schubertsub _parse_platforms {
385*e0c4386eSCy Schubert    my $self = shift;
386*e0c4386eSCy Schubert    my @defs = @_;
387*e0c4386eSCy Schubert
388*e0c4386eSCy Schubert    my %platforms = ();
389*e0c4386eSCy Schubert    foreach (@defs) {
390*e0c4386eSCy Schubert        m{^(!)?};
391*e0c4386eSCy Schubert        my $op = !(defined $1 && $1 eq '!');
392*e0c4386eSCy Schubert        my $def = $';
393*e0c4386eSCy Schubert
394*e0c4386eSCy Schubert        if ($def =~ m{^_?WIN32$})                   { $platforms{$&} = $op; }
395*e0c4386eSCy Schubert        if ($def =~ m{^__FreeBSD__$})               { $platforms{$&} = $op; }
396*e0c4386eSCy Schubert# For future support
397*e0c4386eSCy Schubert#       if ($def =~ m{^__DragonFly__$})             { $platforms{$&} = $op; }
398*e0c4386eSCy Schubert#       if ($def =~ m{^__OpenBSD__$})               { $platforms{$&} = $op; }
399*e0c4386eSCy Schubert#       if ($def =~ m{^__NetBSD__$})                { $platforms{$&} = $op; }
400*e0c4386eSCy Schubert        if ($def =~ m{^OPENSSL_SYS_})               { $platforms{$'} = $op; }
401*e0c4386eSCy Schubert    }
402*e0c4386eSCy Schubert
403*e0c4386eSCy Schubert    return %platforms;
404*e0c4386eSCy Schubert}
405*e0c4386eSCy Schubert
406*e0c4386eSCy Schubertsub _parse_features {
407*e0c4386eSCy Schubert    my $self = shift;
408*e0c4386eSCy Schubert    my @defs = @_;
409*e0c4386eSCy Schubert
410*e0c4386eSCy Schubert    my %features = ();
411*e0c4386eSCy Schubert    foreach (@defs) {
412*e0c4386eSCy Schubert        m{^(!)?};
413*e0c4386eSCy Schubert        my $op = !(defined $1 && $1 eq '!');
414*e0c4386eSCy Schubert        my $def = $';
415*e0c4386eSCy Schubert
416*e0c4386eSCy Schubert        if ($def =~ m{^ZLIB$})                      { $features{$&} =  $op; }
417*e0c4386eSCy Schubert        if ($def =~ m{^OPENSSL_USE_})               { $features{$'} =  $op; }
418*e0c4386eSCy Schubert        if ($def =~ m{^OPENSSL_NO_})                { $features{$'} = !$op; }
419*e0c4386eSCy Schubert    }
420*e0c4386eSCy Schubert
421*e0c4386eSCy Schubert    return %features;
422*e0c4386eSCy Schubert}
423*e0c4386eSCy Schubert
424*e0c4386eSCy Schubertsub _adjust_version {
425*e0c4386eSCy Schubert    my $self = shift;
426*e0c4386eSCy Schubert    my $version = shift;
427*e0c4386eSCy Schubert    my $baseversion = $self->{baseversion};
428*e0c4386eSCy Schubert
429*e0c4386eSCy Schubert    $version = $baseversion
430*e0c4386eSCy Schubert        if ($baseversion ne '*' && $version ne '*'
431*e0c4386eSCy Schubert            && cmp_versions($baseversion, $version) > 0);
432*e0c4386eSCy Schubert
433*e0c4386eSCy Schubert    return $version;
434*e0c4386eSCy Schubert}
435*e0c4386eSCy Schubert
436*e0c4386eSCy Schubert=item B<< $ordinals->add SOURCE, NAME, TYPE, LIST >>
437*e0c4386eSCy Schubert
438*e0c4386eSCy SchubertAdds a new item from file SOURCE named NAME with the type TYPE,
439*e0c4386eSCy Schubertand a set of C macros in
440*e0c4386eSCy SchubertLIST that are expected to be defined or undefined to use this symbol, if
441*e0c4386eSCy Schubertany.  For undefined macros, they each must be prefixed with a C<!>.
442*e0c4386eSCy Schubert
443*e0c4386eSCy SchubertIf this symbol already exists in loaded data, it will be rewritten using
444*e0c4386eSCy Schubertthe new input data, but will keep the same ordinal number and version.
445*e0c4386eSCy SchubertIf it's entirely new, it will get a '?' and the current default version.
446*e0c4386eSCy Schubert
447*e0c4386eSCy Schubert=cut
448*e0c4386eSCy Schubert
449*e0c4386eSCy Schubertsub add {
450*e0c4386eSCy Schubert    my $self = shift;
451*e0c4386eSCy Schubert    my $source = shift;         # file where item was defined
452*e0c4386eSCy Schubert    my $name = shift;
453*e0c4386eSCy Schubert    my $type = shift;           # FUNCTION or VARIABLE
454*e0c4386eSCy Schubert    my @defs = @_;              # Macros from #ifdef and #ifndef
455*e0c4386eSCy Schubert                                # (the latter prefixed with a '!')
456*e0c4386eSCy Schubert
457*e0c4386eSCy Schubert    # call signature for debug output
458*e0c4386eSCy Schubert    my $verbsig = "add('$name' , '$type' , [ " . join(', ', @defs) . " ])";
459*e0c4386eSCy Schubert
460*e0c4386eSCy Schubert    croak __PACKAGE__."->add got a bad type '$type'"
461*e0c4386eSCy Schubert        unless $type eq 'FUNCTION' || $type eq 'VARIABLE';
462*e0c4386eSCy Schubert
463*e0c4386eSCy Schubert    my %platforms = _parse_platforms(@defs);
464*e0c4386eSCy Schubert    my %features = _parse_features(@defs);
465*e0c4386eSCy Schubert
466*e0c4386eSCy Schubert    my @items = $self->items(filter => f_name($name));
467*e0c4386eSCy Schubert    my $version = @items ? $items[0]->version() : $self->{currversion};
468*e0c4386eSCy Schubert    my $intnum = @items ? $items[0]->intnum() : ++$self->{maxnum};
469*e0c4386eSCy Schubert    my $number = @items ? $items[0]->number() : '?';
470*e0c4386eSCy Schubert    print STDERR "DEBUG[",__PACKAGE__,":add] $verbsig\n",
471*e0c4386eSCy Schubert        @items ? map { "\t".$_->to_string()."\n" } @items : "No previous items\n",
472*e0c4386eSCy Schubert        if $self->{debug};
473*e0c4386eSCy Schubert    @items = grep { $_->exists() } @items;
474*e0c4386eSCy Schubert
475*e0c4386eSCy Schubert    my $new_item =
476*e0c4386eSCy Schubert        OpenSSL::Ordinals::Item->new( source        => $source,
477*e0c4386eSCy Schubert                                      name          => $name,
478*e0c4386eSCy Schubert                                      type          => $type,
479*e0c4386eSCy Schubert                                      number        => $number,
480*e0c4386eSCy Schubert                                      intnum        => $intnum,
481*e0c4386eSCy Schubert                                      version       =>
482*e0c4386eSCy Schubert                                          $self->_adjust_version($version),
483*e0c4386eSCy Schubert                                      exists        => 1,
484*e0c4386eSCy Schubert                                      platforms     => { %platforms },
485*e0c4386eSCy Schubert                                      features      => [
486*e0c4386eSCy Schubert                                          grep { $features{$_} } keys %features
487*e0c4386eSCy Schubert                                      ] );
488*e0c4386eSCy Schubert
489*e0c4386eSCy Schubert    push @items, $new_item;
490*e0c4386eSCy Schubert    print STDERR "DEBUG[",__PACKAGE__,"::add] $verbsig\n", map { "\t".$_->to_string()."\n" } @items
491*e0c4386eSCy Schubert        if $self->{debug};
492*e0c4386eSCy Schubert    $self->_putback(@items);
493*e0c4386eSCy Schubert
494*e0c4386eSCy Schubert    # If an alias was defined beforehand, add an item for it now
495*e0c4386eSCy Schubert    my $alias = $self->{aliases}->{$name};
496*e0c4386eSCy Schubert    delete $self->{aliases}->{$name};
497*e0c4386eSCy Schubert
498*e0c4386eSCy Schubert    # For the caller to show
499*e0c4386eSCy Schubert    my @returns = ( $new_item );
500*e0c4386eSCy Schubert    push @returns, $self->add_alias($source, $alias->{name}, $name, @{$alias->{defs}})
501*e0c4386eSCy Schubert        if defined $alias;
502*e0c4386eSCy Schubert    return @returns;
503*e0c4386eSCy Schubert}
504*e0c4386eSCy Schubert
505*e0c4386eSCy Schubert=item B<< $ordinals->add_alias SOURCE, ALIAS, NAME, LIST >>
506*e0c4386eSCy Schubert
507*e0c4386eSCy SchubertAdds an alias ALIAS for the symbol NAME from file SOURCE, and a set of C macros
508*e0c4386eSCy Schubertin LIST that are expected to be defined or undefined to use this symbol, if any.
509*e0c4386eSCy SchubertFor undefined macros, they each must be prefixed with a C<!>.
510*e0c4386eSCy Schubert
511*e0c4386eSCy SchubertIf this symbol already exists in loaded data, it will be rewritten using
512*e0c4386eSCy Schubertthe new input data.  Otherwise, the data will just be store away, to wait
513*e0c4386eSCy Schubertthat the symbol NAME shows up.
514*e0c4386eSCy Schubert
515*e0c4386eSCy Schubert=cut
516*e0c4386eSCy Schubert
517*e0c4386eSCy Schubertsub add_alias {
518*e0c4386eSCy Schubert    my $self = shift;
519*e0c4386eSCy Schubert    my $source = shift;
520*e0c4386eSCy Schubert    my $alias = shift;          # This is the alias being added
521*e0c4386eSCy Schubert    my $name  = shift;          # For this name (assuming it exists)
522*e0c4386eSCy Schubert    my @defs = @_;              # Platform attributes for the alias
523*e0c4386eSCy Schubert
524*e0c4386eSCy Schubert    # call signature for debug output
525*e0c4386eSCy Schubert    my $verbsig =
526*e0c4386eSCy Schubert        "add_alias('$source' , '$alias' , '$name' , [ " . join(', ', @defs) . " ])";
527*e0c4386eSCy Schubert
528*e0c4386eSCy Schubert    croak "You're kidding me... $alias == $name" if $alias eq $name;
529*e0c4386eSCy Schubert
530*e0c4386eSCy Schubert    my %platforms = _parse_platforms(@defs);
531*e0c4386eSCy Schubert    my %features = _parse_features(@defs);
532*e0c4386eSCy Schubert
533*e0c4386eSCy Schubert    croak "Alias with associated features is forbidden\n"
534*e0c4386eSCy Schubert        if %features;
535*e0c4386eSCy Schubert
536*e0c4386eSCy Schubert    my $f_byalias = f_name($alias);
537*e0c4386eSCy Schubert    my $f_byname = f_name($name);
538*e0c4386eSCy Schubert    my @items = $self->items(filter => $f_byalias);
539*e0c4386eSCy Schubert    foreach my $item ($self->items(filter => $f_byname)) {
540*e0c4386eSCy Schubert        push @items, $item unless grep { $_ == $item } @items;
541*e0c4386eSCy Schubert    }
542*e0c4386eSCy Schubert    @items = grep { $_->exists() } @items;
543*e0c4386eSCy Schubert
544*e0c4386eSCy Schubert    croak "Alias already exists ($alias => $name)"
545*e0c4386eSCy Schubert        if scalar @items > 1;
546*e0c4386eSCy Schubert    if (scalar @items == 0) {
547*e0c4386eSCy Schubert        # The item we want to alias for doesn't exist yet, so we cache the
548*e0c4386eSCy Schubert        # alias and hope the item we're making an alias of shows up later
549*e0c4386eSCy Schubert        $self->{aliases}->{$name} = { source => $source,
550*e0c4386eSCy Schubert                                      name => $alias, defs => [ @defs ] };
551*e0c4386eSCy Schubert
552*e0c4386eSCy Schubert        print STDERR "DEBUG[",__PACKAGE__,":add_alias] $verbsig\n",
553*e0c4386eSCy Schubert            "\tSet future alias $alias => $name\n"
554*e0c4386eSCy Schubert            if $self->{debug};
555*e0c4386eSCy Schubert        return ();
556*e0c4386eSCy Schubert    } elsif (scalar @items == 1) {
557*e0c4386eSCy Schubert        # The rule is that an alias is more or less a copy of the original
558*e0c4386eSCy Schubert        # item, just with another name.  Also, the platforms given here are
559*e0c4386eSCy Schubert        # given to the original item as well, with opposite values.
560*e0c4386eSCy Schubert        my %alias_platforms = $items[0]->platforms();
561*e0c4386eSCy Schubert        foreach (keys %platforms) {
562*e0c4386eSCy Schubert            $alias_platforms{$_} = !$platforms{$_};
563*e0c4386eSCy Schubert        }
564*e0c4386eSCy Schubert        # We supposedly do now know how to do this...  *ahem*
565*e0c4386eSCy Schubert        $items[0]->{platforms} = { %alias_platforms };
566*e0c4386eSCy Schubert
567*e0c4386eSCy Schubert        my $number =
568*e0c4386eSCy Schubert            $items[0]->number() =~ m|^\?| ? '?+' : $items[0]->number();
569*e0c4386eSCy Schubert        my $alias_item = OpenSSL::Ordinals::Item->new(
570*e0c4386eSCy Schubert            source        => $source,
571*e0c4386eSCy Schubert            name          => $alias,
572*e0c4386eSCy Schubert            type          => $items[0]->type(),
573*e0c4386eSCy Schubert            number        => $number,
574*e0c4386eSCy Schubert            intnum        => $items[0]->intnum(),
575*e0c4386eSCy Schubert            version       => $self->_adjust_version($items[0]->version()),
576*e0c4386eSCy Schubert            exists        => $items[0]->exists(),
577*e0c4386eSCy Schubert            platforms     => { %platforms },
578*e0c4386eSCy Schubert            features      => [ $items[0]->features() ]
579*e0c4386eSCy Schubert           );
580*e0c4386eSCy Schubert        push @items, $alias_item;
581*e0c4386eSCy Schubert
582*e0c4386eSCy Schubert        print STDERR "DEBUG[",__PACKAGE__,":add_alias] $verbsig\n",
583*e0c4386eSCy Schubert            map { "\t".$_->to_string()."\n" } @items
584*e0c4386eSCy Schubert            if $self->{debug};
585*e0c4386eSCy Schubert        $self->_putback(@items);
586*e0c4386eSCy Schubert
587*e0c4386eSCy Schubert        # For the caller to show
588*e0c4386eSCy Schubert        return ( $alias_item->to_string() );
589*e0c4386eSCy Schubert    }
590*e0c4386eSCy Schubert    croak "$name has an alias already (trying to add alias $alias)\n",
591*e0c4386eSCy Schubert        "\t", join(", ", map { $_->name() } @items), "\n";
592*e0c4386eSCy Schubert}
593*e0c4386eSCy Schubert
594*e0c4386eSCy Schubert=item B<< $ordinals->set_version VERSION >>
595*e0c4386eSCy Schubert
596*e0c4386eSCy Schubert=item B<< $ordinals->set_version VERSION BASEVERSION >>
597*e0c4386eSCy Schubert
598*e0c4386eSCy SchubertSets the default version for new symbol to VERSION.
599*e0c4386eSCy Schubert
600*e0c4386eSCy SchubertIf given, BASEVERSION sets the base version, i.e. the minimum version
601*e0c4386eSCy Schubertfor all symbols.  If not given, it will be calculated as follows:
602*e0c4386eSCy Schubert
603*e0c4386eSCy Schubert=over 4
604*e0c4386eSCy Schubert
605*e0c4386eSCy SchubertIf the given version is '*', then the base version will also be '*'.
606*e0c4386eSCy Schubert
607*e0c4386eSCy SchubertIf the given version starts with '0.', the base version will be '0.0.0'.
608*e0c4386eSCy Schubert
609*e0c4386eSCy SchubertIf the given version starts with '1.0.', the base version will be '1.0.0'.
610*e0c4386eSCy Schubert
611*e0c4386eSCy SchubertIf the given version starts with '1.1.', the base version will be '1.1.0'.
612*e0c4386eSCy Schubert
613*e0c4386eSCy SchubertIf the given version has a first number C<N> that's greater than 1, the
614*e0c4386eSCy Schubertbase version will be formed from C<N>: 'N.0.0'.
615*e0c4386eSCy Schubert
616*e0c4386eSCy Schubert=back
617*e0c4386eSCy Schubert
618*e0c4386eSCy Schubert=cut
619*e0c4386eSCy Schubert
620*e0c4386eSCy Schubertsub set_version {
621*e0c4386eSCy Schubert    my $self = shift;
622*e0c4386eSCy Schubert    # '*' is for "we don't care"
623*e0c4386eSCy Schubert    my $version = shift // '*';
624*e0c4386eSCy Schubert    my $baseversion = shift // '*';
625*e0c4386eSCy Schubert
626*e0c4386eSCy Schubert    if ($baseversion eq '*') {
627*e0c4386eSCy Schubert        $baseversion = $version;
628*e0c4386eSCy Schubert        if ($baseversion ne '*') {
629*e0c4386eSCy Schubert            if ($baseversion =~ m|^(\d+)\.|, $1 > 1) {
630*e0c4386eSCy Schubert                $baseversion = "$1.0.0";
631*e0c4386eSCy Schubert            } else {
632*e0c4386eSCy Schubert                $baseversion =~ s|^0\..*$|0.0.0|;
633*e0c4386eSCy Schubert                $baseversion =~ s|^1\.0\..*$|1.0.0|;
634*e0c4386eSCy Schubert                $baseversion =~ s|^1\.1\..*$|1.1.0|;
635*e0c4386eSCy Schubert
636*e0c4386eSCy Schubert                die 'Invalid version'
637*e0c4386eSCy Schubert                    if ($baseversion ne '0.0.0'
638*e0c4386eSCy Schubert                        && $baseversion !~ m|^1\.[01]\.0$|);
639*e0c4386eSCy Schubert            }
640*e0c4386eSCy Schubert        }
641*e0c4386eSCy Schubert    }
642*e0c4386eSCy Schubert
643*e0c4386eSCy Schubert    die 'Invalid base version'
644*e0c4386eSCy Schubert        if ($baseversion ne '*' && $version ne '*'
645*e0c4386eSCy Schubert            && cmp_versions($baseversion, $version) > 0);
646*e0c4386eSCy Schubert
647*e0c4386eSCy Schubert    $self->{currversion} = $version;
648*e0c4386eSCy Schubert    $self->{baseversion} = $baseversion;
649*e0c4386eSCy Schubert    foreach ($self->items(filter => sub { $_[0] eq '*' })) {
650*e0c4386eSCy Schubert        $_->{version} = $self->{currversion};
651*e0c4386eSCy Schubert    }
652*e0c4386eSCy Schubert    return 1;
653*e0c4386eSCy Schubert}
654*e0c4386eSCy Schubert
655*e0c4386eSCy Schubert=item B<< $ordinals->invalidate >>
656*e0c4386eSCy Schubert
657*e0c4386eSCy SchubertInvalidates the whole working database.  The practical effect is that all
658*e0c4386eSCy Schubertsymbols are set to not exist, but are kept around in the database to retain
659*e0c4386eSCy Schubertordinal numbers and versions.
660*e0c4386eSCy Schubert
661*e0c4386eSCy Schubert=cut
662*e0c4386eSCy Schubert
663*e0c4386eSCy Schubertsub invalidate {
664*e0c4386eSCy Schubert    my $self = shift;
665*e0c4386eSCy Schubert
666*e0c4386eSCy Schubert    foreach (@{$self->{contents}}) {
667*e0c4386eSCy Schubert        foreach (@{$_ // []}) {
668*e0c4386eSCy Schubert            $_->{exists} = 0;
669*e0c4386eSCy Schubert        }
670*e0c4386eSCy Schubert    }
671*e0c4386eSCy Schubert    $self->{stats} = {};
672*e0c4386eSCy Schubert}
673*e0c4386eSCy Schubert
674*e0c4386eSCy Schubert=item B<< $ordinals->validate >>
675*e0c4386eSCy Schubert
676*e0c4386eSCy SchubertValidates the current working database by collection statistics on how many
677*e0c4386eSCy Schubertsymbols were added and how many were changed.  These numbers can be retrieved
678*e0c4386eSCy Schubertwith B<< $ordinals->stats >>.
679*e0c4386eSCy Schubert
680*e0c4386eSCy Schubert=cut
681*e0c4386eSCy Schubert
682*e0c4386eSCy Schubertsub validate {
683*e0c4386eSCy Schubert    my $self = shift;
684*e0c4386eSCy Schubert
685*e0c4386eSCy Schubert    $self->{stats} = {};
686*e0c4386eSCy Schubert    for my $i (1..$self->{maxnum}) {
687*e0c4386eSCy Schubert        if ($i > $self->{loaded_maxnum}
688*e0c4386eSCy Schubert                || (!@{$self->{loaded_contents}->[$i] // []}
689*e0c4386eSCy Schubert                    && @{$self->{contents}->[$i] // []})) {
690*e0c4386eSCy Schubert            $self->{stats}->{new}++;
691*e0c4386eSCy Schubert        }
692*e0c4386eSCy Schubert        if ($i <= $self->{maxassigned}) {
693*e0c4386eSCy Schubert            $self->{stats}->{assigned}++;
694*e0c4386eSCy Schubert        } else {
695*e0c4386eSCy Schubert            $self->{stats}->{unassigned}++;
696*e0c4386eSCy Schubert        }
697*e0c4386eSCy Schubert        next if ($i > $self->{loaded_maxnum});
698*e0c4386eSCy Schubert
699*e0c4386eSCy Schubert        my @loaded_strings =
700*e0c4386eSCy Schubert            map { $_->to_string() } @{$self->{loaded_contents}->[$i] // []};
701*e0c4386eSCy Schubert        my @current_strings =
702*e0c4386eSCy Schubert            map { $_->to_string() } @{$self->{contents}->[$i] // []};
703*e0c4386eSCy Schubert
704*e0c4386eSCy Schubert        foreach my $str (@current_strings) {
705*e0c4386eSCy Schubert            @loaded_strings = grep { $str ne $_ } @loaded_strings;
706*e0c4386eSCy Schubert        }
707*e0c4386eSCy Schubert        if (@loaded_strings) {
708*e0c4386eSCy Schubert            $self->{stats}->{modified}++;
709*e0c4386eSCy Schubert        }
710*e0c4386eSCy Schubert    }
711*e0c4386eSCy Schubert}
712*e0c4386eSCy Schubert
713*e0c4386eSCy Schubert=item B<< $ordinals->stats >>
714*e0c4386eSCy Schubert
715*e0c4386eSCy SchubertReturns the statistics that B<validate> calculate.
716*e0c4386eSCy Schubert
717*e0c4386eSCy Schubert=cut
718*e0c4386eSCy Schubert
719*e0c4386eSCy Schubertsub stats {
720*e0c4386eSCy Schubert    my $self = shift;
721*e0c4386eSCy Schubert
722*e0c4386eSCy Schubert    return %{$self->{stats}};
723*e0c4386eSCy Schubert}
724*e0c4386eSCy Schubert
725*e0c4386eSCy Schubert=back
726*e0c4386eSCy Schubert
727*e0c4386eSCy Schubert=head2 Data elements
728*e0c4386eSCy Schubert
729*e0c4386eSCy SchubertData elements, which is each line in an ordinals file, are instances
730*e0c4386eSCy Schubertof a separate class, OpenSSL::Ordinals::Item, with its own methods:
731*e0c4386eSCy Schubert
732*e0c4386eSCy Schubert=over 4
733*e0c4386eSCy Schubert
734*e0c4386eSCy Schubert=cut
735*e0c4386eSCy Schubert
736*e0c4386eSCy Schubertpackage OpenSSL::Ordinals::Item;
737*e0c4386eSCy Schubert
738*e0c4386eSCy Schubertuse strict;
739*e0c4386eSCy Schubertuse warnings;
740*e0c4386eSCy Schubertuse Carp;
741*e0c4386eSCy Schubert
742*e0c4386eSCy Schubert=item B<new> I<%options>
743*e0c4386eSCy Schubert
744*e0c4386eSCy SchubertCreates a new instance of the C<OpenSSL::Ordinals::Item> class.  It takes
745*e0c4386eSCy Schubertoptions in keyed pair form, i.e. a series of C<< key => value >> pairs.
746*e0c4386eSCy SchubertAvailable options are:
747*e0c4386eSCy Schubert
748*e0c4386eSCy Schubert=over 4
749*e0c4386eSCy Schubert
750*e0c4386eSCy Schubert=item B<< source => FILENAME >>, B<< from => STRING >>
751*e0c4386eSCy Schubert
752*e0c4386eSCy SchubertThis will create a new item from FILENAME, filled with data coming from STRING.
753*e0c4386eSCy Schubert
754*e0c4386eSCy SchubertSTRING must conform to the following EBNF description:
755*e0c4386eSCy Schubert
756*e0c4386eSCy Schubert  ordinal string = symbol, spaces, ordinal, spaces, version, spaces,
757*e0c4386eSCy Schubert                   exist, ":", platforms, ":", type, ":", features;
758*e0c4386eSCy Schubert  spaces         = space, { space };
759*e0c4386eSCy Schubert  space          = " " | "\t";
760*e0c4386eSCy Schubert  symbol         = ( letter | "_" ), { letter | digit | "_" };
761*e0c4386eSCy Schubert  ordinal        = number | "?" | "?+";
762*e0c4386eSCy Schubert  version        = number, "_", number, "_", number, [ letter, [ letter ] ];
763*e0c4386eSCy Schubert  exist          = "EXIST" | "NOEXIST";
764*e0c4386eSCy Schubert  platforms      = platform, { ",", platform };
765*e0c4386eSCy Schubert  platform       = ( letter | "_" ) { letter | digit | "_" };
766*e0c4386eSCy Schubert  type           = "FUNCTION" | "VARIABLE";
767*e0c4386eSCy Schubert  features       = feature, { ",", feature };
768*e0c4386eSCy Schubert  feature        = ( letter | "_" ) { letter | digit | "_" };
769*e0c4386eSCy Schubert  number         = digit, { digit };
770*e0c4386eSCy Schubert
771*e0c4386eSCy Schubert(C<letter> and C<digit> are assumed self evident)
772*e0c4386eSCy Schubert
773*e0c4386eSCy Schubert=item B<< source => FILENAME >>, B<< name => STRING >>, B<< number => NUMBER >>,
774*e0c4386eSCy Schubert      B<< version => STRING >>, B<< exists => BOOLEAN >>, B<< type => STRING >>,
775*e0c4386eSCy Schubert      B<< platforms => HASHref >>, B<< features => LISTref >>
776*e0c4386eSCy Schubert
777*e0c4386eSCy SchubertThis will create a new item with data coming from the arguments.
778*e0c4386eSCy Schubert
779*e0c4386eSCy Schubert=back
780*e0c4386eSCy Schubert
781*e0c4386eSCy Schubert=cut
782*e0c4386eSCy Schubert
783*e0c4386eSCy Schubertsub new {
784*e0c4386eSCy Schubert    my $class = shift;
785*e0c4386eSCy Schubert
786*e0c4386eSCy Schubert    if (ref($_[0]) eq $class) {
787*e0c4386eSCy Schubert        return $class->new( map { $_ => $_[0]->{$_} } keys %{$_[0]} );
788*e0c4386eSCy Schubert    }
789*e0c4386eSCy Schubert
790*e0c4386eSCy Schubert    my %opts = @_;
791*e0c4386eSCy Schubert
792*e0c4386eSCy Schubert    croak "No argument given" unless %opts;
793*e0c4386eSCy Schubert
794*e0c4386eSCy Schubert    my $instance = undef;
795*e0c4386eSCy Schubert    if ($opts{from}) {
796*e0c4386eSCy Schubert        my @a = split /\s+/, $opts{from};
797*e0c4386eSCy Schubert
798*e0c4386eSCy Schubert        croak "Badly formatted ordinals string: $opts{from}"
799*e0c4386eSCy Schubert            unless ( scalar @a == 4
800*e0c4386eSCy Schubert                     && $a[0] =~ /^[A-Za-z_][A-Za-z_0-9]*$/
801*e0c4386eSCy Schubert                     && $a[1] =~ /^\d+|\?\+?$/
802*e0c4386eSCy Schubert                     && $a[2] =~ /^(?:\*|\d+_\d+_\d+[a-z]{0,2})$/
803*e0c4386eSCy Schubert                     && $a[3] =~ /^
804*e0c4386eSCy Schubert                                  (?:NO)?EXIST:
805*e0c4386eSCy Schubert                                  [^:]*:
806*e0c4386eSCy Schubert                                  (?:FUNCTION|VARIABLE):
807*e0c4386eSCy Schubert                                  [^:]*
808*e0c4386eSCy Schubert                                  $
809*e0c4386eSCy Schubert                                 /x );
810*e0c4386eSCy Schubert
811*e0c4386eSCy Schubert        my @b = split /:/, $a[3];
812*e0c4386eSCy Schubert        %opts = ( source        => $opts{source},
813*e0c4386eSCy Schubert                  name          => $a[0],
814*e0c4386eSCy Schubert                  number        => $a[1],
815*e0c4386eSCy Schubert                  version       => $a[2],
816*e0c4386eSCy Schubert                  exists        => $b[0] eq 'EXIST',
817*e0c4386eSCy Schubert                  platforms     => { map { m|^(!)?|; $' => !$1 }
818*e0c4386eSCy Schubert                                         split /,/,$b[1] },
819*e0c4386eSCy Schubert                  type          => $b[2],
820*e0c4386eSCy Schubert                  features      => [ split /,/,$b[3] // '' ] );
821*e0c4386eSCy Schubert    }
822*e0c4386eSCy Schubert
823*e0c4386eSCy Schubert    if ($opts{name} && $opts{version} && defined $opts{exists} && $opts{type}
824*e0c4386eSCy Schubert            && ref($opts{platforms} // {}) eq 'HASH'
825*e0c4386eSCy Schubert            && ref($opts{features} // []) eq 'ARRAY') {
826*e0c4386eSCy Schubert        my $version = $opts{version};
827*e0c4386eSCy Schubert        $version =~ s|_|.|g;
828*e0c4386eSCy Schubert
829*e0c4386eSCy Schubert        $instance = { source    => $opts{source},
830*e0c4386eSCy Schubert                      name      => $opts{name},
831*e0c4386eSCy Schubert                      type      => $opts{type},
832*e0c4386eSCy Schubert                      number    => $opts{number},
833*e0c4386eSCy Schubert                      intnum    => $opts{intnum},
834*e0c4386eSCy Schubert                      version   => $version,
835*e0c4386eSCy Schubert                      exists    => !!$opts{exists},
836*e0c4386eSCy Schubert                      platforms => { %{$opts{platforms} // {}} },
837*e0c4386eSCy Schubert                      features  => [ sort @{$opts{features} // []} ] };
838*e0c4386eSCy Schubert    } else {
839*e0c4386eSCy Schubert        croak __PACKAGE__."->new() called with bad arguments\n".
840*e0c4386eSCy Schubert            join("", map { "    $_\t=> ".$opts{$_}."\n" } sort keys %opts);
841*e0c4386eSCy Schubert    }
842*e0c4386eSCy Schubert
843*e0c4386eSCy Schubert    return bless $instance, $class;
844*e0c4386eSCy Schubert}
845*e0c4386eSCy Schubert
846*e0c4386eSCy Schubertsub DESTROY {
847*e0c4386eSCy Schubert}
848*e0c4386eSCy Schubert
849*e0c4386eSCy Schubert=item B<< $item->name >>
850*e0c4386eSCy Schubert
851*e0c4386eSCy SchubertThe symbol name for this item.
852*e0c4386eSCy Schubert
853*e0c4386eSCy Schubert=item B<< $item->number >> (read-write)
854*e0c4386eSCy Schubert
855*e0c4386eSCy SchubertThe positional number for this item.
856*e0c4386eSCy Schubert
857*e0c4386eSCy SchubertThis may be '?' for an unassigned symbol, or '?+' for an unassigned symbol
858*e0c4386eSCy Schubertthat's an alias for the previous symbol.  '?' and '?+' must be properly
859*e0c4386eSCy Schuberthandled by the caller.  The caller may change this to an actual number.
860*e0c4386eSCy Schubert
861*e0c4386eSCy Schubert=item B<< $item->version >> (read-only)
862*e0c4386eSCy Schubert
863*e0c4386eSCy SchubertThe version number for this item.  Please note that these version numbers
864*e0c4386eSCy Schuberthave underscore (C<_>) as a separator for the version parts.
865*e0c4386eSCy Schubert
866*e0c4386eSCy Schubert=item B<< $item->exists >> (read-only)
867*e0c4386eSCy Schubert
868*e0c4386eSCy SchubertA boolean that tells if this symbol exists in code or not.
869*e0c4386eSCy Schubert
870*e0c4386eSCy Schubert=item B<< $item->platforms >> (read-only)
871*e0c4386eSCy Schubert
872*e0c4386eSCy SchubertA hash table reference.  The keys of the hash table are the names of
873*e0c4386eSCy Schubertthe specified platforms, with a value of 0 to indicate that this symbol
874*e0c4386eSCy Schubertisn't available on that platform, and 1 to indicate that it is.  Platforms
875*e0c4386eSCy Schubertthat aren't mentioned default to 1.
876*e0c4386eSCy Schubert
877*e0c4386eSCy Schubert=item B<< $item->type >> (read-only)
878*e0c4386eSCy Schubert
879*e0c4386eSCy SchubertC<FUNCTION> or C<VARIABLE>, depending on what the symbol represents.
880*e0c4386eSCy SchubertSome platforms do not care about this, others do.
881*e0c4386eSCy Schubert
882*e0c4386eSCy Schubert=item B<< $item->features >> (read-only)
883*e0c4386eSCy Schubert
884*e0c4386eSCy SchubertAn array reference, where every item indicates a feature where this symbol
885*e0c4386eSCy Schubertis available.  If no features are mentioned, the symbol is always available.
886*e0c4386eSCy SchubertIf any feature is mentioned, this symbol is I<only> available when those
887*e0c4386eSCy Schubertfeatures are enabled.
888*e0c4386eSCy Schubert
889*e0c4386eSCy Schubert=cut
890*e0c4386eSCy Schubert
891*e0c4386eSCy Schubertour $AUTOLOAD;
892*e0c4386eSCy Schubert
893*e0c4386eSCy Schubert# Generic getter
894*e0c4386eSCy Schubertsub AUTOLOAD {
895*e0c4386eSCy Schubert    my $self = shift;
896*e0c4386eSCy Schubert    my $funcname = $AUTOLOAD;
897*e0c4386eSCy Schubert    (my $item = $funcname) =~ s|.*::||g;
898*e0c4386eSCy Schubert
899*e0c4386eSCy Schubert    croak "$funcname called as setter" if @_;
900*e0c4386eSCy Schubert    croak "$funcname invalid" unless exists $self->{$item};
901*e0c4386eSCy Schubert    return $self->{$item} if ref($self->{$item}) eq '';
902*e0c4386eSCy Schubert    return @{$self->{$item}} if ref($self->{$item}) eq 'ARRAY';
903*e0c4386eSCy Schubert    return %{$self->{$item}} if ref($self->{$item}) eq 'HASH';
904*e0c4386eSCy Schubert}
905*e0c4386eSCy Schubert
906*e0c4386eSCy Schubert=item B<< $item->intnum >> (read-write)
907*e0c4386eSCy Schubert
908*e0c4386eSCy SchubertInternal positional number.  If I<< $item->number >> is '?' or '?+', the
909*e0c4386eSCy Schubertcaller can use this to set a number for its purposes.
910*e0c4386eSCy SchubertIf I<< $item->number >> is a number, I<< $item->intnum >> should be the
911*e0c4386eSCy Schubertsame
912*e0c4386eSCy Schubert
913*e0c4386eSCy Schubert=cut
914*e0c4386eSCy Schubert
915*e0c4386eSCy Schubert# Getter/setters
916*e0c4386eSCy Schubertsub intnum {
917*e0c4386eSCy Schubert    my $self = shift;
918*e0c4386eSCy Schubert    my $value = shift;
919*e0c4386eSCy Schubert    my $item = 'intnum';
920*e0c4386eSCy Schubert
921*e0c4386eSCy Schubert    croak "$item called with extra arguments" if @_;
922*e0c4386eSCy Schubert    $self->{$item} = "$value" if defined $value;
923*e0c4386eSCy Schubert    return $self->{$item};
924*e0c4386eSCy Schubert}
925*e0c4386eSCy Schubert
926*e0c4386eSCy Schubertsub number {
927*e0c4386eSCy Schubert    my $self = shift;
928*e0c4386eSCy Schubert    my $value = shift;
929*e0c4386eSCy Schubert    my $item = 'number';
930*e0c4386eSCy Schubert
931*e0c4386eSCy Schubert    croak "$item called with extra arguments" if @_;
932*e0c4386eSCy Schubert    $self->{$item} = "$value" if defined $value;
933*e0c4386eSCy Schubert    return $self->{$item};
934*e0c4386eSCy Schubert}
935*e0c4386eSCy Schubert
936*e0c4386eSCy Schubert=item B<< $item->to_string >>
937*e0c4386eSCy Schubert
938*e0c4386eSCy SchubertConverts the item to a string that can be saved in an ordinals file.
939*e0c4386eSCy Schubert
940*e0c4386eSCy Schubert=cut
941*e0c4386eSCy Schubert
942*e0c4386eSCy Schubertsub to_string {
943*e0c4386eSCy Schubert    my $self = shift;
944*e0c4386eSCy Schubert
945*e0c4386eSCy Schubert    croak "Too many arguments" if @_;
946*e0c4386eSCy Schubert    my %platforms = $self->platforms();
947*e0c4386eSCy Schubert    my @features = $self->features();
948*e0c4386eSCy Schubert    my $version = $self->version();
949*e0c4386eSCy Schubert    $version =~ s|\.|_|g;
950*e0c4386eSCy Schubert    return sprintf "%-39s %s\t%s\t%s:%s:%s:%s",
951*e0c4386eSCy Schubert        $self->name(),
952*e0c4386eSCy Schubert        $self->number(),
953*e0c4386eSCy Schubert        $version,
954*e0c4386eSCy Schubert        $self->exists() ? 'EXIST' : 'NOEXIST',
955*e0c4386eSCy Schubert        join(',', (map { ($platforms{$_} ? '' : '!') . $_ }
956*e0c4386eSCy Schubert                   sort keys %platforms)),
957*e0c4386eSCy Schubert        $self->type(),
958*e0c4386eSCy Schubert        join(',', @features);
959*e0c4386eSCy Schubert}
960*e0c4386eSCy Schubert
961*e0c4386eSCy Schubert=back
962*e0c4386eSCy Schubert
963*e0c4386eSCy Schubert=head2 Comparators and filters
964*e0c4386eSCy Schubert
965*e0c4386eSCy SchubertFor the B<< $ordinals->items >> method, there are a few functions to create
966*e0c4386eSCy Schubertcomparators based on specific data:
967*e0c4386eSCy Schubert
968*e0c4386eSCy Schubert=over 4
969*e0c4386eSCy Schubert
970*e0c4386eSCy Schubert=cut
971*e0c4386eSCy Schubert
972*e0c4386eSCy Schubert# Go back to the main package to create comparators and filters
973*e0c4386eSCy Schubertpackage OpenSSL::Ordinals;
974*e0c4386eSCy Schubert
975*e0c4386eSCy Schubert# Comparators...
976*e0c4386eSCy Schubert
977*e0c4386eSCy Schubert=item B<by_name>
978*e0c4386eSCy Schubert
979*e0c4386eSCy SchubertReturns a comparator that will compare the names of two OpenSSL::Ordinals::Item
980*e0c4386eSCy Schubertobjects.
981*e0c4386eSCy Schubert
982*e0c4386eSCy Schubert=cut
983*e0c4386eSCy Schubert
984*e0c4386eSCy Schubertsub by_name {
985*e0c4386eSCy Schubert    return sub { $_[0]->name() cmp $_[1]->name() };
986*e0c4386eSCy Schubert}
987*e0c4386eSCy Schubert
988*e0c4386eSCy Schubert=item B<by_number>
989*e0c4386eSCy Schubert
990*e0c4386eSCy SchubertReturns a comparator that will compare the ordinal numbers of two
991*e0c4386eSCy SchubertOpenSSL::Ordinals::Item objects.
992*e0c4386eSCy Schubert
993*e0c4386eSCy Schubert=cut
994*e0c4386eSCy Schubert
995*e0c4386eSCy Schubertsub by_number {
996*e0c4386eSCy Schubert    return sub { $_[0]->intnum() <=> $_[1]->intnum() };
997*e0c4386eSCy Schubert}
998*e0c4386eSCy Schubert
999*e0c4386eSCy Schubert=item B<by_version>
1000*e0c4386eSCy Schubert
1001*e0c4386eSCy SchubertReturns a comparator that will compare the version of two
1002*e0c4386eSCy SchubertOpenSSL::Ordinals::Item objects.
1003*e0c4386eSCy Schubert
1004*e0c4386eSCy Schubert=cut
1005*e0c4386eSCy Schubert
1006*e0c4386eSCy Schubertsub by_version {
1007*e0c4386eSCy Schubert    return sub {
1008*e0c4386eSCy Schubert        # cmp_versions comes from OpenSSL::Util
1009*e0c4386eSCy Schubert        return cmp_versions($_[0]->version(), $_[1]->version());
1010*e0c4386eSCy Schubert    }
1011*e0c4386eSCy Schubert}
1012*e0c4386eSCy Schubert
1013*e0c4386eSCy Schubert=back
1014*e0c4386eSCy Schubert
1015*e0c4386eSCy SchubertThere are also the following filters:
1016*e0c4386eSCy Schubert
1017*e0c4386eSCy Schubert=over 4
1018*e0c4386eSCy Schubert
1019*e0c4386eSCy Schubert=cut
1020*e0c4386eSCy Schubert
1021*e0c4386eSCy Schubert# Filters...  these are called by grep, the return sub must use $_ for
1022*e0c4386eSCy Schubert# the item to check
1023*e0c4386eSCy Schubert
1024*e0c4386eSCy Schubert=item B<f_version VERSION>
1025*e0c4386eSCy Schubert
1026*e0c4386eSCy SchubertReturns a filter that only lets through symbols with a version number
1027*e0c4386eSCy Schubertmatching B<VERSION>.
1028*e0c4386eSCy Schubert
1029*e0c4386eSCy Schubert=cut
1030*e0c4386eSCy Schubert
1031*e0c4386eSCy Schubertsub f_version {
1032*e0c4386eSCy Schubert    my $version = shift;
1033*e0c4386eSCy Schubert
1034*e0c4386eSCy Schubert    croak "No version specified"
1035*e0c4386eSCy Schubert        unless $version && $version =~ /^\d+\.\d+\.\d+[a-z]{0,2}$/;
1036*e0c4386eSCy Schubert
1037*e0c4386eSCy Schubert    return sub { $_[0]->version() eq $version };
1038*e0c4386eSCy Schubert}
1039*e0c4386eSCy Schubert
1040*e0c4386eSCy Schubert=item B<f_number NUMBER>
1041*e0c4386eSCy Schubert
1042*e0c4386eSCy SchubertReturns a filter that only lets through symbols with the ordinal number
1043*e0c4386eSCy Schubertmatching B<NUMBER>.
1044*e0c4386eSCy Schubert
1045*e0c4386eSCy SchubertNOTE that this returns a "magic" value that can not be used as a function.
1046*e0c4386eSCy SchubertIt's only useful when passed directly as a filter to B<items>.
1047*e0c4386eSCy Schubert
1048*e0c4386eSCy Schubert=cut
1049*e0c4386eSCy Schubert
1050*e0c4386eSCy Schubertsub f_number {
1051*e0c4386eSCy Schubert    my $number = shift;
1052*e0c4386eSCy Schubert
1053*e0c4386eSCy Schubert    croak "No number specified"
1054*e0c4386eSCy Schubert        unless $number && $number =~ /^\d+$/;
1055*e0c4386eSCy Schubert
1056*e0c4386eSCy Schubert    return [ F_NUMBER, $number ];
1057*e0c4386eSCy Schubert}
1058*e0c4386eSCy Schubert
1059*e0c4386eSCy Schubert
1060*e0c4386eSCy Schubert=item B<f_name NAME>
1061*e0c4386eSCy Schubert
1062*e0c4386eSCy SchubertReturns a filter that only lets through symbols with the symbol name
1063*e0c4386eSCy Schubertmatching B<NAME>.
1064*e0c4386eSCy Schubert
1065*e0c4386eSCy SchubertNOTE that this returns a "magic" value that can not be used as a function.
1066*e0c4386eSCy SchubertIt's only useful when passed directly as a filter to B<items>.
1067*e0c4386eSCy Schubert
1068*e0c4386eSCy Schubert=cut
1069*e0c4386eSCy Schubert
1070*e0c4386eSCy Schubertsub f_name {
1071*e0c4386eSCy Schubert    my $name = shift;
1072*e0c4386eSCy Schubert
1073*e0c4386eSCy Schubert    croak "No name specified"
1074*e0c4386eSCy Schubert        unless $name;
1075*e0c4386eSCy Schubert
1076*e0c4386eSCy Schubert    return [ F_NAME, $name ];
1077*e0c4386eSCy Schubert}
1078*e0c4386eSCy Schubert
1079*e0c4386eSCy Schubert=back
1080*e0c4386eSCy Schubert
1081*e0c4386eSCy Schubert=head1 AUTHORS
1082*e0c4386eSCy Schubert
1083*e0c4386eSCy SchubertRichard Levitte E<lt>levitte@openssl.orgE<gt>.
1084*e0c4386eSCy Schubert
1085*e0c4386eSCy Schubert=cut
1086*e0c4386eSCy Schubert
1087*e0c4386eSCy Schubert1;
1088