File Coverage

blib/lib/Net/DHCP/Packet.pm
Criterion Covered Total %
statement 167 319 52.3
branch 62 140 44.2
condition 11 35 31.4
subroutine 36 68 52.9
pod 22 22 100.0
total 298 584 51.0


line stmt bran cond sub pod time code
1             #!/bin/false
2             # PODNAME: Net::DHCP::Packet
3             # Author : D. Hamstead
4             # Original Author: F. van Dun, S. Hadinger
5             # ABSTRACT: Object methods to create a DHCP packet.
6 11     11   178806 use strict;
  11         16  
  11         255  
7 11     11   36 use warnings;
  11         11  
  11         220  
8 11     11   99 use 5.8.0;
  11         29  
9              
10             package Net::DHCP::Packet;
11             $Net::DHCP::Packet::VERSION = '0.7_007'; # TRIAL
12              
13 11     11   39 $Net::DHCP::Packet::VERSION = '0.7007';use Carp;
  11         11  
  11         616  
14 11     11   4435 use Net::DHCP::Constants qw(:DEFAULT :dhcp_hashes :dhcp_other %DHO_FORMATS %SUBOPTION_FORMATS);
  11         19  
  11         8063  
15 11     11   4400 use Net::DHCP::Packet::Attributes qw(:all);
  11         16  
  11         1795  
16 11     11   47 use Net::DHCP::Packet::IPv4Utils qw(:all);
  11         9  
  11         944  
17 11     11   41 use List::Util qw(first);
  11         11  
  11         17890  
