~ubuntu-branches/ubuntu/trusty/sphinxsearch/trusty-proposed

« back to all changes in this revision

Viewing changes to contrib/perlapi/Sphinx.pm

  • Committer: Bazaar Package Importer
  • Author(s): Radu Spineanu
  • Date: 2009-11-17 22:19:42 UTC
  • Revision ID: james.westby@ubuntu.com-20091117221942-nm751ur701m9vrzt
Tags: upstream-0.9.8.1
ImportĀ upstreamĀ versionĀ 0.9.8.1

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#
 
2
# Copyright (c) 2001-2006, Len Kranendonk. All rights reserved.
 
3
#
 
4
# This program is free software; you can redistribute it and/or modify
 
5
# it under the terms of the GNU General Public License. You should have
 
6
# received a copy of the GPL license along with this program; if you
 
7
# did not, you can find it at http://www.gnu.org/
 
8
#
 
9
 
 
10
#-------------------------------------------------------------
 
11
# Sphinx Perl searchd client API
 
12
#-------------------------------------------------------------
 
13
 
 
14
package Sphinx;
 
15
 
 
16
use strict;
 
17
use Carp;
 
18
use Socket;
 
19
use base 'Exporter';
 
20
 
 
21
# Constants to export.
 
22
our @EXPORT = qw(       
 
23
                SPH_MATCH_ALL SPH_MATCH_ANY SPH_MATCH_PHRASE SPH_MATCH_BOOLEAN SPH_MATCH_EXTENDED
 
24
                SPH_SORT_RELEVANCE SPH_SORT_ATTR_DESC SPH_SORT_ATTR_ASC SPH_SORT_TIME_SEGMENTS SPH_SORT_EXTENDED
 
25
                SPH_GROUPBY_DAY SPH_GROUPBY_WEEK SPH_GROUPBY_MONTH SPH_GROUPBY_YEAR SPH_GROUPBY_ATTR
 
26
                );
 
27
 
 
28
# known searchd commands
 
29
use constant SEARCHD_COMMAND_SEARCH     => 0;
 
30
use constant SEARCHD_COMMAND_EXCERPT    => 1;
 
31
 
 
32
# current client-side command implementation versions
 
33
use constant VER_COMMAND_SEARCH         => 0x104;
 
34
use constant VER_COMMAND_EXCERPT        => 0x100;
 
35
 
 
36
# known searchd status codes
 
37
use constant SEARCHD_OK                 => 0;
 
38
use constant SEARCHD_ERROR              => 1;
 
39
use constant SEARCHD_RETRY              => 2;
 
40
 
 
41
# known match modes
 
42
use constant SPH_MATCH_ALL              => 0;
 
43
use constant SPH_MATCH_ANY              => 1;
 
44
use constant SPH_MATCH_PHRASE           => 2;
 
45
use constant SPH_MATCH_BOOLEAN          => 3;
 
46
use constant SPH_MATCH_EXTENDED         => 4;
 
47
 
 
48
# known sort modes
 
49
use constant SPH_SORT_RELEVANCE         => 0;
 
50
use constant SPH_SORT_ATTR_DESC         => 1;
 
51
use constant SPH_SORT_ATTR_ASC          => 2;
 
52
use constant SPH_SORT_TIME_SEGMENTS     => 3;
 
53
use constant SPH_SORT_EXTENDED          => 4;
 
54
 
 
55
# known attribute types
 
56
use constant SPH_ATTR_INTEGER           => 1;
 
57
use constant SPH_ATTR_TIMESTAMP         => 2;
 
58
 
 
59
# known grouping functions
 
60
use constant SPH_GROUPBY_DAY            => 0;
 
61
use constant SPH_GROUPBY_WEEK           => 1;
 
62
use constant SPH_GROUPBY_MONTH          => 2;
 
63
use constant SPH_GROUPBY_YEAR           => 3;
 
64
use constant SPH_GROUPBY_ATTR           => 4;
 
65
 
 
66
 
 
67
#-------------------------------------------------------------
 
68
# common stuff
 
69
#-------------------------------------------------------------
 
70
 
 
71
# create a new client object and fill defaults
 
