3 use vars qw( $VERSION );
5 # author: Johan Van den Brande
11 my $class = ref($proto) || $proto;
20 my ($self, $data) = @_;
21 my $tpdu = $self->{TPDU};
22 my @msg = split //, $data;
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 # +-----------+-------------------+------------------+
32 # number of octets for BCD + 1 octet for type of number
34 # 81H : national number (e.g. 0495123456)
35 # 91H : international number (e.g. 32495123456 => need to prepend a '+')
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
41 # -------------------------------------------------------------------------
42 $tpdu->{'TP-SCN'} = $self->getServiceCenterAddress(\@msg);
43 ###########################################################################
45 ###########################################################################
47 # -------------------------------------------------------------------------
48 # Structure: (n) = bits
49 # +--------+----------+---------+-------+-------+--------+
50 # | RP (1) | UDHI (1) | SRI (1) | X (1) | X (1) | MTI(2) |
51 # +--------+----------+---------+-------+-------+--------+
55 # User Data Header Indicator = Does the UD contains a header
56 # 0 : Only the Short Message
57 # 1 : Beginning of UD containsheader information
59 # Status Report Indication.
60 # The SME (Short Message Entity) has requested a status report.
64 # -------------------------------------------------------------------------
65 $tpdu->{'TP-PDU'} = $self->getoctet(\@msg);
66 ###########################################################################
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 # +-----------+-------------------+------------------+
76 # number of BCD digits (This is different for the SCN!)
78 # 81H : national number (e.g. 0495123456)
79 # 91H : international number (e.g. 32495123456 => need to prepend a '+')
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
85 # -------------------------------------------------------------------------
86 $tpdu->{'TP-OA'} = $self->getOriginatingAddress(\@msg);
88 ###########################################################################
89 # 4) Get Protocol identifier (PID)
90 # -------------------------------------------------------------------------
93 # 00H: Short Message (SMS)
94 # 41H: Replace Short Message Type1
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 ###########################################################################
102 ###########################################################################
103 # 5) Get data coding scheme
104 # -------------------------------------------------------------------------
106 # bits 7 6 5 4 3 2 1 0
107 # +--------------+---+---+---+---+
108 # | Coding group | 0 | X | X | X |
109 # +--------------+---+---+---+---+
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
114 # Coding group | Alphabet indication
115 # ---------------+---------------------------------------------------------
116 # 0000 | 0000 Default alphabet
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);
140 ###########################################################################
141 # 6) Get service center timestamp
142 # -------------------------------------------------------------------------
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 ###########################################################################
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));
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'});
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'});
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";
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);
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);
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;
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;
210 } elsif (($dcs & 0xF0) == 0xF0) {
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);
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) {
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;
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;
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;
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'});
255 $tpdu->{'TP-UD'} = "";
257 ###########################################################################
262 my ($self, $servicecenter, $phonenumber, $data, $dcs, $vp, $udhi) = @_;
267 ###########################################################################
268 # 1) Service center address
269 # -------------------------------------------------------------------------
270 # Look at SMSDeliver for notes
271 # -------------------------------------------------------------------------
272 $pdu.=$self->encodeServiceCenterAddress($servicecenter);
273 ###########################################################################
275 ###########################################################################
277 # -------------------------------------------------------------------------
278 # Structure : (n) = bits
279 # +--------+----------+---------+---------+--------+---------+
280 # | RP (1) | UDHI (1) | SRR (1) | VPF (2) | RD (1) | MTI (2) |
281 # +--------+----------+---------+---------+--------+---------+
283 # Reply path : 0 = not set / 1 = set
285 # User data only contains short message : 0
286 # User data contains a header : 1
288 # Status report requested : 0 = no / 1 = yes
290 # Validity period field
293 # 1 0 : VP field present : relative (integer)
294 # 1 1 : VP field present : absolute (semi-octet)
296 # Reject (1) or accept (0) an SMS in the SMSC with the same MR and DA from the same OA
299 # 0 0 : SMS deliver SMSC -> MS
300 # 0 1 : SMS Submit MS->SMSC
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);
313 ###########################################################################
315 ###########################################################################
316 # 3) Message reference
317 # -------------------------------------------------------------------------
318 # The M20 generates this himself, so we can dummy to 00H
319 # -------------------------------------------------------------------------
321 ###########################################################################
323 ###########################################################################
324 # Destination address
325 # -------------------------------------------------------------------------
326 # See SMSDeliver for a description
327 # -------------------------------------------------------------------------
328 $pdu.=$self->encodeDestinationAddress($phonenumber);
329 ###########################################################################
331 ###########################################################################
332 # protocol identifier
333 # -------------------------------------------------------------------------
334 # See SMSDeliver for a description
336 # -------------------------------------------------------------------------
338 ###########################################################################
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 ###########################################################################
353 ###########################################################################
355 # -------------------------------------------------------------------------
356 # Look at encodeValidityPeriod
357 # -------------------------------------------------------------------------
359 # $pdu.=$self->encodeValidityPeriod($vp);
362 ###########################################################################
365 ###########################################################################
366 # Length of message (Length of user data)
367 # -------------------------------------------------------------------------
368 # $pdu.=sprintf("%.2X", length($data));
369 ###########################################################################
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));
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));
384 ###########################################################################
389 # decode a SMSSubmit message (experimental!)
390 sub SMSSubmit_decode {
391 my ($self, $data) = @_;
392 my @msg = split //, $data;
395 my $sca = $self->getServiceCenterAddress(\@msg);
398 my $pdu = $self->getoctet(\@msg);
401 my $mref = $self->getoctet(\@msg);
403 # destination address
404 my $da = $self->getOriginatingAddress(\@msg);
406 # protocol identifier
407 my $pi = $self->getoctet(\@msg);
410 my $ds = $self->getoctet(\@msg);
413 my $vp = $self->getoctet(\@msg);
416 my $dl = $self->getoctet(\@msg);
421 # print join "|", @msg;
425 # we have a user data header
426 my $udhl = hex($msg[0].$msg[1]);
428 # print "udhl ($msg[0]): $udhl\n";
430 $udh = $self->getoctet(\@msg, $udhl+1);
431 $payload = join("", @msg);
433 $payload = $self->decode_7bit( join("", @msg), 160 );
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";
443 return ($da, $pdu, $ds, $udh, $payload);
446 # Get an Adress (OA / DA )
447 sub getServiceCenterAddress {
448 my($self, $ref_msg_arr) = @_;
451 # First get address length
452 my $len = hex($self->getoctet($ref_msg_arr));
454 # Second get Type of address
455 my $typ = $self->getoctet($ref_msg_arr);
457 # Third get address itself ...
458 for (my $pos=0;$pos<$len-1;$pos++) {
459 $adr.= $self->swapoctet($self->getoctet($ref_msg_arr));
462 # If length is odd we have a trailing F;
463 (($len) & 0x1) && chop($adr);
465 # Append a '+' to make a valid international number, when type is 91
466 $adr = ($typ == 91)?'+'.$adr:$adr;
472 # Get an Adress (OA / DA )
473 sub getOriginatingAddress {
474 my($self, $ref_msg_arr) = @_;
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 ...
483 for (my $pos=0;$pos<$len;$pos+=2) {
484 $adr.= $self->swapoctet($self->getoctet($ref_msg_arr));
487 # If length is odd we have a trailing F;
488 (($len) & 0x1) && chop($adr);
490 # Append a '+' to make a valid international number, when type is 91
491 $adr = ($typ == 91)?'+'.$adr:$adr;
497 # For the moment, only integer relative scheme
498 # IN: Validity Period in ns(econds), nm(inutes), nh(ours), nd(ays), nw(eeks)
500 # OUT: integer representation of validity period
501 sub encodeValidityPeriod {
502 my ($self, $ti) = @_;
514 $ti =~/([\d\.]+)([smhdw])/i;
515 my $s = $1 * $timeslice{lc $2}; # So we have it in seconds
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; };
523 return sprintf("%.2X", $vp);
526 sub encodeDataCodingScheme {
527 my ($self, $dcs) = @_;
528 my $c = '00'; # default '7bit'
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; };
539 sub encodeDestinationAddress {
540 my ($self, $number) = @_;
543 # Find type of phonenumber
544 # no + => unknown number, + => international number
545 my $type = (substr($number,0,1) eq '+')?'91':'81';
547 # Delete any non digits => + etc...
550 $pdu.= sprintf("%.2X%s",length($number),$type);
551 $number.= "F"; # For odd number of digits
552 while ($number =~ /^(.)(.)(.*)$/) { # Get pair of digits
560 sub encodeServiceCenterAddress {
561 my ($self, $number) = @_;
564 return '00' if ($number eq '');
566 # Find type of phonenumber
567 # no + => unknown number, + => international number
568 my $type = ($number=~/^\+/)?'91':'81';
570 # Delete any non digits => + etc...
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
583 my ($self, $ar, $len, $swap) = @_;
585 my $o = $ar->[0].$ar->[1];
586 $o=$self->swapoctet($o) if ($swap);
589 while (defined($len) && ($len - 1 > 0)) {
590 my $oo = $ar->[0].$ar->[1];
591 $oo=$self->swapoctet($oo) if ($swap);
602 my @o = split //, $o;
607 my ($self, $ud, $len) = @_;
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) ) {
619 $byte = $byte.unpack('b8', pack('H2', substr($ud, $cnt, 2)));
626 my ($self, $msg) = @_;
627 my ($bits, $ud, $octet);
629 foreach (split(//,$msg)) {
630 $bits .= unpack('b7', $_);
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):"";
641 my ($self, $ud) = @_;
644 while ( length($ud) ) {
645 $msg .= pack('H2',substr($ud,0,2));
652 my ($self, $ud) = @_;
655 #while (length($ud)) {
656 # $msg .= sprintf("%.2X", ord(substr($ud,0,1)));
657 # $ud = substr($ud,1);
663 my ($self, $msg) = @_;
664 $msg=~ tr (\x00\x02) (\@\$);
665 $msg=~ tr (\x07\x0f\x7f\x04\x05\x1f\x5c\x7c\x5e\x7e) (iaaeeEOoUu);
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);
679 GSM::SMS::PDU - Codec for Protocol Data Units.
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.
690 my $pdu = GSM::SMS::PDU->new();
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:
697 my $originating_address = $pdu->{'TP-OA'};
701 Encode a short message for sending from the MS to the SMSC (SMS-SUBMIT).
703 my $encoded = $pdu->SMSSubmit(
704 $servicecenteraddress,
706 $payload, $datacodingscheme,
710 =head2 SMSSubmit_decode
712 Decode a SMS-SUBMIT PDU.
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.
721 Johan Van den Brande <johan@vandenbrande.com>