18              
19             #=======================================================================
20              
21             {
22              
23             my %newargs = (
24              
25             Comment => \&comment,
26             Op => \&op,
27             Htype => \&htype,
28             Hlen => \&hlen,
29             Hops => \&hops,
30             Xid => \&xid,
31             Secs => \&secs,
32             Flags => \&flags,
33             Ciaddr => \&ciaddr,
34             Yiaddr => \&yiaddr,
35             Siaddr => \&siaddr,
36             Giaddr => \&giaddr,
37             Chaddr => \&chaddr,
38             Sname => \&sname,
39             File => \&file,
40             Padding => \&padding,
41             isDhcp => \&isDhcp,
42              
43             );
44              
45             sub new {
46              
47 22     22 1 309858 my $p = shift;
48 22   33     87 my $class = ref($p) || $p;
49              
50 22         193 my $self = {
51             options => {}, # DHCP options
52             options_order => [], # order in which the options were added
53              
54             # defaults
55             comment => undef,
56             op => BOOTREQUEST(),
57             htype => 1, # 10mb ethernet
58             hlen => 6, # Use 6 bytes MAC
59             hops => 0,
60             xid => 0x12345678,
61             secs => 0,
62             flags => 0,
63             ciaddr => "\0\0\0\0",
64             yiaddr => "\0\0\0\0",
65             siaddr => "\0\0\0\0",
66             giaddr => "\0\0\0\0",
67             chaddr => q||,
68             sname => q||,
69             file => q||,
70             padding => q||,
71             isDhcp => 1,
72              
73             };
74              
75 22         28 bless $self, $class;
76 22 100       46 if ( scalar @_ == 1 ) { # we build the packet from a binary string
77 7         17 $self->marshall(shift);
78             }
79             else {
80              
81 15         59 my %args = @_;
82 15         22 my @ordered_args = @_;
83              
84 15         56 for my $k (sort keys %args) { # keep the processing order consistent
85              
86 22 100       47 next if $k =~ m/^\d+$/; # ignore numbered args
87              
88 14 50       26 if ($newargs{$k}) {
89 14         33 $newargs{$k}->($self, $args{$k});
90             next
91 14         13 }
92              
93 0         0 carp sprintf 'Ingoring unknown new() argument: %s', $k;
94              
95             }
96              
97             # TBM add DHCP option parsing
98 15         43 while ( defined( my $key = shift @ordered_args ) ) {
99              
100 22         15 my $value = shift @ordered_args;
101 22 100       54 if ($key =~ m/^\d+$/) {
102 8         11 $self->addOptionValue( $key, $value );
103             }
104             }
105             }
106              
107 19         45 return $self
108              
109             }
110              
111             }
112              
113             sub addOptionRaw {
114 43     43 1 51 my ( $self, $key, $value_bin, $sort ) = @_;
115 43         56 $self->{options}->{$key} = $value_bin;
116 43         33 push @{ $self->{options_order} }, $key;
  43         60  
117              
118 43 100       77 return 1 if $sort;
119              
120             #FIXME @{ $self->{options_order} } = sort optionsorder @{ $self->{options_order} };
121              
122 27         228 return 1
123             }
124              
125             sub addOptionValue {
126 33     33 1 1416 my $self = shift;
127 33         26 my $code = shift; # option code
128 33         25 my $value = shift;
129              
130             # my $value_bin; # option value in binary format
131              
132             carp("addOptionValue: unknown format for code ($code)")
133 33 50       98 unless exists $DHO_FORMATS{$code};
134              
135 33         37 my $format = $DHO_FORMATS{$code};
136              
137 33 50       51 if ( $format eq 'suboption' ) {
138 0         0 carp 'Use addSubOptionValue to add sub options';
139 0         0 return;
140             }
141              
142             # decompose input value into an array
143 33         65 my @values;
144 33 100 66     98 if ( defined $value && $value ne q|| ) {
145 22         67 @values =
146             split( /[\s\/,;]+/, $value ); # array of values, split by space
147             }
148              
149             # verify number of parameters
150 33 100 66     164 if ( $format eq 'string' || $format eq 'csr' ) {
    100          
    100          
151 3         4 @values = ($value); # don't change format
152             }
153             elsif ( $format =~ m/s$/ ) { # ends with an 's', meaning any number of parameters
154             ;
155             }
156             elsif ( $format =~ m/2$/ ) { # ends with a '2', meaning couples of parameters
157 6 100       82 croak(
158             "addOptionValue: only pairs of values expected for option '$code'")
159             if ( ( @values % 2 ) != 0 );
160             }
161             else { # only one parameter
162 16 100       463 croak("addOptionValue: exactly one value expected for option '$code'")
163             if ( @values != 1 );
164             }
165              
166             my %options = (
167              
168 3     3   13 inet => sub { return packinet(shift) },
169 5     5   13 inets => sub { return packinets_array(@_) },
170 5     5   13 inets2 => sub { return packinets_array(@_) },
171 2     2   9 int => sub { return pack( 'N', shift ) },
172 1     1   4 short => sub { return pack( 'n', shift ) },
173             # 255 & trims the input to single octet
174 4     4   23 byte => sub { return pack( 'C', 255 & shift ) },
175             bytes => sub {
176 3     3   7 return pack( 'C*', map { 255 & $_ } @_ );
  10         17  
177             },
178 3     3   5 string => sub { return shift },
179 0     0   0 clientid => sub { return packclientid(shift) },
180 0     0   0 sipserv => sub { return packsipserv(shift) },
181 0     0   0 csr => sub { return packcsr(shift) },
182 0     0   0 suboptions => sub { return packsuboptions(@_) },
183              
184 26         305 );
185              
186             # } elsif ($format eq 'ids') {
187             # $value_bin = $values[0];
188             # # TBM bad format
189              
190             # decode the option if we know how, otherwise use the original value
191             $self->addOptionRaw( $code, $options{$format}
192 26 50       67 ? $options{$format}->(@values)
193             : $value );
194              
195             } # end AddOptionValue
196              
197             sub addSubOptionRaw {
198 0     0 1 0 my ( $self, $key, $subkey, $value_bin ) = @_;
199 0         0 $self->{options}->{$key}->{$subkey} = $value_bin;
200              
201 0 0       0 if ( !grep( /$key/, @{$self->{options_order}} ) ) {
  0         0  
202 0         0 push @{ $self->{options_order} }, $key;
  0         0  
203             }
204 0         0 push @{ $self->{sub_options_order}{$key} }, ($subkey);
  0         0  
205             }
206              
207             sub addSubOptionValue {
208              
209 0     0 1 0 my $self = shift;
210 0         0 my $code = shift; # option code
211 0         0 my $subcode = shift; # sub option code
212 0         0 my $value = shift;
213             # my $value_bin; # option value in binary format
214              
215             # FIXME
216             carp("addSubOptionValue: unknown format for code ($code)")
217 0 0       0 unless exists $DHO_FORMATS{$code};
218              
219             carp("addSubOptionValue: not a suboption parameter for code ($code)")
220 0 0       0 unless ( $DHO_FORMATS{$code} eq 'suboptions' );
221              
222             carp(
223             "addSubOptionValue: unknown format for subcode ($subcode) on code ($code)"
224             )
225 0 0       0 unless ( $DHO_FORMATS{$code} eq 'suboptions' );
226              
227             carp("addSubOptionValue: no suboptions defined for code ($code)?")
228 0 0       0 unless exists $SUBOPTION_CODES{$code};
229              
230             carp(
231             "addSubOptionValue: suboption ($subcode) not defined for code ($code)?")
232 0 0       0 unless exists $SUBOPTION_CODES{$code}->{$subcode};
233              
234 0         0 my $format = $SUBOPTION_FORMATS{$code}->{$subcode};
235              
236             # decompose input value into an array
237 0         0 my @values;
238 0 0 0     0 if ( defined $value && $value ne q|| ) {
239 0         0 @values =
240             split( /[\s\/,;]+/, $value ); # array of values, split by space
241             }
242              
243             # verify number of parameters
244 0 0       0 if ( $format eq 'string' ) {
    0          
    0          
245 0         0 @values = ($value); # don't change format
246             }
247             elsif ( $format =~ m/s$/ )
248             { # ends with an 's', meaning any number of parameters
249             ;
250             }
251             elsif ( $format =~ m/2$/ )
252             { # ends with a '2', meaning couples of parameters
253 0 0       0 croak(
254             "addSubOptionValue: only pairs of values expected for option '$code'"
255             ) if ( ( @values % 2 ) != 0 );
256             }
257             else { # only one parameter
258 0 0       0 croak(
259             "addSubOptionValue: exactly one value expected for option '$code'")
260             if ( @values != 1 );
261             }
262              
263             my %options = (
264 0     0   0 inet => sub { return packinet(shift) },
265 0     0   0 inets => sub { return packinets_array(@_) },
266 0     0   0 inets2 => sub { return packinets_array(@_) },
267 0     0   0 int => sub { return pack( 'N', shift ) },
268 0     0   0 short => sub { return pack( 'n', shift ) },
269 0     0   0 byte => sub { return pack( 'C', 255 & shift ) }
270             , # 255 & trims the input to single octet
271             bytes => sub {
272 0     0   0 return pack( 'C*', map { 255 & $_ } @_ );
  0         0  
273             },
274 0     0   0 string => sub { return shift },
275 0     0   0 hexa => sub { return pack( 'H*', shift ) },
276 0         0 );
277              
278             # } elsif ($format eq 'ids') {
279             # $value_bin = $values[0];
280             # # TBM bad format
281              
282             # decode the option if we know how, otherwise use the original value
283             $self->addSubOptionRaw( $code, $subcode, $options{$format}
284 0 0       0 ? $options{$format}->(@values)
285             : $value );
286              
287             }
288              
289             sub getOptionRaw {
290 49     49 1 55 my ( $self, $key ) = @_;
291             return $self->{options}->{$key}
292 49 100       132 if exists( $self->{options}->{$key} );
293             return
294 12         46 }
295              
296             sub getOptionValue {
297 31     31 1 67 my $self = shift;
298 31         27 my $code = shift;
299              
300             carp("getOptionValue: unknown format for code ($code)")
301 31 50       61 unless exists( $DHO_FORMATS{$code} );
302              
303 31         32 my $format = $DHO_FORMATS{$code};
304 31         22 my $subcodes;
305              
306 31 50       50 if ($format eq 'suboptions') {
307 0   0     0 $subcodes = $REV_SUBOPTION_CODES{$code} || {}
308             }
309              
310 31         47 my $value_bin = $self->getOptionRaw($code);
311              
312 31 100       62 return unless defined $value_bin;
313              
314             # my @values;
315              
316             # hash out these options for speed and sanity
317             my %options = (
318 4     4   12 inet => sub { return unpackinets_array(shift) },
319 3     3   8 inets => sub { return unpackinets_array(shift) },
320 2     2   6 inets2 => sub { return unpackinets_array(shift) },
321 2     2   6 int => sub { return unpack( 'N', shift ) },
322 1     1   3 short => sub { return unpack( 'n', shift ) },
323 0     0   0 shorts => sub { return unpack( 'n*', shift ) },
324 6     6   17 byte => sub { return unpack( 'C', shift ) },
325 3     3   8 bytes => sub { return unpack( 'C*', shift ) },
326 3     3   4 string => sub { return shift },
327 0     0   0 clientid => sub { return unpackclientid(shift) },
328 0     0   0 sipserv => sub { return unpacksipserv(shift) },
329 0     0   0 csr => sub { return unpackcsr(shift) },
330 0     0   0 suboptions => sub { return unpacksuboptions(shift) },
331              
332 24         296 );
333              
334             # } elsif ($format eq 'ids') {
335             # $values[0] = $value_bin;
336             # # TBM, bad format
337              
338             # decode the options if we know the format
339 24 50       53 if ($options{$format}) {
340             $value_bin = join(q|, |,
341 34 50 0     84 map { ref $_ ? sprintf '%s => %s', $subcodes->{$_->[0]} || $_->[0], # FIXME needs to guess if hex or ascii, quote if whitespace padding
342             unpack('a*',$_->[1]) : $_ }
343 24         35 ( $options{$format}->($value_bin) ))
344             }
345              
346             # if we cant work out the format
347 24         220 return $value_bin
348              
349             } # getOptionValue
350              
351             sub getSubOptionRaw {
352 0     0 1 0 my ( $self, $key, $subkey ) = @_;
353             return $self->{options}->{$key}->{$subkey}
354 0 0       0 if exists( $self->{options}->{$key}->{$subkey} );
355 0         0 return;
356             }
357              
358       0 1   sub getSubOptionValue {
359              
360             # FIXME
361             #~ my $self = shift;
362             #~ my $code = shift;
363             #~
364             #~ carp("getOptionValue: unknown format for code ($code)")
365             #~ unless exists( $DHO_FORMATS{$code} );
366             #~
367             #~ my $format = $DHO_FORMATS{$code};
368             #~
369             #~ my $value_bin = $self->getOptionRaw($code);
370             #~
371             #~ return unless defined $value_bin;
372             #~
373             #~ my @values;
374             #~
375             #~ # hash out these options for speed and sanity
376             #~ my %options = (
377             #~ inet => sub { return unpackinets_array(shift) },
378             #~ inets => sub { return unpackinets_array(shift) },
379             #~ inets2 => sub { return unpackinets_array(shift) },
380             #~ int => sub { return unpack( 'N', shift ) },
381             #~ short => sub { return unpack( 'n', shift ) },
382             #~ shorts => sub { return unpack( 'n*', shift ) },
383             #~ byte => sub { return unpack( 'C', shift ) },
384             #~ bytes => sub { return unpack( 'C*', shift ) },
385             #~ string => sub { return shift },
386             #~
387             #~ );
388             #~
389             #~ # } elsif ($format eq 'relays') {
390             #~ # @values = $self->decodeRelayAgent($value_bin);
391             #~ # # TBM, bad format
392             #~ # } elsif ($format eq 'ids') {
393             #~ # $values[0] = $value_bin;
394             #~ # # TBM, bad format
395             #~
396             #~ # decode the options if we know the format
397             #~ return join( q| |, $options{$format}->($value_bin) )
398             #~ if $options{$format};
399             #~
400             #~ # if we cant work out the format
401             #~ return $value_bin
402              
403             } # getSubOptionValue
404              
405             sub removeOption {
406 5     5 1 6 my ( $self, $key ) = @_;
407 5 100       13 if ( exists( $self->{options}->{$key} ) ) {
408             my $i =
409 5     5   7 first { $self->{options_order}->[$_] == $key }
410 3         10 0 .. $#{ $self->{options_order} };
  3         18  
411              
412             # for ( $i = 0 ; $i < @{ $self->{options_order} } ; $i++ ) {
413             # last if ( $self->{options_order}->[$i] == $key );
414             # }
415 3 50       6 if ( $i < @{ $self->{options_order} } ) {
  3         8  
416 3         3 splice @{ $self->{options_order} }, $i, 1;
  3         5  
417             }
418 3         7 delete( $self->{options}->{$key} );
419             }
420             }
421              
422       0 1   sub removeSubOption {
423              
424             # FIXME
425             #~ my ( $self, $key ) = @_;
426             #~ if ( exists( $self->{options}->{$key} ) ) {
427             #~ my $i = first { $self->{options_order}->[$_] == $key } 0..$#{ $self->{options_order} };
428             #~ # for ( $i = 0 ; $i < @{ $self->{options_order} } ; $i++ ) {
429             #~ # last if ( $self->{options_order}->[$i] == $key );
430             #~ # }
431             #~ if ( $i < @{ $self->{options_order} } ) {
432             #~ splice @{ $self->{options_order} }, $i, 1;
433             #~ }
434             #~ delete( $self->{options}->{$key} );
435             #~ }
436              
437             }
438              
439             #=======================================================================
440             my $BOOTP_FORMAT = 'C C C C N n n a4 a4 a4 a4 a16 Z64 Z128 a*';
441              
442             #my $DHCP_MIN_LENGTH = length(pack($BOOTP_FORMAT));
443             #=======================================================================
444             sub serialize {
445 11     11   52 use bytes;
  11         11  
  11         38  
446 8     8 1 788 my ($self) = shift;
447 8         9 my $options = shift; # reference to an options hash for special options
448 8         7 my $bytes = undef;
449              
450             $bytes = pack( $BOOTP_FORMAT,
451             $self->{op}, $self->{htype}, $self->{hlen}, $self->{hops},
452             $self->{xid}, $self->{secs}, $self->{flags}, $self->{ciaddr},
453             $self->{yiaddr}, $self->{siaddr}, $self->{giaddr}, $self->{chaddr},
454 8         63 $self->{sname}, $self->{file} );
455              
456 8 50       22 if ( $self->{isDhcp} ) { # add MAGIC_COOKIE and options
457 8         12 $bytes .= MAGIC_COOKIE();
458 8         10 for my $key ( @{ $self->{options_order} } ) {
  8         21  
459 8 50       15 if ( ref($self->{options}->{$key}) eq 'ARRAY' ) {
    50          
460 0         0 for my $value ( @{$self->{options}->{$key}} ) {
  0         0  
461 0         0 $bytes .= pack( 'C', $key );
462 0         0 $bytes .= pack( 'C/a*', $value );
463             }
464             } elsif ( ref($self->{options}->{$key}) eq 'HASH' ) {
465 0         0 my $subbytes = q{};
466 0         0 for my $subkey ( @{ $self->{sub_options_order}->{$key} } ) {
  0         0  
467 0         0 $subbytes .= pack( 'C', $subkey );
468 0         0 $subbytes .= pack( 'C/a*', $self->{options}->{$key}->{$subkey} );
469             }
470 0         0 $bytes .= pack( 'C', $key );
471 0         0 $bytes .= pack( 'C', length $subbytes ) . $subbytes;
472             } else {
473 8         9 $bytes .= pack( 'C', $key );
474 8         10 $bytes .= pack( 'C/a*', $self->{options}->{$key} );
475             }
476             }
477 8         13 $bytes .= pack( 'C', 255 );
478             }
479              
480 8         8 $bytes .= $self->{padding}; # add optional padding
481              
482             # add padding if packet is less than minimum size
483 8         10 my $min_padding = BOOTP_MIN_LEN() - length($bytes);
484 8 100       16 if ( $min_padding > 0 ) {
485 2         5 $bytes .= "\0" x $min_padding;
486             }
487              
488             # test if packet is not bigger than absolute maximum MTU
489 8 100       17 if ( length($bytes) > DHCP_MAX_MTU() ) {
490 1         78 croak( 'serialize: packet too big ('
491             . length($bytes)
492             . ' greater than max MAX_MTU ('
493             . DHCP_MAX_MTU() );
494             }
495              
496             # test if packet length is not bigger than DHO_DHCP_MAX_MESSAGE_SIZE
497 7 50 66     22 if ( $options
498             && exists( $options->{ DHO_DHCP_MAX_MESSAGE_SIZE() } ) )
499             {
500              
501             # maximum packet size is specified
502 4         4 my $max_message_size = $options->{ DHO_DHCP_MAX_MESSAGE_SIZE() };
503 4 100 100     21 if ( ( $max_message_size >= BOOTP_MIN_LEN() )
504             && ( $max_message_size < DHCP_MAX_MTU() ) )
505             {
506              
507             # relevant message size
508 2 100       7 if ( length($bytes) > $max_message_size ) {
509 1         68 croak( 'serialize: message is bigger than allowed ('
510             . length($bytes)
511             . '), max specified :'
512             . $max_message_size );
513             }
514             }
515             }
516              
517 6         17 return $bytes
518              
519             } # end sub serialize
520              
521             #=======================================================================
522             sub marshall {
523              
524 11     11   3449 use bytes;
  11         11  
  11         42  
525 7     7 1 9 my ( $self, $buf ) = @_;
526 7         6 my $opt_buf;
527              
528 7 100       15 if ( length($buf) < BOOTP_ABSOLUTE_MIN_LEN() ) {
529 1         159 croak( sprintf
530             'marshall: packet too small (%d), absolute minimum size is %d',
531             length($buf),
532             BOOTP_ABSOLUTE_MIN_LEN() );
533             }
534 6 50       12 if ( length($buf) < BOOTP_MIN_LEN() ) {
535 0         0 carp( sprintf
536             'marshall: packet too small (%d), minimum size is %d',
537             length($buf),
538             BOOTP_MIN_LEN() );
539             }
540 6 100       11 if ( length($buf) > DHCP_MAX_MTU() ) {
541 1         138 croak( sprintf
542             'marshall: packet too big (%d), max MTU size is %s',
543             length($buf),
544             DHCP_MAX_MTU() );
545             }
546              
547             # if we are re-using this object, then we need to clear out these arrays
548             delete $self->{options}
549 5 50       14 if $self->{options};
550             delete $self->{options_order}
551 5 50       12 if $self->{options_order};
552              
553             (
554             $self->{op}, $self->{htype}, $self->{hlen}, $self->{hops},
555             $self->{xid}, $self->{secs}, $self->{flags}, $self->{ciaddr},
556             $self->{yiaddr}, $self->{siaddr}, $self->{giaddr}, $self->{chaddr},
557 5         42 $self->{sname}, $self->{file}, $opt_buf
558             ) = unpack( $BOOTP_FORMAT, $buf );
559              
560 5         12 $self->{isDhcp} = 0; # default to BOOTP
561 5 50 33     23 if ( ( length( $opt_buf ) > 4 )
562             && ( substr( $opt_buf, 0, 4 ) eq MAGIC_COOKIE() ) )
563             {
564              
565             # it is definitely DHCP
566 5         4 $self->{isDhcp} = 1;
567              
568 5         5 my $pos = 4; # Skip magic cookie
569 5         5 my $total = length($opt_buf);
570 5         4 my $type;
571              
572 5         16 while ( $pos < $total ) {
573              
574 80         54 $type = ord( substr( $opt_buf, $pos++, 1 ) );
575 80 100       127 next if ( $type eq DHO_PAD() ); # Skip padding bytes
576 20 100       31 last if ( $type eq DHO_END() ); # Type 'FF' signals end of options.
577 16         13 my $len = ord( substr( $opt_buf, $pos++, 1 ) ); # FIXME sanity check length
578 16         12 my $option = substr( $opt_buf, $pos, $len );
579 16         11 $pos += $len;
580 16         15 $self->addOptionRaw( $type, $option, 1 );
581              
582             }
583              
584             # verify that we ended with an "END" code
585 5 100       8 if ( $type != DHO_END() ) {
586 1         133 croak('marshall: unexpected end of options');
587             }
588              
589             # put remaining bytes in the padding attribute
590 4 50       7 if ( $pos < $total ) {
591 4         10 $self->{padding} = substr( $opt_buf, $pos, $total - $pos );
592             }
593             else {
594 0         0 $self->{padding} = q||;
595             }
596              
597             }
598             else {
599              
600             # in bootp, everything is padding
601 0         0 $self->{padding} = $opt_buf;
602              
603             }
604              
605 4         5 return $self
606              
607             } # end sub marshall
608              
609             #=======================================================================
610             sub toString {
611 0     0 1   my $self = shift;
612 0           my $s;
613              
614 0 0         $s .= sprintf( "comment = %s\n", $self->comment() )
615             if defined( $self->comment() );
616             $s .= sprintf(
617             "op = %s\n",
618             (
619             exists( $REV_BOOTP_CODES{ $self->op() } )
620 0   0       && $REV_BOOTP_CODES{ $self->op() }
621             )
622             || $self->op()
623             );
624             $s .= sprintf(
625             "htype = %s\n",
626             (
627             exists( $REV_HTYPE_CODES{ $self->htype() } )
628 0   0       && $REV_HTYPE_CODES{ $self->htype() }
629             )
630             || $self->htype()
631             );
632 0           $s .= sprintf( "hlen = %s\n", $self->hlen() );
633 0           $s .= sprintf( "hops = %s\n", $self->hops() );
634 0           $s .= sprintf( "xid = %x\n", $self->xid() );
635 0           $s .= sprintf( "secs = %i\n", $self->secs() );
636 0           $s .= sprintf( "flags = %x\n", $self->flags() );
637 0           $s .= sprintf( "ciaddr = %s\n", $self->ciaddr() );
638 0           $s .= sprintf( "yiaddr = %s\n", $self->yiaddr() );
639 0           $s .= sprintf( "siaddr = %s\n", $self->siaddr() );
640 0           $s .= sprintf( "giaddr = %s\n", $self->giaddr() );
641 0           $s .= sprintf( "chaddr = %s\n",
642             substr( $self->chaddr(), 0, 2 * $self->hlen() ) );
643 0           $s .= sprintf( "sname = %s\n", $self->sname() );
644 0           $s .= sprintf( "file = %s\n", $self->file() );
645 0           $s .= "Options : \n";
646              
647 0           for my $key ( @{ $self->{options_order} } ) {
  0            
648 0           my $value; # value of option to be printed
649              
650 0 0         if ( $key == DHO_DHCP_MESSAGE_TYPE() ) {
651 0           $value = $self->getOptionValue($key);
652             $value =
653             ( exists( $REV_DHCP_MESSAGE{$value} )
654 0   0       && $REV_DHCP_MESSAGE{$value} )
655             || $self->getOptionValue($key);
656             }
657             else {
658              
659 0 0         if ( exists( $DHO_FORMATS{$key} ) ) {
660 0 0         if ( $DHO_FORMATS{$key} eq 'suboptions' ) {
661 0           for my $subkey ( @{ $self->{sub_options_order}->{$key} } ) {
  0            
662 0           my $subvalue = join( q| |, $self->getSubOptionValue($key,$subkey) ); # FIXME fix the getSubOptionValue function
663 0           $subvalue =~
664 0           s/([[:^print:]])/ sprintf q[\x%02X], ord $1 /eg;
665             $s .= sprintf( " %s(%d) = %s\n",
666 0 0         exists $SUBOPTION_CODES{$key} ? $REV_SUBOPTION_CODES{$key}{$subkey} : '',
667             $key, $subvalue );
668             }
669 0           $value = 'see above';
670             } else {
671 0           $value = join( q| |, $self->getOptionValue($key) );
672             }
673             }
674             else {
675 0           $value = $self->getOptionRaw($key);
676 0           print "went here for $key\n";
677             }
678              
679             # convert to printable text
680 0           $value =~
681 0           s/([[:^print:]])/ sprintf q[\x%02X], ord $1 /eg;
682             }
683             $s .= sprintf( " %s(%d) = %s\n",
684 0 0         exists $REV_DHO_CODES{$key} ? $REV_DHO_CODES{$key} : '',
685             $key, $value );
686             }
687             $s .= sprintf(
688             "padding [%s] = %s\n",
689             length( $self->{padding} ),
690             unpack( 'H*', $self->{padding} )
691 0           );
692              
693 0           return $s
694              
695             } # end toString
696              
697             #=======================================================================
698             # internal utility functions
699              
700             sub packsuboptions {
701 0 0   0 1   my @relay_opt = @_
702             or return;
703              
704 0           my $buf = '';
705 0           for my $opt (@relay_opt) {
706 0           my $value = pack( 'C/a*', $opt->[1]);
707 0           $buf .= pack( 'C', $opt->[0])
708             . pack( 'C', length($value))
709             . $value;
710             }
711              
712 0           return pack( 'C', length($buf) ) . $buf
713             }
714              
715             sub unpacksuboptions { # prints a human readable suboptions
716              
717 11     11   8521 use bytes;
  11         13  
  11         34  
718 0 0   0 1   my $opt_buf = shift or return;
719              
720 0           my @relay_opt;
721 0           my $pos = 0;
722 0           my $total = length($opt_buf);
723              
724 0           while ( $pos < $total ) {
725 0           my $type = ord( substr( $opt_buf, $pos++, 1 ) );
726 0           my $len = ord( substr( $opt_buf, $pos++, 1 ) ); # FIXME check this more
727 0           my $option = substr( $opt_buf, $pos, $len );
728 0           $pos += $len;
729 0           push @relay_opt, [ $type, $option ];
730             }
731              
732             return @relay_opt
733              
734 0           }
735              
736             sub packclientid {
737             return shift
738             # croak('pack clientid field still WIP');
739 0     0 1   }
740              
741             sub unpackclientid {
742              
743 0 0   0 1   my $clientid = shift
744             or return;
745              
746 0           my $type = unpack('C',substr( $clientid, 0, 1 ));
747              
748 0 0         if ($type == 0) { # text
749 0           return substr( $clientid, 1, length($clientid) )
750             }
751 0 0         if ($type == 1) { # ethernet
752 0           return unpack('H*',substr( $clientid, 1, length($clientid) ))
753             }
754              
755 0           return $clientid
756              
757             }
758              
759             sub packsipserv {
760             return shift
761             # croak('pack sipserv field still WIP');
762 0     0 1   }
763              
764             sub unpacksipserv {
765              
766 0 0   0 1   my $sipserv = shift
767             or return;
768              
769 0           my $type = unpack('C',substr( $sipserv, 0, 1 ));
770              
771             # if ($type == 0) { # text
772             # return substr( $sipserv, 1, length($clientid) )
773             # }
774 0 0         if ($type == 1) { # ipv4
775 0           return unpackinet(substr( $sipserv, 1, length($sipserv) ))
776             }
777              
778 0           return $sipserv
779              
780             }
781              
782             sub packcsr {
783             # catch empty value
784 0     0 1   my $results = [ '' ];
785              
786 0           for my $pair ( @{$_[0]} ) {
  0            
787 0 0         push @$results, ''
788             if (length($results->[-1]) > 255 - 8);
789              
790 0           my ($ip, $mask) = split /\//, $pair->[0];
791 0 0         $mask = '32'
792             unless (defined($mask));
793              
794 0           my $addr = packinet($ip);
795 0           $addr = substr $addr, 0, int(($mask - 1)/8 + 1);
796              
797 0           $results->[-1] .= pack('C', $mask) . $addr;
798 0           $results->[-1] .= packinet($pair->[1]);
799             }
800              
801 0           return $results;
802             }
803              
804             sub unpackcsr {
805 0 0   0 1   my $csr = shift
806             or return;
807              
808 0           croak('unpack csr field still WIP');
809              
810             }
811              
812             #=======================================================================
813              
814             1;
815              
816             __END__