72
sub new {
 
73
        my $class = shift;
 
74
        my $self = {
 
75
                _host           => 'localhost',
 
76
                _port           => 3312,
 
77
                _offset         => 0,
 
78
                _limit          => 20,
 
79
                _mode           => SPH_MATCH_ALL,
 
80
                _weights        => [],
 
81
                _sort           => SPH_SORT_RELEVANCE,
 
82
                _sortby         => "",
 
83
                _min_id         => 0,
 
84
                _max_id         => 0xFFFFFFFF,
 
85
                _min            => {},
 
86
                _max            => {},
 
87
                _filter         => {},
 
88
                _groupby        => "",
 
89
                _groupfunc      => SPH_GROUPBY_DAY,
 
90
                _maxmatches     => 1000,
 
91
                _error          => '',
 
92
                _warning        => '',
 
93
        };
 
94
        bless($self, $class);
 
95
        return $self;
 
96
}
 
97
 
 
98
# get last error message (string)
 
99
sub GetLastError {
 
100
        my $self = shift;
 
101
        return $self->{_error};
 
102
}
 
103
 
 
104
# get last warning message (string)
 
105
sub GetLastWarning {
 
106
        my $self = shift;
 
107
        return $self->{_warning};
 
108
}
 
109
 
 
110
# set searchd server
 
111
sub SetServer {
 
112
        my $self = shift;
 
113
        my $host = shift;
 
114
        my $port = shift;
 
115
 
 
116
        croak("host is not defined") unless defined($host);
 
117
        croak("port is not defined") unless defined($port);
 
118
 
 
119
        $self->{_host} = $host;
 
120
        $self->{_port} = $port;
 
121
}
 
122
 
 
123
#-------------------------------------------------------------
 
124
 
 
125
# connect to searchd server
 
126
 
 
127
sub _Connect {
 
128
        my $self = shift;
 
129
 
 
130
        # connect socket
 
131
        my $fp;
 
132
        socket($fp, PF_INET, SOCK_STREAM, getprotobyname('tcp')) || Carp::croak("socket: ".$!);
 
133
        my $dest = sockaddr_in($self->{_port}, inet_aton($self->{_host}));
 
134
        connect($fp, $dest);
 
135
        
 
136
        if($!) {
 
137
                $self->{_error} = "connection to {$self->{_host}}:{$self->{_port}} failed: $!";
 
138
                return 0;
 
139
        }       
 
140
        
 
141
        # check version
 
142
        my $buf = '';
 
143
        recv($fp, $buf, 4, 0) ne "" || croak("recv: ".$!);
 
144
        my $v = unpack("N*", $buf);
 
145
        $v = int($v);
 
146
        if($v < 1) {
 
147
                close($fp) || croak("close: $!");
 
148
                $self->{_error} = "expected searchd protocol version 1+, got version '$v'";
 
149
        }
 
150
 
 
151
        # All ok, send my version
 
152
        send($fp, pack("N", 1),0);
 
153
        return $fp;
 
154
}
 
155
 
 
156
#-------------------------------------------------------------
 
157
 
 
158
# get and check response packet from searchd server
 
159
sub _GetResponse {
 
160
        my $self = shift;
 
161
        my $fp = shift;
 
162
        my $client_ver = shift;
 
163
 
 
164
        my $header;
 
165
        recv($fp, $header, 8, 0) ne "" || croak("recv: ".$!);
 
166
 
 
167
        my ($status, $ver, $len ) = unpack("n2N", $header);
 
168
        my ($chunk, $response);
 
169
        while(defined($chunk = <$fp>)) {
 
170
                $response .= $chunk;
 
171
        }
 
172
        close ( $fp );
 
173
 
 
174
        # check response
 
175
        if ( !$response || length($response) != $len ) {
 
176
                $self->{_error} = $len 
 
177
                        ? "failed to read searchd response (status=$status, ver=$ver, len=$len, read=". length($response) . ")"
 
178
                        : "received zero-sized searchd response";
 
179
                return 0;
 
180
        }
 
181
 
 
182
        # check status
 
183
        if ( $status==SEARCHD_ERROR ) {
 
184
                $self->{_error} = "searchd error: " . substr ( $response, 4 );
 
185
                return 0;
 
186
        }
 
187
        if ( $status==SEARCHD_RETRY ) {
 
188
                $self->{_error} = "temporary searchd error: " . substr ( $response, 4 );
 
189
                return 0;
 
190
        }
 
191
        if ( $status!=SEARCHD_OK ) {
 
192
                $self->{_error} = "unknown status code '$status'";
 
193
                return 0;
 
194
        }
 
195
 
 
196
        # check version
 
197
        if ( $ver<$client_ver ) {
 
198
                $self->{_warning} = sprintf ( "searchd command v.%d.%d older than client's v.%d.%d, some options might not work",
 
199
                                $ver>>8, $ver&0xff, $client_ver>>8, $client_ver&0xff );
 
200
        }
 
201
        return $response;
 
202
}
 
