:pserver:anonymous@intra.tektonica.com:/opt/cvs - gsmperl - Fri Dec 21 07:37 CET...
[gsmperl.git] / GSM / SMS / PDU.pm
1 package GSM::SMS::PDU;
2 use strict;
3 use vars qw( $VERSION );
4 # (c) 1999 tektonica
5 # author: Johan Van den Brande 
6
7 $VERSION = '0.1';
8
9 sub new {
10     my $proto = shift;
11     my $class = ref($proto) || $proto;
12
13         my $self = {};
14         $self->{TPDU} = {};
15         bless($self, $class);
16         return $self;
17 }
18
19 sub SMSDeliver {
20         my ($self, $data) = @_;
21         my $tpdu = $self->{TPDU};
22         my @msg = split //, $data;
23         
24         ###########################################################################
25         # 1) Get SERVICE CENTER ADDRESS 
26         # -------------------------------------------------------------------------
27         # Structure:    (n) = number of octets
28         # +-----------+-------------------+------------------+
29         # | length(1) | type of number(2) | BCD digits(0..8) |
30         # +-----------+-------------------+------------------+
31         #       length :
32         #               number of octets for BCD + 1 octet for type of number
33         #       type of number :
34         #               81H     :       national number (e.g. 0495123456)
35         #               91H :   international number (e.g. 32495123456 => need to prepend a '+')         
36         #       BCD:
37         #               If the number of BCD octets is odd, the last digit shall be filled with an end
38         #               mark, coded as FH (H = Hex ...)
39         #               Every Octet get's splitted in 2 nibbles. Per octet we need to swap the nibbles to get
40         #               the correct order.
41         # -------------------------------------------------------------------------
42         $tpdu->{'TP-SCN'} = $self->getServiceCenterAddress(\@msg);
43         ###########################################################################
44         
45         ###########################################################################
46         # 2) Get PDU type
47         # -------------------------------------------------------------------------
48         # Structure: (n) = bits
49         # +--------+----------+---------+-------+-------+--------+
50         # | RP (1) | UDHI (1) | SRI (1) | X (1) | X (1) | MTI(2) |
51         # +--------+----------+---------+-------+-------+--------+
52         #       RP:
53         #               Reply path
54         #       UDHI:
55         #               User Data Header Indicator = Does the UD contains a header
56         #               0 : Only the Short Message
57         #               1 : Beginning of UD containsheader information
58         #       SRI:
59         #               Status Report Indication.
60         #               The SME (Short Message Entity) has requested a status report.
61         #       MTI:
62         #               00 for SMS-Deliver
63         #
64         # -------------------------------------------------------------------------     
65         $tpdu->{'TP-PDU'} = $self->getoctet(\@msg);
66         ###########################################################################
67         
68         ###########################################################################
69         # 3) Get originating address
70         # -------------------------------------------------------------------------
71         # Structure:    (n) = number of octets
72         # +-----------+-------------------+------------------+
73         # | length(1) | type of number(2) | BCD digits(0..10) |
74         # +-----------+-------------------+------------------+
75         #       length :
76         #               number of BCD digits    (This is different for the SCN!)
77         #       type of number :
78         #               81H     :       national number (e.g. 0495123456)
79         #               91H :   international number (e.g. 32495123456 => need to prepend a '+')         
80         #       BCD:
81         #               If the number of BCD octets is odd, the last digit shall be filled with an end
82         #               mark, coded as FH (H = Hex ...)
83         #               Every Octet get's splitted in 2 nibbles. Per octet we need to swap the nibbles to get
84         #               the correct order.
85         # -------------------------------------------------------------------------     
86         $tpdu->{'TP-OA'} = $self->getOriginatingAddress(\@msg);
87
88         ###########################################################################
89         # 4) Get Protocol identifier (PID)
90         # -------------------------------------------------------------------------
91         # Structure:
92         #       XXH
93         #               00H:    Short Message (SMS) 
94         #               41H:    Replace Short Message Type1
95         #               ...
96         #               47H:    Replace Short Message Type7
97         #               Can be used to replace previously sent SMS messgaes in the MS (Mobile Station)
98         # -------------------------------------------------------------------------     
99         $tpdu->{'TP-PID'} = $self->getoctet(\@msg);
100         ###########################################################################     
101         
102         ###########################################################################
103         # 5) Get data coding scheme
104         # -------------------------------------------------------------------------
105         # Structure:
106         #       bits        7 6 5 4      3   2   1   0
107         #           +--------------+---+---+---+---+
108         #                       | Coding group | 0 | X | X | X |        
109         #           +--------------+---+---+---+---+    
110         # Examples:
111         #                               0 0 0 0      0   0   0   0              : 00H   : 7-bit datacoding, default alphabet
112         #                               1 1 1 1      0   1   1   0              : F6H   : 8-bit datacoding Class 2
113         #
114         # Coding group   |      Alphabet indication
115         # ---------------+---------------------------------------------------------
116         #       0000         | 0000             Default alphabet
117         #                | 0001         Reserved
118         #                | ...          "    " 
119         #                | 1111         "    "
120         # ---------------+---------------------------------------------------------
121         #   0001-1110    | Reserved coding groups
122         # ---------------+---------------------------------------------------------
123         #   1111                 | bit 3                : Reserved, always 0
124         #                | bit 2                : Data Coding
125         #                                |                                      0       :       Default alphabet (7bit)
126         #                                |                                      1       :       8 bit encoding INTEL-ASCII
127         #                | bits 1 0     : Message Class
128         #                |                                      0 0     :       Class 0, immedidate display
129         #                |                  0 1 :       Class 1, ME specific    (Mobile Equiment)
130         #                                |                                      1 0 :   Class 2, SIM specific   
131         #                                |                                      1 1 :   Class 3, TE specific    (Terminate Equipment)
132         # ---------------+---------------------------------------------------------
133         #   We have 2 possible ways of interpreting this for our SMS software
134         #       7 bit default alphabet  :       00000000        111100xx
135         #       8 bit intel-ascii       :       111101xx
136         #               x being a wild card
137         ###########################################################################
138         $tpdu->{'TP-DCS'} = $self->getoctet(\@msg);
139         
140         ###########################################################################
141         # 6) Get service center timestamp
142         # -------------------------------------------------------------------------
143         # Structure:
144         #       Octets: 1      1      1      1      1         1         1 
145         #       +------+-------+-----+------+--------+--------+-----------+
146         #               | YEAR | MONTH | DAY | HOUR | MINUTE | SECOND | TIME ZONE | (2 1), means
147         #       | (2 1)| (2 1) |(2 1)| (2 1)|  (2 1) |  (2 1) |    (2 1)  | nibbles need to
148         #       +------+-------+-----+------+--------+--------+-----------+ be swapped for correct order
149         #  The TIMEZONE indicates difference in quarters of an hour, between the
150         #  local time and Greenwhich Main Time (GMT)
151         ###########################################################################
152         $tpdu->{'TP-SCTS'} = $self->getoctet(\@msg, 7, 1);
153         ###########################################################################
154         
155         ###########################################################################
156         # 7) Get User Data (UDL) and decode
157         # -------------------------------------------------------------------------
158         # We need to decode according to the DCS.
159         $tpdu->{'TP-UDL'} = hex($self->getoctet(\@msg));
160
161         #   We have 2 possible ways of interpreting this for our SMS software
162         #       7 bit default alphabet  :       00000000        111100xx
163         #       8 bit intel-ascii       :       111101xx
164         #               x being a wild card
165         my $dcs = hex($tpdu->{'TP-DCS'});
166         if ($dcs == 0) {        
167                 # decode 7 bit
168                 $tpdu->{'TP-UD'} = $self->decode_7bit(join("", @msg), $tpdu->{'TP-UDL'});
169                 # translate to default alphabet
170                 $tpdu->{'TP-UD'} = $self->translate($tpdu->{'TP-UD'});
171                 
172                 # Do we have NBS with Text based headers?
173                 my $ud = $tpdu->{'TP-UD'};
174                 if (substr($ud, 0, 5) eq '//SCK') {
175                         # print "We have a text encoded NBS\n";
176                         $tpdu->{'TP-SCK'}++;
177                         if ($ud=~/\/\/SCK(\w\w)(\w\w)(\w\w)(\w\w)(\w\w)\s/) {
178                                 # print "D: $1, S: $2, DATAGRAM: $3, MAX: $4, SQN: $5\n";
179                 $tpdu->{'TP-DPORT'} = hex($1);
180                 $tpdu->{'TP-SPORT'} = hex($2);                          
181                 $tpdu->{'TP-DATAGRAM'} = hex($3);
182                 $tpdu->{'TP-FRAGMAX'} =  hex($4);
183                 $tpdu->{'TP-FRAGSQN'} =  hex($5);
184                         }
185                         if ($ud=~/\/\/SCKL(\w\w\w\w)(\w\w\w\w)(\w\w)(\w\w)(\w\w)\s/) {
186                 # print "D: $1, S: $2, DATAGRAM: $3, MAX: $4, SQN: $5\n";
187                 $tpdu->{'TP-DPORT'} = hex($1);
188                 $tpdu->{'TP-SPORT'} = hex($2);        
189                 $tpdu->{'TP-DATAGRAM'} = hex($3);
190                 $tpdu->{'TP-FRAGMAX'} =  hex($4);
191                 $tpdu->{'TP-FRAGSQN'} =  hex($5);
192             }
193                         if ($ud=~/\/\/SCK(\w\w)\s/) {
194                                 # print "D: $1, S: $1\n";
195                 $tpdu->{'TP-DPORT'} = hex($1);
196                 $tpdu->{'TP-SPORT'} = hex($1);        
197                 $tpdu->{'TP-DATAGRAM'} = 1;
198                 $tpdu->{'TP-FRAGMAX'} =  1;
199                 $tpdu->{'TP-FRAGSQN'} = 1; 
200                         }
201                         if ($ud=~/\/\/SCKL(\w\w\w\w)\s/) {
202                                 # print "D: $1, S: $1\n";
203                 $tpdu->{'TP-DPORT'} = hex($1);
204                 $tpdu->{'TP-SPORT'} = hex($1);
205                 $tpdu->{'TP-DATAGRAM'} = 1;
206                 $tpdu->{'TP-FRAGMAX'} =  1;
207                 $tpdu->{'TP-FRAGSQN'} = 1;
208                         }
209                 }
210         } elsif (($dcs & 0xF0) == 0xF0) {
211                 # Do we have a UDH?
212                 my $pdu = hex($tpdu->{'TP-PDU'});
213                 if (($pdu & 0x40) == 0x40) {
214                         my $udhl = hex($self->getoctet(\@msg));
215                         my @ud = splice(@msg, 0, $udhl*2);
216                         while ($#ud>-1) {
217                                 my $iei = $self->getoctet(\@ud);
218                                 my $lei = hex($self->getoctet(\@ud));
219                                 my @dei = splice(@ud, 0, $lei*2);
220                                 # print "UDHL: $udhl, IEI: $iei, LEI: $lei, DATA:".join ( "", @dei ) . "\n";
221                                 if (hex($iei) == 5) {
222                                         # 16 bit port
223                                         my $dport = hex( $self->getoctet(\@dei, 2) );
224                                         my $sport = hex( $self->getoctet(\@dei, 2) );
225                                         # print "16 bit @ D:$dport S:$sport\n";                         
226                                         $tpdu->{'TP-DPORT'} = $dport;
227                     $tpdu->{'TP-SPORT'} = $sport;
228                                 
229                                         # When receivingwe do not have necessarily the Fragment idenetifier!, so if not already defined
230                                         # (FI maybe! can come b4 PORTS), set them to a bogus number (1,1,1)
231                                         if (!$tpdu->{'TP-DATAGRAM'}) {
232                                                 $tpdu->{'TP-DATAGRAM'} = 1;
233                                                 $tpdu->{'TP-FRAGMAX'} = 1;
234                                                 $tpdu->{'TP-FRAGSQN'} = 1;
235                                         }
236                                 }
237                                 if (hex($iei) == 0) {
238                                         # Fragment identifier
239                                         my $fdatagram = hex( $self->getoctet(\@dei) );
240                                         my $fmax = hex( $self->getoctet(\@dei) );
241                                         my $fid = hex( $self->getoctet(\@dei) );
242                                         # print "datagram $fdatagram fragment $fid from $fmax\n";
243                     $tpdu->{'TP-DATAGRAM'} = $fdatagram;
244                     $tpdu->{'TP-FRAGMAX'} =  $fmax;
245                     $tpdu->{'TP-FRAGSQN'} =  $fid;
246                                 }
247                         }
248                 }
249                 # decode 8 bit
250                 pop @msg;
251                 $tpdu->{'TP-UD'} = $self->decode_8bit(join("", @msg), $tpdu->{'TP-UDL'});
252                 # translate to default alphabet
253                 $tpdu->{'TP-UD'} = $self->translate($tpdu->{'TP-UD'});
254         } else {
255                 $tpdu->{'TP-UD'} = "";
256         }
257         ###########################################################################
258         return $tpdu;
259 }
260
261 sub SMSSubmit {
262         my ($self, $servicecenter, $phonenumber, $data, $dcs, $vp, $udhi) = @_;
263
264         my $pdu = '';
265         my $pdutype = 0;        
266
267         ###########################################################################
268         # 1) Service center address
269         # -------------------------------------------------------------------------
270         # Look at SMSDeliver for notes
271         # -------------------------------------------------------------------------
272         $pdu.=$self->encodeServiceCenterAddress($servicecenter); 
273         ###########################################################################
274         
275         ###########################################################################
276         # 2) PDU type
277         # -------------------------------------------------------------------------
278         # Structure :   (n) = bits
279         # +--------+----------+---------+---------+--------+---------+
280         # | RP (1) | UDHI (1) | SRR (1) | VPF (2) | RD (1) | MTI (2) |
281         # +--------+----------+---------+---------+--------+---------+
282         # RP:
283         #       Reply path : 0 = not set / 1 = set
284         # UDHI:
285         #       User data only contains short message : 0
286         #       User data contains a header :                   1
287         # SRR:
288         #       Status report requested :       0 = no / 1 = yes
289         # VPF:
290         #       Validity period field
291         #       0 0     :       Not set
292         #       0 1 :   Reserved
293         #       1 0     :       VP field present : relative (integer)
294         #       1 1 :   VP field present : absolute     (semi-octet)
295         # RD:
296         #       Reject (1)  or accept (0) an SMS in the SMSC with the same MR and DA from the same OA
297         # MTI:
298         #       Message type
299         #       0 0     :       SMS deliver SMSC -> MS
300         #       0 1     :       SMS Submit      MS->SMSC
301         #
302         # We default this field to: 00010001, which means
303         #       Validity period in relative format if $vp
304         #       SMSSubmit type of message
305         #       Accept the same message in the SMSC again
306         # -------------------------------------------------------------------------
307         $pdutype=1;                                             # SMS Submit
308         $pdutype|=0x10 if ($vp);                # Vailidity period
309         $pdutype|=0x40 if ($udhi);              # User data header present      
310         $pdu.=sprintf("%02x", $pdutype);
311         # $pdu.='11';
312         # $pdu.='44';
313         ###########################################################################
314
315         ###########################################################################
316         # 3) Message reference
317         # -------------------------------------------------------------------------
318         # The M20 generates this himself, so we can dummy to 00H
319         # -------------------------------------------------------------------------
320         $pdu.='00';                             
321         ###########################################################################
322         
323         ###########################################################################
324         # Destination address
325         # -------------------------------------------------------------------------
326         # See SMSDeliver for a description
327         # -------------------------------------------------------------------------
328         $pdu.=$self->encodeDestinationAddress($phonenumber);
329         ###########################################################################
330                 
331         ###########################################################################     
332         # protocol identifier
333         # -------------------------------------------------------------------------
334         # See SMSDeliver for a description
335         #       00H : SMS
336         # -------------------------------------------------------------------------
337         $pdu.='00';
338         ###########################################################################     
339         
340         ###########################################################################     
341         # Data coding scheme (probably need to experiment withthis one!)
342         # -------------------------------------------------------------------------
343         # See SMSDeliver for a description
344         #       We use  '00' for 7bit, SIM specific                     '7bit'  (default)
345         #                       'F0' for 7bit, immediate display        '7biti'
346         #                       'F6' for 8bit, SIM specific                     '8bit'
347         #                       'F4' for 8bit, immediate display        '8biti'
348         #                       'F5' for 8bit, ME specific                      '8bitm' 
349         # -------------------------------------------------------------------------     
350         $pdu.=$self->encodeDataCodingScheme($dcs);
351         ###########################################################################     
352                 
353         ###########################################################################     
354         # Validity period
355         # -------------------------------------------------------------------------
356         # Look at encodeValidityPeriod
357         # -------------------------------------------------------------------------
358         if ($vp) {
359                 # $pdu.=$self->encodeValidityPeriod($vp);
360                 $pdu.='FF';
361         }
362         ###########################################################################
363         
364         
365         ###########################################################################
366         # Length of message (Length of user data)
367         # -------------------------------------------------------------------------     
368         # $pdu.=sprintf("%.2X", length($data));
369         ###########################################################################
370         
371
372         ###########################################################################
373         # Message of user data. 
374         # -------------------------------------------------------------------------
375         if (($dcs eq '8bit') || ($dcs eq '8biti' || ($dcs eq '8bitm'))) {
376                 $pdu.=sprintf("%.2X", length($data)/2);
377                 $pdu.=$self->encode_8bit(substr($data,0,160*2));
378         } else {
379         # First to the alphabet translation on the data...
380                 $pdu.=sprintf("%.2X", length($data));
381                 $data = $self->inversetranslate($data);
382                 $pdu.=$self->encode_7bit(substr($data,0,160));
383         }       
384         ###########################################################################
385                 
386         return $pdu;
387 }
388
389 # decode a SMSSubmit message (experimental!)
390 sub SMSSubmit_decode {
391         my ($self, $data) = @_;
392         my @msg = split //, $data;
393         
394         # Get service center
395         my $sca = $self->getServiceCenterAddress(\@msg);
396
397         # Get PDU type
398         my $pdu = $self->getoctet(\@msg);
399
400         # message ref
401         my $mref = $self->getoctet(\@msg);
402
403         # destination address
404         my $da = $self->getOriginatingAddress(\@msg);
405
406         #  protocol identifier
407         my $pi = $self->getoctet(\@msg);
408
409         # data scheme
410         my $ds = $self->getoctet(\@msg);
411
412         # vp
413         my $vp = $self->getoctet(\@msg);
414
415         # length
416         my $dl = $self->getoctet(\@msg);
417
418         my $udh;
419         my $payload;
420
421         # print join "|", @msg;
422         # print "\n";
423
424         if ($pdu=~/51/) {
425                 # we have a user data header
426                 my $udhl = hex($msg[0].$msg[1]);
427         
428                 # print "udhl ($msg[0]): $udhl\n";
429
430                 $udh = $self->getoctet(\@msg, $udhl+1); 
431                 $payload = join("", @msg);
432         } else {
433                 $payload = $self->decode_7bit( join("", @msg), 160 );
434         }       
435
436          # print "da : $da\n";
437          # print "pdu type : $pdu\n";
438          # print "data scheme : $ds\n";
439          # print "length : $dl\n";      
440          # print "udh : $udh\n";
441          # print "pay : $payload\n";    
442
443         return ($da, $pdu, $ds, $udh, $payload);
444 }
445
446 # Get an Adress (OA / DA )
447 sub getServiceCenterAddress {
448         my($self, $ref_msg_arr) = @_;
449         my $adr;
450         
451         # First get address length
452         my $len         =        hex($self->getoctet($ref_msg_arr));
453         if ($len>0) {
454                 # Second get Type of address
455                 my $typ         =        $self->getoctet($ref_msg_arr);
456
457                 # Third get  address itself ...
458                 for (my $pos=0;$pos<$len-1;$pos++) {
459                         $adr.= $self->swapoctet($self->getoctet($ref_msg_arr));
460                 }
461
462                 # If length is odd we have a trailing F;
463                 (($len) & 0x1) && chop($adr);
464         
465                 # Append a '+' to make a valid international number, when type is 91
466                 $adr = ($typ == 91)?'+'.$adr:$adr;
467         }
468         return $adr;
469 }
470
471
472 # Get an Adress (OA / DA )
473 sub getOriginatingAddress {
474         my($self, $ref_msg_arr) = @_;
475         my $adr;
476         
477         # First get address length
478         my $len         =        hex($self->getoctet($ref_msg_arr));
479         # Second get Type of address
480         my $typ         =        $self->getoctet($ref_msg_arr);
481         # Third get  address itself ...
482         
483         for (my $pos=0;$pos<$len;$pos+=2) {
484                 $adr.= $self->swapoctet($self->getoctet($ref_msg_arr));
485         }
486
487         # If length is odd we have a trailing F;
488         (($len) & 0x1) && chop($adr);
489         
490         # Append a '+' to make a valid international number, when type is 91
491         $adr = ($typ == 91)?'+'.$adr:$adr;
492         
493         return $adr;
494 }
495         
496 # Validity period
497 # For the moment, only integer relative scheme
498 #       IN: Validity Period in ns(econds), nm(inutes), nh(ours), nd(ays), nw(eeks)
499 #                       n e R
500 #  OUT: integer representation of validity period
501 sub encodeValidityPeriod {
502         my ($self, $ti) = @_;
503
504         my      $vp = 0;
505         
506         my %timeslice = (
507                         's'     =>      1,
508                         'm' =>  60,
509                         'h' =>  60*60,
510                         'd' =>  60*60*24,
511                         'w' =>  60*60*24*7
512                                         );
513                                         
514         $ti =~/([\d\.]+)([smhdw])/i;
515         my $s = $1 * $timeslice{lc $2}; # So we have it in seconds
516                                                 
517         switch: {
518                 $s <= 43200             && do { $vp=($s/300)-1; last switch; };
519                 $s <= 86400             && do { $vp=(($s-(12*3600))/(30*60))+143; last switch; };               
520                 $s <= 2592000   && do { $vp=($s/(24*3600))+166; last switch; };
521                 $s <= 38102400  && do { $vp=($s/(24*3600*7))+192; last switch; };
522         }
523         return sprintf("%.2X", $vp);
524 }
525
526 sub encodeDataCodingScheme {
527         my ($self, $dcs) = @_;
528         my $c = '00';                   # default '7bit'
529         DCS: {
530                 $dcs eq '7bit'  && do { $c = '00';      last; };        
531                 $dcs eq '7biti' && do { $c = 'F0';      last; };
532                 $dcs eq '8bit'  && do { $c = 'F6';      last; };
533                 $dcs eq '8biti' && do { $c = 'F4';      last; };
534                 $dcs eq '8bitm' && do { $c = 'F5';  last; };
535         };
536         return $c;      
537 }
538
539 sub encodeDestinationAddress {
540         my ($self, $number) = @_;
541         my $pdu;
542         
543         # Find type of phonenumber
544         # no + => unknown number, + => international number
545         my $type = (substr($number,0,1) eq '+')?'91':'81';
546         
547         # Delete any non digits => + etc...
548         $number =~ s/\D//g;
549         
550         $pdu.= sprintf("%.2X%s",length($number),$type);
551         $number.= "F";                          # For odd number of digits
552         while ($number =~ /^(.)(.)(.*)$/) {     # Get pair of digits
553                 $pdu.= "$2$1";
554                 $number = $3;
555         }
556         return $pdu;
557 }
558
559
560 sub encodeServiceCenterAddress {
561         my ($self, $number) = @_;
562         my $pdu;
563         
564         return '00' if ($number eq '');
565         
566         # Find type of phonenumber
567         # no + => unknown number, + => international number
568         my $type = ($number=~/^\+/)?'91':'81';
569         
570         # Delete any non digits => + etc...
571         $number =~ s/\D//g;
572         
573         $pdu.= sprintf("%.2X%s",(length($number) >> 1)+1,$type);
574         $number.= "F";                          # For odd number of digits
575         while ($number =~ /^(.)(.)(.*)$/) {     # Get pair of digits
576                 $pdu.= "$2$1";
577                 $number = $3;
578         }
579         return $pdu;
580 }
581
582 sub getoctet {
583         my ($self, $ar, $len, $swap) = @_;
584
585         my $o = $ar->[0].$ar->[1];
586         $o=$self->swapoctet($o) if ($swap);
587         shift @$ar;
588         shift @$ar;
589         while (defined($len) && ($len - 1 > 0)) {
590                 my $oo = $ar->[0].$ar->[1];
591                 $oo=$self->swapoctet($oo) if ($swap);
592                 $o.= $oo;
593                 shift @$ar;
594                 shift @$ar;
595                 $len--;
596         }
597         return $o;      
598 }
599
600 sub swapoctet {
601         my ($self, $o) = @_;
602         my @o = split //, $o;
603         return $o[1].$o[0];
604 }
605
606 sub decode_7bit {
607         my ($self, $ud, $len) = @_;
608         my ($msg,$bits);
609         my $cnt=0;
610         $ud = $ud || "";
611         $len = $len || 0;
612         $msg = "";
613         my $byte = unpack('b8', pack('H2', substr($ud, 0, 2)));
614         while (($cnt<length($ud)) && (length($msg)<$len)) {
615                 $msg.= pack('b7', $byte);
616                 $byte = substr($byte,7,length($byte)-7);
617                 if ( (length( $byte ) < 7) ) {
618                         $cnt+=2; 
619                         $byte = $byte.unpack('b8', pack('H2', substr($ud, $cnt, 2)));
620                 }
621         }
622         return $msg;
623 }
624
625 sub encode_7bit {
626         my ($self, $msg) = @_;
627         my ($bits, $ud, $octet);
628
629         foreach (split(//,$msg)) {
630                 $bits .= unpack('b7', $_);
631         }
632         while (defined($bits) && (length($bits)>0)) {
633                 $octet = substr($bits,0,8);
634                 $ud .= unpack("H2", pack("b8", substr($octet."0" x 7, 0, 8)));
635                 $bits = (length($bits)>8)?substr($bits,8):"";
636         }
637         return uc $ud;
638 }
639
640 sub decode_8bit {
641         my ($self, $ud) = @_;
642         my $msg;
643
644         while ( length($ud) ) {
645                 $msg .= pack('H2',substr($ud,0,2));
646                 $ud = substr($ud,2);
647         }
648         return $msg;
649 }
650
651 sub encode_8bit {
652         my ($self, $ud) = @_;
653         my $msg;
654
655         #while (length($ud)) {
656         #       $msg .= sprintf("%.2X", ord(substr($ud,0,1)));
657         #       $ud = substr($ud,1);
658         #}
659         return $ud;
660 }
661
662 sub translate {
663         my ($self, $msg) = @_;
664         $msg=~ tr (\x00\x02) (\@\$);
665         $msg=~ tr (\x07\x0f\x7f\x04\x05\x1f\x5c\x7c\x5e\x7e) (iaaeeEOoUu);      
666         return $msg;
667 }
668
669 sub inversetranslate {
670         my ($self, $msg) = @_;
671         # $msg=~ tr (\@\$) (\x00\x02);
672         # $msg=~ tr (iaaeeEOoUu) (\x07\x0f\x7f\x04\x05\x1f\x5c\x7c\x5e\x7e);    
673         return $msg;
674 }
675 1;
676
677 =head1 NAME
678
679 GSM::SMS::PDU - Codec for Protocol Data Units.
680
681 =head1 DESCRIPTION
682
683 This module implements 2 PDUs ( Protocol Data Units ) ,SMS-DELIVER and SMS-SUBMIT, as defined in the SM-TL (Short Message Transport Layer ) specifications.
684 These PDUs are defined in the GSM03.40 specification from the ETSI ( www.etsi.org ). These PDUs are sufficient to implement NBS ( Narrow Bandwidth Sockets ).
685 Specification GSM07.05 explains the MMI ( Man Machine Interface ) for the AT+Cellular commands to be able to talk to a GSM modem.
686
687 =head1 METHODS
688
689         use GSM::SMS::PDU;
690         my $pdu = GSM::SMS::PDU->new();
691
692 =head2 SMSDeliver
693
694 Decode a short message that comes from the SMSC (Short Message Service Center) to the MS (Mobile Station) (SMS-DELIVER). 
695 Returns itself as a hash and you can access values the following way:
696         
697         my $originating_address = $pdu->{'TP-OA'}; 
698
699 =head2 SMSSubmit
700
701 Encode a short message for sending from the MS to the SMSC (SMS-SUBMIT).
702
703         my $encoded = $pdu->SMSSubmit( 
704                         $servicecenteraddress, 
705                         $phonenumber, 
706                         $payload, $datacodingscheme, 
707                         $validityperiod, 
708                         $userdataincluded );
709
710 =head2 SMSSubmit_decode
711
712 Decode a SMS-SUBMIT PDU.        
713
714 =head1 ISSUES
715
716 No real OO design. The NBS part that filters out the port-number in the UD ( User Data ) should be migrated to a higher (abstraction) layer.
717 No support for charsets.
718
719 =head1 AUTHOR
720
721 Johan Van den Brande <johan@vandenbrande.com>