File Coverage

blib/lib/NetSDS/Util/SMS.pm
Criterion Covered Total %
statement 15 15 100.0
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 20 20 100.0


line stmt bran cond sub pod time code
1             #===============================================================================
2             #
3             # FILE: SMS.pm
4             #
5             # DESCRIPTION: Routines for SMS data.
6             #
7             # NOTES: ---
8             # AUTHOR: Michael Bochkaryov (Rattler), <misha@rattler.kiev.ua>
9             # COMPANY: Net.Style
10             # CREATED: 25.08.2009 14:12:44 EEST
11             #===============================================================================
12              
13             =head1 NAME
14              
15             NetSDS::Util::SMS - routines for SMS data processing
16              
17             =head1 SYNOPSIS
18              
19             use NetSDS::Util::SMS;
20              
21             # Prepare 400 characters string
22             my $long_line = "zuka"x100;
23              
24             # Split string to SMS parts
25             my @sms = split_text($long_line, COD_7BIT);
26              
27              
28             =head1 DESCRIPTION
29              
30             C<NetSDS> module contains superclass all other classes should be inherited from.
31              
32             =head1 DESCRIPTION
33              
34             I hope you can understand what these routines doing.
35              
36             Few basics:
37              
38             EMS
39             $hex_sms = ie_melody($iMelody,'');
40             $hex_sms = ie_bmp($BMP,'');
41              
42             EMS Message is composed of several Information Elements
43             preceded by the User Data Header Length (1 byte).
44              
45             So I used 'non-object' standard: different subroutines
46             producing different IE-Chunks. We can simply concantenate
47             these chunks in one message and precede it with UDHL (and
48             message-splitting elements if our EMS/IE stream don't fit
49             standard 140 bytes.
50              
51             Resulting stream filled with plain HEX-coded octets.
52             Hexcodes are handy to use and can be easy converted to
53             binary or base64 formats.
54              
55             There is no something-to-imelody converter. Look for it
56             in my ringtone.pm.
57              
58             Pictures used as 1-bit Windows BMPs. I said 1-bit, ok?
59              
60             =cut
61              
62             package NetSDS::Util::SMS;
63              
64 2     2   11550 use 5.8.0;
  2         9  
  2         127  
65 2     2   12 use strict;
  2         4  
  2         65  
66 2     2   12 use warnings;
  2         3  
  2         91  
67              
68 2     2   11 use version; our $VERSION = '0.021';
  2         5  
  2         14  
69              
70 2         1766 use base qw(
71             Exporter
72             NetSDS::Class::Abstract
73 2     2   180 );
  2         5  
74              
75             our @EXPORT_OK = qw(
76             ems_essage
77              
78             ie_melody
79             ie_icon16
80             ie_icon32
81             ie_picture
82             ie_bmp
83              
84             smart_message
85             smart_bmp
86             smart_logo
87             smart_card
88             smart_cli
89             smart_ringtone
90             smart_clear
91             smart_push_wap
92              
93             siemens_header
94             siemens_message
95             siemens
96              
97             split_text
98             );
99              
100             use POSIX;
101             use NetSDS::Const;
102             use NetSDS::Const::Message;
103             use NetSDS::Util::Convert;
104             use NetSDS::Util::String;
105              
106             # File Format Signatures => SEO Types:
107              
108             my %SIGN = (
109             'MT' => 'mid',
110             'BM' => 'bmp'
111             );
112              
113             ########################################################################
114             # EMS
115             ########################################################################
116             #***********************************************************************
117              
118             =head1 EXPORTS
119              
120             =over
121              
122             =item B<ems_essage(...)>
123              
124             $message_str = ems_essage ( ie_stream, text_data )
125              
126             Produce a EMS message.
127             Sure that ie data + text can not exceed 139 bytes.
128              
129             =cut
130              
131             #-----------------------------------------------------------------------
132             sub ems_essage {
133             my ( $ie_stream, $text, $encoding, $transport ) = @_;
134              
135             unless ( defined($text) ) {
136             $text = '';
137             }
138              
139             my $coding = COD_7BIT;
140             if ( $text =~ m/[^\x00-\x7f]/ ) {
141             $text = str_recode( $text, defined($encoding) ? $encoding : DEFAULT_ENCODING, to_enc => ENC_UNICODE );
142             $coding = COD_UNICODE;
143             }
144              
145             unless ( defined($transport) ) {
146             $transport = TRANSPORT_ANY;
147             }
148              
149             my $udhl = defined($ie_stream) ? bytes::length($ie_stream) : 0;
150             if ($udhl) {
151             # EMS Information Elements present...
152             return [ { udh => pack( 'C', $udhl ) . $ie_stream, ud => $text, coding => $coding, transport => $transport } ];
153             } else {
154             # Plain text message. What a mess?..
155             return [ { udh => '', ud => $text, coding => $coding, transport => $transport } ];
156             }
157             } ## end sub ems_essage
158              
159             #***********************************************************************
160              
161             =item B<ie_melody(...)>
162              
163             $ie_str = ie_melody ( melody )
164              
165             Produce an iMelody Information Element.
166              
167             WARNING: Melodies larger than 128 bytes will be CROPPED!
168              
169             =cut
170              
171             #-----------------------------------------------------------------------
172             sub ie_melody {
173             my ( $raw, $text, $encoding, $transport ) = @_;
174              
175             my $l = length($raw);
176             if ( $l > 128 ) {
177             $raw = substr( $raw, 0, 128 );
178             $l = 128;
179             }
180              
181             return ems_essage( IEC_MELODY . pack( 'C*', ++$l, 0 ) . $raw, $text, $encoding, $transport );
182             }
183              
184             #***********************************************************************
185              
186             =item B<ie_icon32(...)>
187              
188             =cut
189              
190             #-----------------------------------------------------------------------
191             sub ie_icon32 {
192             my ( $raw, $text, $encoding, $transport ) = @_;
193              
194             my $l = length $raw;
195             if ( $l > 128 ) {
196             $raw = substr( $raw, 0, 128 );
197             $l = 128;
198             }
199              
200             return ems_essage( IEC_ICON32 . pack( 'C*', ++$l, 0 ) . $raw, $text, $encoding, $transport );
201             }
202              
203             #***********************************************************************
204              
205             =item B<ie_icon16(...)>
206              
207             =cut
208              
209             #-----------------------------------------------------------------------
210             sub ie_icon16 {
211             my ( $raw, $text, $encoding, $transport ) = @_;
212              
213             my $l = length $raw;
214             if ( $l > 32 ) {
215             $raw = substr( $raw, 0, 32 );
216             $l = 32;
217             }
218              
219             return ems_essage( IEC_ICON16 . pack( 'C*', ++$l, 0 ) . $raw, $text, $encoding, $transport );
220             }
221              
222             #***********************************************************************
223              
224             =item B<ie_picture(...)>
225              
226             =cut
227              
228             #-----------------------------------------------------------------------
229             sub ie_picture {
230             my ( $raw, $width, $height, $text, $encoding, $transport ) = @_;
231              
232             if ( $width % 8 ) {
233             return __PACKAGE__->error("Non-8x width");
234             }
235              
236             my $squa = $width * $height / 8;
237             if ( $squa > 128 ) {
238             $height = int( 128 * 8 / $width );
239             $squa = $width * $height / 8;
240             }
241              
242             my $l = length($raw);
243             if ( $l > $squa ) {
244             $raw = substr( $raw, 0, $squa );
245             $l = $squa;
246             }
247              
248             return ems_essage( IEC_PICTURE . pack( 'C*', $l + 3, 0, $width / 8, $height + 0 ) . $raw, $text, $encoding, $transport );
249             } ## end sub ie_picture
250              
251             #***********************************************************************
252              
253             =item B<ie_bmp(...)>
254              
255             =cut
256              
257             #-----------------------------------------------------------------------
258             sub ie_bmp {
259             my ( $bmp, $text, $encoding, $transport ) = @_;
260              
261             if ( substr( $bmp, 0, 2 ) ne 'BM' ) {
262             return __PACKAGE__->error("Not a BMP");
263             }
264              
265             if ( unpack( 'L', substr( $bmp, 30, 4 ) ) ) {
266             return __PACKAGE__->error("Compressed BMP");
267             }
268              
269             unless ( unpack( 'S', substr( $bmp, 28, 2 ) ) == 1 ) {
270             return __PACKAGE__->error("Need 1bpp monochrome BMP");
271             }
272              
273             my $w = unpack( 'L', substr( $bmp, 18, 4 ) );
274             my $h = unpack( 'L', substr( $bmp, 22, 4 ) );
275             my $ofs = unpack( 'L', substr( $bmp, 10, 4 ) );
276              
277             my @bitmap = split( //, substr( $bmp, $ofs, length($bmp) ) );
278              
279             # Line Width in bytes
280             my $line = int( $w / 8 );
281             $line++ if ( $w % 8 );
282              
283             # Pad to 4x bytes
284             my $padding = 0;
285             $padding = 4 - $line % 4 if ( $line % 4 );
286              
287             my $raw = '';
288             for ( my $y = 0 ; $y < $h ; $y++ ) {
289             my $ll = '';
290             for ( my $x = 0 ; $x < $line ; $x++ ) {
291             $ll .= ~$bitmap[ $y * ( $line + $padding ) + $x ];
292             }
293             $raw = $ll . $raw;
294             }
295              
296             if ( ( $w == 16 ) && ( $h == 16 ) ) {
297             return ie_icon16( $raw, $text, $encoding, $transport );
298             } elsif ( ( $w == 32 ) && ( $h == 32 ) ) {
299             return ie_icon32( $raw, $text, $encoding, $transport );
300             } else {
301             return ie_picture( $raw, $w, $h, $text, $encoding, $transport );
302             }
303             } # end sub ie_bmp
304              
305             ########################################################################
306             # NOKIA
307             ########################################################################
308             #***********************************************************************
309              
310             =item B<smart_message(...)>
311              
312             @messages = smart_message ( destination_port, user_data )
313              
314             Produce a Nokia Smart Messages with application port addressing scheme.
315              
316             =cut
317              
318             #-----------------------------------------------------------------------
319             sub smart_message {
320             my ( $port, $data, $transport ) = @_;
321              
322             unless ( defined($data) ) {
323             $data = '';
324             }
325              
326             unless ( defined($transport) ) {
327             $transport = TRANSPORT_ANY;
328             }
329              
330             if ( length($data) + 7 <= 140 ) {
331             # Fit in single message & Short UDH
332             return [ { udh => "\x06\x05\x04" . $port . "\x00\x00", ud => $data, coding => COD_8BIT, transport => $transport } ];
333             } else {
334             # Messages Chain
335             my $udh = "\x0B\x05\x04" . $port . "\x00\x00\x00\x03"; # UDH with concatenation
336             my $refnum = int( rand(256) ); # Chain Reference Number
337             my $qty = int( length($data) / 128 ); # Messages in Chain
338              
339             $qty++ if ( length($data) % 128 );
340              
341             if ( $qty > 255 ) {
342             return __PACKAGE__->error("This doesn't fit anyway");
343             }
344              
345             $udh .= pack( 'C*', $refnum, $qty );
346              
347             # Making Messages
348             my $result = [];
349             for ( my $i = 1 ; $i <= $qty ; $i++ ) {
350             push( @{$result}, { udh => $udh . pack( 'C', $i ), ud => substr( $data, 128 * ( $i - 1 ), 128 ), coding => COD_8BIT, transport => $transport } );
351             }
352              
353             return $result;
354             } ## end else [ if ( length($data) + 7...
355             } # end sub smart_message
356              
357             #***********************************************************************
358              
359             =item B<smart_push_wap(...)>
360              
361             http://www.devx.com/xml/Article/16754/1954?pf=true
362             http://www.w3.org/TR/wbxml/
363              
364             =cut
365              
366             #-----------------------------------------------------------------------
367             sub smart_push_wap {
368             my ( $url, $title, $encoding, $transport ) = @_;
369              
370             unless ( defined($title) ) {
371             $title = '';
372             }
373              
374             if ( defined($encoding) and ( $title =~ m/[^\x00-\x7f]/ ) ) {
375             $title = str_recode( $title, $encoding, to_enc => XML_ENCODING );
376             }
377              
378             $url =~ s/^\w+:\/\///;
379              
380             my $data = "\xDC" . # Push ID
381             "\x06" . # Push PDU
382             "\x01\xAE" . # Content-Type: application/vnd.wap.sic
383             "\x02\x05\x6A" . # version / si / utf-8
384             "\x00\x45\xC6" . # string / si / indication
385             "\x0C\x03" . $url . "\x00" . # http:// zstring <url> \0
386             "\x01" . # Indication
387             "\x03" . $title . "\x00" . # zstring <title> \0
388             "\x01\x01"; # Indication / SI
389              
390             return smart_message( PORT_PUSHWAP, $data, $transport );
391             } ## end sub smart_push_wap
392              
393             #***********************************************************************
394              
395             =item B<smart_bmp(...)>
396              
397             =cut
398              
399             #-----------------------------------------------------------------------
400             sub smart_bmp {
401             my ($bmp) = @_;
402              
403             if ( substr( $bmp, 0, 2 ) ne 'BM' ) {
404             return __PACKAGE__->error("Not a BMP");
405             }
406              
407             if ( unpack( 'L', substr( $bmp, 30, 4 ) ) ) {
408             return __PACKAGE__->error("Compressed BMP");
409             }
410              
411             unless ( unpack( 'S', substr( $bmp, 28, 2 ) ) == 1 ) {
412             return __PACKAGE__->error("Need 1bpp monochrome BMP");
413             }
414              
415             my $w = unpack( 'L', substr( $bmp, 18, 4 ) );
416             my $h = unpack( 'L', substr( $bmp, 22, 4 ) );
417             my $ofs = unpack( 'L', substr( $bmp, 10, 4 ) );
418              
419             my @bitmap = split( //, substr( $bmp, $ofs, length $bmp ) );
420              
421             my $line = int( $w / 8 );
422             $line++ if ( $w % 8 ); # Line Width in bytes
423              
424             my $padding = 0;
425             $padding = 4 - $line % 4 if ( $line % 4 ); # Pad to 4x bytes
426              
427             my $hdr = "\x00" . pack( 'C*', $w, $h ) . "\x01"; # OTA Bitmap Header
428              
429             my $raw = '';
430             for ( my $y = 0 ; $y < $h ; $y++ ) {
431             my $ll = '';
432             for ( my $x = 0 ; $x < $line ; $x++ ) {
433             $ll .= ~$bitmap[ $y * ( $line + $padding ) + $x ];
434             }
435             $raw = $ll . $raw;
436             }
437              
438             return $hdr . $raw;
439             } # end sub smart_bmp
440              
441             #***********************************************************************
442              
443             =item B<smart_logo(...)>
444              
445             http://www.cisco.com/univercd/cc/td/doc/product/software/ios124/124cg/hmwg_c/mwgfmcc.htm
446             http://users.zipworld.com.au/~rmills/MCCandMNCValues.htm
447             http://www.surfio.de/info/mcc_mnc/mcc_mnc_liste_5.html
448              
449             MCC MNC Land ISO Vorwahl Netzbetreiber
450             255 01 Ukraine UA 380 Ukrainian Mobile Comms (UMC)
451             255 02 Ukraine UA 380 Ukrainian Radio Systems (WellCOM)
452             255 03 Ukraine UA 380 Kyivstar GSM
453             255 05 Ukraine UA 380 Golden Telecom LLC
454             255 06 Ukraine UA 380 Astelit (life:))
455              
456             =cut
457              
458             #-----------------------------------------------------------------------
459             sub smart_logo {
460             my ( $bmp, $mcc, $mnc, $transport ) = @_;
461              
462             my $data = smart_bmp($bmp);
463             unless ($data) {
464             return $data;
465             }
466              
467             $data = "\x30" . str2bcd($mcc) . str2bcd($mnc) . "\x0A" . $data;
468              
469             return smart_message( PORT_LOGO, $data, $transport );
470             }
471              
472             #***********************************************************************
473              
474             =item B<smart_card(...)>
475              
476             =cut
477              
478             #-----------------------------------------------------------------------
479             sub smart_card {
480             my ( $bmp, $transport ) = @_;
481              
482             my $bitmap = smart_bmp($bmp);
483             unless ($bitmap) {
484             return $bitmap;
485             }
486              
487             my $size = int( length($bitmap) );
488             my $data = "\x30\x02" . pack( 'C*', int( $size / 256 ), $size % 256 ) . $bitmap;
489              
490             return smart_message( PORT_ITEMS, $data, $transport );
491             }
492              
493             #***********************************************************************
494              
495             =item B<smart_cli(...)>
496              
497             =cut
498              
499             #-----------------------------------------------------------------------
500             sub smart_cli {
501             my ( $bmp, $transport ) = @_;
502              
503             my $data = smart_bmp($bmp);
504             unless ($data) {
505             return $data;
506             }
507              
508             return smart_message( PORT_CLI, "\x30" . $data, $transport );
509             }
510              
511             #***********************************************************************
512              
513             =item B<smart_ringtone(...)>
514              
515             =cut
516              
517             #-----------------------------------------------------------------------
518             sub smart_ringtone {
519             my ( $ringtone, $transport ) = @_;
520              
521             return smart_message( PORT_RINGTONE, $ringtone, $transport );
522             }
523              
524             #***********************************************************************
525              
526             =item B<smart_clear(...)>
527              
528             Pure shamanism
529              
530             =cut
531              
532             #-----------------------------------------------------------------------
533             sub smart_clear {
534             my ($transport) = @_;
535              
536             return [ { udh => "\x06\x05\x04\x15\x82\x00\x00", ud => "\x30\x00\x00\x00\x0A\x00\x00\x00\x01", coding => COD_8BIT, transport => defined($transport) ? $transport : TRANSPORT_ANY } ];
537             }
538              
539             ########################################################################
540             # SIEMENS
541             ########################################################################
542             #***********************************************************************
543              
544             =item B<siemens_header(...)>
545              
546             =cut
547              
548             #-----------------------------------------------------------------------
549             sub siemens_header {
550             my ( $data_size, $reference_id, $packet_number, $number_of_packets, $object_size, $object_type, $object_name ) = @_;
551              
552             my $result = '//SEO'; # "//Siemens Exchange Object"
553             $result .= pack( "C", SEO_VER ); # SEO Version, uchar
554             $result .= pack( "S", $data_size ); # Data Block Size, uint(2)
555             $result .= pack( "L", $reference_id ); # Reference ID, ulong(4)
556             $result .= pack( "S", $packet_number ); # This Packet Number, uint(2)
557             $result .= pack( "S", $number_of_packets ); # Total Packets, uint(2)
558             $result .= pack( "L", $object_size ); # ObjectSize, ulong(4)
559             $result .= pack( "C", length($object_type) ); # Pascal-string length, uchar
560             $result .= $object_type; # Object Type identifier ('bmp' or 'mid')
561             $result .= pack( "C", length($object_name) ); # Pascal-string length, uchar
562             $result .= $object_name; # Object Name
563              
564             return $result;
565             }
566              
567             #***********************************************************************
568              
569             =item B<siemens_message(...)>
570              
571             =cut
572              
573             #-----------------------------------------------------------------------
574             sub siemens_message {
575             my ( $object, $object_type, $object_name, $transport ) = @_;
576              
577             # Calculating Maximum DataSize
578             my $data_size = SMS_SIZE - SEO_LEN - length($object_type) - length($object_name);
579             my $object_size = length($object);
580             my $full_size = $object_size;
581             my $number_of_packets = 1;
582              
583             if ( $object_size > $data_size ) {
584             # [Zero]-Padding
585             my $padding = '';
586             my $padl = $data_size - ( $object_size % $data_size );
587             $padding .= SEO_FILL x $padl;
588             $object = $object . $padding;
589             $full_size = length($object);
590             # Number of Chunks in Stream
591             $number_of_packets = $full_size / $data_size;
592             } else {
593             $data_size = length($object);
594             }
595              
596             # Unique Reference ID
597             my $reference_id = rand(0xFFFFFFFF);
598              
599             unless ( defined($transport) ) {
600             $transport = TRANSPORT_ANY;
601             }
602              
603             # Make SMSes
604             my $stream = [];
605             for ( my $packet_number = 1 ; $packet_number <= $number_of_packets ; $packet_number++ ) {
606             my $sms = '';
607             $sms .= siemens_header( $data_size, $reference_id, $packet_number, $number_of_packets, $object_size, $object_type, $object_name );
608             $sms .= substr( $object, ( $packet_number - 1 ) * $data_size, $data_size );
609             push( @{$stream}, { udh => '', ud => $sms, coding => COD_8BIT, transport => $transport } );
610             }
611              
612             return $stream;
613             } # end sub siemens_message
614              
615             #***********************************************************************
616              
617             =item B<siemens(...)>
618              
619             @smses = siemens ( $data [, $name] )
620              
621             Produce a SEO messages stream. $data can contain MIDI or BMP data.
622             $name looks like old plain filename. Can be omitted.
623              
624             =cut
625              
626             #-----------------------------------------------------------------------
627             sub siemens {
628             my ( $object, $o_name, $transport ) = @_;
629              
630             unless ($object) {
631             return $object;
632             }
633              
634             my $sig = substr( $object, 0, 2 );
635              
636             my $o_type = $SIGN{$sig};
637             unless ($o_type) {
638             return $o_type;
639             }
640              
641             unless ( defined($o_name) and ( $o_name ne '' ) ) {
642             $o_name = 'Nib' . rand(1000) . '.' . $o_type;
643             }
644              
645             return siemens_message( $object, $o_type, $o_name, $transport );
646             } ## end sub siemens
647              
648             #***********************************************************************
649              
650             =item B<split_text()> - ????????
651              
652             Paramters: text string (utf-8), SMS coding
653              
654             Returns: array of SMS hashrefs
655              
656             This method implements text SMS splitting to send concatenated messages.
657              
658             =cut
659              
660             #-----------------------------------------------------------------------
661              
662             sub split_text {
663              
664             my ( $text, $coding ) = @_;
665              
666             $text = str_decode($text);
667              
668             my @ret = ();
669              
670             if ( $coding eq COD_7BIT ) {
671              
672             if ( length($text) <= 160 ) {
673             push @ret, { udh => undef, ud => $text, coding => COD_7BIT };
674             } else {
675             my $udh = "\x05\x00\x03";
676             my $refnum = int( rand(256) );
677             my $qty = ceil( length($text) / 153 );
678             $udh .= pack( 'C*', $refnum, $qty );
679              
680             for ( my $i = 1 ; $i <= $qty ; $i++ ) {
681             push @ret, { udh => $udh . pack( 'C', $i ), ud => substr( $text, 153 * ( $i - 1 ), 153 ), coding => COD_7BIT };
682             }
683             }
684              
685             } elsif ( $coding eq COD_UNICODE ) {
686              
687             $text = str_encode($text);
688             $text = str_decode( $text, "UTF-16BE" );
689              
690             if ( length($text) <= 140 ) {
691             push @ret, ( { udh => undef, ud => $text, coding => COD_UNICODE } );
692             } else {
693             my $udh = "\x05\x00\x03";
694             my $refnum = int( rand(256) );
695              
696             my $qty = ceil( length($text) / 134 );
697             $udh .= pack( 'C*', $refnum, $qty );
698              
699             for ( my $i = 1 ; $i <= $qty ; $i++ ) {
700             my $part = substr( $text, 134 * ( $i - 1 ), 134 );
701             $part = str_encode( $part, "UTF-16BE" );
702             $part = str_decode( $part, "UTF-16BE" );
703             push @ret, { udh => $udh . pack( 'C', $i ), ud => $part, coding => COD_UNICODE };
704             }
705             }
706              
707             } ## end elsif ( $coding eq COD_UNICODE)
708              
709             return @ret;
710              
711             } # end sub smart_message
712              
713             #**************************************************************************
714             1;
715             __END__
716              
717             =back
718              
719             =head1 EXAMPLES
720              
721              
722             =head1 BUGS
723              
724             Unknown yet
725              
726             =head1 SEE ALSO
727              
728             None
729              
730             =head1 TODO
731              
732             None
733              
734             =head1 AUTHOR
735              
736             Valentyn Solomko <pere@pere.org.ua>
737              
738             Michael Bochkaryov <misha@rattler.kiev.ua>
739              
740             =head1 LICENSE
741              
742             Copyright (C) 2008 Michael Bochkaryov
743              
744             This program is free software; you can redistribute it and/or modify
745             it under the terms of the GNU General Public License as published by
746             the Free Software Foundation; either version 2 of the License, or
747             (at your option) any later version.
748              
749             This program is distributed in the hope that it will be useful,
750             but WITHOUT ANY WARRANTY; without even the implied warranty of
751             MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
752             GNU General Public License for more details.
753              
754             You should have received a copy of the GNU General Public License
755             along with this program; if not, write to the Free Software
756             Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
757              
758             =cut
759