203
 
 
204
 
 
205
#-------------------------------------------------------------
 
206
# searching
 
207
#-------------------------------------------------------------
 
208
 
 
209
# set match offset/limits
 
210
sub SetLimits {
 
211
        my $self = shift;
 
212
        my $offset = shift;
 
213
        my $limit = shift;
 
214
        my $max = shift || 0;
 
215
        croak("offset should be an integer >= 0") unless ($offset =~ /^\d+$/ && $offset >= 0) ;
 
216
        croak("limit should be an integer >= 0") unless ($limit =~ /^\d+/ && $offset >= 0);
 
217
        $self->{_offset} = $offset;
 
218
        $self->{_limit}  = $limit;
 
219
        if($max > 0) {
 
220
                $self->{_maxmatches} = $max;
 
221
        }
 
222
}
 
223
 
 
224
# set match mode
 
225
sub SetMatchMode {
 
226
        my $self = shift;
 
227
        my $mode = shift;
 
228
        croak("Match mode not defined") unless defined($mode);
 
229
        croak("Unknown matchmode: $mode") unless ( $mode==SPH_MATCH_ALL || $mode==SPH_MATCH_ANY
 
230
                || $mode==SPH_MATCH_PHRASE || $mode==SPH_MATCH_BOOLEAN || $mode==SPH_MATCH_EXTENDED );
 
231
        $self->{_mode} = $mode;
 
232
}
 
233
 
 
234
# set sort mode
 
235
sub SetSortMode {
 
236
        my $self = shift;
 
237
        my $mode = shift;
 
238
        my $sortby = shift;
 
239
        croak("Sort mode not defined") unless defined($mode);
 
240
        croak("Unknown sort mode: $mode") unless ( $mode==SPH_SORT_RELEVANCE
 
241
                || $mode==SPH_SORT_ATTR_DESC || $mode==SPH_SORT_ATTR_ASC
 
242
                || $mode==SPH_SORT_TIME_SEGMENTS || $mode==SPH_SORT_EXTENDED );
 
243
        croak("Sortby must be defined") unless ($mode==SPH_SORT_RELEVANCE || length($sortby));
 
244
        $self->{_sort} = $mode;
 
245
        $self->{_sortby} = $sortby;
 
246
}
 
247
 
 
248
# set per-field weights
 
249
sub SetWeights {
 
250
        my $self = shift;
 
251
        my $weights = shift;
 
252
        croak("Weights is not an array reference") unless (ref($weights) eq 'ARRAY');
 
253
        foreach my $weight (@$weights) {
 
254
                croak("Weight: $weight is not an integer") unless ($weight =~ /^\d+$/);
 
255
        }
 
256
        $self->{_weights} = $weights;
 
257
}
 
258
 
 
259
# set IDs range to match
 
260
# only match those records where document ID
 
261
# is beetwen $min and $max (including $min and $max)
 
262
sub SetIDRange {
 
263
        my $self = shift;
 
264
        my $min = shift;
 
265
        my $max = shift;
 
266
        croak("min_id is not an integer") unless ($min =~ /^\d+$/);
 
267
        croak("max_id is not an integer") unless ($max =~ /^\d+$/);
 
268
        croak("min_id is larger than or equal to max_id") unless ($min < $max);
 
269
        $self->{_min_id} = $min;
 
270
        $self->{_max_id} = $max;
 
271
}
 
