File Coverage

blib/lib/CDR/Parser/SI3000.pm
Criterion Covered Total %
statement 266 352 75.5
branch 25 56 44.6
condition 21 42 50.0
subroutine 25 32 78.1
pod 1 25 4.0
total 338 507 66.6


line stmt bran cond sub pod time code
1             package CDR::Parser::SI3000;
2              
3 2     2   52036 use 5.10.0;
  2         41  
  2         127  
4 2     2   12 use strict;
  2         4  
  2         86  
5 2     2   11 use warnings FATAL => 'all';
  2         10  
  2         134  
6 2     2   2512 use Data::Dumper;
  2         25668  
  2         172  
7 2     2   7844 use IO::File ();
  2         23780  
  2         7248  
8              
9             =head1 NAME
10              
11             CDR::Parser::SI3000 - parser for binary CDR files (*.ama) produced by Iskratel SI3000 MSCN telephony product
12              
13             CDR = Call Detail Records
14              
15             =head1 VERSION
16              
17             Version 0.01
18              
19             =cut
20              
21             our $VERSION = '0.02';
22              
23             our $VERBOSE = 0;
24              
25             =head1 SYNOPSIS
26              
27             Whis module parses the binary file format and returns it as Perl data.
28              
29             Usage example
30              
31             use CDR::Parser::SI3000;
32              
33             my ($cdr_list, $num_failed) = CDR::Parser::SI3000->parse_file('somefile.ama');
34              
35             There
36             $cdr_list is a array-reference containing individual records as hash-ref.
37             $num_failed is a number of unparseable records
38              
39              
40             =head1 SUBROUTINES/METHODS
41              
42             =head2 parse_file
43              
44             Get filename as input, open it, read it, returns the parsed result
45              
46             =cut
47              
48              
49             #--
50              
51             # $| = 1;
52              
53             sub _log {
54 901     901   2226 my($format, @args) = @_;
55 901 50       3028 return if(! $VERBOSE);
56 0         0 printf $format."\n", @args;
57             }
58              
59             # public
60             sub parse_file {
61 1     1 1 22 my($class, $filename) = @_;
62 1 50       7 die "No filename argument" if(! $filename);
63 1         6 _log('Parsing file %s', $filename);
64              
65 1   50     12 my $fh = IO::File->new($filename) || die "Failed to open $filename - $!";
66 1         149 binmode($fh, ':bytes');
67              
68 1         4 my @records = ();
69              
70 1         2 my $rows = 0;
71 1         3 my $failed = 0;
72              
73 1         2 while(1) {
74 16         66 my $call = parse_record($fh);
75 16 100       40 if($call) {
76 15         52 push @records, $call;
77             }
78             else {
79 1 50       6 last if($call == 0);
80 0         0 $failed++;
81             }
82             }
83              
84 1         17 $fh->close;
85              
86 1         35 return (\@records, $failed);
87             }
88              
89             #------ private implementation ------
90              
91             # 100. Called number
92             sub block_100 {
93 15     15 0 24 my($call,$variable) = @_;
94              
95 15         30 _log('100. Called number');
96 15         25 my $cld_len;
97 15         92 ($cld_len, $$variable) = unpack('C a*', $$variable);
98 15         76 my $cut = $cld_len;
99 15 50       62 $cut++ if($cld_len % 2 == 1);
100 15         19 my $cld;
101 15         93 ($cld, $$variable) = unpack("H$cut a*", $$variable);
102 15 50       55 if($cut > $cld_len) {
103 15         42 $cld = substr($cld, 0, -1);
104             }
105 15         42 _log(' CLD: %s', $cld);
106 15         119 $call->{cld} = $cld;
107             }
108              
109             # 101. Call accepting party number
110             # 102. Start Date and Time
111             sub block_102 {
112 15     15 0 26 my($call, $var) = @_;
113 15         35 _log("102. Start Date and Time");
114 15         17 my($year,$month,$day,$hour,$min,$sec,$msec,$reserved);
115 15         245 ($year,$month,$day,$hour,$min,$sec,$msec,$reserved,$$var) = unpack('CCCCCCC H2 a*', $$var);
116 15         67 $year += 2000;
117 15         106 my $start_time = sprintf "%04d-%02d-%02d %02d:%02d:%02d.%02d", $year,$month,$day,$hour,$min,$sec,$msec;
118 15         38 _log(' Start Time: %s', $start_time);
119 15         126 $call->{start_time} = $start_time;
120             }
121             # 103. End Date and Time
122             sub block_103 {
123 15     15 0 27 my($call, $var) = @_;
124 15         28 _log("103. End Date and Time");
125 15         26 my($year,$month,$day,$hour,$min,$sec,$msec,$reliable);
126 15         176 ($year,$month,$day,$hour,$min,$sec,$msec,$reliable,$$var) = unpack('CCCCCCC H2 a*', $$var);
127 15         66 $year += 2000;
128 15         83 my $end_time = sprintf "%04d-%02d-%02d %02d:%02d:%02d.%02d", $year,$month,$day,$hour,$min,$sec,$msec;
129 15         61 _log(' End Time: %s', $end_time);
130 15         115 $call->{end_time} = $end_time;
131             }
132             # 104. Number of charging units
133             sub block_104 {
134 15     15 0 32 my($call, $var) = @_;
135 15         25 _log("104. Number of charging units");
136 15         24 my($unit1,$unit2,$unit3);
137 15         114 ($unit1,$unit2,$unit3,$$var) = unpack('CCC a*', $$var);
138 15         126 my $units = ($unit1 << 16) + ($unit2 << 8) + $unit3;
139 15         45 _log(' Units: %d', $units);
140 15         101 $call->{charging_units} = $units;
141             }
142             # 105. Basic service
143             sub block_105 {
144 15     15 0 26 my($call, $var) = @_;
145 15         34 _log("105. Basic service");
146 15         66 my %bearer_label = (
147             0 => '64 kbit/s for speech information transfer',
148             8 => '64 kbit/s unrestricted',
149             16 => '64 kbit/s for 3.1kHz audio information transfer',
150             );
151 15         31 my %service_label = (
152             1 => 'Telephony',
153             # .... TODO
154             );
155 15         19 my($bearer,$service);
156 15         92 ($bearer,$service,$$var) = unpack('C C a*', $$var);
157 15   50     94 _log(" Bearer: %d / %s", $bearer, $bearer_label{ $bearer } // 'UNKNOWN');
158 15   50     76 _log(" Service: %d / %s", $service, $service_label{ $service } // 'UNKNOWN');
159 15   50     107 $call->{bearer} = $bearer_label{ $bearer } // 'UNKNOWN';
160 15   50     145 $call->{service} = $service_label{ $service } // 'UNKNOWN';
161             }
162             # 106. Supplementary service used by calling subscriber
163             sub block_106 {
164 0     0 0 0 my($call, $var) = @_;
165 0         0 _log('106. Supplementary service used by calling subscriber');
166 0         0 my($sup_service);
167 0         0 ($sup_service, $$var) = unpack('C a*', $$var);
168 0         0 _log(' Supplementary calling service: %d', $sup_service);
169 0         0 $call->{supplementary_calling_service} = $sup_service;
170             }
171             # 107. Supplementary service used by called subscriber
172             sub block_107 {
173 0     0 0 0 my($call, $var) = @_;
174 0         0 _log('106. Supplementary service used by called subscriber');
175 0         0 my($sup_service);
176 0         0 ($sup_service, $$var) = unpack('C a*', $$var);
177 0         0 _log(' Supplementary called service: %d', $sup_service);
178 0         0 $call->{supplementary_called_service} = $sup_service;
179             }
180             # 108. Subscriber controlled input
181             # 109. Dialed digits
182             # 110. Origin category
183             sub block_110 {
184 15     15 0 26 my($call, $var) = @_;
185 15         32 _log("110. Origin category");
186 15         19 my($origin);
187 15         89 ($origin,$$var) = unpack('C a*', $$var);
188 15         48 _log(" Origin category: %d", $origin);
189 15         94 $call->{origin_category} = $origin;
190             }
191             # 111. Tariff direction
192             sub block_111 {
193 15     15 0 28 my($call, $var) = @_;
194 15         35 _log("111. Tariff direction");
195 15         22 my($tariff);
196 15         73 ($tariff,$$var) = unpack('C a*', $$var);
197 15         48 _log(" Tariff direction: %d", $tariff);
198 15         91 $call->{tariff_direction} = $tariff;
199             }
200             # 112. Call failure cause
201             # 113. Incoming trunk data
202             sub block_113 {
203 15     15 0 29 my($call, $var) = @_;
204 15         33 _log("113. Incoming trunk data");
205 15         26 my($group,$id,$shelf,$port,$channel);
206 15         128 ($group,$id,$shelf,$port,$channel,$$var) = unpack('n n C n C a*', $$var);
207 15         64 _log(" Trunk group: %d\n Trunk identification: %d\n Shelf identification: %d\n Port identification: %d\n Channel identification: %d",
208             $group,$id,$shelf,$port,$channel);
209 15         90 $call->{incoming_trunk_group} = $group;
210 15         44 $call->{incoming_trunk} = $id;
211 15         49 $call->{incoming_shelf} = $shelf;
212 15         34 $call->{incoming_port} = $port;
213 15         105 $call->{incoming_channel} = $channel;
214             }
215             # 114. Outgoing trunk data
216             sub block_114 {
217 15     15 0 30 my($call, $var) = @_;
218 15         28 _log("114. Outgoing trunk data");
219 15         28 my($group,$id,$shelf,$port,$channel);
220 15         155 ($group,$id,$shelf,$port,$channel,$$var) = unpack('n n C n C a*', $$var);
221 15         60 _log(" Trunk group: %d\n Trunk identification: %d\n Shelf identification: %d\n Port identification: %d\n Channel identification: %d",
222             $group,$id,$shelf,$port,$channel);
223 15         45 $call->{outgoing_trunk_group} = $group;
224 15         31 $call->{outgoing_trunk} = $id;
225 15         35 $call->{outgoing_shelf} = $shelf;
226 15         30 $call->{outgoing_port} = $port;
227 15         97 $call->{outgoing_channel} = $channel;
228             }
229             # 115. Call duration
230             sub block_115 {
231 15     15 0 24 my($call, $var) = @_;
232 15         30 _log("115. Call duration");
233 15         21 my($duration);
234 15         76 ($duration,$$var) = unpack('N a*', $$var);
235 15         130 _log(" Call duration: %d msec / %.5f sec", $duration, $duration / 1000.0);
236 15         184 $call->{call_duration} = sprintf '%.5f', $duration / 1000.0;
237 15         103 $call->{call_duration_ms} = $duration;
238             }
239             # 116. Checksum
240             sub block_116 {
241 15     15 0 28 my($call, $var) = @_;
242 15         30 _log("116. Checksum");
243 15         22 my($len,$checksum);
244 15         130 ($len,$checksum,$$var) = unpack('C a2 a*', $$var);
245 15         65 _log(" Checksum: 0x%s", unpack('H*', $checksum));;
246 15         114 $call->{checksum} = unpack('H*', $checksum);
247             }
248             # 117. Business and Centrex group ID
249             sub block_117 {
250 0     0 0 0 my($call, $var) = @_;
251 0         0 _log("117. Business and Centrex group ID");
252 0         0 my($len,$business,$centrex);
253 0         0 ($len,$business,$centrex,$$var) = unpack('C N N a*', $$var);
254 0         0 _log(" Business group ID: %d", $business);
255 0         0 _log(" Centrex group ID: %d", $centrex);
256 0         0 $call->{business_group} = $business;
257 0         0 $call->{centrex_group} = $centrex;
258             }
259             # 118. Carrier access code
260             # 119. Original calling party number
261             sub block_119 {
262 0     0 0 0 my($call, $var) = @_;
263 0         0 _log("119. Original calling party number");
264 0         0 my($len,$num_len);
265 0         0 ($len,$num_len,$$var) = unpack('C C a*', $$var);
266 0         0 my $cut = $num_len;
267 0 0       0 $cut++ if($num_len %2 == 1);
268 0         0 my $num;
269 0         0 ($num,$$var) = unpack("H$cut a*", $$var);
270 0 0       0 if($cut > $num_len) {
271 0         0 $num = substr($num, 0, -1);
272             }
273 0         0 _log(" Original CLI: %s", $num);
274 0         0 $call->{original_cli} = $num;
275             }
276             # 120. Prepaid account recharge data
277             # 121. Call release cause
278             sub block_121 {
279 15     15 0 35 my($call, $var) = @_;
280 15         29 _log("121. Call release cause");
281 15         60 my %cause_label = (
282             16 => 'normal call clearing',
283             41 => 'temporary failure',
284             );
285 15         29 my %coding_label = (
286             0 => 'ITU-T standard',
287             );
288 15         137 my %location_label = (
289             0 => 'user',
290             1 => 'private network serving the local user',
291             2 => 'public network serving the local user',
292             3 => 'transit network',
293             4 => 'public network serving the remote user',
294             5 => 'private network serving the remote user',
295             7 => 'international network',
296             10 => 'network beyond interworking point',
297             );
298 15         19 my($len,$cause,$flag);
299 15         121 ($len,$cause,$flag,$$var) = unpack('C n C a*', $$var);
300 15         64 my $coding = (($flag & 0xF0) >> 4);
301 15         34 my $location = ($flag & 0x0F);
302 15   50     61 _log(" Cause: %d / %s", $cause, $cause_label{ $cause } // 'UNKNOWN');;
303 15   50     63 _log(" Coding standard: %d / %s", $coding, $coding_label{ $coding } // 'UNKNOWN');
304 15   50     65 _log(" Location: %d / %s", $location, $location_label{ $location } // 'UNKNOWN');
305 15   50     59 $call->{call_release_cause} = $cause_label{ $cause } // 'UNKNOWN';
306 15         41 $call->{call_release_cause_code} = $cause;
307 15   50     70 $call->{call_coding_standard} = $coding_label{ $coding } // 'UNKNOWN';
308 15   50     167 $call->{call_location} = $location_label{ $location } // 'UNKNOWN';
309             }
310             # 122. CBNO (Charge Band Number)
311             # 123. Common call ID
312             # 124. Durations before answer
313             # 125. VoIP Info (old)
314             # 126. Amount of Transferred Data (old)
315             # 127. IP Address
316             sub block_127 {
317 15     15 0 31 my($call, $var) = @_;
318 15         33 _log("127. IP Address");
319 15         26 my($len,$ip_data);
320 15         77 ($len,$$var) = unpack('C a*', $$var);
321 15         46 $len -= 2;
322 15         94 ($ip_data,$$var) = unpack("a$len a*", $$var);
323 15         34 my($flag,$reserved,@ip);
324 15         79 ($flag,$reserved,@ip) = unpack('C C N*', $ip_data);
325 15         43 @ip = map { join '.', unpack 'C4', pack 'N', $_ } @ip;
  30         328  
326 15 50       85 if($flag & 0x1) {
327 15         37 my $ip = shift(@ip);
328 15         39 _log(" Origin side remote RTP IP: %s", $ip);
329 15         67 $call->{origin_remote_rtp} = $ip;
330             }
331 15 50       46 if($flag & 0x2) {
332 0         0 my $ip = shift(@ip);
333 0         0 _log(" Origin side local RTP IP: %s", $ip);
334 0         0 $call->{origin_local_rtp} = $ip;
335             }
336 15 50       45 if($flag & 0x4) {
337 15         33 my $ip = shift(@ip);
338 15         39 _log(" Terminating side remote RTP IP: %s", $ip);
339 15         60 $call->{terminating_remote_rtp} = $ip;
340             }
341 15 50       48 if($flag & 0x8) {
342 0         0 my $ip = shift(@ip);
343 0         0 _log(" Terminating side local RTP IP: %s", $ip);
344 0         0 $call->{terminating_local_rtp} = $ip;
345             }
346 15 50       43 if($flag & 0x10) {
347 0         0 my $ip = shift(@ip);
348 0         0 _log(' Origin side remote signaling IP: %s', $ip);
349 0         0 $call->{origin_remote_signaling} = $ip;
350             }
351 15 50       45 if($flag & 0x20) {
352 0         0 my $ip = shift(@ip);
353 0         0 _log(' Origin side local signaling IP: %s', $ip);
354 0         0 $call->{origin_local_signaling} = $ip;
355             }
356 15 50       47 if($flag & 0x40) {
357 0         0 my $ip = shift(@ip);
358 0         0 _log(' Terminating side remote signaling IP: %s', $ip);
359 0         0 $call->{terminating_remote_signaling} = $ip;
360             }
361 15 50       128 if($flag & 0x80) {
362 0         0 my $ip = shift(@ip);
363 0         0 _log(' Terminating side local signaling IP: %s', $ip);
364 0         0 $call->{terminating_local_signaling} = $ip;
365             }
366             }
367             # 128. VoIP info
368             sub block_128 {
369 15     15 0 27 my($call, $var) = @_;
370 15         34 _log("128. VoIP info");
371 15         25 my($len,$rx_codec,$tx_codec,$rx_period,$tx_period,$rx_bandwidth,$tx_bandwidth,$max_jitter,$flag);
372 15         249 ($len,$rx_codec,$tx_codec,$rx_period,$tx_period,$rx_bandwidth,$tx_bandwidth,$max_jitter,$flag,$$var) = unpack('CCCCCnnnC a*', $$var);
373 15         184 my %codec_label = (
374             0 => 'Undefined',
375             8 => 'G711Alaw64k',
376             9 => 'G711Ulaw64k',
377             66 => 'G728',
378             67 => 'G729',
379             68 => 'G729annexA',
380             70 => 'G729wAnnexB',
381             71 => 'G729AnnexAwAnnexB',
382             72 => 'GsmFullRate',
383             80 => 'G7231A5_3k',
384             81 => 'G7231A6_3k',
385             129 => 'FaxT38',
386             );
387 15         41 my %side_label = ( 0 => 'origin side', 1 => 'terminating side');
388 15         51 my %type_label = (
389             0 => 'Undefined',
390             1 => 'Audio',
391             2 => 'Data',
392             3 => 'Fax',
393             );
394              
395 15   50     67 _log(" Rx codec: %d / %s", $rx_codec, $codec_label{ $rx_codec } // 'UNKNOWN');
396 15   50     122 _log(" Tx codec: %d / %s", $tx_codec, $codec_label{ $tx_codec } // 'UNKNOWN');
397 15         36 _log(" Rx packetization period: %d ms", $rx_period);
398 15         31 _log(" Tx packetization period: %d ms", $tx_period);
399 15         33 _log(" Rx bandwidth: %d kbit/s", $rx_bandwidth);
400 15         34 _log(" Tx bandwidth: %d kbit/s", $tx_bandwidth);
401 15         34 _log(" Max. jitter buffer size: %d ms", $max_jitter);
402 15         47 my $call_side = ($flag & 0x80) >> 7;
403 15         34 my $call_type = $flag & 0x7F;
404 15   50     60 _log(" Call side: %d / %s", $call_side, $side_label{ $call_side } // 'UNKNOWN');
405 15   50     61 _log(" VoIP call type: %d / %s", $call_type, $type_label{ $call_type } // 'UNKNOWN');
406              
407 15   50     137 $call->{voip_rx_codec} = $codec_label{ $rx_codec } // 'UNKNOWN';
408 15   50     61 $call->{voip_tx_codec} = $codec_label{ $tx_codec } // 'UNKNOWN';
409 15         63 $call->{voip_rx_packetization} = $rx_period;
410 15         51 $call->{voip_tx_packetization} = $tx_period;
411 15         38 $call->{voip_rx_bandwidth} = $rx_bandwidth;
412 15         37 $call->{voip_tx_bandwidth} = $tx_bandwidth;
413 15         33 $call->{voip_max_jitter} = $max_jitter;
414 15   50     50 $call->{voip_call_size} = $side_label{ $call_side } // 'UNKNOWN';
415 15   50     207 $call->{voip_call_type} = $type_label{ $call_type } // 'UNKNOWN';
416             }
417             # 129. Amount of transferred data
418             sub block_129 {
419 15     15 0 59 my($call, $var) = @_;
420 15         30 _log("129. Amount of transferred data");
421 15         23 my($len,$side,$rx_packets,$tx_packets,$rx_octets,$tx_octets,$lost,$jitter,$latency);
422 15         190 ($len,$side,$rx_packets,$tx_packets,$rx_octets,$tx_octets,$lost,$jitter,$latency,$$var) = unpack('CCNNNNNCC a*', $$var);
423 15         89 my %side_label = ( 0 => 'origin side', 1 => 'terminating side');
424 15   50     61 _log(" Call side: %d / %s", $side, $side_label{ $side } // 'UNKNOWN');
425 15         63 _log(" Rx packets: %d", $rx_packets);
426 15         36 _log(" Tx packets: %d", $tx_packets);
427 15         32 _log(" Rx octets: %d", $rx_octets);
428 15         41 _log(" Tx octets: %d", $tx_octets);
429 15         35 _log(" Packets lost: %d", $lost);
430 15         42 _log(" Average jitter: %d ms", $jitter);
431 15         33 _log(" Average latency: %d ms", $latency);
432 15   50     64 $call->{call_side} = $side_label{ $side } // 'UNKNOWN';
433 15         46 $call->{rx_packets} = $rx_packets;
434 15         38 $call->{tx_packets} = $tx_packets;
435 15         36 $call->{rx_octets} = $rx_octets;
436 15         31 $call->{tx_octets} = $tx_octets;
437 15         34 $call->{packets_lost} = $lost;
438 15         31 $call->{average_jitter} = $jitter;
439 15         130 $call->{everage_latency} = $latency;
440             }
441             # 130. Service control data
442             # 131. New destination number
443             # 132. VoIP Quality of Service data (QoS VoIP Data)
444             # 133. Additional Centrex data
445             # 134. Additional statistics data
446             sub block_134 {
447 15     15 0 32 my($call, $var) = @_;
448 15         33 _log("134. Additional statistics data");
449 15         21 my($len,$stats);
450 15         75 ($len,$$var) = unpack('C a*', $$var);
451 15         40 $len -= 2;
452 15         96 ($stats,$$var) = unpack("a$len a*", $$var);
453 15         66 _log(" Stats: 0x%s (len %d)", unpack('H*', $stats), $len);
454             # no useful info?
455             }
456             # TODO NOT IMPLEMENTED YET:
457             # 135. IMS charging identifier
458             # 136. Inter Operator Identifiers – IOI)
459             # 137. Supplementary service additional info
460             # 138. Calling Party Number
461             # 139. Additional calling number
462             # 140. Called party number
463             # 141. Sent called party number
464             # 142. Third party number
465             # 143. Redirecting party number
466             # 144. Incoming trunk data - Name
467             # 145. Outgoing trunk data - name
468             # 146. Node info
469             # 147. Global call reference
470             # 148. MLPP Data
471             # 149. Customer Data
472             # 150. Received Called Party Number
473             # 151. Call Type
474             # 152. IN Service Data
475             # 153. URI (Universal Resource Identification)
476             # 154. Free Format Operator Specific Data
477             # 155. -----
478             # 156. Additional Numbers
479              
480             sub dump_var {
481 0     0 0 0 my $var = shift;
482 0         0 _log("---- Variable data (%d) ----", length($var));
483 0         0 _log('%s', unpack('H*', $var));
484             }
485              
486             # Parse individual record
487             #
488             # Each record has fixed part 16+(2..19) bytes
489             # and optional set of additional blocks
490             sub parse_record {
491 16     16 0 25 my $fh = shift;
492              
493 16         20 my $type_id;
494 16 100       321 sysread($fh, $type_id, 1) || return 0;
495 15         47 my $code = unpack('H2', $type_id);
496              
497             # Recort type:
498             # d2 -- Record at date and time changes (parsed but ignored)
499             # d3 -- Record of the loss of a certain amount of records (NOT SUPPORTED)
500             # d4 -- Restart record (NOT SUPPORTED)
501             # c8 -- Call record
502 15 50       73 if($code eq 'd2') {
    50          
    50          
503 0         0 parse_time_change_record($fh, $code);
504             # next
505 0         0 return parse_record($fh);
506             }
507             elsif($code eq 'd4') {
508 0         0 parse_reboot_record($fh, $code);
509 0         0 return parse_record($fh);
510             }
511             elsif($code ne 'c8') {
512 0         0 die "Unknown record type: $code";
513             }
514 15         49 _log('Found Call Record marker %s', $code);
515              
516 15         37 my %call = ();
517              
518             # Statis header
519             # 1 - c8
520             # 2 - record length
521             # 4 - record index (in file?)
522             # 4 - call identifier (sequentially incremented number, unique -
523             # but incomplete calls can have call-id repeated again in later file)
524             # 3 - flags
525             # 1 - Sequence (4bits) / Charge status (4bits)
526             # 1 - Area code length (3bits) / Subscriber number length (5bits)
527             # ... - Area code and subscriber number of record owner
528 15         19 my $len;
529 15 50       108 sysread($fh, $len, 2) || die $!;
530 15         45 $len = unpack('n', $len);
531             #printf "Record lengh: %d bytes\n", $len;
532              
533 15         31 my $data;
534 15 50       126 sysread($fh, $data, $len - 3) || die $!;
535             #print unpack('H*', $data), "\n";
536              
537 15         122 my($rec_index,$call_id,$flags,$seq,$area,$variable) = unpack('N N H6 H2 C a*', $data);
538 15         50 _log("Header");
539 15         37 _log(" Record index: %d", $rec_index);
540 15         65 _log(" Call ID: %d / %s", $call_id, unpack('H*', pack('N', $call_id)));
541 15         66 _log(" Flags: 0x%s", unpack('H6', pack('H6', $flags)));
542 15         56 _log(" Record sequence: %d", (($seq & 0xF0) >> 4));
543 15         41 _log(" Charge status: %d", ($seq & 0x0F));
544              
545 15         53 $call{record_index} = $rec_index;
546 15         44 $call{call_id} = $call_id;
547 15         29 $call{flags} = $flags;
548 15         31 $call{record_sequence} = (($seq & 0xF0) >> 4);
549 15         28 $call{charge_status} = ($seq & 0x0F);
550              
551 15         62 my($area_len) = ($area & 0xE0) >> 5;
552 15         39 my($subscriber_len) = ($area & 0x1F);
553             #my($subscriber_len) = ($area & 0x07);
554             #printf "Area/Subscriber: %s / %d . %d\n", $area, $area_len, $subscriber_len;
555             #printf " Area code length: %d\n", $area_len;
556             #printf " Subscriber number length: %d\n", $subscriber_len;
557              
558             #printf "---- Variable data (%d) ----\n", length($variable);
559             #print unpack('H*', $variable), "\n";
560              
561 15         29 my $area_code = '';
562 15 50       43 if($area_len) {
563 0         0 ($area_code,$variable) = unpack("H[$area_len] a*", $variable);
564 0         0 _log(' Aread: %s', $area_code);
565             }
566              
567 15         30 my $cli_len = $subscriber_len;
568 15 50       54 if($subscriber_len % 2 == 1) {
569 15         28 $cli_len++;
570             }
571 15         18 my $cli;
572 15         95 ($cli, $variable) = unpack("H$cli_len a*", $variable);
573 15 50       96 if($cli_len > $subscriber_len) {
574 15         45 $cli = substr($cli, 0, -1);
575             }
576 15         61 _log(" CLI: %s", $cli);
577 15         33 $call{cli_area} = $area_code;
578 15         24 $call{cli} = $cli;
579              
580             # dynamic part:
581 15         20 my $block_marker;
582 15         43 while( length($variable) > 0) {
583             # each block has type marker + variable data, which depends on type
584 240         1268 ($block_marker, $variable) = unpack('C a*', $variable);
585             #_log('Found block marker: %s', $block_marker);
586             {
587 2     2   22 no strict 'refs';
  2         4  
  2         862  
  240         537  
588 240         652 my $sub = 'block_' . $block_marker;
589 240         808 $sub->(\%call, \$variable);
590             }
591             #dump_var($variable);
592             }
593              
594 15         32 _log('');
595 15         115 return \%call;
596             }
597              
598             # d2 - time change event
599             sub parse_time_change_record {
600 0     0 0   my ($fh, $code) = @_;
601              
602 0           _log('Found Date Time Change marker %s', $code);
603              
604 0           my $data;
605             # 7 - old date and time
606             # 7 - new date and time
607             # 1 - cause of change
608 0 0         sysread($fh, $data, 15) || die $!;
609 0           my($year,$month,$day,$hour,$min,$sec,$msec,$reason);
610 0           ($year,$month,$day,$hour,$min,$sec,$msec,$data) = unpack('CCCCCCC a*', $data);
611 0           $year += 2000;
612 0           my $dtime = sprintf "%04d-%02d-%02d %02d:%02d:%02d.%02d", $year,$month,$day,$hour,$min,$sec,$msec;
613 0           _log(' Old Time: %s', $dtime);
614              
615 0           ($year,$month,$day,$hour,$min,$sec,$msec,$reason) = unpack('CCCCCCC C', $data);
616 0           $year += 2000;
617 0           $dtime = sprintf "%04d-%02d-%02d %02d:%02d:%02d.%02d", $year,$month,$day,$hour,$min,$sec,$msec;
618 0           _log(' New Time: %s', $dtime);
619              
620             # 1 - The real-time clock correction
621             # 2 - Summer / winter time changes
622 0           _log(' Change reason: %s', $reason);
623             }
624              
625             # d4 - System was rebooted
626             sub parse_reboot_record {
627 0     0 0   my ($fh, $code) = @_;
628 0           _log('Found Restart marker %s', $code);
629              
630 0           my $data;
631             # 7 - restart date and time
632             # 4 - reserved
633 0 0         sysread($fh, $data, 7) || die $!;
634 0           my($year,$month,$day,$hour,$min,$sec,$msec);
635 0           ($year,$month,$day,$hour,$min,$sec,$msec) = unpack('CCCCCCC', $data);
636 0           $year += 2000;
637 0           my $reboot_time = sprintf "%04d-%02d-%02d %02d:%02d:%02d.%02d", $year,$month,$day,$hour,$min,$sec,$msec;
638 0           _log(' Restart time: %s', $reboot_time);
639              
640             # ignored...
641 0 0         sysread($fh, $data, 4) || die $!;
642             }
643              
644             1;
645             #--
646              
647             =head1 AUTHOR
648              
649             Sergey Leschenko, C<< >>
650              
651             =head1 BUGS
652              
653             Please note that some blocks are not implemented, as I haven't seen them in real data files.
654              
655             Please report any bugs or feature requests to C, or through
656             the web interface at L. I will be notified, and then you'll
657             automatically be notified of progress on your bug as I make changes.
658              
659              
660              
661              
662             =head1 SUPPORT
663              
664             You can find documentation for this module with the perldoc command.
665              
666             perldoc CDR::Parser::SI3000
667              
668              
669             You can also look for information at:
670              
671             =over 4
672              
673             =item * RT: CPAN's request tracker (report bugs here)
674              
675             L
676              
677             =item * AnnoCPAN: Annotated CPAN documentation
678              
679             L
680              
681             =item * CPAN Ratings
682              
683             L
684              
685             =item * Search CPAN
686              
687             L
688              
689             =back
690              
691              
692             =head1 ACKNOWLEDGEMENTS
693              
694              
695             =head1 LICENSE AND COPYRIGHT
696              
697             Copyright 2013 Sergey Leschenko.
698              
699             This program is free software; you can redistribute it and/or modify it
700             under the terms of the the Artistic License (2.0). You may obtain a
701             copy of the full license at:
702              
703             L
704              
705             Any use, modification, and distribution of the Standard or Modified
706             Versions is governed by this Artistic License. By using, modifying or
707             distributing the Package, you accept this license. Do not use, modify,
708             or distribute the Package, if you do not accept this license.
709              
710             If your Modified Version has been derived from a Modified Version made
711             by someone other than you, you are nevertheless required to ensure that
712             your Modified Version complies with the requirements of this license.
713              
714             This license does not grant you the right to use any trademark, service
715             mark, tradename, or logo of the Copyright Holder.
716              
717             This license includes the non-exclusive, worldwide, free-of-charge
718             patent license to make, have made, use, offer to sell, sell, import and
719             otherwise transfer the Package with respect to any patent claims
720             licensable by the Copyright Holder that are necessarily infringed by the
721             Package. If you institute patent litigation (including a cross-claim or
722             counterclaim) against any party alleging that the Package constitutes
723             direct or contributory patent infringement, then this Artistic License
724             to you shall terminate on the date that such litigation is filed.
725              
726             Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER
727             AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
728             THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
729             PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY
730             YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR
731             CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR
732             CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE,
733             EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
734              
735              
736             =cut
737              
738             1; # End of CDR::Parser::SI3000