File Coverage

lib/Data/Identifier/Util.pm
Criterion Covered Total %
statement 105 156 67.3
branch 74 128 57.8
condition 10 38 26.3
subroutine 12 13 92.3
pod 5 5 100.0
total 206 340 60.5


line stmt bran cond sub pod time code
1             # Copyright (c) 2023-2025 Philipp Schafft
2              
3             # licensed under Artistic License 2.0 (see LICENSE file)
4              
5             # ABSTRACT: format independent identifier object
6              
7              
8             package Data::Identifier::Util;
9              
10 2     2   1018775 use v5.14;
  2         7  
11 2     2   75 use strict;
  2         6  
  2         103  
12 2     2   13 use warnings;
  2         3  
  2         123  
13              
14 2     2   10 use parent qw(Data::Identifier::Interface::Userdata Data::Identifier::Interface::Subobjects);
  2         5  
  2         14  
15              
16 2     2   103 use Carp;
  2         3  
  2         124  
17              
18 2     2   624 use Data::Identifier;
  2         4  
  2         15  
19              
20             our $VERSION = v0.28;
21              
22             my $_DEFAULT_INSTANCE = __PACKAGE__->new;
23              
24             my %_4plus12_prefix = (
25             sni => (0<<15)|(0<<14),
26             sid => (0<<15)|(1<<14),
27             hdi => (1<<15)|(0<<14),
28             udi => (1<<15)|(1<<14),
29             );
30              
31             my $_logical = '5e80c7b7-215e-4154-b310-a5387045c336';
32              
33             my %_raes_to_raen = (
34             NONE => 0,
35             NOENT => 2,
36             NOSYS => 6,
37             NOTSUP => 7,
38             NOMEM => 12,
39             INVAL => 13,
40             FAULT => 18,
41             IO => 19,
42             NODATA => 25,
43             NOSPC => 38,
44             TYPEMM => 39,
45             RO => 45,
46             ILLSEQ => 56,
47             BADEXEC => 79,
48             BADFH => 83,
49             );
50             my %_logicals_to_sni = (
51             sni => 10,
52             sid => 115,
53             raen => 116,
54             chat0w => 118,
55             uuid => 119,
56             uri => 121,
57             asciicp => 122,
58             oid => 120,
59             wd => 123,
60             logical => 129,
61             false => 189,
62             true => 190,
63             );
64             my %_logicals_to_sid = (
65             asi => 1,
66             tagname => 3,
67             SEEK_SET => 34,
68             SEEK_CUR => 35,
69             SEEK_END => 36,
70             backwards => 43,
71             forwards => 44,
72             black => 61,
73             white => 62,
74             grey => 63,
75             red => 119,
76             green => 120,
77             blue => 121,
78             cyan => 122,
79             magenta => 123,
80             yellow => 124,
81             orange => 125,
82             gtin => 160,
83             left => 192,
84             right => 193,
85             up => 194,
86             down => 195,
87             north => 208,
88             east => 209,
89             south => 210,
90             west => 211,
91             );
92              
93              
94             sub new {
95 3     3 1 410 my ($pkg, @opts) = @_;
96              
97 3 50       9 croak 'Stray options passed' if scalar @opts;
98              
99 3         9 return bless {}, $pkg;
100             }
101              
102              
103             sub pack {
104 9     9 1 2854 my ($self, $template, $identifier, @opts) = _normalise_args(@_);
105 9         11 my $pack_template;
106             my $v;
107              
108 9 50       16 croak 'Stray options passed' if scalar @opts;
109              
110 9   50     15 $template //= '';
111 9 50       9 $identifier = Data::Identifier->new(from => $identifier) unless eval {$identifier->isa('Data::Identifier')};
  9         31  
112              
113 9 100       59 if ($template =~ /^(sid|sni|hdi|udi)([1-9][0-9]*)$/) {
    50          
    100          
    100          
    50          
114 6         21 my $bits = int($2);
115              
116 6         34 $v = $identifier->as(Data::Identifier->new(wellknown => $1), no_defaults => 1);
117              
118 6 100       22 if ($bits == 8) {
    100          
    50          
119 2         4 $pack_template = 'C';
120             } elsif ($bits == 16) {
121 2         5 $pack_template = 'n';
122             } elsif ($bits == 32) {
123 2         4 $pack_template = 'N';
124             } else {
125 0         0 croak 'Invalid width: '.$bits;
126             }
127             } elsif ($template eq '4+12') {
128 0         0 my $prefix;
129              
130 0         0 foreach my $type (qw(sid sni hdi udi)) {
131 0         0 $v = $identifier->as(Data::Identifier->new(wellknown => $type), no_defaults => 1, default => undef);
132 0 0       0 if (defined $v) {
133 0 0 0     0 if ($v < 0 || $v > 0x0FFF) {
134 0         0 next;
135             }
136 0         0 $v |= $_4plus12_prefix{$type};
137 0         0 $pack_template = 'n';
138 0         0 last;
139             }
140             }
141             } elsif ($template eq 'uuid128') {
142 1         5 return pack('H*', $identifier->uuid(no_defaults => 1) =~ tr/-//dr);
143             } elsif ($template eq 'uuidhexdash') {
144 1         29 return $identifier->uuid(no_defaults => 1);
145             } elsif ($template eq 'uuidHEXDASH') {
146 1         3 return $identifier->uuid(no_defaults => 1) =~ tr/a-f/A-F/r;
147             }
148              
149 6 50 33     50 if (defined($v) && defined($pack_template)) {
150 6         6 my ($min, $max);
151              
152 6 100       18 if ($pack_template eq 'C') {
    100          
    50          
153 2         4 ($min, $max) = (0, 0xFF);
154             } elsif ($pack_template eq 'n') {
155 2         5 ($min, $max) = (0, 0xFFFF);
156             } elsif ($pack_template eq 'N') {
157 2         4 ($min, $max) = (0, 0xFFFF_FFFF);
158             }
159              
160 6 50 33     31 if ((defined($min) && $v < $min) || (defined($max) && $v > $max)) {
      33        
      33        
161 0         0 croak 'Identifier not in range for '.$template.': '.$v;
162             }
163              
164 6         50 return pack($pack_template, $v);
165             }
166              
167 0         0 croak 'Unknown template: '.$template;
168             }
169              
170              
171             sub unpack {
172 9     9 1 22 my ($self, $template, $data, @opts) = _normalise_args(@_);
173 9         14 my $pack_template;
174             my $type;
175              
176 9 50       14 croak 'Stray options passed' if scalar @opts;
177              
178 9 100       63 if ($template =~ /^(sid|sni|hdi|udi)([1-9][0-9]*)$/) {
    50          
    100          
    100          
    50          
179 6         18 my $bits = int($2);
180 6         10 $type = $1;
181              
182 6 100       17 if ($bits == 8) {
    100          
    50          
183 2         5 $pack_template = 'C';
184             } elsif ($bits == 16) {
185 2         2 $pack_template = 'n';
186             } elsif ($bits == 32) {
187 2         4 $pack_template = 'N';
188             } else {
189 0         0 croak 'Invalid width: '.$bits;
190             }
191             } elsif ($template eq '4+12') {
192 0         0 my $v;
193             my $prefix;
194              
195 0 0       0 croak 'Input has bad length, expected 2 bytes, got '.length($data) unless length($data) == 2;
196              
197 0         0 $v = unpack('n', $data);
198 0         0 $prefix = $v & 0xF000;
199 0         0 $v = $v & 0x0FFF;
200              
201 0         0 foreach my $key (keys %_4plus12_prefix) {
202 0 0       0 if ($prefix == $_4plus12_prefix{$key}) {
203 0         0 return Data::Identifier->new($key => $v);
204             }
205             }
206              
207 0         0 croak sprintf('Invalid/unknown prefix: 0x%04x', $prefix);
208             } elsif ($template eq 'uuid128') {
209 1 50       4 croak 'Input has bad length, expected 16 bytes, got '.length($data) unless length($data) == 16;
210 1         77 return Data::Identifier->new(uuid => join('-', unpack('H8H4H4H4H12', $data)));
211             } elsif ($template eq 'uuidhexdash') {
212 1         4 return Data::Identifier->new(uuid => $data);
213             } elsif ($template eq 'uuidHEXDASH') {
214 1         4 return Data::Identifier->new(uuid => $data);
215             }
216              
217 6 50 33     22 if (defined($type) && defined($pack_template)) {
218 6         6 my $len = length($data);
219 6         7 my $exp;
220              
221 6 100       13 if ($pack_template eq 'C') {
    100          
    50          
222 2         3 $exp = 1;
223             } elsif ($pack_template eq 'n') {
224 2         2 $exp = 2;
225             } elsif ($pack_template eq 'N') {
226 2         3 $exp = 4;
227             }
228              
229 6 50       11 croak 'Input has bad length, expected '.$exp.' bytes, got '.$len unless $len == $exp;
230              
231 6         30 return Data::Identifier->new($type => unpack($pack_template, $data));
232             }
233              
234 0         0 croak 'Unknown template: '.$template;
235             }
236              
237              
238             sub parse_sirtx {
239 16     16 1 3295 my ($self, $data, @opts) = _normalise_args(@_);
240              
241 16 50       34 croak 'Stray options passed' if scalar @opts;
242              
243 16         35 $self->_load_well_known;
244              
245 16         68 $data =~ s/^\[(.+)\]$/$1/;
246              
247             # Experimental:
248 16 100       52 if (my ($d, $v) = $data =~ /^(\[.+?\]):(.+)$/) {
249 1         4 $d = $self->parse_sirtx($d);
250 1         10 $v =~ s/^\[(.+)\]$/$1/;
251 1         6 return Data::Identifier->new($d => $v);
252             }
253              
254 15 100       159 if ($data =~ /^'([0-9]*)$/) {
    50          
    100          
    100          
    50          
    50          
    100          
    100          
    50          
    100          
    100          
    50          
    50          
255 2   50     15 my $num = int($1 || '0');
256 2         734 require Data::Identifier::Generate;
257 2         11 return Data::Identifier::Generate->integer($num);
258             } elsif ($data =~ /^\/([0-9]+)$/) {
259 0         0 return Data::Identifier->new('d73b6550-5309-46ad-acc9-865c9261065b' => int($1));
260             } elsif ($data =~ /^(sid|sni):([0-9]+)$/) {
261 4         20 return Data::Identifier->new($1 => int($2));
262             } elsif ($data =~ /^uuid:([0-9a-fA-F-]+)$/) {
263 2         8 return Data::Identifier->new(uuid => $1);
264             } elsif ($data =~ /^wd:([QPL][1-9][0-9]*)$/) {
265 0         0 return Data::Identifier->new(wd => $1);
266             } elsif ($data =~ /^~([0-9]+)$/) {
267 0         0 return Data::Identifier->new(hdi => int($1));
268             } elsif ($data =~ /^raen:([0-9]+)$/) {
269 1         7 return Data::Identifier->new('2bffc55d-7380-454e-bd53-c5acd525d692' => int($1));
270             } elsif ($data =~ /^chat0w:([0-9]+)$/) {
271 1         14 return Data::Identifier->new('2c7e15ed-aa2f-4e2f-9a1d-64df0c85875a' => int($1));
272             } elsif ($data =~ /^asciicp:([0-9]+)$/) {
273 0         0 require Data::Identifier::Generate;
274 0         0 return Data::Identifier::Generate->unicode_character(ascii => int($1));
275             } elsif ($data =~ /^raes:(.+)/) {
276 1 50       6 if (defined(my $raen = $_raes_to_raen{$1})) {
277 1         4 return Data::Identifier->new('2bffc55d-7380-454e-bd53-c5acd525d692' => $raen);
278             }
279             } elsif (defined $_logicals_to_sni{$data}) {
280 2         7 return Data::Identifier->new(sni => $_logicals_to_sni{$data});
281             } elsif (defined $_logicals_to_sid{$data}) {
282 0         0 return Data::Identifier->new(sid => $_logicals_to_sid{$data});
283             } elsif ($data =~ /^logical:(.+)$/) {
284 2         6 $data = $1;
285 2 50       5 if (defined $_logicals_to_sni{$data}) {
    0          
286 2         10 return Data::Identifier->new(sni => $_logicals_to_sni{$data});
287             } elsif (defined $_logicals_to_sid{$data}) {
288 0         0 return Data::Identifier->new(sid => $_logicals_to_sid{$data});
289             }
290             }
291              
292 0         0 croak 'Unsupported/invalid SIRTX identifier';
293             }
294              
295              
296             sub render_sirtx {
297 0     0 1 0 my ($self, $identifier, @opts) = _normalise_args(@_);
298 0         0 state $map = [
299             [sid => Data::Identifier->new(wellknown => 'sid')->register],
300             [sni => Data::Identifier->new(wellknown => 'sni')->register],
301             [wd => Data::Identifier->new(wellknown => 'wd')->register],
302             ['/' => Data::Identifier->new(uuid => 'd73b6550-5309-46ad-acc9-865c9261065b')->register],
303             [raen => Data::Identifier->new(uuid => '2bffc55d-7380-454e-bd53-c5acd525d692')->register],
304             [chat0w => Data::Identifier->new(uuid => '2c7e15ed-aa2f-4e2f-9a1d-64df0c85875a')->register],
305             ['~' => Data::Identifier->new(wellknown => 'hdi')->register],
306             ];
307              
308 0 0       0 croak 'Stray options passed' if scalar @opts;
309              
310 0         0 $identifier = Data::Identifier->new(from => $identifier);
311              
312 0 0 0     0 if (defined(my Data::Identifier $generator = $identifier->generator(default => undef)) && defined(my $req = $identifier->request(default => undef))) {
313 0 0 0     0 if ($generator->eq('53863a15-68d4-448d-bd69-a9b19289a191')) {
    0          
314 0         0 return sprintf('\'%u', $req);
315             } elsif ($generator->eq('d74f8c35-bcb8-465c-9a77-01010e8ed25c') && $req =~ /^[Uu]\+([0-9a-fA-F]{4,6})$/) {
316 0         0 my $cp = hex $1;
317 0 0       0 if ($cp < 0x80) {
318 0         0 return sprintf('asciicp:%u', $cp);
319             }
320             }
321             }
322              
323 0         0 foreach my $ent (@{$map}) {
  0         0  
324 0   0     0 my $v = $identifier->as($ent->[1], no_defaults => 1, default => undef) // next;
325 0 0       0 $v = sprintf($ent->[0] =~ /^[a-z]/ ? '%s:%s' : '%s%s', $ent->[0], $v);
326 0 0       0 $v = '['.$v.']' if $v =~ /-/;
327 0         0 return $v;
328             }
329              
330             # Fallback:
331 0         0 return sprintf('[uuid:%s]', $identifier->uuid);
332             }
333              
334             # ---- Private helpers ----
335              
336             sub _normalise_args {
337 34     34   71 my (@args) = @_;
338              
339 34 50       76 if (scalar(@args)) {
340 34 50 33     83 if (ref($args[0]) && eval {$args[0]->isa(__PACKAGE__)}) {
  34 0       177  
341             # no-op
342             } elsif ($args[0] eq __PACKAGE__) {
343 0         0 $args[0] = $_DEFAULT_INSTANCE;
344             } else {
345 0         0 unshift(@args, $_DEFAULT_INSTANCE);
346             }
347              
348 34         132 return @args;
349             }
350              
351 0         0 return ($_DEFAULT_INSTANCE);
352             }
353              
354             sub _load_well_known {
355 16     16   18 state $done = do {
356 1         4 my %meta = (
357             sni => \%_logicals_to_sni,
358             sid => \%_logicals_to_sid,
359             );
360              
361 1         3 foreach my $type (keys %meta) {
362 2         3 my $hash = $meta{$type};
363              
364 2         2 foreach my $key (keys %{$hash}) {
  2         33  
365 38         78 my $id = Data::Identifier->new($type => $hash->{$key});
366              
367 38 100       62 next unless defined $id->uuid(no_defaults => 1, default => undef);
368              
369 14   50     17 $id->{id_cache} //= {};
370 14   33     38 $id->{id_cache}->{$_logical} //= $key;
371              
372 14         33 $id->register;
373             }
374             }
375             };
376             }
377              
378             1;
379              
380             __END__