From: Mitch Jackson Date: Fri, 2 Feb 2018 07:31:50 +0000 (-0600) Subject: RT# 78592 CDR Import for Acme Packet VOIP Switches X-Git-Url: http://git.freeside.biz/gitweb/?p=freeside.git;a=commitdiff_plain;h=f2d06c14cb92b2ec00147dbdaf73d6cf0d70d85f RT# 78592 CDR Import for Acme Packet VOIP Switches --- diff --git a/FS/FS/cdr.pm b/FS/FS/cdr.pm index c4e9c47a3..331ac0f2f 100644 --- a/FS/FS/cdr.pm +++ b/FS/FS/cdr.pm @@ -1733,6 +1733,14 @@ sub _cdr_date_parse { # Telos 2014-10-10T05:30:33Z ($year, $mon, $day, $hour, $min, $sec) = ( $1, $2, $3, $4, $5, $6 ); $options{gmt} = 1; + } elsif ( $date =~ /^(\d+):(\d+):(\d+)\.\d+ \w+ (\w+) (\d+) (\d+)$/ ) { + ($hour, $min, $sec, $mon, $day, $year) = ( $1, $2, $3, $4, $5, $6 ); + $mon = { # Acme Packet: 15:54:56.868 PST DEC 18 2017 + # My best guess of month abbv they may use + JAN => '01', FEB => '02', MAR => '03', APR => '04', + MAY => '05', JUN => '06', JUL => '07', AUG => '08', + SEP => '09', OCT => '10', NOV => '11', DEC => '12' + }->{$mon}; } else { die "unparsable date: $date"; #maybe we shouldn't die... } @@ -1932,4 +1940,3 @@ L, schema.html from the base documentation. =cut 1; - diff --git a/FS/FS/cdr/acmepacket.pm b/FS/FS/cdr/acmepacket.pm new file mode 100644 index 000000000..e0e80cdd6 --- /dev/null +++ b/FS/FS/cdr/acmepacket.pm @@ -0,0 +1,157 @@ +package FS::cdr::acmepacket; + +=head1 NAME + +FS:cdr::acmepacket - CDR import definition based on Acme Packet Net-Net 4000 + +=head1 DESCRIPTION + +The Acme Packet NetNet 4000 S-CX6.4.0 generates an odd cdr log format: + + - Each row in the CSV may be in one of many various formats, some of + them undocumented. + - Columns are inconsistently enclosed with " characters + - Quoted column values may, themselves, contain unescaped quote marks. + This breaks Text::CSV (well technically the FORMAT is broken, not + Text::CSV). + - A single call can generate multiple CDR records. The only records we're + interested in are billable calls: + - These are called "Stop Record CSV Placement" in Acme Packet documentation + - These will always contain a "2" as the first column value + - These rows may be 175 or 269 fields in length. It's unclear if the + undocumented 269 column rows are an intentional Acme Packet format, or + a bug in their switch. The extra columns are inserted at idx 115, + and can safely be disregarded. + +NOTE ON DATE PARSING: + + The Acme Packet manual doesn't describe it's date format in detail. The sample + we were given contains only records from December. Dates are formatted like + so: 15:54:56.868 PST DEC 18 2017 + + I gave my best guess how they will format the month text in the parser + FS::cdr::_cdr_date_parse(). If this format doesn't import records on a + particular month, check there. + +=cut + +use strict; +use warnings; +use vars qw(%info); +use base qw(FS::cdr); +use FS::cdr qw(_cdr_date_parse); +use Text::CSV; + +my $DEBUG = 0; + +my $cbcsv = Text::CSV->new({binary => 1}) + or die "Error loading Text::CSV - ".Text::CSV->error_diag(); + +# Used to map source format into the contrived format created for import_fields +# $cdr_premap[ IMPORT_FIELDS_IDX ] = SOURCE_FORMAT_IDX +my @cdr_premap = ( + 9, # clid + 9, # src + 10, # dst + 22, # channel + 21, # dstchannel + 26, # src_ip_addr + 28, # dst_ip_addr + 13, # startdate + 14, # answerdate + 15, # enddate + 12, # duration + 12, # billsec + 3, # userfield +); + +our %info = ( + name => 'Acme Packet', + weight => 600, + header => 0, + type => 'csv', + + import_fields => [ + # freeside # [idx] acmepacket + 'clid', # 9 Calling-Station-Id + 'src', # 9 Calling-Station-Id + 'dst', # 10 Called-Station-Id + 'channel', # 22 Acme-Session-Ingress-Realm + 'dstchannel', # 23 Acme-Session-Egress-Realm + 'src_ip_addr', # 26 Acme-Flow-In-Src-Adr_FS1_f + 'dst_ip_addr', # 28 Acme-Flow-In-Dst-Addr_FS1_f + 'startdate', # 13 h323-setup-time + 'answerdate', # 14 h323-connect-time + 'enddate', # 15 h323-disconnect-time + 'duration', # 12 Acct-Session-Time + 'billsec', # 12 Acct-Session-Time + 'userfield', # 3 Acct-Session-Id + ], + + row_callback => sub { + my $line = shift; + + # Only process records whose first column contains a 2 + return undef unless $line =~ /^2\,/; + + # Replace unescaped quote characters within quote-enclosed text cols + $line =~ s/(?parse($line) ) { + warn "Text::CSV Error parsing Acme Packet CDR: ".$cbcsv->error_diag(); + return undef; + } + + my @row = $cbcsv->fields(); + if (@row == 269) { + # Splice out the extra columns + @row = (@row[0..114], @row[209..@row-1]); + } elsif (@row != 175) { + warn "Unknown row format parsing Acme Packet CDR"; + return undef; + } + + my @out = map{ $row[$_] } @cdr_premap; + + if ($DEBUG) { + warn "~~~~~~~~~~pre-processed~~~~~~~~~~~~~~~~ \n"; + warn "$_ $out[$_] \n" for (0..@out-1); + } + + # answerdate, enddate, startdate + for (7,8,9) { + $out[$_] = _cdr_date_parse($out[$_]); + if ($out[$_] =~ /\D/) { + warn "Unparsable date in Acme Packet CDR: ($out[$_])"; + return undef; + } + } + + # clid, dst, src CDR field formatted as one of the following: + # 'WIRELESS CALLER';tag=SDepng302 + # ;tag=SDepng302 + + # clid + $out[0] = $out[0] =~ /^\'(.+)\'/ ? $1 : ""; + + # src, dst + # All of the sample data given shows sip connections. In case the same + # switch is hooked into another circuit type in the future, we'll just + # tease out a length 7+ number not contained in the caller-id-text field + for (1,2) { + $out[$_] =~ s/^\'.+\'//g; # strip caller id label portion + $out[$_] = $out[$_] =~ /(\d{7,})/ ? $1 : undef; + } + + if ($DEBUG) { + warn "~~~~~~~~~~post-processed~~~~~~~~~~~~~~~~ \n"; + warn "$_ $out[$_] \n" for (0..@out-1); + } + + # I haven't seen commas in sample data text fields. Extra caution, + # mangle commas into periods as we pass back to importer + join ',', map{ $_ =~ s/\,/\./g; $_ } @out; + }, +); + +1;