272
 
 
273
sub SetFilter {
 
274
        my $self = shift;
 
275
        my $attribute = shift;
 
276
        my $values = shift;
 
277
        croak("attribute is not defined") unless (defined $attribute);
 
278
        croak("values is not an array reference") unless (ref($values) eq 'ARRAY');
 
279
        croak("values reference is empty") unless (scalar(@$values));
 
280
 
 
281
        foreach my $value (@$values) {
 
282
                croak("value $value is not an integer") unless ($value =~ /^\d+$/);
 
283
        }
 
284
        $self->{_filter}{$attribute} = $values;
 
285
}
 
286
 
 
287
# set range filter
 
288
# only match those records where $attribute column value
 
289
# is beetwen $min and $max (including $min and $max)
 
290
sub SetFilterRange {
 
291
        my $self = shift;
 
292
        my $attribute = shift;
 
293
        my $min = shift;
 
294
        my $max = shift;
 
295
        croak("attribute is not defined") unless (defined $attribute);
 
296
        croak("min: $min is not an integer") unless ($min =~ /^\d+$/);
 
297
        croak("max: $max is not an integer") unless ($max =~ /^\d+$/);
 
298
        croak("min value should be <= max") unless ($min <= $max);
 
299
        
 
300
        $self->{_min}{$attribute} = $min;
 
301
        $self->{_max}{$attribute} = $max;
 
302
}
 
303
 
 
304
# set grouping
 
305
# if grouping
 
306
sub SetGroupBy {
 
307
        my $self = shift;
 
308
        my $attribute = shift;
 
309
        my $func = shift;
 
310
        croak("attribute is not defined") unless (defined $attribute);
 
311
        croak("Unknown grouping function: $func") unless ($func==SPH_GROUPBY_DAY
 
312
                                                        || $func==SPH_GROUPBY_WEEK
 
313
                                                        || $func==SPH_GROUPBY_MONTH
 
314
                                                        || $func==SPH_GROUPBY_YEAR
 
315
                                                        || $func==SPH_GROUPBY_ATTR );
 
316
 
 
317
        $self->{_groupby} = $attribute;
 
318
        $self->{_groupfunc} = $func;
 
319
}
 
320
 
 
321
# connect to searchd server and run given search query
 
322
#
 
323
# $query is query string
 
324
# $query is index name to query, default is "*" which means to query all indexes
 
325
#
 
326
# returns false on failure
 
327
# returns hash which has the following keys on success:
 
328
#          "matches"
 
329
#                     array containing hashes with found documents ( "doc", "weight", "group", "stamp" )
 
330
#          "total"
 
331
#                     total amount of matches retrieved (upto SPH_MAX_MATCHES, see sphinx.h)
 
332
#          "total_found"
 
333
#                     total amount of matching documents in index
 
334
#          "time"
 
335
#                     search time
 
336
#          "words"
 
337
#                     hash which maps query terms (stemmed!) to ( "docs", "hits" ) hash
 
