package perfSONAR_PS::Topology::ID; use strict; use warnings; use base 'Exporter'; our $VERSION = 0.08; our @EXPORT = ('idConstruct', 'idIsFQ', 'idAddLevel', 'idRemoveLevel', 'idBaseLevel', 'idEncode', 'idDecode', 'idSplit', 'idCompare', 'idMatch', 'idIsAmbiguous'); sub idConstruct { my ($type1, $field1, $type2, $field2, $type3, $field3, $type4, $field4) = @_; my $id = ""; $id .= "urn:ogf:network"; return $id if ($type1 eq "" or $field1 eq ""); $id .= ":".$type1."=".idEncode($field1); return $id if ($type2 eq "" or $field2 eq ""); $id .= ":".$type2."=".idEncode($field2); return $id if ($type3 eq "" or $field3 eq ""); $id .= ":".$type3."=".idEncode($field3); return $id if ($type4 eq "" or $field4 eq ""); $id .= ":".$type4."=".idEncode($field4); return $id; } sub idIsFQ { my ($id, $type) = @_; my ($new_type, $value); return 0 if (!($id =~ /^urn:ogf:network:(.*)$/)); return 1 if ($type eq ""); my @fields = split(':', $id); if ($type eq "domain") { ($new_type, $value) = split("=", $fields[3]); return -1 if ($new_type ne "domain" or not defined $value); return 1; } elsif ($type eq "path" or $type eq "network") { if ($#fields == 3) { ($new_type, $value) = split("=", $fields[3]); return -1 if ($new_type ne $type or not defined $value); return 1; } elsif ($#fields == 4) { ($new_type, $value) = split("=", $fields[3]); return -1 if ($new_type ne "domain" or not defined $value); ($new_type, $value) = split("=", $fields[4]); return -1 if ($new_type ne $type or not defined $value); return 1; } else { return -1; } } elsif ($type eq "node") { return -1 if ($#fields != 4); ($type, $value) = split("=", $fields[3]); return -1 if ($type ne "domain" or not defined $value); ($type, $value) = split("=", $fields[4]); return -1 if ($type ne "node" or not defined $value); return 1; } elsif ($type eq "port") { return -1 if ($#fields != 5); ($type, $value) = split("=", $fields[3]); return -1 if ($type ne "domain" or not defined $value); ($type, $value) = split("=", $fields[4]); return -1 if ($type ne "node" or not defined $value); ($type, $value) = split("=", $fields[5]); return -1 if ($type ne "port" or not defined $value); return 1; } elsif ($type eq "link") { if ($#fields == 4) { ($type, $value) = split("=", $fields[3]); return -1 if ($type ne "domain" or not defined $value); ($type, $value) = split("=", $fields[4]); return -1 if ($type ne "link" or not defined $value); return 1; } elsif ($#fields == 6) { ($type, $value) = split("=", $fields[3]); return -1 if ($type ne "domain" or not defined $value); ($type, $value) = split("=", $fields[4]); return -1 if ($type ne "node" or not defined $value); ($type, $value) = split("=", $fields[5]); return -1 if ($type ne "port" or not defined $value); ($type, $value) = split("=", $fields[6]); return -1 if ($type ne "link" or not defined $value); return 1; } else { return -1; } } else { return -1; } } sub idAddLevel { my ($id, $new_type, $new_level) = @_; $new_level = idEncode($new_level); if ($id =~ /^urn:ogf:network:$/) { $id .= $new_type."=".$new_level; } else { $id .= ":".$new_type."=".$new_level; } return $id; } sub idRemoveLevel { my ($id, $ret_type) = @_; my $ret_id; if ($id =~ /(^urn:ogf:network.*):[^:]+$/) { if ($1 eq "urn:ogf:network") { $ret_id = ""; } else { $ret_id = $1; } } else { $ret_id = $id; } if (defined $ret_type and $ret_type ne "") { if ($ret_id ne "") { my $type; my $value = idBaseLevel($ret_id, \$type); $$ret_type = $type; } else { $$ret_type = ""; } } return $ret_id; } sub idBaseLevel { my ($id, $ret_type) = @_; my $ret_id; if (!($id =~ /^urn:ogf:network/)) { $$ret_type = "" if (defined $ret_type and $ret_type ne ""); return $id; } if ($id =~ /^urn:ogf:network$/) { $$ret_type = "" if (defined $ret_type and $ret_type ne ""); return ""; }; if ($id =~ /^urn:ogf:network.*:([^:]+)$/) { $ret_id = $1; } my ($type, $value) = split('=', $ret_id); if (defined $ret_type and $ret_type ne "") { $$ret_type = $type; } return idDecode($value); } sub idEncode { my ($id) = @_; $id =~ s/%/%25/g; $id =~ s/:/%3A/g; $id =~ s/#/%23/g; $id =~ s/\//%2F/g; $id =~ s/\?/%3F/g; return $id; } sub idDecode { my ($id) = @_; $id =~ s/%3A/:/g; $id =~ s/%23/#/g; $id =~ s/%2F/\//g; $id =~ s/%3F/?/g; $id =~ s/%25/%/g; return $id; } sub idCompare { my ($id1, $id2, $compare_to) = @_; my @results_id1 = idSplit($id1, 0, 1); if ($results_id1[0] == -1) { my $msg = "ID \"$id1\" is not properly qualified"; return (-1, $msg); } my @results_id2 = idSplit($id2, 0, 1); if ($results_id2[0] == -1) { my $msg = "ID \"$id2\" is not properly qualified"; return (-1, $msg); } for(my $i = 2; $i <= $#results_id1; $i += 2) { if (not defined $results_id2[$i]) { return (-1, "ID element $compare_to not found"); } if ($results_id1[$i] ne $results_id2[$i] or $results_id1[$i + 1] ne $results_id2[$i + 1]) { return (-1, $results_id1[$i]."=".$results_id1[$i + 1] . " != " . $results_id2[$i] . "=" . $results_id2[$i + 1]); } return (0, "") if ($results_id1[$i] eq $compare_to); } return (-1, "ID element $compare_to not found"); } sub idIsAmbiguous { my ($id) = @_; return ($id =~ /(=\*:|:\*$|=\*$)/); } sub idMatch { my ($ids, $idExp) = @_; my @idExpFields = split(/:/, $idExp); my @fields = (); my $finished = 0; for(my $i = 0; $i <= $#idExpFields; $i++) { if ($finished) { return; } if ($idExpFields[$i] =~ /([^=]*)=(.*)/) { $fields[$i][0] = $1; $fields[$i][1] = $2; } elsif ($idExpFields[$i] eq "*") { $fields[$i][0] = '*'; $finished = 1; } } my @matchingIds = (); foreach my $id (@{ $ids }) { my @idFields = split(/:/, $id); for(my $i = 3; $i <= $#idFields; $i++) { # if we get here, we're being asked to match a value, # we haven't encountered a ":*" and we've hit the end # of the id expression so we've got a mismatch. last if ($i > $#fields); if ($idFields[$i] =~ /([^=]*)=(.*)/) { # if we've hit a :* portion of the id, then the # rest of the id matches. if ($fields[$i][0] eq "*") { push @matchingIds, $id; last; } # if the field name of the id doesn't match the # field name in the id expression. if ($fields[$i][0] ne $1) { last; } # if the expression field value isn't the 'any # value' and it's not what the user specified, # quit checking. if ($fields[$i][1] ne "*" and $fields[$i][1] ne $2) { last; } # if we've hit the end of both sets of fields # and we haven't had an error, its a match. if ($i == $#idFields and $i == $#fields) { push @matchingIds, $id; } } } } return \@matchingIds; } sub idSplit { my ($id, $fq, $top_down) = @_; if (idIsFQ($id, "") == 0) { my $msg = "ID \"$id\" is not fully qualified"; return (-1, $msg); } my @fields = split(':', $id); if ($#fields > 6 or $#fields < 3) { my $msg = "ID \"$id\" has an invalid number of fields: $#fields"; return (-1, $msg); } my ($type1, $field1); my ($type2, $field2); my ($type3, $field3); my ($type4, $field4); ($type1, $field1) = split('=', $fields[3]) if defined $fields[3]; ($type2, $field2) = split('=', $fields[4]) if defined $fields[4]; ($type3, $field3) = split('=', $fields[5]) if defined $fields[5]; ($type4, $field4) = split('=', $fields[6]) if defined $fields[6]; my $id_type; if (defined $type4) { if ($type4 eq "link") { $id_type = $type4; } else { my $msg = "Fourth field of ID is of unknown type \"$type4\""; return (-1, $msg); } } elsif (defined $type3) { if ($type3 eq "port") { $id_type = $type3; } else { my $msg = "Third field of ID is of unknown type \"$type3\""; return (-1, $msg); } } elsif (defined $type2) { if ($type2 eq "node" or $type2 eq "link" or $type2 eq "path" or $type2 eq "network") { $id_type = $type2; } else { my $msg = "Second field of ID is of unknown type \"$type2\""; return (-1, $msg); } } elsif (defined $type1) { if ($type1 eq "domain" or $type1 eq "path" or $type1 eq "network") { $id_type = $type1; } else { my $msg = "First field of ID is of unknown type \"$type1\""; return (-1, $msg); } } else { $id_type = ""; } if ($fq) { $field1 = "urn:ogf:network:".$fields[3] if defined $fields[3]; $field2 = $field1.":".$fields[4] if defined $fields[4]; $field3 = $field2.":".$fields[5] if defined $fields[5]; $field4 = $field3.":".$fields[6] if defined $fields[6]; } else { $field1 = idDecode($field1) if defined $field1; $field2 = idDecode($field2) if defined $field2; $field3 = idDecode($field3) if defined $field3; $field4 = idDecode($field4) if defined $field4; } my @res; push @res, 0; push @res, $id_type; if ($top_down) { push @res, $type1 if defined $type1; push @res, $field1 if defined $field1; push @res, $type2 if defined $type2; push @res, $field2 if defined $field2; push @res, $type3 if defined $type3; push @res, $field3 if defined $field3; push @res, $type4 if defined $type4; push @res, $field4 if defined $field4; } else { push @res, $type4 if defined $type4; push @res, $field4 if defined $field4; push @res, $type3 if defined $type3; push @res, $field3 if defined $field3; push @res, $type2 if defined $type2; push @res, $field2 if defined $field2; push @res, $type1 if defined $type1; push @res, $field1 if defined $field1; } return @res; } 1; __END__ =head1 NAME perfSONAR_PS::Topology::ID - A module that provides various utility functions for Topology IDs. =head1 DESCRIPTION This module contains a set of utility functions that are used to interact with Topology IDs. =head1 SYNOPSIS =head1 DETAILS =head1 API =head2 idConstruct($type1, $field1, $type2, $field2, $type3, $field3, $type4, $field4) Constructs an a fully-qualified id based on the specified fields. No sanity checking is performed to verify that the created ID makes sense. The $type parameters are values like 'domain', 'node', etc whereas the $field parameter is the ID for that element like "I2" or "HOPI". All values past the first blank ("") type or field are ignored. =head2 idIsFQ($id, $type) Checks if the specified ID is a fully-qualified ID of the specified type. If it is not a fully-qualified id, the function returns 0. If it is an incorrect fully-qualified id(e.g. too many elements), it returns -1. If it is a correctly specified fully-qualified id, it returns 1. =head2 idAddLevel($id, $new_type, $new_level) Takes a fully-qualified id and adds a new level onto it. No sanity checking is done, it simply returns the ID created from the values requested. =head2 idRemoveLevel($id, $ret_type) Takes a fully-qualified id and returns the parent level for the id. If you'd like to know the type of the parent, you can add a reference to a variable for $ret_type and the function will fill it in with the type of the returned id. e.g. urn:ogf:network:domain=hopi:node=losa would return 'urn:ogf:network:domain=hopi' and $ret_type would be filled in with 'domain' =head2 idBaseLevel($id, $ret_type) Returns the base level of the specified id. If you want to be informed fo the type of the base element, you can add a reference to a variable for $ret_type and the function will fill it in with the type of the element. e.g. urn:ogf:network:domain=hopi:node=losa would return 'losa' and $ret_type would be filled in with 'node' =head2 idEncode($element) Performs any necessary encoding of the specified element for inclusion in a fully-qualified id. =head2 idDecode($element) Decodes the specified element from a fully-qualified id. =head2 idCompare($id1, $id2, $compare_to) Compares the given ids to see if they match up to the specified field. $compare_to can be any ID element type that the IDs have in common. It returns an array containing two values. The first is either 0 or -1 and tells whether the function failed or succeeded. If the function failed, the next element in the array is the error message. =head2 idSplit($id, $fq, $top_down) Splits the specified fully-qualified id into its component elements. If $fq is 1, the returns components are all fully-qualified. The components are returned in an array. The first value of the array is the 0 or -1 specifying whether the function succeeded or failed. The next element is a string for the type of the ID. Each subsequent pair of elements corresponds to the type of the element followed by the element itself. If $top_down is 0, the order is the most specific element to least specific element. If $top_down is 1, however, the order is reversed. =head1 SEE ALSO To join the 'perfSONAR-PS' mailing list, please visit: https://mail.internet2.edu/wws/info/i2-perfsonar The perfSONAR-PS subversion repository is located at: https://svn.internet2.edu/svn/perfSONAR-PS Questions and comments can be directed to the author, or the mailing list. =head1 VERSION $Id$ =head1 AUTHOR Aaron Brown, Eaaron@internet2.eduE =head1 LICENSE You should have received a copy of the Internet2 Intellectual Property Framework along with this software. If not, see =head1 COPYRIGHT Copyright (c) 2004-2007, Internet2 and the University of Delaware All rights reserved. =cut # vim: expandtab shiftwidth=4 tabstop=4