File Coverage

blib/lib/Data/BISON/Decoder.pm
Criterion Covered Total %
statement 27 141 19.1
branch 0 38 0.0
condition 0 12 0.0
subroutine 9 23 39.1
pod 1 1 100.0
total 37 215 17.2


line stmt bran cond sub pod time code
1             package Data::BISON::Decoder;
2              
3 3     3   42984 use warnings;
  3         4  
  3         87  
4 3     3   14 use strict;
  3         5  
  3         76  
5 3     3   14 use Carp;
  3         6  
  3         167  
6 3     3   532 use Data::BISON::Constants;
  3         8  
  3         370  
7 3     3   734 use Data::BISON::yEnc qw(decode_yEnc);
  3         6  
  3         150  
8 3     3   941 use Encode qw();
  3         10637  
  3         71  
9 3     3   18 use Config;
  3         5  
  3         119  
10              
11 3     3   15 use version; our $VERSION = qv( '0.0.3' );
  3         4  
  3         19  
12              
13             our @ISA = qw(Data::BISON::Base);
14 3     3   942 use Data::BISON::Base {};
  3         5  
  3         17  
15              
16             sub _make_object {
17 0     0     my ( $self, $obj ) = @_;
18 0 0         if ( $self->{backref} ) {
19 0           push @{ $self->{objects} }, $obj;
  0            
20             }
21 0           return $obj;
22             }
23              
24             sub _decode_int {
25 0     0     my ( $self, $type, $data ) = @_;
26              
27             # TODO: Speed this up using unpack where we can
28 0           my $len = $type - ( INT8 - 1 );
29 0           my @rep = splice @$data, 0, $len;
30 0           my $byte = pop @rep;
31 0 0         my $flip = ( $byte & 0x80 ) ? 0xFF : 0x00;
32 0           my $val = $byte ^ $flip;
33              
34 0           for ( 2 .. $len ) {
35 0           $val = $val * 256 + pop @rep ^ $flip;
36             }
37              
38 0 0         if ( $flip ) {
39              
40             # Restore 2s complement
41 0           $val = -$val - 1;
42             }
43              
44 0           return $val;
45             }
46              
47             sub _decode_float {
48 0     0     my ( $self, $type, $data ) = @_;
49 0 0         my ( $len, $format ) = ( $type == FLOAT ) ? ( 4, 'f' ) : ( 8, 'd' );
50 0           my @rep = splice @$data, 0, $len;
51              
52 0 0         if ( $Config{byteorder} eq '4321' ) {
53 0           @rep = reverse @rep;
54             }
55              
56 0           return unpack( $format, join '', map { chr $_ } @rep );
  0            
57             }
58              
59             sub _decode_string {
60 0     0     my ( $self, $type, $data ) = @_;
61              
62 0           my @str = ();
63 0           my $byte = shift @$data;
64 0           while ( $byte ) {
65 0 0         $byte = shift @$data if $byte == 0x5C;
66 0           push @str, $byte;
67 0           $byte = shift @$data;
68             }
69              
70 0           return Encode::decode( UTF8, join '', map { chr $_ } @str );
  0            
71             }
72              
73             sub _decode_size {
74 0     0     my ( $self, $data ) = @_;
75 0           my ( $lo, $hi ) = splice @$data, 0, 2;
76 0           my $size = $lo + 256 * $hi;
77 0 0 0       if ( $self->{version} > 1 && $size & 0x8000 ) {
78 0           $size &= 0x7FFF;
79 0           my ( $lo, $hi ) = splice @$data, 0, 2;
80 0           $size += ( $lo + 256 * $hi ) << 15;
81             }
82 0           return $size;
83             }
84              
85             sub _decode_version {
86 0     0     my ( $self, $data ) = @_;
87 0           my ( $tag, $lo, $hi ) = splice @$data, 0, 3;
88 0           my $version = $lo + 256 * $hi;
89              
90 0           $self->{version} = $version & 0x7FFF;
91 0           $self->{backref} = $version & 0x8000;
92             }
93              
94             sub _decode_array {
95 0     0     my ( $self, $type, $data ) = @_;
96 0           my $size = $self->_decode_size( $data );
97 0           my $ar = $self->_make_object( [] );
98 0           for ( 1 .. $size ) {
99 0           push @$ar, $self->_decode( $data );
100             }
101              
102 0           return $ar;
103             }
104              
105             sub _read_hash {
106 0     0     my ( $self, $data, $size ) = @_;
107 0           my $obj = $self->_make_object( {} );
108 0           for ( 1 .. $size ) {
109 0           my $key = $self->_decode_string( STRING, $data );
110 0           $obj->{$key} = $self->_decode( $data );
111             }
112 0           return $obj;
113             }
114              
115             sub _decode_hash {
116 0     0     my ( $self, $type, $data ) = @_;
117 0           my $size = $self->_decode_size( $data );
118 0           return $self->_read_hash( $data, $size );
119             }
120              
121             sub _decode_object {
122 0     0     my ( $self, $type, $data ) = @_;
123 0           my $size = $self->_decode_size( $data );
124 0           my $class = $self->_decode_string( STRING, $data );
125              
126             # TODO: Map classname here
127              
128             # Validate it. We don't want to eval just /anything/
129 0 0         die "Bad class name '$class'\n"
130             unless $class =~ /^ \w+ (?: :: \w+ ) * $/x;
131              
132             # TODO: Find out whether the class exists before we attempt to use
133             # it - it may have been defined in some other package.
134             # Try to load the class
135 0           eval "use $class";
136 0 0         if ( $@ ) {
137 0           chomp $@;
138 0           die "Failed to load class ($@)\n";
139             }
140              
141 0           my $obj = $self->_read_hash( $data, $size );
142              
143 0           return bless $obj, $class;
144             }
145              
146             sub _decode_backref {
147 0     0     my ( $self, $type, $data ) = @_;
148              
149 0 0         die "Unexpected backref\n"
150             unless $self->{backref};
151              
152 0           my $ref = $self->_decode_size( $data );
153              
154 0           die "Backref out of range\n"
155 0 0 0       if $ref < 0 || $ref >= @{ $self->{objects} };
156              
157 0           return $self->{objects}->[$ref];
158             }
159              
160             sub _decode_stream {
161 0     0     my ( $self, $type, $data ) = @_;
162 0           my $size = $self->_decode_size( $data );
163 0           my @rep = splice @$data, 0, $size;
164 0           return join '', map { chr $_ } @rep;
  0            
165             }
166              
167             my @TYPE_MAP = (
168             undef,
169             sub { return undef },
170             sub { return undef },
171             sub { return 1 },
172             sub { return 0 },
173             sub { my $self = shift; return $self->_decode_int( @_ ) },
174             sub { my $self = shift; return $self->_decode_int( @_ ) },
175             sub { my $self = shift; return $self->_decode_int( @_ ) },
176             sub { my $self = shift; return $self->_decode_int( @_ ) },
177             sub { my $self = shift; return $self->_decode_int( @_ ) },
178             sub { my $self = shift; return $self->_decode_int( @_ ) },
179             sub { my $self = shift; return $self->_decode_int( @_ ) },
180             sub { my $self = shift; return $self->_decode_int( @_ ) },
181             sub { my $self = shift; return $self->_decode_float( @_ ) },
182             sub { my $self = shift; return $self->_decode_float( @_ ) },
183             sub { my $self = shift; return $self->_decode_string( @_ ) },
184             sub { my $self = shift; return $self->_decode_array( @_ ) },
185             sub { my $self = shift; return $self->_decode_hash( @_ ) },
186             sub { my $self = shift; return $self->_decode_stream( @_ ) },
187             sub { my $self = shift; return $self->_decode_object( @_ ) },
188             sub { my $self = shift; return $self->_decode_backref( @_ ) },
189             );
190              
191             sub _decode {
192 0     0     my $self = shift;
193 0           my $data = shift;
194              
195 0           my $type = shift @$data;
196 0 0         die "Unexpected end of data\n"
197             unless defined $type;
198              
199 0 0         if ( my $handler = $TYPE_MAP[$type] ) {
200 0           my $obj = $handler->( $self, $type, $data );
201              
202             # We only push scalars here to save doing it in the individual
203             # handlers. HASHes, ARRAYs and OBJECTs must push themselves
204             # early in case they contain a reference to themself.
205              
206 0 0 0       if ( $type < ARRAY && $self->{backref} ) {
207 0           push @{ $self->{objects} }, $obj;
  0            
208             }
209 0           return $obj;
210             }
211             else {
212 0           die sprintf( "Unrecognised object type 0x%02x\n", $type );
213             }
214             }
215              
216             sub decode {
217 0     0 1   my $self = shift;
218              
219 0           $self->{version} = 1;
220 0           $self->{backref} = 0;
221 0           $self->{objects} = [];
222              
223 0 0         croak __PACKAGE__ . "->decode takes a single argument"
224             unless @_ == 1;
225              
226 0           my $data = shift;
227              
228 0 0         if ( substr( $data, 0, 3 ) eq PWL ) {
229 0           $data = decode_yEnc( $data );
230             }
231              
232 0 0         croak "Unrecognised BISON data (no signature found)"
233             unless substr( $data, 0, 3 ) eq FMB;
234              
235 0           my @data = map { ord $_ } split //, $data;
  0            
236 0           my $len = @data;
237 0           splice @data, 0, 3;
238              
239 0 0 0       if ( @data && $data[0] == VERSION ) {
240 0           $self->_decode_version( \@data );
241             }
242              
243 0           my $obj = eval { $self->_decode( \@data ) };
  0            
244              
245 0 0         if ( $@ ) {
246 0           my $pos = $len - @data - 1;
247 0           chomp $@;
248 0           croak
249             sprintf( "%s at offset %d (0x%x) in data stream", $@, $pos, $pos );
250             }
251              
252 0           return $obj;
253             }
254              
255             1;
256             __END__