File Coverage

blib/lib/CDR/Parser/SI3000.pm
Criterion Covered Total %
statement 269 369 72.9
branch 25 58 43.1
condition 21 42 50.0
subroutine 24 32 75.0
pod 1 26 3.8
total 340 527 64.5


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