1 package GSM::SMS::NBS::Message;
6 use GSM::SMS::NBS::Lib;
10 use vars qw($VERSION);
13 use constant DEFAULT_DCS_UDH => 0xF5; # 8bit, ME specific
14 use constant DEFAULT_DCS_NOUDH => 0x00; # default DCS (typically 7bit, SIM specific)
15 use constant DEFAULT_VALIDITY => "1d"; # FIXME: currently fixed to 1 day
16 use constant DEFAULT_SMS_CENTER => ""; # FIXME: currently fixed to empty SMS center address
18 use constant USERDATA_LENGTH => 140;
19 use constant DECONCAT_TOTAL => "_deconcat_total"; # internal: undef=>try single message, *=>try & messages
20 use constant TEXT_POSITION => "text_position"; # numeric position in $payload, UDH IE will be present only once
22 use constant IEI_IMELODY => 0x0C; #IEI: User Defined Sound (iMelody max 128 bytes)
23 use constant IEI_ANIMATION_LARGE => 0x0E; #IEI: EMS large animation (16x16 x4 = 128 bytes)
24 use constant IEI_ANIMATION_SMALL => 0x0F; #IEI: EMS small animation ( 8x 8 x4 = 32 bytes)
25 use constant IEI_PICTURE_LARGE => 0x10; #IEI: EMS large picture (32x32 = 128 bytes)
26 use constant IEI_PICTURE_SMALL => 0x11; #IEI: EMS small picture (16x16 = 32 bytes)
27 use constant IEI_PICTURE_VARIABLE => 0x12; #IEI: EMS variable picture (other)
28 use constant IEI_ALCATEL => 0x80; #IEI: Alcatel proprietary "data download" type
30 # SAR for NBS messages
31 # --------------------
32 # This part does the segmentation ... look in Stack.pm for reassembly
36 my $class = ref($proto) || $proto;
39 $self->{'__FRAMES__'} = [];
45 # Create a message from a payload -- compatibility cludge!
47 my ($self, $number, $payload, $destination_port, $source_port, $datacodingscheme) = @_;
49 "7bit" =>0x00, # default DCS (typically 7bit, SIM specific)
50 "7biti"=>0xF0, # 7bit, immediate display
51 "8bit" =>0xF6, # 8bit, SIM specific
52 "8biti"=>0xF4, # 8bit, immediate display
53 "8bitm"=>0xF5, # 8bit, ME specific
56 $datacodingscheme=$dcs_compat{$datacodingscheme} if $datacodingscheme && exists $dcs_compat{$datacodingscheme};
58 return $self->store($number, pack("H*", $payload),
59 "dcs"=>$datacodingscheme,
62 "type" =>"port16", #IEI: application port addressing scheme, 16 bit address
63 "destination_port"=>$destination_port,
64 "source_port" =>$source_port,
71 my ($self, $iei, @iedata) = @_;
73 return ($iei,scalar(@iedata),@iedata);
77 my ($self, %args) = @_;
79 return () if !defined $args{"destination_port"};
80 my $source_port=$args{"source_port"} || $args{"destination_port"};
82 return $self->_iebuilder(0x05, #IEI: application port addressing scheme, 16 bit address
83 ($args{"destination_port"} >> 8), ($args{"destination_port"} & 0xFF),
84 ($source_port >> 8), ($source_port & 0xFF),
89 my ($self, %args) = @_;
91 return $self->_iebuilder(0x00, #IEI: concatenated short message, 8 bit reference
92 $args{"drn"}, # reference number
93 $args{"fmax"}, # maximum number of short messages
94 $args{"fsn"}, # sequence number of the current short message
98 # Use $args{"force_variable"} to force type IEI_PICTURE_VARIABLE
99 # It needs to be used for compatibility with Alcatel 311 (firmware 101.01)
100 # - reported/tested by Radek Kadner <radek.kadner@atspraha.cz>
102 sub _ie_ems_picture {
103 my ($self, %args) = @_;
104 my $bitmap = $args{"bitmap"}; # GSM::SMS::Bitmap instance
106 $bitmap->crop(0xFF*8,0xFF);
107 my ($width, $height) = ($bitmap->{"width"}, $bitmap->{"height"});
110 if ($width == 16 && $height == 16) { $iei = IEI_PICTURE_SMALL ; }
111 elsif ($width == 32 && $height == 32) { $iei = IEI_PICTURE_LARGE ; }
112 else { $iei = IEI_PICTURE_VARIABLE; }
113 $iei = IEI_PICTURE_VARIABLE if $args{"force_variable"};
115 return $self->_iebuilder($iei,
116 $args{TEXT_POSITION}, # position in the SMS
117 ($iei != IEI_PICTURE_VARIABLE ? () : (int(($width+7)/8), $height)), # image size (optional)
118 $bitmap->pixlist_horiz()
123 sub ems_animation_maxsize
127 if (4 != @$bitmaps) {
128 carp "Invalid length of EMS animation (4 required)"; return ();
130 my $bitmap = $$bitmaps[0];
131 my ($width, $height) = ($bitmap->{"width"}, $bitmap->{"height"});
132 for my $testbitmap (@$bitmaps) {
133 $width =max($width ,$testbitmap->{"width" });
134 $height=max($height,$testbitmap->{"height"});
136 return ($width,$height);
139 sub _ie_ems_animation {
140 my ($self, %args) = @_;
141 my $bitmaps = $args{"bitmaps"}; # list of GSM::SMS::Bitmap instances
143 my ($width, $height) = ems_animation_maxsize($bitmaps);
146 if ($width <= 8 && $height <= 8) { $iei = IEI_ANIMATION_SMALL ; }
147 elsif ($width <= 16 && $height <= 16) { $iei = IEI_ANIMATION_LARGE ; }
149 $iei = IEI_ANIMATION_LARGE;
150 carp "Invalid frame size of EMS animation (<=16x<=16 required)";
153 return $self->_iebuilder($iei,
154 $args{TEXT_POSITION}, # position in the SMS
157 for my $bitmap (@$bitmaps) {
158 push @r,$bitmap->pixlist_horiz();
166 my ($self, %args) = @_;
168 return $self->_iebuilder(IEI_IMELODY,
169 $args{TEXT_POSITION}, # position in the SMS
170 unpack("C*",$args{"textdata"}),
175 my ($self, %args) = @_;
177 return $self->_iebuilder(IEI_ALCATEL,
178 (!defined($args{"name"}) ? (0x00) : # bit 7=0 (use GSM charset)
179 (length($args{"name"}), unpack("C*",
180 encode_payload(0xF1, GSM::SMS::PDU->inversetranslate($args{"name"})))) # 0xF1 DCS=any 7bit
182 (!!$args{"ems_compat"} << 7) # bit 7=EMS compatibility
183 |($args{"alcatel_type"} & 0x07), # bits 0..2=Alcatel message type
184 ($args{"length"} >> 8), ($args{"length"} & 0xFF),
189 my ($self, @srcs) = @_;
193 my @list=map { &{"_ie_".$$_{"type"}}($self,%$_); } @srcs;
196 # if it is too long it will be recalculated anyway
197 return pack "C*",(@list<=0xFF ? scalar(@list) : 0xFF),@list;
200 sub udh_prepare_ems_picture
204 my $bitmap=$udh->{"bitmap"}; # GSM::SMS::Bitmap instance
205 $bitmap->crop(0xFF*8,0xFF);
206 my ($width, $height) = ($bitmap->{"width"}, $bitmap->{"height"});
208 # pixel-payload of concat8 SMS w/one IEI_PICTURE_VARIABLE is 129 bytes
209 my $stripe=int(129/int(($width+7)/8));
210 carp "Image width too big to fit in one SMS part" if $stripe<=0; # ==0
211 return $udh if $stripe>=$height;
213 # Here we can't touch the text and thus we do split to lines without
214 # giving there a newline! For proper way see &gsmcmd::ems_picture_send
215 for (my $y=0;$y<$height;$y+=$stripe) {
218 "bitmap"=>GSM::SMS::Bitmap->new($bitmap,0,$y,$width,min($stripe,$height-$y)),
224 sub udh_prepare_ems_animation
228 my $bitmaps = $udh->{"bitmaps"}; # list of GSM::SMS::Bitmap instances
229 my ($width, $height) = ems_animation_maxsize($bitmaps);
230 return $udh if $width<=16 && $height<=16;
234 # Here we can't touch the text and thus we do split to lines without
235 # giving there a newline! For proper way see &gsmcmd::ems_animation_send
236 for (my $y=0;$y<$height;$y+=$height_now) {
237 $height_now=($y+8>=$height ? 8 : 16);
239 for (my $x=0;$x<$width;$x+=$width_now) {
240 # Never risk undeterministic wrapping if some rows yet to follow
241 # And we must be a square, not much other possibilies exist
242 $width_now=$height_now;
246 GSM::SMS::Bitmap->new($_,$x,$y,$width_now,$height_now);
258 return udh_prepare_ems_picture($udh) if $udh->{"type"} eq "ems_picture";
259 return udh_prepare_ems_animation($udh) if $udh->{"type"} eq "ems_animation";
263 # Create a message from a payload
264 # returns either error message or list (DECONCAT_TOTAL,total messages)
266 my ($self, $number, $payload, %args) = @_;
268 # Reset the FRAME array
269 $self->{'__FRAMES__'} = [];
272 my $PDU = GSM::SMS::PDU->new();
274 my @udhs=($args{"udh"} ? @{$args{"udh"}} : ());
275 @udhs=map({ udh_prepare($_); } @udhs);
277 # We put ME-specific default if ANY element of UDH was specified
278 # Autodetect $dcs BEFORE possible concatenation UDH IEs get inserted!
279 my $dcs=$args{"dcs"} || (@udhs ? DEFAULT_DCS_UDH : DEFAULT_DCS_NOUDH);
280 # print "DCS: $dcs\n";
282 my $deconcat_ieref; # defined only if deconcatenating
283 if (defined $args{DECONCAT_TOTAL}) {
286 "drn"=>int(rand(0xFF)), # reference number
287 "fmax"=>$args{DECONCAT_TOTAL}, # maximum number of short messages
288 "fsn"=>0x00, # sequence number of the current short message
290 $deconcat_ieref=\%deconcat_ie;
291 push @udhs,$deconcat_ieref;
292 return "Too long message (".$deconcat_ie{"fmax"}." elements)" if (0xFF < $deconcat_ie{"fmax"});
296 my @udhpositioned=();
297 for my $udh (@udhs) {
298 if (exists $udh->{TEXT_POSITION}) {
299 push(@udhpositioned, $udh);
301 push(@udhstatic, $udh);
304 @udhpositioned = sort { $a->{TEXT_POSITION} <=> $b->{TEXT_POSITION}; } @udhpositioned;
306 my $position=0; # absolute current position in the whole long SMS
307 while (length $payload || @udhpositioned) {
308 my @udhpositioned_now=(); # items from @udhpositioned placed in the current SMS part
309 my $payload_now=""; # text from $payload placed in the current SMS part
311 if (@udhpositioned && $udhpositioned[0]{TEXT_POSITION} <= $position) { # "<=" should be "=="
312 if (USERDATA_LENGTH < length($self->_udh_build(@udhstatic,@udhpositioned_now,$udhpositioned[0]))
313 +nail_payload_len($dcs,length($payload_now))) {
314 return (DECONCAT_TOTAL, undef) if !$deconcat_ieref;
315 return "UDH IE (User Data Header Information Element) too long to fit in one SMS fragment"
316 if $payload_now eq "" && !@udhpositioned_now;
320 my %udhei=%{shift @udhpositioned}; # copy it to local state - we will be modifying it
321 # we subtract the absolute position base of current SMS part:
322 $udhei{TEXT_POSITION} -= $position-length($payload_now);
324 push @udhpositioned_now,\%udhei;
327 my $minlen=length $payload;
328 $minlen=min($minlen,$udhpositioned[0]{TEXT_POSITION}-$position) if @udhpositioned;
329 $minlen=min($minlen,empty_subload($dcs,
330 USERDATA_LENGTH-length($self->_udh_build(@udhstatic,@udhpositioned_now))-length($payload_now)))
331 if defined $args{DECONCAT_TOTAL};
332 last if $minlen<=0; # "<=" should be "=="
333 $payload_now.=substr($payload,0,$minlen);
334 $payload=substr($payload,$minlen);
338 # We will ship out the current SMS part here
339 ++$deconcat_ieref->{"fsn"} if $deconcat_ieref;
340 # We exceeded userdata size, this is probably the first undeconcatenaed try from store()!
341 my $userdata=$self->_udh_build(@udhstatic,@udhpositioned_now).encode_payload($dcs,$payload_now);
343 return (DECONCAT_TOTAL, undef) if USERDATA_LENGTH < length($userdata);
345 my $pdu = $PDU->SMSSubmit(DEFAULT_SMS_CENTER, $number, $userdata,
346 $dcs, DEFAULT_VALIDITY, !!@udhstatic || !!@udhpositioned_now
348 # Push on to frame array
349 # print "--> $pdu\n";
350 push(@{$self->{"__FRAMES__"}}, $pdu);
353 carp "Last fsn==".$deconcat_ieref->{"fsn"}." but fmax==".$deconcat_ieref->{"fmax"}
354 if $args{DECONCAT_TOTAL} && $deconcat_ieref->{"fsn"}!=$deconcat_ieref->{"fmax"};
355 return (undef,(!$deconcat_ieref ? 1 : $deconcat_ieref->{"fsn"})); # success, return number of _PRODUCED_ MSes
359 my ($self, $number, $payload, %args) = @_;
362 ($err, $total) = $self->_store_try($number, $payload, %args, DECONCAT_TOTAL=>undef ); return $err if !$err || $err ne DECONCAT_TOTAL;
363 ($err, $total) = $self->_store_try($number, $payload, %args, DECONCAT_TOTAL=>0 ); return $err if $err;
364 ($err, $total) = $self->_store_try($number, $payload, %args, DECONCAT_TOTAL=>$total); return $err;
370 return $self->{'__FRAMES__'};
378 GSM::SMS::NBS::Message - SAR functionality for NBS messages.
382 Implements the segmentation in the SAR engine ( Segmentation And Reassembly ).
386 Johan Van den Brande <johan@vandenbrande.com>