File Coverage

blib/lib/Data/Transform/Block.pm
Criterion Covered Total %
statement 70 73 95.8
branch 31 44 70.4
condition 3 6 50.0
subroutine 13 13 100.0
pod 3 3 100.0
total 120 139 86.3


line stmt bran cond sub pod time code
1             # vim: ts=3 sw=3 expandtab
2             package Data::Transform::Block;
3 1     1   1527 use strict;
  1         2  
  1         39  
4 1     1   589 use Data::Transform;
  1         3  
  1         30  
5              
6 1     1   6 use vars qw($VERSION @ISA);
  1         2  
  1         71  
7             $VERSION = '0.01';
8             @ISA = qw(Data::Transform);
9              
10 1     1   4 use Carp qw(croak);
  1         2  
  1         18935  
11              
12             sub BUFFER () { 0 }
13             sub FRAMING_BUFFER () { 1 }
14             sub BLOCK_SIZE () { 2 }
15             sub EXPECTED_SIZE () { 3 }
16             sub ENCODER () { 4 }
17             sub DECODER () { 5 }
18              
19             =head1 NAME
20              
21             Data::Transform::Block - translate data between streams and blocks
22              
23             =head1 SYNOPSIS
24              
25             #!perl
26              
27             use warnings;
28             use strict;
29             use Data::Transform::Block;
30              
31             my $filter = Data::Transform::Block->new( BlockSize => 8 );
32              
33             # Prints three lines: abcdefgh, ijklmnop, qrstuvwx.
34             # Bytes "y" and "z" remain in the buffer and await completion of the
35             # next 8-byte block.
36              
37             $filter->get_one_start([ "abcdefghijklmnopqrstuvwxyz" ]);
38             while (1) {
39             my $block = $filter->get_one();
40             last unless @$block;
41             print $block->[0], "\n";
42             }
43              
44             # Print one line: yz123456
45              
46             $filter->get_one_start([ "123456" ]);
47             while (1) {
48             my $block = $filter->get_one();
49             last unless @$block;
50             print $block->[0], "\n";
51             }
52              
53             =head1 DESCRIPTION
54              
55             Data::Transform::Block translates data between serial streams and blocks.
56             It can handle fixed-length and length-prepended blocks, and it may be
57             extended to handle other block types.
58              
59             Fixed-length blocks are used when Block's constructor is called with a
60             BlockSize value. Otherwise the Block filter uses length-prepended
61             blocks.
62              
63             Users who specify block sizes less than one deserve what they get.
64              
65             In variable-length mode, a LengthCodec parameter may be specified.
66             The LengthCodec value should be a reference to a list of two
67             functions: the length encoder, and the length decoder:
68              
69             LengthCodec => [ \&encoder, \&decoder ]
70              
71             The encoder takes a reference to a buffer and prepends the buffer's
72             length to it. The default encoder prepends the ASCII representation
73             of the buffer's length and a chr(0) byte to separate the length from
74             the actual data:
75              
76             sub _default_encoder {
77             my $stuff = shift;
78             substr($$stuff, 0, 0) = length($$stuff) . "\0";
79             return;
80             }
81              
82             The corresponding decoder returns the block length after removing it
83             and the separator from the buffer. It returns nothing if no length
84             can be determined.
85              
86             sub _default_decoder {
87             my $stuff = shift;
88             unless ($$stuff =~ s/^(\d+)\0//s) {
89             warn length($1), " strange bytes removed from stream"
90             if $$stuff =~ s/^(\D+)//s;
91             return;
92             }
93             return $1;
94             }
95              
96             This filter holds onto incomplete blocks until they are completed.
97              
98             =head1 METHODS
99              
100             Data::Transform::Block implements the L API. Only
101             differences and additions are documented here.
102              
103             =cut
104              
105             sub _default_decoder {
106 16     16   21 my $stuff = shift;
107              
108 16 100       64 unless ($$stuff =~ s/^(\d+)\0//s) {
109 11 50       29 warn length($1), " strange bytes removed from stream"
110             if $$stuff =~ s/^(\D+)//s;
111 11         592 return;
112             }
113              
114 5         34 return $1;
115             }
116              
117             sub _default_encoder {
118 8     8   11 my $stuff = shift;
119              
120 8         17 substr($$stuff, 0, 0) = length($$stuff) . "\0";
121              
122 8         11 return;
123             }
124              
125             sub new {
126 5     5 1 7233 my $type = shift;
127              
128 5 50       21 croak "$type must be given an even number of parameters" if @_ & 1;
129 5         16 my %params = @_;
130              
131 5         7 my ($encoder, $decoder);
132 5         11 my $block_size = delete $params{BlockSize};
133 5 100       14 if (defined $block_size) {
134 3 50       8 croak "$type doesn't support zero or negative block sizes"
135             if $block_size < 1;
136 3 50       9 croak "Can't use both LengthCodec and BlockSize at the same time"
137             if exists $params{LengthCodec};
138             }
139             else {
140 2         5 my $codec = delete $params{LengthCodec};
141 2 100       6 if ($codec) {
142 1 50       4 croak "LengthCodec must be an array reference"
143             unless ref($codec) eq "ARRAY";
144 1 50       4 croak "LengthCodec must contain two items"
145             unless @$codec == 2;
146 1         2 ($encoder, $decoder) = @$codec;
147 1 50       4 croak "LengthCodec encoder must be a code reference"
148             unless ref($encoder) eq "CODE";
149 1 50       4 croak "LengthCodec decoder must be a code reference"
150             unless ref($decoder) eq "CODE";
151             }
152             else {
153 1         3 $encoder = \&_default_encoder;
154 1         4 $decoder = \&_default_decoder;
155             }
156             }
157              
158 5         15 my $self = [
159             [], # BUFFER
160             '', # FRAMING_BUFFER
161             $block_size, # BLOCK_SIZE
162             undef, # EXPECTED_SIZE
163             $encoder, # ENCODER
164             $decoder, # DECODER
165             ];
166              
167 5         48 return bless $self, $type;
168             }
169              
170             sub clone {
171 1     1 1 3447 my $self = shift;
172              
173 1         6 my $new = [
174             [],
175             '',
176             $self->[BLOCK_SIZE],
177             undef,
178             $self->[ENCODER],
179             $self->[DECODER],
180             ];
181              
182 1         6 return bless $new, ref $self
183             }
184              
185             sub get_pending {
186 2     2 1 10 my $self = shift;
187              
188 2         3 my @ret = @{$self->[BUFFER]};
  2         6  
189 2 50       14 if (length $self->[FRAMING_BUFFER]) {
190 0 0 0     0 if (not defined $self->[BLOCK_SIZE] and
191             defined $self->[EXPECTED_SIZE] ) {
192 0         0 unshift @ret, $self->[ENCODER]->($self->FRAMING_BUFFER);
193             } else {
194 0         0 unshift @ret, $self->[FRAMING_BUFFER];
195             }
196             }
197 2 100       12 return @ret ? \@ret : undef;
198             }
199              
200             # get() is inherited from Data::Transform.
201             # get_one_start() is inherited from Data::Transform.
202             # get_one() is inherited from Data::Transform.
203              
204             sub _handle_get_data {
205 58     58   68 my ($self, $data) = @_;
206              
207 58 100       135 $self->[FRAMING_BUFFER] .= $data
208             if (defined $data);
209              
210             # Need to check lengths in octets, not characters.
211 1 50   1   3 BEGIN { eval { require bytes } and bytes->import; }
  1         32  
212              
213             # If a block size is specified, then pull off a block of that many
214             # bytes.
215              
216 58 100       227 if (defined $self->[BLOCK_SIZE]) {
217 16 100       57 return unless length($self->[FRAMING_BUFFER]) >= $self->[BLOCK_SIZE];
218 7         14 my $block = substr($self->[FRAMING_BUFFER], 0, $self->[BLOCK_SIZE]);
219 7         13 substr($self->[FRAMING_BUFFER], 0, $self->[BLOCK_SIZE]) = '';
220 7         25 return $block;
221             }
222              
223             # Otherwise we're doing the variable-length block thing. Look for a
224             # length marker, and then pull off a chunk of that length. Repeat.
225              
226 42 100 100     215 if (
227             defined($self->[EXPECTED_SIZE]) ||
228             defined(
229             $self->[EXPECTED_SIZE] = $self->[DECODER]->(\$self->[FRAMING_BUFFER])
230             )
231             ) {
232 18 100       100 return if length($self->[FRAMING_BUFFER]) < $self->[EXPECTED_SIZE];
233              
234             # Four-arg substr() would be better here, but it's not compatible
235             # with Perl as far back as we support.
236 10         22 my $block = substr($self->[FRAMING_BUFFER], 0, $self->[EXPECTED_SIZE]);
237 10         15 substr($self->[FRAMING_BUFFER], 0, $self->[EXPECTED_SIZE]) = '';
238 10         13 $self->[EXPECTED_SIZE] = undef;
239              
240 10         32 return $block;
241             }
242              
243 24         202 return;
244             }
245              
246             sub _handle_put_data {
247 23     23   33 my ($self, $block) = @_;
248              
249             # Need to check lengths in octets, not characters.
250 1 50   1   278 BEGIN { eval { require bytes } and bytes->import; }
  1         20  
251              
252             # If a block size is specified, then just assume the put is right.
253             # This will cause quiet framing errors on the receiving side. Then
254             # again, we'll have quiet errors if the block sizes on both ends
255             # differ. Ah, well!
256 23 100       61 if (defined $self->[BLOCK_SIZE]) {
257 7         33 return $block;
258             }
259              
260             # No specified block size. Do the variable-length block
261             # thing. This steals a lot of Artur's code from the
262             # Reference filter.
263 16         41 $self->[ENCODER]->(\$block);
264 16         100 return $block;
265             }
266              
267             1;
268              
269             __END__