338
sub Query {
 
339
        my $self = shift;
 
340
        my $query = shift;
 
341
        my $index = shift;
 
342
        $index ||= "*";
 
343
 
 
344
        my $fp = $self->_Connect();
 
345
        return 0 unless ($fp);
 
346
        
 
347
        ##################
 
348
        # build request
 
349
        ##################
 
350
        
 
351
        my $req;
 
352
        $req = pack ( "NNNN", $self->{_offset}, $self->{_limit}, $self->{_mode}, $self->{_sort} ); # mode and limits
 
353
        $req .= pack ( "N", length($self->{_sortby}) ) . $self->{_sortby};
 
354
        $req .= pack ( "N", length($query) ) . $query; # query itself
 
355
        $req .= pack ( "N", scalar(@{$self->{_weights}}) ); # weights
 
356
        foreach my $weight (@{$self->{_weights}}) {
 
357
                $req .= pack ( "N", int($weight));
 
358
        }
 
359
        $req .= pack ( "N", length($index) ) . $index; # indexes
 
360
        $req .= # id range
 
361
                pack ( "N", int($self->{_min_id}) ) .
 
362
                pack ( "N", int($self->{_max_id}) );
 
363
 
 
364
        # filters
 
365
        $req .= pack ( "N", scalar(keys %{$self->{_min}}) + scalar(keys %{$self->{_filter}}) );
 
366
 
 
367
        foreach my $attr (keys %{$self->{_min}}) {
 
368
                $req .= 
 
369
                        pack ( "N", length($attr) ) . $attr .
 
370
                                pack ( "NNN", 0, $self->{_min}{$attr}, $self->{_max}{$attr} );
 
371
        }
 
372
 
 
373
        foreach my $attr (keys %{$self->{_filter}}) {
 
374
                my $values = $self->{_filter}{$attr};
 
375
                $req .=
 
376
                        pack ( "N", length($attr) ) . $attr .
 
377
                                pack ( "N", scalar(@$values) );
 
378
 
 
379
                foreach my $value ( @$values ) {
 
380
                        $req .= pack ( "N", $value );
 
381
                }
 
382
        }
 
383
 
 
384
        # group-by
 
385
        $req .= pack ( "NN", $self->{_groupfunc}, length($self->{_groupby}) ) . $self->{_groupby};
 
386
 
 
387
        # max matches to retrieve
 
388
        $req .= pack ( "N", $self->{_maxmatches} );
 
389
        
 
390
        ##################
 
391
        # send query, get response
 
392
        ##################
 
393
 
 
394
        my $len = length($req);
 
395
        $req = pack ( "nnN", SEARCHD_COMMAND_SEARCH, VER_COMMAND_SEARCH, $len ) . $req; # add header
 
396
        send($fp, $req ,0);
 
397
 
 
398
        my $response = $self->_GetResponse ( $fp, VER_COMMAND_SEARCH );
 
399
        return 0 unless ($response);
 
400
 
 
401
        ##################
 
402
        # parse response
 
403
        ##################
 
404
        
 
405
        my $result = {};                # Empty hash ref
 
406
        $result->{matches} = [];        # Empty array ref
 
407
        my $max = length($response);    # Protection from broken response
 
408
 
 
409
        # read schema
 
410
        my $p = 0;
 
411
        my @fields;
 
412
        my (%attrs, @attr_list);
 
413
 
 
414
        my $nfields = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
 
415
        while ( $nfields-->0 && $p<$max ) {
 
416
                my $len = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
 
417
                push(@fields, substr ( $response, $p, $len )); $p += $len;
 
418
        }
 
419
        $result->{"fields"} = \@fields;
 
420
 
 
421
        my $nattrs = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
 
422
        while ( $nattrs-->0 && $p<$max  ) {
 
423
                        my $len = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
 
424
                        my $attr = substr ( $response, $p, $len ); $p += $len;
 
425
                        my $type = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
 
426
                        $attrs{$attr} = $type;
 
427
                        push(@attr_list, $attr);
 
428
        }
 
429
        $result->{"attrs"} = \%attrs;
 
430
 
 
431
        # read match count
 
432
        my $count = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
 
433
        
 
434
        # read matches
 
435
        while ( $count-->0 && $p<$max ) {
 
436
                my $data = {};
 
437
                ( $data->{doc}, $data->{weight} ) = unpack("N*N*", substr($response,$p,8));
 
438
                $p += 8;
 
439
 
 
440
                foreach my $attr (@attr_list) {
 
441
                        $data->{$attr} = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4;
 
442
                }
 
443
                push(@{$result->{matches}}, $data);
 
444
        }
 
445
        my $words;
 
446
        ($result->{total}, $result->{total_found}, $result->{time}, $words) = unpack("N*N*N*N*", substr($response, $p, 16));
 
447
        $result->{time} = sprintf ( "%.3f", $result->{"time"}/1000 );
 
448
        $p += 16;
 
449
 
 
450
        while ( $words-->0 ) {
 
451
                my $len = unpack ( "N*", substr ( $response, $p, 4 ) ); 
 
452
                $p += 4;
 
453
                my $word = substr ( $response, $p, $len ); 
 
454
                $p += $len;
 
455
                my ($docs, $hits) = unpack ("N*N*", substr($response, $p, 8));
 
456
                $p += 8;
 
457
                $result->{words}{$word} = {
 
458
                                                "docs" => $docs,
 
459
                                                "hits" => $hits
 
460
                                        };
 
461
        }
 
462
        return $result;
 
463
}
 
464
 
 
465
#-------------------------------------------------------------
 
466
# excerpts generation
 
467
#-------------------------------------------------------------
 
468
 
 
469
# connect to searchd server and generate exceprts from given documents
 
470
#
 
471
# $index is a string specifiying the index which settings will be used
 
472
# for stemming, lexing and case folding
 
473
# $docs is an array reference of strings which represent the documents' contents
 
474
# $words is a string which contains the words to highlight
 
475
# $opts is a hash which contains additional optional highlighting parameters:
 
476
#             "before_match"
 
477
#                     a string to insert before a set of matching words, default is "<b>"
 
478
#             "after_match"
 
479
#                     a string to insert after a set of matching words, default is "<b>"
 
480
#             "chunk_separator"
 
481
#                     a string to insert between excerpts chunks, default is " ... "
 
482
#             "limit"
 
483
#                     max excerpt size in symbols (codepoints), default is 256
 
484
#             "around"
 
485
#                     how much words to highlight around each match, default is 5
 
486
#
 
487
# returns false on failure
 
488
# retrurns an array of string excerpts on success
 
489
sub BuildExcerpts {
 
490
        my ($self, $docs, $index, $words, $opts) = @_;
 
491
        $opts ||= {};
 
492
        croak("BuildExcepts() called with incorrect parameters") unless (ref($docs) eq 'ARRAY' 
 
493
                                                                        && defined($index) 
 
494
                                                                        && defined($words) 
 
495
                                                                        && ref($opts) eq 'HASH');
 
496
        my $fp = $self->_Connect();
 
497
        return 0 unless ($fp);
 
498
 
 
499
        ##################
 
500
        # fixup options
 
501
        ##################
 
502
        $opts->{"before_match"} ||= "<b>";
 
503
        $opts->{"after_match"} ||= "</b>";
 
504
        $opts->{"chunk_separator"} ||= " ... ";
 
505
        $opts->{"limit"} ||= 256;
 
506
        $opts->{"around"} ||= 5;
 
507
 
 
508
        ##################
 
509
        # build request
 
510
        ##################
 
511
 
 
512
        # v.1.0 req
 
513
        my $req;
 
514
        $req = pack ( "NN", 0, 1 ); # mode=0, flags=1 (remove spaces)
 
515
        $req .= pack ( "N", length($index) ) . $index; # req index
 
516
        $req .= pack ( "N", length($words) ) . $words; # req words
 
517
 
 
518
        # options
 
519
        $req .= pack ( "N", length($opts->{"before_match"}) ) . $opts->{"before_match"};
 
520
        $req .= pack ( "N", length($opts->{"after_match"}) ) . $opts->{"after_match"};
 
521
        $req .= pack ( "N", length($opts->{"chunk_separator"}) ) . $opts->{"chunk_separator"};
 
522
        $req .= pack ( "N", int($opts->{"limit"}) );
 
523
        $req .= pack ( "N", int($opts->{"around"}) );
 
524
 
 
525
        # documents
 
526
        $req .= pack ( "N", scalar(@$docs) );
 
527
        foreach my $doc (@$docs) {
 
528
                croak('BuildExcepts: Found empty document in $docs') unless ($doc);
 
529
                $req .= pack("N", length($doc)) . $doc;
 
530
        }
 
531
 
 
532
        ##########################
 
533
        # send query, get response
 
534
        ##########################
 
535
 
 
536
        my $len = length($req);
 
537
        $req = pack ( "nnN", SEARCHD_COMMAND_EXCERPT, VER_COMMAND_EXCERPT, $len ) . $req; # add header
 
538
        send($fp, $req ,0);
 
539
        
 
540
        my $response = $self->_GetResponse($fp, VER_COMMAND_EXCERPT);
 
541
        return 0 unless ($response);
 
542
 
 
543
        my ($pos, $i) = 0;
 
544
        my $res = [];   # Empty hash ref
 
545
        my $rlen = length($response);
 
546
        for ( $i=0; $i< scalar(@$docs); $i++ ) {
 
547
                my $len = unpack ( "N*", substr ( $response, $pos, 4 ) );
 
548
                $pos += 4;
 
549
 
 
550
                if ( $pos+$len > $rlen ) {
 
551
                        $self->_error = "incomplete reply";
 
552
                        return 0;
 
553
                }
 
554
                push(@$res, substr ( $response, $pos, $len ));
 
555
                $pos += $len;
 
556
        }
 
557
        return $res;
 
558
}
 
559
 
 
560
1;