{
  "name" : "main_config",
  "notes" : "",
  "privilege_level" : 0,
  "sig_memory_schemes" : [
  ],
  "unapplied_changes" : false,
  "validation_status" : "Successful",
  "web_profile" : {
    "cas" : false,
    "firewall" : true,
    "gateway" : true,
    "h248" : false,
    "hardware_unit" : false,
    "isdn" : false,
    "isup" : false,
    "monitoring" : false,
    "sccp_tcap" : false,
    "sigtran_iua" : false,
    "sigtran_m2pa" : false,
    "sigtran_m2ua" : false,
    "sigtran_m3ua" : false,
    "sip" : true,
    "statistics_history" : false,
    "tms_ip" : false
  },
  "applications" : {
    "gateway" : {
      "application_type" : "Gateway",
      "bin_path" : "@{PKG_BIN}/gateway",
      "command_line_arguments" : "",
      "log_parameters" : {
        "default_trace_level" : "Level 4 (quiet)",
        "log_file_path" : "",
        "max_gzipped_log_files_size" : "100 MB",
        "max_log_file_segment_size" : "10 MB"
      },
      "name" : "gateway",
      "network_lib_parameter" : {
        "communication_down_delay" : "10 seconds",
        "network_polling_delay" : "2 seconds",
        "network_switchover_delay" : "4500 milliseconds"
      },
      "working_path" : "../gateway/"
    },
    "tboamapp" : {
      "application_type" : "TBOAM application",
      "bin_path" : "@{PKG_BIN}/tboamapp",
      "command_line_arguments" : "",
      "log_parameters" : {
        "default_trace_level" : "Level 2",
        "log_file_path" : "",
        "max_gzipped_log_files_size" : "100 MB",
        "max_log_file_segment_size" : "10 MB"
      },
      "name" : "tboamapp",
      "network_lib_parameter" : {
        "communication_down_delay" : "10 seconds",
        "network_polling_delay" : "2 seconds",
        "network_switchover_delay" : "4500 milliseconds"
      },
      "tboamapp_cfg" : {
        "allow_upgrade_of_all_units_simultaneously" : false,
        "auto_reboot_unit_on_license_update" : false,
        "management_shutdown_delay" : "1 minute",
        "shutdown_delay" : "20 seconds"
      },
      "working_path" : "../tboamapp/"
    },
    "tbsip" : {
      "application_type" : "TbSip",
      "bin_path" : "@{PKG_BIN}/tbsip",
      "command_line_arguments" : "",
      "log_parameters" : {
        "default_trace_level" : "Level 3",
        "log_file_path" : "",
        "max_gzipped_log_files_size" : "100 MB",
        "max_log_file_segment_size" : "10 MB"
      },
      "name" : "tbsip",
      "network_lib_parameter" : {
        "communication_down_delay" : "10 seconds",
        "network_polling_delay" : "2 seconds",
        "network_switchover_delay" : "4500 milliseconds"
      },
      "working_path" : "../tbsip/"
    },
    "tbsnmpagent" : {
      "application_type" : "TbSnmpAgent",
      "bin_path" : "@{PKG_BIN}/tbsnmpagent",
      "command_line_arguments" : "",
      "log_parameters" : {
        "default_trace_level" : "Level 2",
        "log_file_path" : "",
        "max_gzipped_log_files_size" : "100 MB",
        "max_log_file_segment_size" : "10 MB"
      },
      "name" : "tbsnmpagent",
      "network_lib_parameter" : {
        "communication_down_delay" : "10 seconds",
        "network_polling_delay" : "2 seconds",
        "network_switchover_delay" : "4500 milliseconds"
      },
      "working_path" : "../tbsnmpagent/"
    },
    "tbsyslog" : {
      "application_type" : "TbSyslog",
      "bin_path" : "@{PKG_BIN}/tbsyslog",
      "command_line_arguments" : "",
      "log_parameters" : {
        "default_trace_level" : "Level 2",
        "log_file_path" : "",
        "max_gzipped_log_files_size" : "100 MB",
        "max_log_file_segment_size" : "10 MB"
      },
      "name" : "tbsyslog",
      "network_lib_parameter" : {
        "communication_down_delay" : "10 seconds",
        "network_polling_delay" : "2 seconds",
        "network_switchover_delay" : "4500 milliseconds"
      },
      "working_path" : "../tbsyslog/"
    },
    "tbuctwriter" : {
      "application_type" : "TbUctWriter",
      "bin_path" : "@{PKG_BIN}/tbuctwriter",
      "command_line_arguments" : "",
      "log_parameters" : {
        "default_trace_level" : "Level 2",
        "log_file_path" : "",
        "max_gzipped_log_files_size" : "100 MB",
        "max_log_file_segment_size" : "10 MB"
      },
      "name" : "tbuctwriter",
      "network_lib_parameter" : {
        "communication_down_delay" : "10 seconds",
        "network_polling_delay" : "2 seconds",
        "network_switchover_delay" : "4500 milliseconds"
      },
      "tbuctwriter_cfg" : {
        "consulted_calls_recall" : "12 hours",
        "maximum_calls" : 10000,
        "maximum_compressed_directory_size" : "100 MB",
        "maximum_file_size" : "5 MB",
        "trace_level" : "Level 1",
        "uct_filter_trace_level" : "Level 1",
        "uct_filter_type" : "",
        "uct_filter_value" : ""
      },
      "working_path" : "../tbuctwriter/"
    },
    "toolpack_engine" : {
      "application_type" : "Toolpack Engine",
      "bin_path" : "@{PKG_BIN}/toolpack_engine",
      "command_line_arguments" : "",
      "log_parameters" : {
        "default_trace_level" : "Level 3",
        "log_file_path" : "",
        "max_gzipped_log_files_size" : "100 MB",
        "max_log_file_segment_size" : "10 MB"
      },
      "name" : "toolpack_engine",
      "network_lib_parameter" : {
        "communication_down_delay" : "10 seconds",
        "network_polling_delay" : "2 seconds",
        "network_switchover_delay" : "4500 milliseconds"
      },
      "toolpack_engine_cfg" : {
        "call_alert_timeout" : "5 minutes",
        "call_answer_timeout" : "5 minutes",
        "call_reset_timeout" : "30 seconds",
        "call_terminating_timeout" : "30 seconds",
        "call_transfer_timeout" : "10 seconds",
        "default_system_unavailable_audio_file" : "",
        "delay_writing_nap_stats_to_main_log_file" : "10 minutes",
        "delay_writing_nap_stats_to_nap_stats_log_file" : "1 minute",
        "probe_isup_circuits_upon_switchover" : true,
        "toolpack_call_control_guard_timeout" : "0 millisecond"
      },
      "working_path" : "../toolpack_engine/"
    },
    "toolpack_sys_mgr" : {
      "application_type" : "Toolpack Sys Manager",
      "bin_path" : "@{PKG_BIN}/toolpack_sys_manager",
      "command_line_arguments" : "",
      "log_parameters" : {
        "default_trace_level" : "Level 2",
        "log_file_path" : "",
        "max_gzipped_log_files_size" : "100 MB",
        "max_log_file_segment_size" : "10 MB"
      },
      "name" : "toolpack_sys_mgr",
      "network_lib_parameter" : {
        "communication_down_delay" : "10 seconds",
        "network_polling_delay" : "2 seconds",
        "network_switchover_delay" : "4500 milliseconds"
      },
      "toolpack_sys_mgr_cfg" : {
        "auto_reboot_unit_on_advanced_parameter_update" : false,
        "auto_reboot_unit_on_failure" : true,
        "unit_watchdog_timeout" : "1 minute"
      },
      "working_path" : "../toolpack_sys_manager/"
    }
  },
  "date_time_services" : {
    "configuration" : {
      "ntp_parameters" : {
        "ip_header_tos" : 0,
        "servers" : [
          "0.centos.pool.ntp.org",
          "1.centos.pool.ntp.org",
          "2.centos.pool.ntp.org",
          "3.centos.pool.ntp.org"
        ]
      },
      "setting" : "NTP",
      "timezone" : "America/Montreal"
    }
  },
  "dns_services" : {
    "configuration" : {
      "dns_parameters" : {
        "domains" : [
          "localdomain"
        ],
        "ip_header_tos" : 0,
        "servers" : [
          "192.168.211.2"
        ]
      },
      "managed_by_web" : false
    }
  },
  "file_dbs" : {
  },
  "firewalls" : {
    "firewall" : {
      "advanced_parameters" : {
        "connection_debug_mode" : false,
        "default_ban_duration" : "15 minutes",
        "default_values_for_H248_rules" : {
          "default_entry_duration" : "15 minutes",
          "default_maximum_bandwidth" : "2 MBps",
          "default_maximum_bandwidth_packets" : "4 Kpps",
          "default_temporary_ban_duration" : "5 minutes"
        },
        "default_values_for_manual_rules" : {
          "default_entry_duration" : "15 minutes",
          "default_maximum_bandwidth" : "200 KBps",
          "default_maximum_bandwidth_packets" : "400 pps",
          "default_temporary_ban_duration" : "5 minutes"
        },
        "default_values_for_OAMP_rules" : {
          "default_entry_duration" : "15 minutes",
          "default_maximum_bandwidth" : "0 Bps",
          "default_maximum_bandwidth_packets" : "0 pps",
          "default_temporary_ban_duration" : "5 minutes"
        },
        "default_values_for_SIGTRAN_rules" : {
          "default_entry_duration" : "15 minutes",
          "default_maximum_bandwidth" : "4 MBps",
          "default_maximum_bandwidth_packets" : "20 Kpps",
          "default_temporary_ban_duration" : "5 minutes"
        },
        "default_values_for_SIP_rules" : {
          "default_entry_duration" : "15 minutes",
          "default_maximum_bandwidth" : "0 Bps",
          "default_maximum_bandwidth_packets" : "10 Kpps",
          "default_temporary_ban_duration" : "30 seconds"
        },
        "list_sizes" : {
          "maximum_accept_entries" : 4000,
          "maximum_drop_entries" : 2000,
          "maximum_filters" : 1200
        }
      },
      "enabled" : true,
      "name" : "firewall",
      "rules" : {
        "DDOS_PROTECTION" : {
          "automatic" : true,
          "description" : "Drop all packets reaching this rule when in DOS/DDOS mode",
          "enabled" : true,
          "firewall_entry_type" : "DDOS_PROTECTION",
          "name" : "DDOS_PROTECTION",
          "priority" : 4,
          "system_enabled" : true,
          "filters" : {
          }
        },
        "HTTP_default_service" : {
          "automatic" : true,
          "description" : "",
          "enabled" : true,
          "firewall_entry_type" : "ACCEPT",
          "name" : "HTTP_default_service",
          "priority" : 1,
          "system_enabled" : true,
          "filters" : {
            "ALL" : {
              "custom_protocol" : 0,
              "destination_ip" : "0.0.0.0",
              "destination_netmask" : "32",
              "destination_port" : "12358",
              "entry_duration" : "15 minutes",
              "ip_interface" : "",
              "maximum_bandwidth" : "0 Bps",
              "maximum_bandwidth_packets" : "0 pps",
              "name" : "ALL",
              "override_default_thresholds" : false,
              "protocol" : "TCP",
              "source_ip" : "0.0.0.0",
              "source_netmask" : "32",
              "source_port" : "0",
              "temporary_ban_duration" : "5 minutes"
            }
          }
        },
        "NAP_NAP_SIP" : {
          "automatic" : true,
          "description" : "",
          "enabled" : true,
          "firewall_entry_type" : "ACCEPT",
          "name" : "NAP_NAP_SIP",
          "priority" : 6,
          "system_enabled" : true,
          "filters" : {
            "ACL_TS_WAN_00_0.0.0.0_0" : {
              "custom_protocol" : 0,
              "destination_ip" : "0.0.0.0",
              "destination_netmask" : "32",
              "destination_port" : "5060",
              "entry_duration" : "15 minutes",
              "ip_interface" : "Host.WAN_00",
              "maximum_bandwidth" : "0 Bps",
              "maximum_bandwidth_packets" : "10 Kpps",
              "name" : "ACL_TS_WAN_00_0.0.0.0_0",
              "override_default_thresholds" : false,
              "protocol" : "UDP",
              "source_ip" : "0.0.0.0",
              "source_netmask" : "0",
              "source_port" : "0",
              "temporary_ban_duration" : "30 seconds"
            }
          }
        },
        "NAP_Registrar_NAP" : {
          "automatic" : true,
          "description" : "",
          "enabled" : true,
          "firewall_entry_type" : "ACCEPT",
          "name" : "NAP_Registrar_NAP",
          "priority" : 5,
          "system_enabled" : true,
          "filters" : {
            "TS_LAN_00" : {
              "custom_protocol" : 0,
              "destination_ip" : "0.0.0.0",
              "destination_netmask" : "32",
              "destination_port" : "5060",
              "entry_duration" : "15 minutes",
              "ip_interface" : "Host.LAN_00",
              "maximum_bandwidth" : "0 Bps",
              "maximum_bandwidth_packets" : "10 Kpps",
              "name" : "TS_LAN_00",
              "override_default_thresholds" : false,
              "protocol" : "UDP",
              "source_ip" : "10.98.20.10",
              "source_netmask" : "32",
              "source_port" : "5060",
              "temporary_ban_duration" : "30 seconds"
            }
          }
        },
        "SNMP_configuration" : {
          "automatic" : true,
          "description" : "",
          "enabled" : true,
          "firewall_entry_type" : "ACCEPT",
          "name" : "SNMP_configuration",
          "priority" : 3,
          "system_enabled" : true,
          "filters" : {
            "ALL" : {
              "custom_protocol" : 0,
              "destination_ip" : "0.0.0.0",
              "destination_netmask" : "32",
              "destination_port" : "161",
              "entry_duration" : "15 minutes",
              "ip_interface" : "",
              "maximum_bandwidth" : "0 Bps",
              "maximum_bandwidth_packets" : "0 pps",
              "name" : "ALL",
              "override_default_thresholds" : false,
              "protocol" : "UDP",
              "source_ip" : "0.0.0.0",
              "source_netmask" : "32",
              "source_port" : "0",
              "temporary_ban_duration" : "5 minutes"
            }
          }
        },
        "SSH_default_service" : {
          "automatic" : true,
          "description" : "",
          "enabled" : true,
          "firewall_entry_type" : "ACCEPT",
          "name" : "SSH_default_service",
          "priority" : 2,
          "system_enabled" : true,
          "filters" : {
            "ALL" : {
              "custom_protocol" : 0,
              "destination_ip" : "0.0.0.0",
              "destination_netmask" : "32",
              "destination_port" : "22",
              "entry_duration" : "15 minutes",
              "ip_interface" : "",
              "maximum_bandwidth" : "0 Bps",
              "maximum_bandwidth_packets" : "0 pps",
              "name" : "ALL",
              "override_default_thresholds" : false,
              "protocol" : "TCP",
              "source_ip" : "0.0.0.0",
              "source_netmask" : "32",
              "source_port" : "0",
              "temporary_ban_duration" : "5 minutes"
            }
          }
        }
      }
    }
  },
  "gateways" : {
    "gateway" : {
      "advanced_parameters" : {
        "call_recording_options" : {
          "called" : "",
          "calling" : "",
          "incoming_nap" : "(Any)",
          "outgoing_nap" : "(Any)",
          "record_all_legs_of_a_call_to_one_file" : false,
          "termination_name" : ""
        },
        "enable_call_recording" : false,
        "forward_connected_number" : false,
        "forward_redirecting_number" : true,
        "forward_redirection_number" : true,
        "forward_ss7_raw_message_bytes" : true,
        "forward_user_to_user_information" : true,
        "route_retry_mode" : "Call Alerted",
        "route_retry_relay_progress_msgs" : true,
        "route_retry_timeout" : "10 seconds"
      },
      "cdr_options" : {
        "cdr_mode" : "",
        "enable_periodic_cdr_update" : false,
        "periodic_cdr_update_time" : "0 minute",
        "reverse_cdr_call_origin" : false,
        "text_cdr_parameters" : {
          "cdr_format_end" : "@{Timestamp:%Y-%m-%d %H:%M:%S.@m%z},END,SessionId='@{SessionId}',LegId='@{LegId}',StartTime='@{StartTime}',ConnectedTime='@{ConnectedTime}',EndTime='@{EndTime}',FreedTime='@{Timestamp}',Duration='@{CallDuration}',TerminationCause='@{TerminationCauseString}',TerminationSource='@{TerminationSource}',Calling='@{CallingNumber}',Called='@{CalledNumber}',NAP='@{NAP}',Direction='@{OriginatorName}',Media='@{RemoteMediaInfo}',Rtp:Rx='@{Stat:Rtp:Rx}',Rtp:Tx='@{Stat:Rtp:Tx}',T38:Rx='@{Stat:T38:Rx}',T38:Tx='@{Stat:T38:Tx}',Error:FromNetwork='@{Error:FromNetwork}',Error:ToNetwork='@{Error:ToNetwork}',MOS='@{Stat:MOS:Ingress}','@{Stat:MOS:Egress}',NetworkQuality='@{Stat:NetworkQuality:Ingress}','@{Stat:NetworkQuality:Egress}'",
          "cdr_format_start" : "@{Timestamp:%Y-%m-%d %H:%M:%S.@m%z},BEG,SessionId='@{SessionId}',LegId='@{LegId}',StartTime='@{StartTime}',ConnectedTime='@{ConnectedTime}',Calling='@{CallingNumber}',Called='@{CalledNumber}',NAP='@{NAP}',Protocol='@{Protocol}',Direction='@{OriginatorName}'",
          "cdr_format_update" : "@{Timestamp:%Y-%m-%d %H:%M:%S.@m%z},UPD,SessionId='@{SessionId}',LegId='@{LegId}',Rtp:Rx='@{Stat:Rtp:Rx}',Rtp:Tx='@{Stat:Rtp:Tx}',T38:Rx='@{Stat:T38:Rx}',T38:Tx='@{Stat:T38:Tx}',Error:FromNetwork='@{Error:FromNetwork}',Error:ToNetwork='@{Error:ToNetwork}'",
          "cdr_log_file_path" : "@{TB_SETUP_HOME}/cdr_logs/cdr",
          "max_total_size_of_gzipped_cdr_logs" : "10 GB",
          "maximum_cdr_log_file_segment_size" : "100 MB",
          "rotation_delay" : "1 hour"
        }
      },
      "cdr_system_id" : 50074,
      "routing_script" : "default_routing_script",
      "sip_privacy_options" : {
        "use_alternate_anonymous_address" : false
      },
      "translate_privacy_information" : true,
      "use_cdr_behavior" : false
    }
  },
  "hardware_units" : {
  },
  "host_ip_interfaces" : {
    "LAN_00" : {
      "fixed_management_host" : "",
      "gateway" : "10.98.20.1",
      "host_vlan" : "LAN_00.LAN_00",
      "ip_address" : "10.98.20.55",
      "mtu" : 1500,
      "name" : "LAN_00",
      "netmask" : "255.255.255.0",
      "service_types" : [
        "RTP",
        "SIP"
      ],
      "unique_name" : "Host.LAN_00",
      "use_as_default_gateway" : false,
      "use_dhcp" : false
    },
    "tbsbc-3_mgmt0" : {
      "fixed_management_host" : "tbsbc-3",
      "gateway" : "",
      "host_vlan" : "mgmt0.mgmt0",
      "ip_address" : "",
      "mtu" : 1500,
      "name" : "tbsbc-3_mgmt0",
      "netmask" : "",
      "service_types" : [
        "FIXED MANAGEMENT"
      ],
      "unique_name" : "Host.tbsbc-3_mgmt0",
      "use_as_default_gateway" : false,
      "use_dhcp" : true
    },
    "WAN_00" : {
      "fixed_management_host" : "",
      "gateway" : "192.168.1.1",
      "host_vlan" : "WAN_00.WAN_00",
      "ip_address" : "192.168.1.55",
      "mtu" : 1500,
      "name" : "WAN_00",
      "netmask" : "255.255.255.0",
      "service_types" : [
        "RTP",
        "SIP"
      ],
      "unique_name" : "Host.WAN_00",
      "use_as_default_gateway" : false,
      "use_dhcp" : false
    }
  },
  "host_port_ranges" : {
    "pr_LAN_00" : {
      "ip_interface" : "Host.LAN_00",
      "name" : "pr_LAN_00",
      "port_max" : 40000,
      "port_min" : 20000,
      "unique_name" : "Host.pr_LAN_00"
    },
    "pr_WAN_00" : {
      "ip_interface" : "Host.WAN_00",
      "name" : "pr_WAN_00",
      "port_max" : 40000,
      "port_min" : 20000,
      "unique_name" : "Host.pr_WAN_00"
    }
  },
  "host_virtual_ports" : {
    "LAN_00" : {
      "ethernet_ports" : [
        "LAN_00"
      ],
      "hosts" : [
        "tbsbc-3"
      ],
      "name" : "LAN_00",
      "host_vlans" : {
        "LAN_00" : {
          "allow_icmp" : true,
          "allow_ip_fragmentation" : true,
          "name" : "LAN_00",
          "read_only" : false,
          "unique_name" : "LAN_00.LAN_00",
          "untagged" : true,
          "vid" : 0
        }
      }
    },
    "mgmt0" : {
      "ethernet_ports" : [
        "mgmt0"
      ],
      "hosts" : [
        "tbsbc-3"
      ],
      "name" : "mgmt0",
      "host_vlans" : {
        "mgmt0" : {
          "allow_icmp" : true,
          "allow_ip_fragmentation" : true,
          "name" : "mgmt0",
          "read_only" : true,
          "unique_name" : "mgmt0.mgmt0",
          "untagged" : true,
          "vid" : 0
        }
      }
    },
    "WAN_00" : {
      "ethernet_ports" : [
        "WAN_00"
      ],
      "hosts" : [
        "tbsbc-3"
      ],
      "name" : "WAN_00",
      "host_vlans" : {
        "WAN_00" : {
          "allow_icmp" : true,
          "allow_ip_fragmentation" : true,
          "name" : "WAN_00",
          "read_only" : false,
          "unique_name" : "WAN_00.WAN_00",
          "untagged" : true,
          "vid" : 0
        }
      }
    }
  },
  "hosts" : {
    "tbsbc-3" : {
      "applications" : [
        "gateway",
        "tboamapp",
        "tbsip",
        "tbsnmpagent",
        "tbsyslog",
        "tbuctwriter",
        "toolpack_engine",
        "toolpack_sys_mgr"
      ],
      "name" : "tbsbc-3",
      "periodic_polling_for_os_package_updates" : {
        "enabled" : false,
        "period" : "1 day",
        "start_time" : "00:00:00"
      },
      "ethernet_ports" : {
        "LAN_00" : {
          "host_port_rate" : 1000,
          "name" : "LAN_00",
          "port_purpose" : "LAN/WAN"
        },
        "mgmt0" : {
          "host_port_rate" : 1000,
          "name" : "mgmt0",
          "port_purpose" : "MANAGEMENT"
        },
        "WAN_00" : {
          "host_port_rate" : 1000,
          "name" : "WAN_00",
          "port_purpose" : "LAN/WAN"
        }
      },
      "folder_cleanups" : {
        "Automatic databases backups" : {
          "file_name_mask" : "*.gz",
          "folder_path" : "@{TB_SETUP_PATH}/apps/tboamapp/database_backups_auto",
          "maximum_file_age" : "0 second",
          "maximum_files_size_per_subfolder" : "0 MB",
          "maximum_files_size_total" : "100 MB",
          "maximum_number_of_files_per_subfolder" : 0,
          "maximum_number_of_files_total" : 50,
          "name" : "Automatic databases backups"
        },
        "Core dumps" : {
          "file_name_mask" : "*core.*",
          "folder_path" : "@{TB_SETUP_PATH}/apps",
          "maximum_file_age" : "0 second",
          "maximum_files_size_per_subfolder" : "2500 MB",
          "maximum_files_size_total" : "5 GB",
          "maximum_number_of_files_per_subfolder" : 0,
          "maximum_number_of_files_total" : 0,
          "name" : "Core dumps"
        },
        "Firewall core dumps" : {
          "file_name_mask" : "*core.*",
          "folder_path" : "@{TB_PKG_HOME}/../tbrouter",
          "maximum_file_age" : "0 second",
          "maximum_files_size_per_subfolder" : "2500 MB",
          "maximum_files_size_total" : "2500 MB",
          "maximum_number_of_files_per_subfolder" : 0,
          "maximum_number_of_files_total" : 0,
          "name" : "Firewall core dumps"
        },
        "Firewall minidumps" : {
          "file_name_mask" : "*.dmp",
          "folder_path" : "@{TB_PKG_HOME}/../tbrouter",
          "maximum_file_age" : "0 second",
          "maximum_files_size_per_subfolder" : "1 GB",
          "maximum_files_size_total" : "1 GB",
          "maximum_number_of_files_per_subfolder" : 0,
          "maximum_number_of_files_total" : 0,
          "name" : "Firewall minidumps"
        },
        "Firewall packet captures" : {
          "file_name_mask" : "*.pcap",
          "folder_path" : "@{TB_PKG_HOME}/../tbrouter/pcap",
          "maximum_file_age" : "0 second",
          "maximum_files_size_per_subfolder" : "5 GB",
          "maximum_files_size_total" : "5 GB",
          "maximum_number_of_files_per_subfolder" : 0,
          "maximum_number_of_files_total" : 0,
          "name" : "Firewall packet captures"
        },
        "IRI record backups" : {
          "file_name_mask" : "*.1",
          "folder_path" : "@{TB_SETUP_PATH}/apps/gateway/iri_backup",
          "maximum_file_age" : "0 second",
          "maximum_files_size_per_subfolder" : "10 MB",
          "maximum_files_size_total" : "0 MB",
          "maximum_number_of_files_per_subfolder" : 1000,
          "maximum_number_of_files_total" : 0,
          "name" : "IRI record backups"
        },
        "Minidumps" : {
          "file_name_mask" : "*.dmp",
          "folder_path" : "@{TB_SETUP_PATH}/apps",
          "maximum_file_age" : "0 second",
          "maximum_files_size_per_subfolder" : "1 GB",
          "maximum_files_size_total" : "2 GB",
          "maximum_number_of_files_per_subfolder" : 0,
          "maximum_number_of_files_total" : 0,
          "name" : "Minidumps"
        },
        "Recorded calls" : {
          "file_name_mask" : "*.wav;*.alaw;*.ulaw;*.vox",
          "folder_path" : "@{TB_SETUP_HOME}/recorded_calls",
          "maximum_file_age" : "30 days",
          "maximum_files_size_per_subfolder" : "0 MB",
          "maximum_files_size_total" : "100 MB",
          "maximum_number_of_files_per_subfolder" : 0,
          "maximum_number_of_files_total" : 1000,
          "name" : "Recorded calls"
        },
        "Temporary Toolpack installer files" : {
          "file_name_mask" : "RackMultipart*",
          "folder_path" : "/tmp",
          "maximum_file_age" : "1 day",
          "maximum_files_size_per_subfolder" : "0 MB",
          "maximum_files_size_total" : "1 GB",
          "maximum_number_of_files_per_subfolder" : 0,
          "maximum_number_of_files_total" : 0,
          "name" : "Temporary Toolpack installer files"
        }
      }
    }
  },
  "http_services" : {
    "default_service" : {
      "ip_header_tos" : 0,
      "ip_interfaces" : [
        "ANY"
      ],
      "name" : "default_service",
      "port" : 12358,
      "use_https" : false
    }
  },
  "icmp_services" : {
    "configuration" : {
      "ip_header_tos" : 0
    }
  },
  "internal_nats" : {
  },
  "lawful_interceptions" : {
  },
  "nap_columns" : {
    "called_pre_remap" : {
      "default_value" : "",
      "name" : "called_pre_remap",
      "type_attributes" : "regex"
    },
    "priority" : {
      "default_value" : "10",
      "name" : "priority",
      "type_attributes" : "integer"
    },
    "routesets_definition" : {
      "default_value" : "",
      "name" : "routesets_definition",
      "type_attributes" : "dbfile"
    },
    "routesets_digitmap" : {
      "default_value" : "",
      "name" : "routesets_digitmap",
      "type_attributes" : "dbfile"
    },
    "weight" : {
      "default_value" : "50",
      "name" : "weight",
      "type_attributes" : "integer"
    }
  },
  "nap_columns_values" : {
    "NAP_SIP" : {
      "called_pre_remap" : "",
      "priority" : "10",
      "routesets_definition" : "",
      "routesets_digitmap" : "",
      "weight" : "50"
    },
    "Registrar_NAP" : {
      "called_pre_remap" : "",
      "priority" : "10",
      "routesets_definition" : "",
      "routesets_digitmap" : "",
      "weight" : "50"
    }
  },
  "naps" : {
    "NAP_SIP" : {
      "call_rate_limiting" : {
        "maximum_call_burst" : 0,
        "maximum_calls_per_second" : 0,
        "maximum_incoming_calls_per_second" : 0,
        "maximum_outgoing_calls_per_second" : 0,
        "maximum_simultaneous_incoming_calls" : 0,
        "maximum_simultaneous_outgoing_calls" : 0,
        "maximum_simultaneous_total_calls" : 0,
        "processing_delay_high_threshold" : "3 seconds",
        "processing_delay_low_threshold" : "1 second"
      },
      "congestion_threshold" : {
        "nb_calls_per_period" : 1,
        "period_duration" : "1 minute"
      },
      "default_profile" : "default",
      "enabled" : true,
      "name" : "NAP_SIP",
      "port_ranges" : [
        "Host.pr_WAN_00"
      ],
      "sip_cfg" : {
        "accept_only_authorized_users" : true,
        "advanced_parameters" : {
          "183_triggers_call_progress" : false,
          "map_any_response_to_available_status" : true,
          "privacy_type" : "P-Asserted-Identity"
        },
        "authentication_parameters" : {
          "ignore_realm" : false,
          "password" : "",
          "realm" : "",
          "reuse_challenge" : false,
          "user" : ""
        },
        "filtering_parameters" : {
          "filter_by_local_port" : true,
          "filter_by_proxy_address" : true,
          "filter_by_proxy_port" : true
        },
        "nap_sip_acls" : [
          "0.0.0.0/0"
        ],
        "network_address_translation" : {
          "local_method_rtp" : "",
          "local_method_sip" : "",
          "remote_method_rtp" : "None",
          "remote_method_sip" : "None"
        },
        "poll_remote_proxy" : true,
        "proxy_address" : "",
        "proxy_polling_interval" : "1 minute",
        "proxy_polling_response_timeout" : "30 seconds",
        "proxy_port" : 5060,
        "proxy_port_type" : "UDP",
        "registration_parameters" : {
          "address_to_register" : "",
          "register_to_proxy" : false
        },
        "sip_use_proxy" : false,
        "sipi_parameters" : {
          "append_f_to_outgoing_calls" : false,
          "call_progress_method" : "183 Call Progress",
          "content_type" : "itu-t",
          "enable" : false,
          "isup_protocol_variant" : "ITU"
        }
      },
      "sip_registration_domains" : [
        "SIP_Registrar_FQDN"
      ],
      "sip_transport_servers" : [
        "TS_WAN_00"
      ]
    },
    "Registrar_NAP" : {
      "call_rate_limiting" : {
        "maximum_call_burst" : 0,
        "maximum_calls_per_second" : 0,
        "maximum_incoming_calls_per_second" : 0,
        "maximum_outgoing_calls_per_second" : 0,
        "maximum_simultaneous_incoming_calls" : 0,
        "maximum_simultaneous_outgoing_calls" : 0,
        "maximum_simultaneous_total_calls" : 0,
        "processing_delay_high_threshold" : "3 seconds",
        "processing_delay_low_threshold" : "1 second"
      },
      "congestion_threshold" : {
        "nb_calls_per_period" : 1,
        "period_duration" : "1 minute"
      },
      "default_profile" : "default",
      "enabled" : true,
      "name" : "Registrar_NAP",
      "port_ranges" : [
        "Host.pr_LAN_00"
      ],
      "sip_cfg" : {
        "accept_only_authorized_users" : false,
        "advanced_parameters" : {
          "183_triggers_call_progress" : false,
          "map_any_response_to_available_status" : true,
          "privacy_type" : "P-Asserted-Identity"
        },
        "authentication_parameters" : {
          "ignore_realm" : false,
          "password" : "",
          "realm" : "",
          "reuse_challenge" : false,
          "user" : ""
        },
        "filtering_parameters" : {
          "filter_by_local_port" : true,
          "filter_by_proxy_address" : true,
          "filter_by_proxy_port" : true
        },
        "nap_sip_acls" : [
        ],
        "network_address_translation" : {
          "local_method_rtp" : "",
          "local_method_sip" : "",
          "remote_method_rtp" : "None",
          "remote_method_sip" : "None"
        },
        "poll_remote_proxy" : true,
        "proxy_address" : "10.98.20.10",
        "proxy_polling_interval" : "1 minute",
        "proxy_polling_response_timeout" : "30 seconds",
        "proxy_port" : 5060,
        "proxy_port_type" : "UDP",
        "registration_parameters" : {
          "address_to_register" : "",
          "register_to_proxy" : false
        },
        "sip_use_proxy" : true,
        "sipi_parameters" : {
          "append_f_to_outgoing_calls" : false,
          "call_progress_method" : "183 Call Progress",
          "content_type" : "itu-t",
          "enable" : false,
          "isup_protocol_variant" : "ITU"
        }
      },
      "sip_registration_domains" : [
        "SIP_Registrar_FQDN"
      ],
      "sip_transport_servers" : [
        "TS_LAN_00"
      ]
    }
  },
  "nat_traversals" : {
  },
  "np1_groups" : {
  },
  "profiles" : {
    "asterisk" : {
      "call_transfer_options" : {
        "call_transfer_mode" : "Relay",
        "call_transfer_timeout" : "1 minute",
        "terminate_transferor_immediately" : false
      },
      "ivr_record" : {
        "vad_enabled" : false,
        "vad_noise_floor_level" : "-30"
      },
      "lnp_options" : {
        "incoming_lnp_mode" : "Terminate LNP",
        "outgoing_lnp_mode" : "No LNP insertion"
      },
      "mlpp_options" : {
        "look_ahead_for_busy" : "LFB allowed",
        "network_identity" : 0,
        "outgoing_mode" : "No MLPP insertion",
        "precedence_level" : "PL flash override",
        "service_domain" : 0
      },
      "name" : "asterisk",
      "tdm_line" : {
        "subject_to_electric_hybrid_echo" : true
      },
      "telephony_services_profile" : {
        "cnam_collection_timeout_ms" : "1 second",
        "cnam_telephony_service" : "",
        "telephony_services_cnam_mode" : "Never"
      },
      "tones_and_call_progress_options" : {
        "busy_tone_max_duration" : "30 seconds",
        "default_digit_duration" : 100,
        "default_interval_between_digits" : 60,
        "early_media_relay_and_ring_tone_generation" : {
          "connect_full_duplex_during_early_media" : false,
          "delay_before_connecting_early_media" : "0 millisecond",
          "incoming_calls_early_media_mode" : "Forward from outgoing to incoming call",
          "outgoing_calls_early_media_mode" : "Automatic early media detection",
          "ring_tone_starting_state" : "Call Alerted"
        },
        "generate_busy_tone" : false,
        "tone_definition_profile" : "USA-Canada"
      },
      "custom_cause_maps" : {
      },
      "tdm" : {
        "ss7" : {
          "default_backward_call_indicator" : "0x5404",
          "default_backward_call_indicator_enforce_mask" : "0x0000",
          "default_forward_call_indicator" : "0x2001",
          "default_forward_call_indicator_enforce_mask" : "0x0000",
          "dont_forward_early_acm" : false,
          "force_backward_call_indicator_in_anm" : false,
          "forward_application_transport_ie" : false,
          "forward_call_cpg_hold_retrieval" : true,
          "forward_call_indicator_international" : false,
          "forward_call_suspend_resume" : true,
          "hop_counter_ie_handling" : "Decrement and forward",
          "send_charge_number_ie_in_iam" : false
        },
        "ss7_or_isdn" : {
          "always_send_alerting" : false,
          "cnam_collection_timeout" : "1 second",
          "cnam_using_alternate_methods" : false,
          "colr" : false,
          "information_transfer_capability" : "Relay",
          "isup_release_location" : "Private network LU",
          "network_specific_facilities_ie" : "",
          "relay_release_location_from_other_leg" : true,
          "send_connected_number_ie" : false,
          "send_display_ie" : false,
          "support_2_calling_number_ie" : false
        }
      },
      "voip" : {
        "fax_modem_relay" : {
          "detection_mode" : "Standard",
          "enable_early_fax_modem_tone_detection" : false,
          "enable_fax_modem_relay" : false,
          "expected_cng_tones" : 2,
          "fax_modem_passthrough_parameters" : {
            "codec" : "PCMU",
            "detection_type" : "NONE",
            "force_v152_vbd" : false,
            "jitter_buffer_depth" : 40,
            "packet_duration" : "20ms",
            "support_v152_vbd" : false,
            "use_nse" : false
          },
          "fax_modem_tones_detection_duration" : "30 seconds",
          "force_nse" : false,
          "modem_vs_fax_distinction_timeout" : "0 millisecond",
          "relay_mode" : "T.38",
          "switch_to_fax_relay_upon_cng_tone" : true,
          "switch_to_passthrough_upon_bell_ans" : true,
          "t38_parameters" : {
            "fill_bit_removal" : false,
            "max_bitrate" : "14400",
            "prevent_direct_invite" : false,
            "redundancy_count" : 0,
            "transmission_level_minus_dbm" : 10,
            "use_v34" : false
          }
        },
        "media_relay_options" : {
          "allow_audio_transcoding" : true,
          "allow_low_delay_media_relay" : true,
          "codecs_selection_mode" : "Compatible codecs only",
          "low_delay_media_relay_options" : {
            "allow_other_codecs" : false
          },
          "use_rtp_no_anchoring" : false
        },
        "rtp_and_audio" : {
          "clear_channel_packet_duration" : "20ms",
          "codec_g726_aal2_format" : false,
          "drop_call_thresholds_no_rx_packets" : "0 second",
          "echo_cancellation" : {
            "echo_cancellation_enabled" : true,
            "enable_coefficient_update" : true,
            "enable_dc_removal_filter" : true,
            "gain_power_level" : "0",
            "non_linear_processor_tune_option" : "Normal"
          },
          "enable_mos_calculation" : false,
          "enable_ssrc_randomization" : true,
          "jitter_buffer" : {
            "initial_depth" : 20,
            "max_depth" : 200,
            "min_depth" : 20,
            "smooth_deletion" : true
          },
          "packet_network" : {
            "packet_loss_concealment" : true,
            "type_of_service" : 0
          },
          "rtcp" : {
            "rtcp_enabled" : true,
            "sdes_cname" : "",
            "transmission_interval" : "5 seconds",
            "use_xrtcp" : false
          },
          "tdm_volume_control" : {
            "rx_gain_level" : "0",
            "tx_gain_level" : "0"
          },
          "vad_noise_floor_level" : "-45"
        },
        "sdp" : {
          "force_fax_tones_as_telephony_event" : false,
          "profile_sdp_description" : "m=audio 0 RTP/AVP 0 8 101\r\na=rtpmap:101 telephone-event/8000"
        },
        "sip" : {
          "custom_sip_bye_headers" : "P-RTP-Stat: PS=@{Stat:Rtp:Tx:IPPackets}, OS=@{Stat:Rtp:Tx:IPBytes}, PR=@{Stat:Rtp:Rx:IPPackets}, OR=@{Stat:Rtp:Rx:IPBytes}, PL=@{Stat:Rtp:Rx:Error:LostPackets}, JI=@{Stat:Rtcp:Jitter}, LA=@{Stat:Rtcp:RoundTripDelay}, DU=@{CallDuration}",
          "custom_sip_bye_headers_enabled" : false,
          "detect_180_with_sdp_as_early_media" : true,
          "diversion_custom_sip_header" : false,
          "dont_forward_183_progress" : false,
          "dtmf_relay_scheme" : [
            "Relay DTMF according to RFC2833",
            "Relay DTMF using SIP INFO messages",
            "Relay DTMF in band"
          ],
          "enable_sip_custom_headers" : false,
          "forward_sip_hold_type" : "SS7 Remote Hold/Retrieval",
          "forward_ss7_cpg_hold_retrieval" : true,
          "forward_ss7_suspend_resume" : true,
          "max_forwards_handling" : "Decrement and forward",
          "sdp_combining_options" : [
            "Use sender's codec order"
          ],
          "sdp_generation_options" : [
          ],
          "send_180_with_sdp" : false,
          "use_isup_oli_format" : false,
          "use_non_ambiguous_from_tag" : false,
          "use_reason_header" : true,
          "use_strict_routing" : false,
          "user_to_user_encoding" : "Escaped text"
        }
      }
    },
    "default" : {
      "call_transfer_options" : {
        "call_transfer_mode" : "Relay",
        "call_transfer_timeout" : "1 minute",
        "terminate_transferor_immediately" : false
      },
      "ivr_record" : {
        "vad_enabled" : false,
        "vad_noise_floor_level" : "-30"
      },
      "lnp_options" : {
        "incoming_lnp_mode" : "Terminate LNP",
        "outgoing_lnp_mode" : "No LNP insertion"
      },
      "mlpp_options" : {
        "look_ahead_for_busy" : "LFB allowed",
        "network_identity" : 0,
        "outgoing_mode" : "No MLPP insertion",
        "precedence_level" : "PL flash override",
        "service_domain" : 0
      },
      "name" : "default",
      "tdm_line" : {
        "subject_to_electric_hybrid_echo" : true
      },
      "telephony_services_profile" : {
        "cnam_collection_timeout_ms" : "1 second",
        "cnam_telephony_service" : "",
        "telephony_services_cnam_mode" : "Never"
      },
      "tones_and_call_progress_options" : {
        "busy_tone_max_duration" : "30 seconds",
        "default_digit_duration" : 100,
        "default_interval_between_digits" : 60,
        "early_media_relay_and_ring_tone_generation" : {
          "connect_full_duplex_during_early_media" : false,
          "delay_before_connecting_early_media" : "0 millisecond",
          "incoming_calls_early_media_mode" : "Forward from outgoing to incoming call",
          "outgoing_calls_early_media_mode" : "Automatic early media detection",
          "ring_tone_starting_state" : "Call Alerted"
        },
        "generate_busy_tone" : false,
        "tone_definition_profile" : "USA-Canada"
      },
      "custom_cause_maps" : {
      },
      "tdm" : {
        "ss7" : {
          "default_backward_call_indicator" : "0x5404",
          "default_backward_call_indicator_enforce_mask" : "0x0000",
          "default_forward_call_indicator" : "0x2001",
          "default_forward_call_indicator_enforce_mask" : "0x0000",
          "dont_forward_early_acm" : false,
          "force_backward_call_indicator_in_anm" : false,
          "forward_application_transport_ie" : false,
          "forward_call_cpg_hold_retrieval" : true,
          "forward_call_indicator_international" : false,
          "forward_call_suspend_resume" : true,
          "hop_counter_ie_handling" : "Decrement and forward",
          "send_charge_number_ie_in_iam" : false
        },
        "ss7_or_isdn" : {
          "always_send_alerting" : false,
          "cnam_collection_timeout" : "1 second",
          "cnam_using_alternate_methods" : false,
          "colr" : false,
          "information_transfer_capability" : "Relay",
          "isup_release_location" : "Private network LU",
          "network_specific_facilities_ie" : "",
          "relay_release_location_from_other_leg" : true,
          "send_connected_number_ie" : false,
          "send_display_ie" : false,
          "support_2_calling_number_ie" : false
        }
      },
      "voip" : {
        "fax_modem_relay" : {
          "detection_mode" : "Standard",
          "enable_early_fax_modem_tone_detection" : false,
          "enable_fax_modem_relay" : true,
          "expected_cng_tones" : 2,
          "fax_modem_passthrough_parameters" : {
            "codec" : "PCMU",
            "detection_type" : "Silence suppression off",
            "force_v152_vbd" : false,
            "jitter_buffer_depth" : 40,
            "packet_duration" : "20ms",
            "support_v152_vbd" : false,
            "use_nse" : false
          },
          "fax_modem_tones_detection_duration" : "30 seconds",
          "force_nse" : false,
          "modem_vs_fax_distinction_timeout" : "0 millisecond",
          "relay_mode" : "T.38",
          "switch_to_fax_relay_upon_cng_tone" : true,
          "switch_to_passthrough_upon_bell_ans" : true,
          "t38_parameters" : {
            "fill_bit_removal" : false,
            "max_bitrate" : "14400",
            "prevent_direct_invite" : false,
            "redundancy_count" : 0,
            "transmission_level_minus_dbm" : 10,
            "use_v34" : false
          }
        },
        "media_relay_options" : {
          "allow_audio_transcoding" : true,
          "allow_low_delay_media_relay" : true,
          "codecs_selection_mode" : "Compatible codecs only",
          "low_delay_media_relay_options" : {
            "allow_other_codecs" : false
          },
          "use_rtp_no_anchoring" : false
        },
        "rtp_and_audio" : {
          "clear_channel_packet_duration" : "20ms",
          "codec_g726_aal2_format" : false,
          "drop_call_thresholds_no_rx_packets" : "0 second",
          "echo_cancellation" : {
            "echo_cancellation_enabled" : true,
            "enable_coefficient_update" : true,
            "enable_dc_removal_filter" : true,
            "gain_power_level" : "0",
            "non_linear_processor_tune_option" : "Normal"
          },
          "enable_mos_calculation" : false,
          "enable_ssrc_randomization" : true,
          "jitter_buffer" : {
            "initial_depth" : 20,
            "max_depth" : 200,
            "min_depth" : 20,
            "smooth_deletion" : true
          },
          "packet_network" : {
            "packet_loss_concealment" : true,
            "type_of_service" : 0
          },
          "rtcp" : {
            "rtcp_enabled" : true,
            "sdes_cname" : "",
            "transmission_interval" : "5 seconds",
            "use_xrtcp" : false
          },
          "tdm_volume_control" : {
            "rx_gain_level" : "0",
            "tx_gain_level" : "0"
          },
          "vad_noise_floor_level" : "-45"
        },
        "sdp" : {
          "force_fax_tones_as_telephony_event" : false,
          "profile_sdp_description" : "m=audio 0 RTP/AVP 0 8 4 18 101 13 9\r\na=rtpmap:101 telephone-event/8000\r\na=fmtp:101 0-15,32-36"
        },
        "sip" : {
          "custom_sip_bye_headers" : "P-RTP-Stat: PS=@{Stat:Rtp:Tx:IPPackets}, OS=@{Stat:Rtp:Tx:IPBytes}, PR=@{Stat:Rtp:Rx:IPPackets}, OR=@{Stat:Rtp:Rx:IPBytes}, PL=@{Stat:Rtp:Rx:Error:LostPackets}, JI=@{Stat:Rtcp:Jitter}, LA=@{Stat:Rtcp:RoundTripDelay}, DU=@{CallDuration}",
          "custom_sip_bye_headers_enabled" : false,
          "detect_180_with_sdp_as_early_media" : true,
          "diversion_custom_sip_header" : false,
          "dont_forward_183_progress" : false,
          "dtmf_relay_scheme" : [
            "Relay DTMF according to RFC2833",
            "Relay DTMF using SIP INFO messages",
            "Relay DTMF in band"
          ],
          "enable_sip_custom_headers" : false,
          "forward_sip_hold_type" : "SS7 Remote Hold/Retrieval",
          "forward_ss7_cpg_hold_retrieval" : true,
          "forward_ss7_suspend_resume" : true,
          "max_forwards_handling" : "Decrement and forward",
          "sdp_combining_options" : [
            "Use sender's codec order"
          ],
          "sdp_generation_options" : [
          ],
          "send_180_with_sdp" : false,
          "use_isup_oli_format" : false,
          "use_non_ambiguous_from_tag" : false,
          "use_reason_header" : true,
          "use_strict_routing" : false,
          "user_to_user_encoding" : "Escaped text"
        }
      }
    },
    "freeswitch" : {
      "call_transfer_options" : {
        "call_transfer_mode" : "Relay",
        "call_transfer_timeout" : "1 minute",
        "terminate_transferor_immediately" : false
      },
      "ivr_record" : {
        "vad_enabled" : false,
        "vad_noise_floor_level" : "-30"
      },
      "lnp_options" : {
        "incoming_lnp_mode" : "Terminate LNP",
        "outgoing_lnp_mode" : "No LNP insertion"
      },
      "mlpp_options" : {
        "look_ahead_for_busy" : "LFB allowed",
        "network_identity" : 0,
        "outgoing_mode" : "No MLPP insertion",
        "precedence_level" : "PL flash override",
        "service_domain" : 0
      },
      "name" : "freeswitch",
      "tdm_line" : {
        "subject_to_electric_hybrid_echo" : true
      },
      "telephony_services_profile" : {
        "cnam_collection_timeout_ms" : "1 second",
        "cnam_telephony_service" : "",
        "telephony_services_cnam_mode" : "Never"
      },
      "tones_and_call_progress_options" : {
        "busy_tone_max_duration" : "30 seconds",
        "default_digit_duration" : 100,
        "default_interval_between_digits" : 60,
        "early_media_relay_and_ring_tone_generation" : {
          "connect_full_duplex_during_early_media" : false,
          "delay_before_connecting_early_media" : "0 millisecond",
          "incoming_calls_early_media_mode" : "Forward from outgoing to incoming call",
          "outgoing_calls_early_media_mode" : "Automatic early media detection",
          "ring_tone_starting_state" : "Call Alerted"
        },
        "generate_busy_tone" : false,
        "tone_definition_profile" : "USA-Canada"
      },
      "custom_cause_maps" : {
      },
      "tdm" : {
        "ss7" : {
          "default_backward_call_indicator" : "0x5404",
          "default_backward_call_indicator_enforce_mask" : "0x0000",
          "default_forward_call_indicator" : "0x2001",
          "default_forward_call_indicator_enforce_mask" : "0x0000",
          "dont_forward_early_acm" : false,
          "force_backward_call_indicator_in_anm" : false,
          "forward_application_transport_ie" : false,
          "forward_call_cpg_hold_retrieval" : true,
          "forward_call_indicator_international" : false,
          "forward_call_suspend_resume" : true,
          "hop_counter_ie_handling" : "Decrement and forward",
          "send_charge_number_ie_in_iam" : false
        },
        "ss7_or_isdn" : {
          "always_send_alerting" : false,
          "cnam_collection_timeout" : "1 second",
          "cnam_using_alternate_methods" : false,
          "colr" : false,
          "information_transfer_capability" : "Relay",
          "isup_release_location" : "Private network LU",
          "network_specific_facilities_ie" : "",
          "relay_release_location_from_other_leg" : true,
          "send_connected_number_ie" : false,
          "send_display_ie" : false,
          "support_2_calling_number_ie" : false
        }
      },
      "voip" : {
        "fax_modem_relay" : {
          "detection_mode" : "Standard",
          "enable_early_fax_modem_tone_detection" : false,
          "enable_fax_modem_relay" : false,
          "expected_cng_tones" : 2,
          "fax_modem_passthrough_parameters" : {
            "codec" : "PCMU",
            "detection_type" : "NONE",
            "force_v152_vbd" : false,
            "jitter_buffer_depth" : 40,
            "packet_duration" : "20ms",
            "support_v152_vbd" : false,
            "use_nse" : false
          },
          "fax_modem_tones_detection_duration" : "30 seconds",
          "force_nse" : false,
          "modem_vs_fax_distinction_timeout" : "0 millisecond",
          "relay_mode" : "T.38",
          "switch_to_fax_relay_upon_cng_tone" : true,
          "switch_to_passthrough_upon_bell_ans" : true,
          "t38_parameters" : {
            "fill_bit_removal" : false,
            "max_bitrate" : "14400",
            "prevent_direct_invite" : false,
            "redundancy_count" : 0,
            "transmission_level_minus_dbm" : 10,
            "use_v34" : false
          }
        },
        "media_relay_options" : {
          "allow_audio_transcoding" : true,
          "allow_low_delay_media_relay" : true,
          "codecs_selection_mode" : "Compatible codecs only",
          "low_delay_media_relay_options" : {
            "allow_other_codecs" : false
          },
          "use_rtp_no_anchoring" : false
        },
        "rtp_and_audio" : {
          "clear_channel_packet_duration" : "20ms",
          "codec_g726_aal2_format" : false,
          "drop_call_thresholds_no_rx_packets" : "0 second",
          "echo_cancellation" : {
            "echo_cancellation_enabled" : true,
            "enable_coefficient_update" : true,
            "enable_dc_removal_filter" : true,
            "gain_power_level" : "0",
            "non_linear_processor_tune_option" : "Normal"
          },
          "enable_mos_calculation" : false,
          "enable_ssrc_randomization" : true,
          "jitter_buffer" : {
            "initial_depth" : 20,
            "max_depth" : 200,
            "min_depth" : 20,
            "smooth_deletion" : true
          },
          "packet_network" : {
            "packet_loss_concealment" : true,
            "type_of_service" : 0
          },
          "rtcp" : {
            "rtcp_enabled" : true,
            "sdes_cname" : "",
            "transmission_interval" : "5 seconds",
            "use_xrtcp" : false
          },
          "tdm_volume_control" : {
            "rx_gain_level" : "0",
            "tx_gain_level" : "0"
          },
          "vad_noise_floor_level" : "-45"
        },
        "sdp" : {
          "force_fax_tones_as_telephony_event" : false,
          "profile_sdp_description" : "m=audio 0 RTP/AVP 0 8 101\r\na=rtpmap:101 telephone-event/8000"
        },
        "sip" : {
          "custom_sip_bye_headers" : "P-RTP-Stat: PS=@{Stat:Rtp:Tx:IPPackets}, OS=@{Stat:Rtp:Tx:IPBytes}, PR=@{Stat:Rtp:Rx:IPPackets}, OR=@{Stat:Rtp:Rx:IPBytes}, PL=@{Stat:Rtp:Rx:Error:LostPackets}, JI=@{Stat:Rtcp:Jitter}, LA=@{Stat:Rtcp:RoundTripDelay}, DU=@{CallDuration}",
          "custom_sip_bye_headers_enabled" : false,
          "detect_180_with_sdp_as_early_media" : true,
          "diversion_custom_sip_header" : false,
          "dont_forward_183_progress" : false,
          "dtmf_relay_scheme" : [
            "Relay DTMF according to RFC2833",
            "Relay DTMF using SIP INFO messages",
            "Relay DTMF in band"
          ],
          "enable_sip_custom_headers" : false,
          "forward_sip_hold_type" : "SS7 Remote Hold/Retrieval",
          "forward_ss7_cpg_hold_retrieval" : true,
          "forward_ss7_suspend_resume" : true,
          "max_forwards_handling" : "Decrement and forward",
          "sdp_combining_options" : [
            "Use sender's codec order"
          ],
          "sdp_generation_options" : [
          ],
          "send_180_with_sdp" : false,
          "use_isup_oli_format" : false,
          "use_non_ambiguous_from_tag" : false,
          "use_reason_header" : true,
          "use_strict_routing" : false,
          "user_to_user_encoding" : "Escaped text"
        }
      }
    }
  },
  "radius_clients" : {
  },
  "radius_servers" : {
  },
  "route_columns" : {
    "priority" : {
      "default_value" : "",
      "name" : "priority",
      "type_attributes" : "integer"
    },
    "private_address" : {
      "default_value" : "",
      "name" : "private_address",
      "type_attributes" : "text"
    },
    "remapped_private_address" : {
      "default_value" : "",
      "name" : "remapped_private_address",
      "type_attributes" : "text"
    },
    "weight" : {
      "default_value" : "",
      "name" : "weight",
      "type_attributes" : "integer"
    }
  },
  "routes" : {
    "from_Registrar" : {
      "called" : "",
      "calling" : "",
      "custom_attributes" : {
        "priority" : "",
        "private_address" : "",
        "remapped_private_address" : "",
        "weight" : ""
      },
      "name" : "from_Registrar",
      "nap" : "Registrar_NAP",
      "remapped_called" : "",
      "remapped_calling" : "",
      "remapped_destination_leg_profile" : "(same as NAP)",
      "remapped_nap" : "(By registered user)",
      "remapped_source_leg_profile" : "(same as NAP)",
      "routeset_name" : ""
    },
    "to_Registrar" : {
      "called" : "",
      "calling" : "",
      "custom_attributes" : {
        "priority" : "",
        "private_address" : "",
        "remapped_private_address" : "",
        "weight" : ""
      },
      "name" : "to_Registrar",
      "nap" : "NAP_SIP",
      "remapped_called" : "",
      "remapped_calling" : "",
      "remapped_destination_leg_profile" : "(same as NAP)",
      "remapped_nap" : "Registrar_NAP",
      "remapped_source_leg_profile" : "(same as NAP)",
      "routeset_name" : ""
    }
  },
  "routing_scripts" : {
    "default_routing_script" : {
      "name" : "default_routing_script",
      "script_files" : {
        "all_call_query.rb" : {
          "load_on_startup" : false,
          "name" : "all_call_query.rb",
          "script_type" : "filter",
          "source" : "#\n# Script version 1.0\n#\n# Version history:\n#  1.0  First version of this script\n#\n# This before-matching filter is used to handle All Call Query (ACQ) call\n# flows.\n#\n# An ACQ call flow consists of, for each incoming call, calling a redirect\n# server on a dedicated NAP. The redirect server then provides the routing\n# number which is then used to route the call to the correct NAP.\n#\n# This script does not forward the call to the redirect server if the LRN\n# is already present (:ported_number).\n#\n# By default, this script assumes the redirect server is reachable through\n# the \"NAP_LNP\" NAP.\n#\n#\n# In order to use this module, follow the steps below:\n# 1- Add the \"require 'all_call_query'\" statement at the top of the main\n#    script.\n# 2- Add the \"include AllCallQuery\" statement in the main routing class.\n# 3- Add the filter \"before_filter :method => :all_call_query\" in the main\n#    routing class.\n# 4- Create and configure a NAP with the name \"NAP_LNP\" (without quotes),\n#    which will be used for outgoing calls to the redirect server.\n#\n\n\nmodule AllCallQuery\n  \n  def init_all_call_query(params)\n    params\n  end\n  \n  def all_call_query(params)\n    # Verify if we must find the routing number\n    call = params[:call]\n    ported_number = call[:ported_number]\n    must_query = (ported_number.nil? || ported_number == \"\")\n    \n    # Verify if this routing query is for an incoming call\n    # To find this, we look at the contact index (it should be zero)\n    contacts = params[:contacts]\n    contact_index = contacts[:index].to_i\n    incoming_call = (contact_index == 0)\n    \n    if must_query && incoming_call\n      # Ignore current routes. Force a single route pointing to NAP_LNP.\n      nap_lnp = \"NAP_LNP\"\n      routes = []\n      routes << { :name => nap_lnp, :remapped_nap => nap_lnp }\n      params[:routes] = routes\n    end\n  \n    params\n  end\nend"
        },
        "announcement.rb" : {
          "load_on_startup" : false,
          "name" : "announcement.rb",
          "script_type" : "filter",
          "source" : "#\n# Script version 1.0\n#\n# Version history:\n#  1.0  First version of this script\n#\n#\n######################\n# Overview:\n######################\n#\n# This after-matching filter is used to determine if an announcement must be played to the incoming leg\n# before the outgoing leg is placed.\n#\n# For more information, please refer to on-line documentation about playing announcement tones.\n#\n\n\n#\n# Announcement example module, to include in scripts that want to perform announcement playback\n# This class is just an example that show how to fill the \"bridge\" parameters\n# to configure playback of an announcement prompt before the outgoing call is placed.\n#\nmodule Announcement\n\n  #\n  # Initialization method\n  #\n  def init_announcement( params )\n  end\n\n  #\n  # Actual filter implementation\n  #\n  def announcement( params )\n\n    # Keep reference to \"call\" parameters\n    call   = params[:call]\n    # Keep reference to \"bridge\" parameters\n    bridge = params[:bridge]\n\n    # Check if we need to play an announcement to the incoming call\n    # Just an example here: Decision is based on calling or called number.\n    # A real-life application could load a csv file that contains instrutions\n    # on how and when to play announcement prompts\n    if call[ :calling ] == \"12345678\"\n      bridge[ :announcement_tone ]          = \"your_call_is_being_transfered.wav\"\n      bridge[ :announcement_tone_answer ]   = \"false\"\n    end\n    \n    params\n  end\n\nend\n"
        },
        "asr_routing.rb" : {
          "load_on_startup" : false,
          "name" : "asr_routing.rb",
          "script_type" : "main",
          "source" : "#\n# Script version 1.1\n#\n# Version history:\n#  1.1  (start of version history tracking)\n#\n\nrequire 'base_routing'\n\n# This script routes calls according to the ASR values of the destination naps. This kind of routing will try to\n# improve overall system ASR by always using the best naps. It will also help client perception by cleanly dropping\n# calls that would almost certainly fail anyway.\n#\n# Routing is in the following order:\n#\n# * Sorting: <tt>BaseRouting.route_order</tt>\n#   * <tt>:method => :order_by_asr</tt> - Sort the routes using BaseRouting.order_by_asr.\n# * Matching: <tt>BaseRouting.route_match</tt>\n#   * <tt>:call_field_name => :called</tt> - Match the called number of the call to a route.\n#   * <tt>:call_field_name => :calling</tt> - Match the calling number of the call to a route.\n#   * <tt>:call_field_name => :nap</tt> - Match the nap of the call to a route.\n#   * <tt>:method => :match_nap_availability</tt> - Match the call to a route using BaseRouting.match_nap_availability.\n#   * <tt>:method => :match_asr_threshhold</tt> - Match the call to a route using BaseRouting.match_asr_threshhold.\n# * Remapping: <tt>BaseRouting.route_remap</tt>\n#   * <tt>:call_field_name => :called, :route_field_name => :remapped_called</tt> - Remap the called\n#     number for the outgoing call.\n#   * <tt>:call_field_name => :calling, :route_field_name => :remapped_calling</tt> - Remap the calling\n#     number for the outgoing call.\n#   * <tt>:call_field_name => :nap, :route_field_name => :remapped_nap</tt> - Remap the nap for the outgoing call.\n#     This means setting the destination nap for the route.\n#\nclass AsrRouting < BaseRouting\n  route_match :call_field_name => :called\n  route_match :call_field_name => :calling\n  route_match :call_field_name => :nap\n#  route_match :method => :match_nap_availability\n  route_match :method => :match_asr_threshhold\n  route_remap :call_field_name => :called, :route_field_name => :remapped_called\n  route_remap :call_field_name => :calling, :route_field_name => :remapped_calling\n  route_remap :call_field_name => :nap, :route_field_name => :remapped_nap\n\n  route_order :method => :order_by_asr\nend\n\n@@routing = AsrRouting.new\n\ndef init_routes( routes )\n  @@routing.init routes\nend\n\ndef route( call, nap_list )\n  @@routing.route call, nap_list\nend\n"
        },
        "base_routing.rb" : {
          "load_on_startup" : false,
          "name" : "base_routing.rb",
          "script_type" : "",
          "source" : "#\n# Script version 1.32\n#\n# Version history:\n#  1.32 Added better protection against filter methods that don't return correct object type\n#  1.31 Added support for matching routes by sip host and port (user@sip_host:sip_port)\n#  1.30 Added automatic creation of registered users routes (only when a static routes has nil outbound NAP)\n#  1.29 Added new type of filter :after_remap that is called after the remapping (:after filters are called before remapping)\n#  1.28 Remapped \"puts\" to \"log_trace :always\", which gets printed in Gateway's application log\n#  1.27 Optimize routing scripts (cleaning code + adding Toolpack Wrappers)\n#  1.26 Change remaping regex to replace only the first occurence of the matching patern. Same implementation as C++ remapping.\n#  1.25 Updated version after fix for issue #12608\n#  1.24 (script parameters feature: calling caf_call methods: get_script_params, push_script_params)\n#  1.23 (start of version history tracking)\n#\n\nrequire 'db_file'\nrequire 'utils'\nrequire 'toolpack_wrappers'\n\nmodule ClassLevelInheritableAttributes\n  def self.included(base)\n    base.extend(ClassMethods)\n  end\n\n  module ClassMethods\n    def cattr_inheritable(*args)\n      @cattr_inheritable_attrs ||= [:cattr_inheritable_attrs]\n      @cattr_inheritable_attrs += args\n      args.each do |arg|\n        class_eval %(\n          class << self; attr_accessor :#{arg} end\n        )\n      end\n      @cattr_inheritable_attrs\n    end\n\n    def inherited(subclass)\n      @cattr_inheritable_attrs.each do |inheritable_attribute|\n        instance_var = \"@#{inheritable_attribute}\"\n        subclass.instance_variable_set(instance_var, instance_variable_get(instance_var))\n      end\n    end\n  end\nend\n\nclass RoutingException < StandardError\n  attr_reader :reason\n  def initialize(reason)\n    @reason = reason\n  end\nend\n\n# A base class that can be used to facilitate writing routing scripts. For a sample script inheriting from\n# this, look for SimpleRouting.\n#\n# This class provides helper functions for route operations:\n#\n# * <tt>matching</tt>: Find the matching route for a certain call.\n# * <tt>remapping</tt>: Change the call attributes for the outgoing call.\n# * <tt>ordering</tt>: Order route for a call before trying to find a match.\n#\n# == Filters\n# Filters enable routing controller to run pre- and post-processing code for its actions. These filters can be used to do\n# pre-routing remapping, caching, or auditing before the intended routing is performed. Or to do perform a custom action\n# after the routing has been performed. Filters have access to the call, naps, routes and all the instance\n# variables set by other filters in the chain or by the action (in the case of after filters).\n#\n# == Filters methods\n#\n# Filters must have two public methods, One <tt>initialization</tt> method, and one <tt>apply</tt> method.\n#\n# Filter initialization <tt>params</tt>:\n# * <tt>:naps</tt> The nap_list. Hash of naps attributes hashed by nap name. ie: { \"nap_name\" =>  { :attr => \"value\" } }\n# * <tt>:routes</tt> The route_list. The array of route attributes. ie: [ { :attr => \"value\" }, {:attr => \"value\"} ]\n# * <tt>:params</tt> Extra parameters passed to :before_filter or :after_filter\n#\n# Filter <tt>params</tt>:\n# * <tt>:call</tt> The call. Single Hash of call attributes. ie: { :attr => \"value\", }\n# * <tt>:naps</tt> The nap_list. Hash of naps attributes hashed by nap name. ie: { \"nap_name\" =>  { :attr => \"value\", } }\n# * <tt>:routes</tt> The route_list. The array of route attributes. ie: [ { :attr => \"value\", }, {:attr => \"value\", }, ]\n# * <tt>:params</tt> Extra parameters passed to :before_filter or :after_filter\n#\n# == Filter types\n#\n# A filter can take one of two forms: method reference (symbol) or inline method (proc). The first\n# is the most common and works by referencing a protected or private method somewhere in the inheritance hierarchy of\n# the controller by use of a symbol.\n#\n#   module CalledPreRemap\n#     def init_called_pre_remap params\n#       naps = params[ :naps ]     # We have access to naps\n#       routes = params[ :routes ] # We have access to routes\n#       @remap = Regexp.new /^1(.*)$/\n#       @replace = '\\1'\n#     end\n#     def called_pre_remap params\n#       naps = params[ :naps ]      # We have access to naps\n#       routes = params[ :routes ]  # We have access to routes\n#       call = params[ :call ]      # We have access to the call\n#       call[ :called ].gsub!( @remap, @replace )\n#\n#       params # We must return the modified version of params attributes\n#     end\n#\n#   class RoutesetsRouting < BaseRouting\n#     include CalledPreRemap\n#\n#     before_filter :method => :called_pre_remap\n#   end\n#\n\nclass BaseRouting\n  include ClassLevelInheritableAttributes\n\n  # Constants\n  REGISTERED_USERS_REMAPPED_NAP  = \"---registered_users---\"\n\n  # Create class level instance variable\n  cattr_inheritable :match_fields_class\n  @match_fields_class = {}\n  cattr_inheritable :remap_fields_class\n  @remap_fields_class = {}\n  cattr_inheritable :order_params_class\n  @order_params_class = {}\n  cattr_inheritable :filters_class\n  @filters_class = {}\n\n  def initialize_base_routing\n    @match_fields = self.class.match_fields_class[self.class.to_s] || []\n    @remap_fields = self.class.remap_fields_class[self.class.to_s] || []\n    @order_params = self.class.order_params_class[self.class.to_s] || {}\n    @filters = self.class.filters_class[self.class.to_s] || BaseRouting.default_filters\n\n    @base_routing_initialized = true\n  end\n\n  def base_routing_initialized?\n    raise \"Base routing was not initialized\" if @base_routing_initialized != true\n  end\n\n  def self.default_filters\n    { :before => [], :after => [], :after_remap => []}\n  end\n\n  # A route match always tries to match a call parameter using a certain condition or by calling\n  # a user supplied proc.\n  #\n  #  The options are:\n  #\n  # * <tt>:call_field_name</tt>: The field name of the call to try to match\n  # * <tt>:route_field_name</tt>: The field name of the route to try to match with. The default is\n  #   to use the call field name. If the value of the field is empty, the match is considered positive.\n  #   The value of the route field can be a regular expression.\n  #   e.g. /555000./\n  # * <tt>:proc</tt> A user supplied proc to call instead of trying to do the match internally.\n  #   It should accept three argument: route, call, nap list and return a boolean to indicate the match.\n  # * <tt>:method</tt> A user supplied method to call instead of trying to do the match internally.\n  #   It should accept three argument: route, call, nap list and return a boolean to indicate the match.\n  #\n  def self.route_match( params )\n    match_field = {}\n    match_field[:call_field_name] = params.delete(:call_field_name)\n    match_field[:route_field_name] = params.delete(:route_field_name) || match_field[:call_field_name]\n    match_field[:proc] = params.delete(:proc)\n    match_field[:method] = params.delete(:method)\n\n    match_field[:params] = params\n\n    @match_fields_class[self.to_s] = [] if @match_fields_class[self.to_s].nil?\n    @match_fields_class[self.to_s] << match_field\n  end\n\n  # A route remap always remaps a call parameter using a certain condition or by calling\n  # a user supplied proc.\n  #\n  #  The options are:\n  #\n  # * <tt>:call_field_name</tt> The field name of the call to remap\n  # * <tt>:route_field_name</tt> The field name of the route to remap with. The default is\n  #   to use the call field name. If the value of the field is empty, the incoming call's attribute is used.\n  #   The value of the route field can be a regular expression remap.\n  #   e.g. /(555000.)/001\\1/\n  # * <tt>:proc</tt> A user supplied proc to call instead of trying to do the remap internally.\n  #   It should accept three argument: route, call, nap list and return a hash of remapped fields.\n  # * <tt>:method</tt> A user supplied method to call instead of trying to do the match internally.\n  #   It should accept three argument: route, call, nap list and return a boolean to indicate the match.\n  #\n  def self.route_remap( params )\n    remap_field = {}\n    remap_field[:call_field_name] = params.delete(:call_field_name)\n    remap_field[:route_field_name] = params.delete(:route_field_name) || remap_field[:call_field_name]\n    remap_field[:proc] = params.delete(:proc)\n    remap_field[:method] = params.delete(:method)\n\n    remap_field[:params] = params\n\n    @remap_fields_class[self.to_s] = [] if @remap_fields_class[self.to_s].nil?\n    @remap_fields_class[self.to_s] << remap_field\n  end\n\n  # A route order will order the route list before trying to find a match. This is useful if multiple routes\n  # may match but one is preferred over the others, a good example is least cost routing.\n  #\n  #  The options are:\n  #\n  # * <tt>:route_field_name</tt> The field name of the route to order with. The value of the route field\n  #   should be an integer so that it can be compared.\n  # * <tt>:proc</tt> A user supplied proc to call instead of trying to order internally.\n  #   It should accept two argument: route list, nap list and return the sorted route list.\n  # * <tt>:method</tt> A user supplied method to call instead of trying to do the match internally.\n  #   It should accept two argument: route list, nap list and return the sorted route list.\n  #\n  def self.route_order( params )\n    route_order = {}\n    route_order[:route_field_name] = params.delete(:route_field_name)\n    route_order[:proc] = params.delete(:proc)\n    route_order[:method] = params.delete(:method)\n\n    route_order[:params] = params\n\n    @order_params_class[self.to_s] = route_order\n  end\n\n  # The passed <tt>filters</tt> will be appended to the filter_chain and\n  # will execute before the action on this controller is performed.\n  #\n  # <tt>params</tt> are:\n  # * <tt>:proc</tt> A user supplied proc to call.\n  #   It should accept one argument: params and return a modified version params.\n  # * <tt>:method</tt> A user supplied method to call.\n  #   It should accept one argument: params and return a modified version params.\n  # * Other parameters are being passed to the filter at initialization.\n  #\n  def self.before_filter( params )\n    add_filter( :before, params )\n  end\n\n  # The passed <tt>filters</tt> will be appended to the filter_chain and\n  # will execute after route_match (but before route_remap)\n  #\n  # <tt>params</tt> are:\n  # * <tt>:proc</tt> A user supplied proc to call.\n  #   It should accept one argument: params and return a modified version params.\n  # * <tt>:method</tt> A user supplied method to call.\n  #   It should accept one argument: params and return a modified version params.\n  # * Other parameters are being passed to the filter at initialization.\n  def self.after_filter( params )\n    add_filter( :after, params )\n  end\n\n  # The passed <tt>filters</tt> will be appended to the filter_chain and\n  # will execute at the very end of filters chain (after route_remap)\n  #\n  # <tt>params</tt> are:\n  # * <tt>:proc</tt> A user supplied proc to call.\n  #   It should accept one argument: params and return a modified version params.\n  # * <tt>:method</tt> A user supplied method to call.\n  #   It should accept one argument: params and return a modified version params.\n  # * Other parameters are being passed to the filter at initialization.\n  def self.after_remap_filter( params )\n    add_filter( :after_remap, params )\n  end\n\n  # Common code for various filters\n  def self.add_filter( type, params )\n    filter = {}\n    filter[:proc] = params.delete(:proc)\n    filter[:method] = params.delete(:method)\n    filter[:params] = params\n\n    @filters_class[self.to_s] = default_filters if @filters_class[self.to_s].nil?\n    @filters_class[self.to_s][ type ] << filter\n  end\n\n  # A common matching method for nap availability. It will match any route who's destination nap\n  # is available. No dynamic attributes are required by this function.\n  #\n  # The nap availability is provided by the <tt>availability_percent</tt> nap status (0-100%).\n  # The nap is declared unavailable only when this value is '0'.\n  #\n  # Subclasses can use this by declaring the following:\n  #   route_match :method => :match_nap_availability\n  #\n  def match_nap_availability( route, call, nap_list )\n    nap = nap_list[route[:remapped_nap].upcase.to_sym]\n    if nap.nil?\n      puts \"match_nap_availability: Missing Nap status (#{route[:remapped_nap]}).\"\n      return false\n    end\n\n    availability_percent = nap[:availability_percent]\n    if availability_percent.nil?\n      puts \"match_nap_availability: Nap (#{route[:remapped_nap]}) is not present.\"\n      return false\n    end\n\n    availability_percent.to_i != 0\n  end\n\n  # A common matching method for an asr threshold. It will match any route who's destination nap\n  # has a higher asr than the treshold stored for that nap. Two dynamic nap attributes are required:\n  #\n  # * <tt>asr_threshhold_type</tt>: Will store the type of asr statistic to compare with. Possible candidates are:\n  #   * <tt>global</tt>\n  #   * <tt>last_24h</tt>\n  #   * <tt>current_hour</tt>\n  #   * <tt>last_hour</tt>\n  #\n  # * <tt>asr_threshhold</tt>: Will store the asr value to compare with (0-100%).\n  #\n  # Subclasses can use this by declaring the following:\n  #   route_match :method => :match_asr_threshhold\n  #\n  def match_asr_threshhold( route, call, nap_list )\n    nap = nap_list[route[:remapped_nap].to_sym]\n    if nap.nil?\n      puts \"match_asr_threshhold: Missing Nap status (#{route[:remapped_nap]}).\"\n      return false\n    end\n\n    asr_threshhold_type = nap[:asr_threshhold_type]\n    if asr_threshhold_type.nil?\n      puts 'match_asr_threshhold: Missing Nap status \\'asr_threshhold_type\\'.'\n      return false\n    end\n\n    asr_threshhold = nap[:asr_threshhold]\n    if asr_threshhold.nil?\n      puts 'match_asr_threshhold: Missing Nap status \\'asr_threshhold\\'.'\n      return false\n    end\n\n    case asr_threshhold_type\n    when 'global'\n      nap_asr = nap[:global_asr_percent]\n      if nap_asr.nil?\n        puts 'match_asr_threshhold: Missing Nap status \\'global_asr_percent\\'.'\n        return false\n      end\n    when 'last_24h'\n      nap_asr = nap[:last_24h_asr_percent]\n      if nap_asr.nil?\n        puts 'match_asr_threshhold: Missing Nap status \\'last_24h_asr_percent\\'.'\n        return false\n      end\n    when 'current_hour'\n      nap_asr = nap[:current_hour_asr_percent]\n      if nap_asr.nil?\n        puts 'match_asr_threshhold: Missing Nap status \\'current_hour_asr_percent\\'.'\n        return false\n      end\n    when 'last_hour'\n      nap_asr = nap[:last_hour_asr_percent]\n      if nap_asr.nil?\n        puts 'match_asr_threshhold: Missing Nap status \\'last_hour_asr_percent\\'.'\n        return false\n      end\n    else\n      puts 'match_asr_threshhold: Invalid asr threshold type.'\n      return false\n    end\n\n    nap_asr >= asr_threshhold\n  end\n\n  # A common sorting method for nap asr. It will order routes giving priority to routes\n  # who's destination nap is better. One dynamic nap attribute is required:\n  #\n  # * <tt>asr_type</tt>: Will store the type of asr statistic to order with. Possible candidates\n  #   are: global, last_24h, current_hour, last_hour.\n  #\n  # Subclasses can use this by declaring the following:\n  #   route_order :method => :order_by_asr\n  #\n  def order_by_asr( routes, nap_list )\n    routes.sort do |route_a, route_b|\n      nap_a = nap_list[route_a[:remapped_nap].to_sym]\n      if nap_a.nil?\n        puts \"order_by_asr: Missing Nap status (#{route_a[:remapped_nap]}).\"\n      end\n      nap_b = nap_list[route_b[:remapped_nap].to_sym]\n      if nap_b.nil?\n        puts \"order_by_asr: Missing Nap status (#{route_b[:remapped_nap]}).\"\n      end\n      nap_a_asr = 0\n      nap_b_asr = 0\n\n      if nap_a && nap_b\n        nap_a_asr_threshhold_type = nap_a[:asr_threshhold_type]\n        if nap_a_asr_threshhold_type\n          case nap_a_asr_threshhold_type\n          when 'global'\n            nap_a_asr = nap_a[:global_asr_percent]\n            if nap_a_asr.nil?\n              puts 'order_by_asr: Missing Nap status \\'global_asr_percent\\'.'\n              nap_a_asr = 0\n            end\n          when 'last_24h'\n            nap_a_asr = nap_a[:last_24h_asr_percent]\n            if nap_a_asr.nil?\n              puts 'order_by_asr: Missing Nap status \\'last_24h_asr_percent\\'.'\n              nap_a_asr = 0\n            end\n          when 'current_hour'\n            nap_a_asr = nap_a[:current_hour_asr_percent]\n            if nap_a_asr.nil?\n              puts 'order_by_asr: Missing Nap status \\'current_hour_asr_percent\\'.'\n              nap_a_asr = 0\n            end\n          when 'last_hour'\n            nap_a_asr = nap_a[:last_hour_asr_percent]\n            if nap_a_asr.nil?\n              puts 'order_by_asr: Missing Nap status \\'last_hour_asr_percent\\'.'\n              nap_a_asr = 0\n            end\n          end\n        else\n          puts 'order_by_asr: Missing Nap status \\'asr_threshhold_type\\'.'\n        end\n\n        nap_b_asr_threshhold_type = nap_b[:asr_threshhold_type]\n        if nap_b_asr_threshhold_type\n          case nap_b_asr_threshhold_type\n          when 'global'\n            nap_b_asr = nap_b[:global_asr_percent]\n            if nap_b_asr.nil?\n              puts 'order_by_asr: Missing Nap status \\'global_asr_percent\\'.'\n              nap_b_asr = 0\n            end\n          when 'last_24h'\n            nap_b_asr = nap_b[:last_24h_asr_percent]\n            if nap_b_asr.nil?\n              puts 'order_by_asr: Missing Nap status \\'last_24h_asr_percent\\'.'\n              nap_b_asr = 0\n            end\n          when 'current_hour'\n            nap_b_asr = nap_b[:current_hour_asr_percent]\n            if nap_b_asr.nil?\n              puts 'order_by_asr: Missing Nap status \\'current_hour_asr_percent\\'.'\n              nap_b_asr = 0\n            end\n          when 'last_hour'\n            nap_b_asr = nap_b[:last_hour_asr_percent]\n            if nap_b_asr.nil?\n              puts 'order_by_asr: Missing Nap status \\'last_hour_asr_percent\\'.'\n              nap_b_asr = 0\n            end\n          end\n        else\n          puts 'order_by_asr: Missing Nap status \\'asr_threshhold_type\\'.'\n        end\n      end\n\n      nap_b_asr.to_i <=> nap_a_asr.to_i\n    end\n  end\n\n  def init( routes )\n    log_trace :always, \"Init of Ruby Routing Class '#{self.class.name}' starting with #{routes.size} routes\"\n\n    routes.each do |route|\n      log_trace 1, \"  Route '#{route[:route_name]}': \" \\\n        \"Matching[ NAP #{route[:nap]}, calling #{route[:calling]}, called #{route[:called]} ], \" \\\n        \"priority #{route[:priority]}, weight #{route[:weight]}, remapped NAP #{route[:remapped_nap]}\"\n    end\n\n    initialize_base_routing\n\n    # Store all routes\n    @routes = routes\n    @naps   = list_naps\n\n    # Cleanup Route arrays of empty strings values.\n    @routes.each { |item| item.delete_if { |key,value| value.nil? || value.empty? } }\n\n    # Freeze static values\n    Utils.deep_freeze @routes\n    Utils.deep_freeze @naps\n\n    # init Db File class\n    DbFile.init get_current_tbgw_id\n\n    # init filters\n    init_filters :routes => @routes , :naps => @naps\n\n    # Compile regular expression for matching\n    @match_fields.each do |match_field|\n      next if match_field[:proc] || match_field[:method]\n\n      match_field[:route_field_regex] ||= {}\n\n      @routes.each do |route|\n        route_field_value = route[match_field[:route_field_name]]\n        next if route_field_value.nil?\n\n        if route_field_value =~ /^\\s*\\/(.*)\\/\\s*$/\n          match_field[:route_field_regex][route_field_value] = { :regex => Regexp.new($1) }\n        end\n      end\n    end\n\n    # Compile regular expression for remapping\n    @remap_fields.each do |remap_field|\n      next if remap_field[:proc] || remap_field[:method]\n\n      remap_field[:route_field_regex] ||= {}\n\n      @routes.each do |route|\n        route_field_value = route[remap_field[:route_field_name]]\n        next if route_field_value.nil?\n\n        route_field_value_split = route_field_value.split('/')\n        if route_field_value_split.length >= 3\n          remap_field[:route_field_regex][route_field_value] = { :regex => Regexp.new(route_field_value_split[1]), :replace => route_field_value_split[2] }\n        end\n      end\n    end\n\n    log_trace :always, \"Init of Ruby Routing Class '#{self.class.name}' is done\"\n  end\n\n  def route( caf_call, naps )\n    @routes_cloned = false\n    base_routing_initialized?\n\n    begin\n      call = Call.new(caf_call)\n      script_params = ScriptParams.new(caf_call, :attributes => {:routes => @routes, :naps => naps, :call => call})\n\n      script_params = apply_filters( :before, script_params )\n      # Lets route\n      script_params[:routes] = match_routes( script_params[:routes], script_params[:call], script_params[:naps], 8 )\n      script_params[:routes] = create_registered_users_routes( script_params )\n      script_params[:routes] = order_route( script_params[:routes], script_params[:naps] )\n      script_params = apply_filters :after, script_params\n\n      # No route -> refuse call and return\n      raise RoutingException, :no_route if script_params[:routes].empty?\n\n      out_calls = []\n      routes = clone_routes(script_params[:routes]) # Get a unfrozen copy of route to get modified\n      routes.each do |route|\n        out_call = call.dup\n\n        out_call = remap_call( route, out_call, naps )\n\n        # Take value from the NAP if route does not have it's own value\n        set_route_defaults naps, route\n\n        out_calls << out_call\n      end\n\n      # Apply the \"after_remap\" filters\n      script_params[ :out_calls ] = out_calls # Store all outgoing calls, the filters may need to access that\n      script_params[ :routes ]    = routes    # Push the updated routes\n      script_params = apply_filters :after_remap, script_params\n\n      routes.size.times do | index |\n        out_calls[ index ].accept( routes[ index ] )\n      end\n\n      script_params.push_all\n\n    rescue RoutingException => error\n      script_params.push_all\n\n      caf_call.refuse :reason => error.reason\n    rescue\n      caf_call.refuse :reason => :temporary_failure\n      raise $!\n    end\n  end\n\nprivate\n  def return_value_check( filter_type, proc, result, expected_class )\n    expected_class_string = expected_class.class == TrueClass ? \"boolean\" : expected_class.class.to_s\n    raise \"#{filter_type} '#{proc}': Invalid return type (#{result.class}), should return #{expected_class_string}\" unless result.class == expected_class.class\n  end\n  \n  def set_route_defaults naps, route\n    nap = naps[ route[:remapped_nap].upcase.to_sym ]\n    return unless nap\n    route[:route_retry_mode]    ||=  nap[:route_retry_mode]    if nap[:route_retry_mode]\n    route[:route_retry_timeout] ||=  nap[:route_retry_timeout] if nap[:route_retry_timeout]\n    route[:route_retry_relay_progress] ||=  nap[:route_retry_relay_progress] if nap[:route_retry_relay_progress]\n  end\n\n  def order_route( routes, nap_list )\n    if @order_params[:route_field_name]\n      sorted_routes = routes.sort do |x,y|\n        x_field = x[@order_params[:route_field_name]]\n        y_field = y[@order_params[:route_field_name]]\n        x_int_field = 0\n        y_int_field = 0\n\n        if x_field || y_field\n          x_int_field = x_field.to_i\n          y_int_field = y_field.to_i\n        else\n          puts 'Route field not present. Routes will not ordered properly'\n        end\n\n        x_int_field <=> y_int_field\n      end\n    elsif @order_params[:proc]\n      sorted_routes = @order_params[:proc].call( routes, nap_list )\n      return_value_check( \"route_order\", @order_params[:proc], sorted_routes, routes )\n    elsif @order_params[:method]\n      sorted_routes = send( @order_params[:method], routes, nap_list )\n      return_value_check( \"route_order\", @order_params[:method], sorted_routes, routes )\n    else\n      sorted_routes = routes\n    end\n\n    sorted_routes\n  end\n\n  def clone_routes( routes )\n    klone = []\n    routes.each do | route |\n      if @routes_cloned\n        # Routes were already cloned once. Optimization -> Don't clone again!\n        klone << route\n      else\n        klone << route.dup\n      end\n    end\n    @routes_cloned = true\n    klone\n  end\n\n  def remap_call( route, call, nap_list )\n\n    @remap_fields.each do | remap_field |\n      if remap_field[:call_field_name]\n        call_field_name = remap_field[:call_field_name]\n        route_field_name = remap_field[:route_field_name]\n\n        if remap_field[:route_field_regex][route[route_field_name]]\n          remap_field_regex = remap_field[:route_field_regex][route[route_field_name]][:regex]\n          remap_field_replace = remap_field[:route_field_regex][route[route_field_name]][:replace]\n\n          # Try to match+remap the 'user' section first, then try to match+remap the whole thing\n          if remap_field_regex.match( call[call_field_name])\n            call[call_field_name].sub!( remap_field_regex, remap_field_replace ) if call[call_field_name]\n          elsif remap_field_regex.match( assemble_call_field(call, call_field_name) )\n            assembled = assemble_call_field(call, call_field_name)\n            assembled.sub!( remap_field_regex , remap_field_replace)\n            call = split_call_field(assembled, call , call_field_name )\n          end\n        elsif route[route_field_name] && route[route_field_name].size() > 0\n          # Route field doesn't contain a Regex + replace, just replace the whole field.\n          call[call_field_name] = route[route_field_name]\n        end\n      elsif remap_field[:proc]\n        result = remap_field[:proc].call( route, call[:call_object], nap_list, call )\n        return_value_check( \"route_remap\", remap_field[:proc], result, call )\n        call.merge! result\n      elsif remap_field[:method]\n        result = send( remap_field[:method], route, call[:call_object], nap_list, call )\n        return_value_check( \"route_remap\", remap_field[:method], result, call )\n        call.merge! result\n      end\n    end\n\n    call\n  end\n\n  def match_routes( routes, call, nap_list, max_nb_match )\n    matched_routes = []\n    #print \"In match Routes\\n\"\n    routes.each do | route |\n      match = false # Don't match any route if no matching criteria are used at all\n      @match_fields.each do | match_field |\n\n        if match_field[:call_field_name]\n\n          route_field_name = match_field[:route_field_name]\n          call_field_name = match_field[:call_field_name]\n          call_field_value = call[ call_field_name ]\n          #print \"#{match_field[:call_field_name]} -> call[] = #{call[ call_field_name ]}\\n\"\n          if match_field[:route_field_regex][route[route_field_name]]\n            # Use regular expression matching\n            match = match_field[:route_field_regex][route[route_field_name]][:regex].match(call_field_value) != nil\n            # If it doesn't match on the user value only, try the whole call_field\n            assembled_call_field_value = assemble_call_field( call, call_field_name)\n            match = match_field[:route_field_regex][route[route_field_name]][:regex].match(assembled_call_field_value) != nil unless match\n\n          else\n            match = true # It matches until we find a matching criteria that fails to match\n\n            # Use string compare matching (with potential sip_host and sip_port matching)\n            route_field_value = route[route_field_name].to_s\n\n            if route_field_value.include?( '@' )\n              # This route needs to match sip host and port too\n              #print \"In route field value include @\\n\"\n              # Split the route field value at @ and :\n              route_field_value, route_field_sip_host_value = route_field_value.split( \"@\" )\n              #print \"route_field_value = #{route_field_value}, route_field_sip_host_value = #{route_field_sip_host_value}\\n\"\n              if route_field_sip_host_value\n                route_field_sip_host_value, route_field_sip_port_value = route_field_sip_host_value.split( \":\" )\n              end\n              route_field_value           ||= \"\"\n              route_field_sip_host_value  ||= \"\"\n              route_field_sip_port_value  ||= \"\"\n              #print \"route_field_value = #{route_field_value}, route_field_sip_host_value = #{route_field_sip_host_value}, route_field_sip_port_value=#{route_field_sip_port_value}\\n\"\n              #print \"call_field_value= #{ call_field_value}, call_field_sip_host_value= #{( call[ \"#{call_field_name}_sip_host\" ] || \"\" ).to_s}\"\n              if route_field_sip_host_value.empty?\n                # SIP host not specified in the route -> It always matches\n              else\n                # Check if route's sip_host value matches call's sip_host value\n                call_field_sip_host_value = ( call[ \"#{call_field_name}_sip_host\".to_sym ] || \"\" ).to_s\n                match = ( route_field_sip_host_value.casecmp( call_field_sip_host_value ) == 0 )\n                break if !match\n              end\n\n              if route_field_sip_port_value.empty?\n                # SIP port not specified in the route -> It always matches\n              else\n                # Check if route's sip_port value matches call's sip_port value\n                call_field_sip_port_value = ( call[ \"#{call_field_name}_sip_port\".to_sym ] || \"\" ).to_s\n                match = ( route_field_sip_port_value.casecmp( call_field_sip_port_value ) == 0 )\n                break if !match\n              end\n            end\n\n            if route_field_value.empty?\n              # Value not specified in the route -> It always matches\n            else\n              # Check if route's value matches call's value\n              match = ( route_field_value.casecmp( call_field_value.to_s ) == 0 )\n              break if !match\n            end\n          end\n\n        elsif match_field[:proc]\n          match = match_field[:proc].call( route, call, nap_list )\n          return_value_check( \"route_match\", match_field[:proc], match, true )\n        elsif match_field[:method]\n          match = send( match_field[:method], route, call, nap_list )\n          return_value_check( \"route_match\", match_field[:method], match, true )\n        end\n\n        break if !match\n      end\n      if match\n        matched_routes << route\n        if matched_routes.length >= max_nb_match\n          log_trace :always, \"Warning: Reached maximum number of matching routes (#{max_nb_match}) defined in base_routing\"\n          break\n        end\n      end\n    end\n\n    matched_routes\n  end\n\n  def create_registered_users_routes( params )\n    routes = []\n\n    begin\n      registered_users_priority = nil\n      registered_users_weight   = nil\n\n      # Remove any \"registered users\" routes from the list of matching routes\n      params[:routes].each do | route |\n        if( route[:remapped_nap] == REGISTERED_USERS_REMAPPED_NAP )\n          # Get the actual priority and weight for this route\n          priority  = get_route_or_nap_attribute( route, params[:naps], :priority )\n          weight    = get_route_or_nap_attribute( route, params[:naps], :weight )\n\n          # Keep the matching static route with highest priority\n          if registered_users_priority.nil? || priority < registered_users_priority\n            registered_users_priority = priority\n            registered_users_weight   = weight\n          end\n        else\n          # This is a 'normal' route\n          routes << route\n        end\n      end\n\n      # If we have not found any \"registered users route\", we're done here (we won't be using dynamic users)\n      if registered_users_priority.nil?\n        log_trace 0, \"Not using dynamic registered-users routes (no matching static route remapping to registered users)\"\n        return routes\n      end\n\n      # Check if there are matching routes that point to registered users\n      if params[:registered_user].nil? || params[:registered_user][:contact_list].nil? || params[:registered_user][:contact_list].empty?\n        log_trace 1, \"Not using dynamic registered-users routes (call does not match a registered user)\"\n        return routes\n      end\n\n      # Add the \"registered users routes\".\n      # Note: :contact_list is order descending by q value\n      params[:registered_user][:contact_list].each do |contact|\n        # Remap called host with contact's or registree's source\n        #\n        #  Info: params[:routing_method] is configures at \"Sip Domain\"-> \"Routing Method\"\n        #\n        if params[:routing_method] == \"ROUTING_METHOD_BY_REGISTER_SOURCE\"\n          remapped_called = \"#{contact[:name]}@#{contact[:src_host]}:#{contact[:src_port]}\"\n          remapped_nap    = contact[:nap_in]\n        else\n          remapped_called = \"#{contact[:name]}@#{contact[:host]}:#{contact[:port]}\"\n          remapped_nap    = contact[:nap_in]\n        end\n\n        route = {\n          :route_name       => \"registered_contact=#{contact[:contact]}\",\n          :remapped_called  => remapped_called,\n          :remapped_nap     => remapped_nap,\n          :route_type       => :dynamic,\n          :priority         => registered_users_priority.to_s,\n          :weight           => registered_users_weight.to_s\n        }\n\n        routes << route\n\n        log_trace 2, \"Registered user route created: #{route.inspect}\"\n      end\n    rescue => e\n      log_trace :error, \"create_registered_users_routes failed: #{e}\\n  \" + e.backtrace.join(\"\\n  \" )\n    end\n\n    return routes\n  end\n\n  def get_route_or_nap_attribute( route, nap_list, attribute )\n    # Check if the route has the attribute already\n    value = 0\n    if( route[ attribute ] && !route[ attribute ].empty? )\n      value = route[ attribute ].to_i\n    elsif nap_list[ route[:remapped_nap].to_sym ][ attribute ]\n      # Take the attribute from the remapped NAP\n      value = nap_list[ route[:remapped_nap].to_sym ][ attribute ].to_i\n    end\n    return value\n  end\n\n  def init_filters params\n    base_routing_initialized?\n\n    [ :after_remap, :after, :before ].each do |what|\n      @filters[ what ].each do |filter|\n        next unless filter[:method]\n        filter_params = params.merge filter[:params]\n        send \"init_#{filter[:method]}\", filter_params\n      end\n    end\n  end\n\n  def apply_filters what, params\n    @filters[ what ].each do |filter|\n      filter_params = if filter[:param]\n        params.merge( filter[:param] )\n      else\n        params\n      end\n\n      if filter[:proc]\n        result = filter[:proc].call( filter_params )\n        return_value_check( \"#{what}_filter\", filter[:proc], result, params )\n        params = result\n      elsif filter[:method]\n        result = send(filter[:method], filter_params)\n        return_value_check( \"#{what}_filter\", filter[:method], result, params )\n        params = result\n      end\n\n     end\n     params\n  end\n\n  # assemble_call_field take hask like call and append the host and port if present.\n  def assemble_call_field(call, call_field_name )\n    assembled_call_field =  call[ call_field_name.to_sym ]\n    if call[\"#{call_field_name}_sip_host\".to_sym ]\n      assembled_call_field += \"@\" + call[\"#{call_field_name}_sip_host\".to_sym ].to_s\n    end\n    if call[\"#{call_field_name}_sip_port\".to_sym ]\n      assembled_call_field += \":\" + call[\"#{call_field_name}_sip_port\".to_sym ].to_s\n    end\n    assembled_call_field\n  end\n\n  def split_call_field( assembled_call_field ,call, call_field_name )\n    call[call_field_name], assembled_call_field  = assembled_call_field.split(\"@\")\n    host, port  = assembled_call_field.split(\":\")\n    call[\"#{call_field_name}_sip_host\".to_sym ] = host\n    call[\"#{call_field_name}_sip_port\".to_sym ] = port\n    call\n  end\n\nend\n\nclass BaseRoutingSelector\nend\n\n# Re-define \"puts\" as \"log_trace :always\"\n# (otherwise users that are used to \"puts\" when debugging get confused\n#  because by default it does not get printed)\ndef puts string\n  log_trace :always, string\nend\n"
        },
        "black_white_listing.rb" : {
          "load_on_startup" : false,
          "name" : "black_white_listing.rb",
          "script_type" : "filter",
          "source" : "#\n# Script version 1.2\n#\n# Version history:\n#  1.1: Initial version\n#  1.2: Each inbound NAP can be assigned a different white/black listing file.\n#       NAP attribute name for the white/black CSV file is 'black_white_list'\n#       Global black/white listing is still available.\n#\n# This before-matching filter is used to do WHITE&BLACK listing\n# before routing takes place.\n#\n# Longest prefix matching:\n# The lookup will always find the longest prefix matching for the number.  \n# If the complete number is not present, then the closest and longest prefix entry is used\n# That is true for both white and black list lookups.\n#\n# WHITELISTING:\n# Whitelisting can be enabled for called and/or calling numbers.\n# When enabled, any number or prefix not found during lookup will result in call rejection\n# \n# BLACKLISTING:\n# Any number or prefix can be blacklisted\n#\n# CSV File Format:\n# calling,calling_params,called,called_params\n#     555,             w,      ,\n# 5551212,             b,      ,\n#        ,              ,   444,            b\n#        ,              ,444121,            w\n#     666,             w,   777,            w\n#\n#  ** There is no relation between a calling number and a called number on the same line. **\n#\n# Parameters:\n#  :scope\t\t\t\t\t\t                The scope for the black/white listing\n#     :scope => :global             Global file to use for all naps.\n#                                   For global scope, the :file parameter must be specified.\n#     :scope => :per_nap            White/Black listing per inbound NAP.  \n#                                   Filename is specified in NAP custom attribute \n#                                   named 'black_white_list'.\n#                                   The 'Type attribute' for this custom field must be 'dbfile'.\n#\n#   :file                           CSV filename in FileDb under \"Custom Files:\"\n#                                   Can be specified for :scope => :global to override \n#                                   default filename.\n#                                   Default value is 'black_white_list.csv'\n#     :file => 'my_bw_list.csv'\n#\n\nrequire 'trie'\n\nALLOWED_PARAMS = [ 'w', 'W', 'b', 'B' ]\nSCOPE_VALUES = [ :global, :per_nap ]\n\nmodule BlackWhiteListing\n\n  # Initialization method\n  #\n  def init_black_white_listing(params)\n    @bw_file      = params[ :file ] || 'black_white_list.csv'      # Filename in FileDb under \"Custom Files:\"\n    @whitelist    = {}                   # number types on which to apply whitelist-> :called and or :calling\n    @lookup_types = []                   # number types needing lookup in the trie -> :called and or :calling\n    @naps\t\t      = params[:naps]\t\t     # Naps parameters where we will find the black_white_list files\n    @scope        = params[:scope]       # scope of black/white listing -> :global or :per_nap\n    @tries        = {}\n\t\n    # Filter parameters validation\n    if !SCOPE_VALUES.include? @scope\n      raise \"black_white_listing: Allowed values for :scope are #{SCOPE_VALUES.inspect}\" \n    end\n\n    # Depending on the scope, we will create a single and global trie, or one trie per inbound NAP\n    case @scope\n    when :per_nap\n      @naps.each do |nap_name,nap_params|\n        @bw_file = nap_params[:black_white_list]\n        next if @bw_file.nil?\n        next if @bw_file.lstrip.empty?\n        @tries[ nap_name.to_s ] = build_trie( nap_name.to_s, @bw_file.lstrip.rstrip )\n      end\n    when :global\n      @tries[ :global ] = build_trie( :global, @bw_file )\n    end\n  end\n\n  # call-seq:\n  #   white_listing( params ) -> modified params\n  #\n  def black_white_listing(params)\n    call = params[ :call ]\n    nap  = call[ :nap ]       # inbound NAP\n    #puts 'black_white_listing'\n\t\n    case @scope\n    when :per_nap  # Select the trie for the inbound NAP\n      trie = @tries[ nap ]\n    when :global   # Select the global for the inbound NAP\n      trie = @tries[ :global ]\n      nap  = :global\n    end\n\n    # No trie for this NAP -> Normal case where a NAP doesn't use black/white listing\n    return params unless trie\n   \n    # number_lookup_dirs is an array that looks like [ :called, :calling ]\n    @lookup_types.each do |number_dir|\n      number = call[ number_dir ]\n      longest_prefix = trie.longest_prefix number\n      number_params = trie[ longest_prefix ]\n      case number_params\n        when 'w','W':\n          # Let the call through\n          log_trace 1, \"black_white_listing: #{number_dir}=#{number} -> WhiteListed -> Allowing\"\n        when nil: \n          # We didn't find the number in our list\n          if @whitelist[nap].include? number_dir\n            # If white listing is enabled we must refuse the call\n            log_trace 1, \"black_white_listing: #{number_dir}=#{number} -> Unlisted -> Rejecting\"\n            raise RoutingException, :call_rejected\n          else\n            log_trace 1, \"black_white_listing: #{number_dir}=#{number} -> Unlisted -> Allowing\"\n          end\n        when 'b','B':\n          # This number is specifically blacked out, so refusing\n          log_trace 1, \"black_white_listing: #{number_dir}=#{number} -> BlackListed -> Rejecting\"\n          raise RoutingException, :call_rejected\n        else\n          log_trace :error, \"black_white_listing: Missing ruby code -> unhandled case #{number_params}\"\n          raise RoutingException, :call_rejected\n        end\n        #puts \"number_dir:#{number_dir},number:#{call[ number_dir ]},number_params:#{number_params}\"\n    end\n      \n    params\n  end\n\nprivate\n  def strip_colvalue(col_value)\n  if col_value\n      col_value = col_value.strip   # remove white spaces\n      if col_value.size == 0\n        col_value = nil\n      end\n    end\n  col_value\n  end\n\n  # To optimize the number lookup at runtime, we remeber if we will \n  # need to perform lookups on both called and calling numbers\n  def register_lookup_type(lookup_type)\n     @lookup_types.push lookup_type unless @lookup_types.include? lookup_type\n  end\n  \n  # We need to remember if whitelisting is use for each NAP  \n  def register_numberdir_whitelist(nap, number_dir, csv_params)\n    @whitelist[nap] ||= []\n    case csv_params\n    when 'w','W'\n      @whitelist[nap].push number_dir unless @whitelist[nap].include? number_dir\n    end\n  end\n  \n  # Verify validity of csv parameters (called_params and calling_params)\n  def verify_csv_params(params, name, filename, row_idx)\n    return unless params\n    unless ALLOWED_PARAMS.include? params\n      log_trace :error, \"black_white_listing: Invalid parameter value '#{params}' for '#{name}' in file #{filename} at line #{row_idx}\"\n      raise \"black_white_listing: Invalid parameter value '#{params}' for '#{name}' in file #{filename} at line #{row_idx+1}\"\n    end\n  end\n  \n  def build_trie(nap_name, filename)\n    # Fill the trie with white/black listed numbers\n    trie = Trie.new\n    begin\n      file = DbFile.get_file filename\n    rescue Exception => error\n      raise \"black_white_listing: Could not load file:'#{filename}' associated to nap:'#{nap_name}'.\"\n    end\n    \n    file.csv_parse do |col_names, col_vals, row_idx|\n      next if row_idx == 0 # First row is column name definition\n\n      [:calling, :called].each do |call_dir|\n        calldir         = strip_colvalue( col_vals[call_dir.to_s] )\n        calldir_params  = strip_colvalue( col_vals[\"#{call_dir.to_s}_params\"] )\n        verify_csv_params( calldir_params, \"#{call_dir.to_s}_params\", filename, row_idx )\n      \n        if calldir && calldir_params\n          trie.push calldir, calldir_params\n          register_lookup_type( call_dir ) #Remember we have to do calling/called number lookups\n          register_numberdir_whitelist( nap_name, call_dir, calldir_params ) #Remember we have whitelisting enable or not\n          #puts \"calldir:#{calldir},params:#{calldir_params}\"\n        end\n      end\n    end\n    \n    trie\n  end\n\nend\n"
        },
        "call_recording.rb" : {
          "load_on_startup" : false,
          "name" : "call_recording.rb",
          "script_type" : "filter",
          "source" : "#\n# Script version 1.0\n#\n# Version history:\n#  1.0  First version of this script\n#  1.1  Same script, improved documentation\n#\n#\n######################\n# Overview:\n######################\n#\n# This after-matching filter is used to determine if incoming and/or outgoing call legs need to be recorded.\n#\n# For more information, please refer to on-line documentation about routing script call recording.\n#\n#\n######################\n# Script parameters that control recording\n######################\n#\n# Call recording is controlled, through routing scripts, by adding a specific\n# parameter to the call attributes.\n#\n# To record the incoming call leg, the parameter is found in the \"bridge\" section.\n# The parameter name is \":record_incoming\"\n# For example:\n#   First, get reference to the \"bridge\" hash inside the \"params\" hash:\n#     bridge = params[:bridge]\n#   Then add the \":record_incoming\" attribute to the bride hash:\n#     bridge[ :record_incoming ] = \"whatever_path_i_want_to_record_to.wav\"\n# \n# To record the outgoing call leg, the parameter is found in the routes of the call,\n# since each matching route may result in a different outgoing leg, and can be recorded\n# to a different file path\n# Also, note that the \"route\" hash must be duplicated in order to be modified by the function.\n# For example:\n#   First, make a copy of matching routes before starting to modify them:\n#     unless routes\n#       routes = clone_routes params[:routes]\n#     end\n#   Then indicate which route(s) must be recorded:\n#     routes.each do |route|\n#       route[ :record_outgoing ]  = \"whatever_path_i_want_to_record_to.wav\"\n#     end\n#\n#\n######################\n# Variables\n######################\n# The file path to use when recording can be built using some variables replaed by Toolpack (useful for some\n# information that is not known from the \"params\" in this routing script)\n#\n# Variables that can be used:\n#  @{CURRENT_PKG}:      Version of current package\n#                       Ex: 2.6.45\n#  @{DATE format}:      Prints the date, where 'format' is expressed as described for the 'strftime' function\n#                       Ex: @{DATE %Y-%m-%d} => 2013-01-28\n#  @{DefaultName}:      Replaced by the default file name for recording.\n#                       The default file name contains:\n#                         - LinkId:     Id common between all legs of this call bridge\n#                         - LegId:      Unique Id for this leg\n#                         - Nap:        Current NAP name this call leg is from\n#                         - Direction:  \"IN\" or \"OUT\" (depends if call leg is incoming or outgoing leg)\n#                         - Calling:    Calling number\n#                         - Called:     Called number\n#                         - Protocol:   Protocol type of this call (SS7, ISDN, CASR2, SIP)\n#                         - Media info: Codec + IP/Port for SIP calls, Trunk/Timeslot for TDM calls\n#                       Ex: 73EBA698-F3D67B4B-NAP_SS7-IN-5550000-5550001-SS7-TRUNK_BELL_11-24.wav\n#                       Ex: 73EBA698-73EBA698-NAP_SIP-OUT-5550000-5550001-SIP-G723-10.3.10.101-1050.wav\n#  @{DefaultPath}:      Default recording folder and file name: \"@{RECORD_PATH}/@{DATE %Y-%m-%d}/@{DefaultName}\"\n#                       Ex: /lib/tb/toolpack/setup/recorded_calls/2013-01-28/73EBA698-F3D67B4B-NAP_SS7-IN-5550000-5550001-SS7-TRUNK_BELL_11-24.wav\n#                       Ex: /lib/tb/toolpack/setup/recorded_calls/2013-01-28/73EBA698-73EBA698-NAP_SIP-OUT-5550000-5550001-SIP-G723-10.3.10.101-1050.wav\n#  @{Direction}:        Direction of current leg (IN our OUT)\n#                       Ex: IN\n#  @{LegId}:            Current LegId (Unique Id for this leg)\n#                       Ex: F3D67B4B\n#  @{LinkId}:           Current LinkId (Id common between all legs of this call bridge)\n#                       Ex: 73EBA698\n#  @{PKG_HOME}:         Path where packages are stored.\n#                       Note: It's not recomended to use that path on redundant systems, package file replication may cause confusion in recorded files.\n#                       Ex: /lib/tb/toolpack/pkg\n#  @{PROMPT_PATH}:      Default path where audio prompts are stored\n#                       Note: It's not recomended to use that path on redundant systems, package file replication may cause confusion in recorded files.\n#                       Ex: /lib/tb/toolpack/pkg/prompts\n#  @{Protocol}:         Protocol of current leg\n#                       Ex: SS7\n#  @{RECORD_PATH}:      Default recording folder: \"@{TB_SETUP_HOME}/recorded_calls/\"\n#  @{TBX_GW_PORT}:      Current \"System Id\" (also called \"Gateway Port\")\n#                       Ex: 12358\n#\n#\n######################\n# Special variables:\n######################\n#\n# @{MixWithIncoming}:   Special variable to indicate that both incoming and outgoing call legs must be \"mixed|\n#                       together and recorded as a single file.\n#                       This variable must be set to the outgoing leg recording path:\n#                         route[ :record_outgoing ] = \"@{MixWithIncoming}\"\n#                       The recorded file path will be the path provided for the incoming leg ( \"bridge[ :record_incoming ]\" )\n#\n######################\n# Relative paths:\n######################\n# Note about relative paths:\n# When path is provided to record a file, the path is relative to the \"working\" directory of the tbstreamserver\n# application (this is the application that performs play/record operations).\n# This folder is in the \"setup\" toolpack directory, under tbstreamserver application directory. For example:\n#  /lib/tb/toolpack/setup/12358/2.7/apps/tbstreamserver/\n#\n\n\n#\n# CallRecording example module, to include in scripts that want to perform recording\n# This class is just an example that show how to fill the \"bridge\" and \"route\" parameters\n# to configure an incoming or outgoing call recording.\n#\nmodule CallRecording\n\n  #\n  # Initialization method\n  #\n  def init_enable_call_recording params\n  end\n\n  #\n  # Actual filter implementation\n  #\n  def enable_call_recording params\n\n    # Keep reference to \"call\" parameters\n    call   = params[:call]\n    # Keep reference to \"bridge\" parameters\n    bridge = params[:bridge]\n    # We may have to make a modified copy of routes...\n    routes = nil\n\n    # Check if we need to record the incoming call\n    # Just an example here: Record based on calling or called number.\n    # A real-life application could load a csv file of numbers to monitor and record\n    if call[ :calling ] == \"12345678\" || call[ :called ] == \"12345678\"\n\n      # Enable recording on incoming call\n      bridge[ :record_incoming ]  = \"@{DefaultPath}.wav\"\n\n      # Enable recording on outgoing call (no matter which route is working)\n      unless routes\n        # Make a copy of \"routes\" parameters (they need to be copied if we want to modify them)\n        routes = clone_routes params[:routes]\n      end\n      routes.each do |route|\n        route[ :record_outgoing ]  = \"@{DefaultPath}.wav\"\n      end\n    end\n\n    if routes\n      params[:routes] = routes  \n    end\n    params\n  end\n\nend\n"
        },
        "call_stub.rb" : {
          "load_on_startup" : false,
          "name" : "call_stub.rb",
          "script_type" : "test",
          "source" : "#\n# Script version 1.0\n#\n# Version history:\n#  1.0  First version of this script\n#\nclass CallStub\n  attr_accessor :call_params, :route_params\n  attr_reader :chosen_routes, :routed_calls, :params\n\n  def initialize\n    @call_params = {}\n    @route_params = {}\n    @routed_calls = []\n    @chosen_routes = []\n    @params = {}\n    @@accepted = 0\n    @@refused = 0\n  end\n\n  def accepted\n    @@accepted\n  end\n  def refused\n    @@refused\n  end\n\n  def get( param )\n    @call_params[param]\n  end\n\n  def list_params\n    @call_params.keys\n  end\n\n  def get_script_params\n    @params ||= {}\n    @params\n  end\n\n  def accept( call_params, route_params )\n    if call_params[:nap].nil?\n      puts 'Nap attribute must be remapped'\n    end\n    @routed_calls << call_params\n    @chosen_routes << route_params\n    @@accepted += 1\n  end\n\n  def push_script_params( script_params )\n    @params = script_params\n  end\n\n  def refuse( call_params )\n    @routed_calls << call_params\n    @@refused += 1\n  end\nend\n\n"
        },
        "called_pre_remap.rb" : {
          "load_on_startup" : false,
          "name" : "called_pre_remap.rb",
          "script_type" : "filter",
          "source" : "#\n# Script version 1.2\n#\n# Version history:\n#  1.2  (start of version history tracking)\n#\n# Filter module that pre-remap the called number, before routing takes place.\n#\n# The pre-remapping is done using a Regex provided from the attribute <tt>called_pre_remap</tt> of the incoming NAP\n# This attributes is specified in a dynamic NAP column attribute in the gateway configuration view.\n#\nmodule CalledPreRemap\n\n  # call-seq:\n  #   routesets_digit_analyzer( params ) -> modified params\n  #\n  # Apply the pre-remapping of the call[:called_number] with the source NAP called_pre_remap attribute.\n  #\n  # params[ :call ][ :called ] gets modified\n  def called_pre_remap params\n    call = params[ :call ]\n    pre_remap = @called_pre_remap[ call[ :nap ] ]\n    if pre_remap then\n      call[ :called ].gsub!( pre_remap[ :regex ], pre_remap[ :replace ] )\n    end\n    params\n  end\n\n  # Initialization method\n  # Precompiles all pre-remap regular expression an instance variable.\n  def init_called_pre_remap params\n    @called_pre_remap = {}\n    # Compile pre_remap regex for each nap if pre_remap column is present\n    params[ :naps ].each do |nap_name,nap_attr|\n      next unless nap_attr[ :called_pre_remap ]\n      pre_remap_split = nap_attr[ :called_pre_remap ].split('/')\n      if pre_remap_split.length >= 3\n        @called_pre_remap[ nap_name.to_s ] = {\n            :regex   => Regexp.new(pre_remap_split[1]),\n            :replace => pre_remap_split[2]\n            }\n      end\n    end\n  end\nend\n"
        },
        "called_pre_remap_and_ruri.rb" : {
          "load_on_startup" : false,
          "name" : "called_pre_remap_and_ruri.rb",
          "script_type" : "main",
          "source" : "#\n# Script version 1.2\n#\n# Filter module that pre-remap the called number, before routing takes place.\n#\n# The pre-remapping is done using a Regex provided from the attribute <tt>called_pre_remap</tt> of the incoming NAP\n# This attributes is specified in a dynamic NAP column attribute in the gateway configuration view.\n#\nmodule CalledPreRemapAndRuri\n\n  # call-seq:\n  #   routesets_digit_analyzer( params ) -> modified params\n  #\n  # Apply the pre-remapping of the call[:called_number] with the source NAP called_pre_remap attribute.\n  #\n  # params[ :call ][ :called ] gets modified\n  def called_pre_remap_and_ruri params\n    call = params[ :call ]\n  \n  # Replaces the called number with the request-uri (instead of the to: field)\n  if call[:request_uri] && call[:request_uri] =~ /sip:(.*)@.*/\n       call[ :called ] = $1\n    end\n  \n    pre_remap = @called_pre_remap[ call[ :nap ] ]\n    if pre_remap then\n      call[ :called ].gsub!( pre_remap[ :regex ], pre_remap[ :replace ] )\n    end\n    params\n  end\n\n  # Initialization method\n  # Precompiles all pre-remap regular expression an instance variable.\n  def init_called_pre_remap_and_ruri params\n    @called_pre_remap = {}\n    # Compile pre_remap regex for each nap if pre_remap column is present\n    params[ :naps ].each do |nap_name,nap_attr|\n      next unless nap_attr[ :called_pre_remap ]\n      pre_remap_split = nap_attr[ :called_pre_remap ].split('/')\n      if pre_remap_split.length >= 3\n        @called_pre_remap[ nap_name.to_s ] = {\n            :regex   => Regexp.new(pre_remap_split[1]),\n            :replace => pre_remap_split[2]\n            }\n      end\n    end\n  end\nend\n"
        },
        "called_pre_remap_csv.rb" : {
          "load_on_startup" : false,
          "name" : "called_pre_remap_csv.rb",
          "script_type" : "filter",
          "source" : "#\n# Script version 1.1\n#\n# This before-matching filter is used to replace the called number before the routing takes place. At initialization, the script\n# will load a .csv file to gather the numbers to remap.\n#\n# This remapping filter requires two NAP custom columns\n# * <tt>custom_files</tt>: Filename of the .csv (comma separated value) file taken from the FileDB (in the custom_files section).\n#                          Field type is <dbfile> and default value should be left empty.\n# * <tt>default_remap</tt>: Default called number value if incoming called number isn't found.\n#                          Field type is <integer>.\n#\n# The format of the .csv file should be the following:\n#     First line (title):   called,remapped_called\n#     Subsequent lines:     <original called number>,<new called number>\n#     e.g                   4506558993,5146558993\n#     e.g.                  5554448888,1112227777\n#\n# The remapping .csv file must be imported before it can be assigned to a NAP.  Use the \"Import custom files\" from the \"File DB\"\n# to input new remapping files.  Then, you can assign it to a NAP by populating the \"custom_files\" column in the \"Naps\" tab.\n#\nmodule CalledRemapCsv\n\n  # Initialization method\n  #\n  def init_called_remap params\n    @called= {}\n    @naps       = params[ :naps ]\n\n    # Get all the digits\n    called_filenames.each do |filename|\n      file = DbFile.get_file filename\n      file.csv_parse do |col_names, col_vals, row_idx|\n        next if row_idx == 0 # First row is column name definition\n\n        @called [col_vals[\"called\"]] = col_vals[\"remapped_called\"]\n      end\n    end\n  end\n\n  # call-seq:\n  #   called_remap( params ) -> modified params\n  #\n  # params[ :routes ] gets modified to include only useful routes.\n  #\n  def called_remap params\n    call = params[ :call ]\n\n    called_number = @called [call[:called]]\n    if called_number then\n      call[:called] = called_number\n    else\n      incoming_nap = params[:call][:nap].to_sym\n      default_number = @naps [ incoming_nap ][:default_remap]\n      if default_number != nil then\n        call[:called] = default_number\n      end\n    end\n    params[ :call ] = call\n    params\n  end\n\nprivate\n  def called_filenames\n    called_filenames = @naps.collect { |nap| nap[1][:custom_files] }\n    called_filenames.uniq!\n    called_filenames = called_filenames.select { |filename| !filename.nil? && !filename.empty? }\n    called_filenames\n  end\nend\n"
        },
        "charge_nb_remapper.rb" : {
          "load_on_startup" : false,
          "name" : "charge_nb_remapper.rb",
          "script_type" : "filter",
          "source" : "#\n# Script version 1.0\n#\n# This before-matching filter is used to replace the charge number of an incoming call according to values found in\n# custom columns of the incoming NAP.\n#\n# This remapping filter requires three NAP and/or route custom columns\n# * <tt>charge_nb_remap</tt>: Phone number that will overwrite the current incoming charge number.\n#                             Field type is <text> and default value should be left empty.\n# * <tt>charge_nb_noa_remap</tt>: Nature of address description that will overwrite the current incoming charge number NOA.\n#                             Field type is <text> and default value should be left empty.\n# * <tt>charge_nb_npi_remap</tt>: Numbering plan information description that will overwrite the current incoming charge number NPI.\n#                             Field type is <text> and default value should be left empty.\n#\n# NOA values for the <tt>charge_nb_noa_remap</tt> remapped parameter are:\n# * <tt>unknown_number</tt>\n# * <tt>international_number</tt>\n# * <tt>national_number</tt>\n# * <tt>subscriber_number</tt>\n# * <tt>network_specific</tt>\n# * <tt>network_routing_national_format</tt>\n# * <tt>network_routing_international_format</tt>\n# * <tt>abbreviated_number</tt>\n# * <tt>subscriber_number_operator_requested</tt>\n# * <tt>national_number_operator_requested</tt>\n# * <tt>international_number_operator_requested</tt>\n# * <tt>no_number_present_operator_requested</tt>\n# * <tt>no_number_present_cut_through_call_to_carrier</tt>\n# * <tt>test_line_test_code</tt>\n# * <tt>non_unique_subscriber_number</tt>\n# * <tt>non_unique_national_number</tt>\n# * <tt>non_unique_international_number</tt>\n# * <tt>call_950_number</tt>\n# * <tt>Any numerical value<tt>\n#\n# NPI values for the <tt>charge_nb_npi_remap</tt> remapped parameter are:\n# * <tt>unknown_number</tt>\n# * <tt>isdn</tt>\n# * <tt>telephony</tt>\n# * <tt>private</tt>\n# * <tt>data</tt>\n# * <tt>telex</tt>\n# * <tt>national</tt>\n#\nmodule ChargeNumberRemap\n\n  #\n  # Initialization method\n  #\n  def init_filter_charge_number_remap params\n  end\n\n  #\n  # Actual filter implementation\n  #\n  def filter_charge_number_remap params\n    # Retrieve the incoming NAP\n    call = params[ :call ]\n    incoming_nap = params[:naps][call[ :nap ].to_sym]\n    \n    # Check if we need to remap the charge number\n    if (incoming_nap)\n      if ( incoming_nap[:charge_nb_remap] && (incoming_nap[:charge_nb_remap].length > 0) )\n        call[:charge_number] = incoming_nap[:charge_nb_remap].to_str\n      end\n      \n      # Check if we need to remap the NOA\n      if ( incoming_nap[:charge_nb_noa_remap] && (incoming_nap[:charge_nb_noa_remap].length > 0) )\n        if ((incoming_nap[:charge_nb_noa_remap].to_i == 0) && (incoming_nap[:charge_nb_noa_remap].to_str != \"0\"))\n          # The entry should be considered a symbol\n          call[:charge_number_noa] = incoming_nap[:charge_nb_noa_remap].to_sym\n        else\n          call[:charge_number_noa] = incoming_nap[:charge_nb_noa_remap].to_i\n        end\n      end\n  \n      # Check if we need to remap the NPI\n      if ( incoming_nap[:charge_nb_npi_remap] && (incoming_nap[:charge_nb_npi_remap].length > 0) )\n        call[:charge_number_npi] = incoming_nap[:charge_nb_npi_remap].to_sym\n      end\n    end\n\n    params\n  end\n\nend\n"
        },
        "charge_number_remapping.rb" : {
          "load_on_startup" : false,
          "name" : "charge_number_remapping.rb",
          "script_type" : "main",
          "source" : "#\n# Script version 1.0\n#\n# This example makes use of a \"after_filter\" to remap/replace the charge number information based on user\n# configuration in the incoming NAP.\n#  \n# This example requires three route and/or NAP custom columns\n# * <tt>charge_nb_remap</tt>: Phone number that will overwrite the current incoming charge number.\n#                             Field type is <sttring> and default value should be left empty.\n# * <tt>charge_nb_noa_remap</tt>: Nature of address description that will overwrite the current incoming charge number NOA.\n#                             Field type is <sttring> and default value should be left empty.\n# * <tt>charge_nb_npi_remap</tt>: Numbering plan information description that will overwrite the current incoming charge number NPI.\n#                             Field type is <sttring> and default value should be left empty.\n#\n# Refer to the description in the filter to get a more precise description of the parameters\n#\nrequire 'base_routing'\nrequire 'charge_nb_remapper'\n\n# This class implements basic routing\nclass SimpleRouting< BaseRouting\n  include ChargeNumberRemap\n    \n  # This filter remaps the charge number based on information from the route or NAP columns.\n  before_filter :method => :filter_charge_number_remap   \n    \n  route_match :call_field_name => :called\n  route_match :call_field_name => :calling\n  route_match :call_field_name => :nap\n  route_remap :call_field_name => :called, :route_field_name => :remapped_called\n  route_remap :call_field_name => :calling, :route_field_name => :remapped_calling\n  route_remap :call_field_name => :nap, :route_field_name => :remapped_nap\n  \nend\n\n@@routing = SimpleRouting.new\n\ndef init_routes( routes )\n  @@routing.init routes\nend\n\ndef route( call, nap_list )\n  @@routing.route call, nap_list\nend\n"
        },
        "db_file.rb" : {
          "load_on_startup" : false,
          "name" : "db_file.rb",
          "script_type" : "util",
          "source" : "#\n# Script version 1.6\n#\n# Version history:\n#  1.6  Added validation that \"filename\" is not empty before calling get_file_of_type\n#  1.5  Added support for case insensitive column names when parsing CSV files\n#       by using option {:insensitive_colnames => true}\n#  1.4  Added public function to access tbgw_configuration_id\n#       Added function get_law_enforcement_target_file used by Lawful Intercept filter\n#  1.3  Load the DB files according to the proper configuration (tracking #11748)\n#  1.2  Protection from nil SQL resultset (tracking #11633)\n#  1.1  (start of version history tracking)\n#\n# DbFile is used to retrieve files stored in the Database.  Files can be imported in the database from the gateway\n# configuratiuon views.  DbFiles are expected to be text files, and are converted to UTF-8 during import.\n#\nclass DbFile\n  attr_accessor :content\n  attr_accessor :name\n  attr_accessor :options\n  @@tbgw_configuration_id = nil\n\n\n  #  Class initialization, used to bind to the tbgw_configuration_id of the database.\n  def self.init(tbgw_configuration_id)\n    @@tbgw_configuration_id = tbgw_configuration_id\n  end\n\n  def self.tbgw_configuration_id\n    @@tbgw_configuration_id\n  end\n\n  # call-seq:\n  #   get_file( filename ) -> dbfile\n  #\n  # Retrieves a custom file from the database.\n  #    file = DbFile.get_file filename\n  #    puts file.name\n  #    puts file.content\n  #\n  def self.get_file(filename)\n    raise \"File name missing\" if !filename || filename.empty?\n    get_file_of_type :tbgw_files, filename\n  end\n\n  # call-seq:\n  #   get_routeset_digitmap_file( filename ) -> dbfile\n  #\n  # Retrieves a routeset_digitmap file from the database.\n  def self.get_routeset_digitmap_file(filename)\n    raise \"File name missing\" if !filename || filename.empty?\n    get_file_of_type :tbgw_routesets_digitmaps, filename\n  end\n\n  # call-seq:\n  #   get_law_enforcement_target_file( filename ) -> dbfile\n  #\n  # Retrieves a law_enforcement_target file from the database.\n  def self.get_law_enforcement_target_file(filename)\n    raise \"File name missing\" if !filename || filename.empty?\n    get_file_of_type :tbgw_law_enforcement_targets, filename\n  end\n\n  def self.set_file(filename)\n    raise \"File name missing\" if !filename || filename.empty?\n    set_file_of_type :tbgw_files, filename\n  end\n\n\n  # Parse the csv file\n  #  file.csv_parse do |col_names, col_vals, row_idx|\n  #    puts \"Row has #{col_names.size} columns\"\n  #    puts \"First ColName is: #{col_names[0]}\"\n  #    puts \"Value for #{col_names[0]} is: #{col_vals[col_names[0]]}\"\n  #  end\n  def csv_parse(options={}, &block)\n    require 'csv'\n    col_names = []\n    col_vals  = {}\n    row_idx = 0\n    CSV.parse( content ) do |row|\n      if row_idx == 0 then\n        col_names = row\n        col_names.each{|n| n.downcase!} if options[:insensitive_colnames]\n        row_idx += 1\n        next\n      end\n\n      # For the current row, Hash values by col names\n      col_idx = 0\n      for col_val in row do\n        col_vals[ col_names[col_idx] ] = col_val # hash values by colnames\n        col_idx += 1\n      end\n\n      yield( col_names, col_vals, row_idx ) if block\n      return unless block\n      row_idx += 1\n    end\n  end\n\nprivate\n  def self.get_file_of_type(type, filename)\n    statement =\n      \"SELECT #{type}.*\n      FROM #{type}, tbgw_configurations\n      WHERE #{type}.tbgw_files_db_id = tbgw_configurations.tbgw_files_db_id AND\n      tbgw_configurations.id = #{@@tbgw_configuration_id} AND\n      #{type}.name = '#{filename}'\"\n    sql_result = sql_select_all statement\n    raise \"Could not load DB file #{filename}\\n No file returned by SQL statement:\\n #{statement}\" if sql_result.nil? || sql_result.empty?\n    db_file = DbFile.new\n    db_file.content = sql_result[0].delete( \"content\" )\n    db_file.name    = sql_result[0].delete( \"name\" )\n    db_file.options = sql_result[0]\n    db_file\n  end\n\n  def self.set_file_of_type(type, filename)\n    raise \"This is not tested, please do not use it!\" # TODO\n    # NOTE: sql_select_all(statement) is in fact doing a SQL Exec, not a SELECT\n\n    statement =\n      \"SELECT #{type}.id\n      FROM #{type}, tbgw_configurations\n      WHERE #{type}.tbgw_files_db_id = tbgw_configurations.tbgw_files_db_id AND\n      tbgw_configurations.id = #{tbgw_configuration_id} AND\n      #{type}.name = '#{filename}'\"\n    sql_result = sql_select_all(statement)\n\n    if sql_result.nil? || sql_result.empty?\n      statement = \"SELECT tbgw_files_db_id FROM tbgw_configurations WHERE id = #{tbgw_configuration_id}\"\n      sql_select_all(statement)\n      raise \"Could not find tbgw_files_db_id in tbgw_configuration_id #{tbgw_configuration_id}\\n No file returned by SQL statement:\\n #{statement}\" if sql_result.nil? || sql_result.empty?\n      tbgw_files_db_id = sql_result[0].delete(\"tbgw_files_db_id\")\n\n      # The file doesn't exist, create it\n      statement = \"INSERT INTO #{type} (name, content, tbgw_files_db_id) VALUES (#{filename}, #{content}, #{tbgw_files_db_id}\"\n      sql_select_all(statement)\n    else\n      file_id = sql_result[0].delete( \"id\" )\n      statement = \"UPDATE #{type} SET content=#{content} WHERE id=#{file_id}\"\n      sql_select_all(statement)\n    end\n\n    return true\n  end\nend\n"
        },
        "f_digit_removal.rb" : {
          "load_on_startup" : false,
          "name" : "f_digit_removal.rb",
          "script_type" : "main",
          "source" : "#\n# Script version 1.4\n#\n# Version history:\n#  1.4  (start of version history tracking)\n#\n\nrequire 'base_routing'\n\n# This script demonstrates how to peform 'F' digit removal in the Called number based on a NAP custom field.\n# If the destination NAP (remapped NAP) 'remove_f_digit' custom field is set, the script removes the end 'F' digit from the called number.\n# It also routes calls in the most simple fashion. The routing behavior is the same as routing with 'SimpleRouting'.\n#\n# This script routes calls in the most simple fashion. The behavior is the same as routing without ruby scripts\n# with the addition of nap availability.\n#\n# Routing is in the following order:\n# * Matching: <tt>BaseRouting.route_match</tt>\n#   * <tt>:call_field_name => :called</tt> - Match the called number of the call to a route.\n#   * <tt>:call_field_name => :calling</tt> - Match the calling number of the call to a route.\n#   * <tt>:call_field_name => :nap</tt> - Match the nap of the call to a route.\n#   * <tt>:method => :match_nap_availability</tt> - Match the call to a route using BaseRouting.match_nap_availability.\n# * Remapping: <tt>BaseRouting.route_remap</tt>\n#   * <tt>:call_field_name => :nap, :route_field_name => :remapped_nap</tt> - Remap the nap for the outgoing call.\n#     This means setting the destination nap for the route.\n#   * <tt>:method => :optional_called_remap</tt> - Performs the 'F' digit removal from called number, if required\n#   * <tt>:method => :remap_called_noa_npi</tt> - NOA and NPI remapping based on the Called number prefix.\n#   * <tt>:call_field_name => :called, :route_field_name => :remapped_called</tt> - Remap the called\n#     number for the outgoing call with the choosen route field named 'remapped_called'.\n#   * <tt>:call_field_name => :calling, :route_field_name => :remapped_calling</tt> - Remap the calling\n#     number for the outgoing call with the choosen route field named 'remapped_calling'.\n#\nclass FdigitRemoval < BaseRouting\n  route_match :call_field_name => :called\n  route_match :call_field_name => :calling\n  route_match :call_field_name => :nap\n#  route_match :method => :match_nap_availability\n  route_remap :call_field_name => :nap, :route_field_name => :remapped_nap\n  route_remap :method => :optional_called_remap\n  route_remap :call_field_name => :called, :route_field_name => :remapped_called\n  route_remap :call_field_name => :calling, :route_field_name => :remapped_calling\n\n  # This function optionally remove the 'F' digit from the Called number based on the option 'remove_f_digit' set\n  # in the remapped NAP (route destination NAP)\n  #\n  def optional_called_remap( route, caf_call, nap_list, call_params )\n    if @saved_static_nap_list_option.empty?\n      # Save the static NAP column option on the first call only\n      nap_list.each do |key, value|\n        @saved_static_nap_list_option[key] = {:remove_f_digit => value[:remove_f_digit]}\n      end\n    end\n    remove_f_option = @saved_static_nap_list_option[call_params[:nap].intern][:remove_f_digit]\n    if remove_f_option == \"1\"\n      called_value = call_params[:called]\n      call_params[:called] = called_value.gsub(@remove_f_regex, '\\1')\n    end\n  call_params\n  end\n\n  def initialize\n    @saved_static_nap_list_option = {}\n    # Match any number with optional end 'F' digit. Capturing group not enclosing 'F' digit.\n    @remove_f_regex = Regexp.new(/^(\\d+)F?$/)\n  end\nend\n\n\n@@routing = FdigitRemoval.new\n\ndef init_routes( routes )\n  @@routing.init routes\nend\n\ndef route( call, nap_list )\n  @@routing.route call, nap_list\nend\n"
        },
        "filter_by_route_groups.rb" : {
          "load_on_startup" : false,
          "name" : "filter_by_route_groups.rb",
          "script_type" : "filter",
          "source" : "#\n# Script version 1.2\n#\n# This filter is used limit what ROUTEs an incoming NAP can use to route a call.\n# The ROUTEs are grouped together with a ROUTE custom column called 'route_group'.  Each route can belong to only one group.\n# The NAPs are limited to use route groups listed in the NAP custom column called 'route_groups' (comma separated list)\n# Example:\n#   if NAP 'abc' as the custom cloumn 'route_groups' set to \"default, ss7_out\", then incomming calls from 'abc' \n#   will be allowed to use ROUTEs with custom column set to \"default\" or \"ss7_out\"\n#    \n#\n# Version history:\n#  1.2 Added optimized route filtering when 'filter_by_route_groups' is the first :before_filter in the chain\n#  1.1 Added validation at init that NAP custom column 'route_groups' exists\n#  1.0 (start of version history tracking)\n#\n\nmodule FilterByRouteGroups\n  def init_filter_by_route_groups(params)\n    @route_group_for_nap = {}\n    @optimized_route_group_lookup = false\n    naps   = params[:naps]  # naps is built like [ [ nap_name, { #nap attributes hash } ] ]\n    routes = params[:routes]\n\n    log_trace :always, \"Init of Ruby Routing Filter 'filter_by_route_groups'\"\n\n    # Validate that 'route_groups' NAP custom column exist\n    if( naps.first[1][:route_groups].nil? )\n      raise \"NAP custom column named 'route_groups' is missing, required by 'filter_by_route_groups' routing filter script\"\n    end\n\n    # Optimize route group lookup when the filter_by_route_groups filter is the first :before_filter in the chain\n    first_filter = @filters[:before].first\n    if( first_filter[:method] == :filter_by_route_groups )\n      # build route group cache for each naps\n      naps.each do |nap_name,nap|\n        @route_group_for_nap[nap_name] = get_route_groups_for_nap(nap,routes)\n      end\n      @optimized_route_group_lookup = true\n    else\n      log_trace :warning, \"'filter_by_route_groups' routing filter script should be the first :before_filter for better performance\"\n    end\n  end\n  \n  def filter_by_route_groups(params)\n    call = params[:call]\n    incoming_nap_name = call[:nap].to_sym\n    final_routes = []\n\n    if( @optimized_route_group_lookup )\n      # Optimized route lookup, when 'filter_by_route_groups' is the first :before_filter in the chain\n      final_routes = @route_group_for_nap[incoming_nap_name]\n    else\n      final_routes = get_route_groups_for_nap( params[:naps][incoming_nap_name], params[:routes] )\n    end\n    \n    # Replace the system's list of routes by our sorted list of routes!\n    params[:routes] = final_routes\n    params\n  end\n\nprivate\n  def get_route_groups_for_nap(nap,routes)\n    # Get the route groups allowed for calls on the incomming nap\n    route_groups_for_incoming_nap = nap[:route_groups].split(\",\").collect(&:strip)\n    # Select only routes matching route_groups allowed for the incoming nap\n    final_routes = routes.select{ |route| route_groups_for_incoming_nap.include?(route[:route_group]) }\n  end\nend"
        },
        "flexible_noa.rb" : {
          "load_on_startup" : false,
          "name" : "flexible_noa.rb",
          "script_type" : "main",
          "source" : "#\n# Script version 1.2\n#\n# Version history:\n#  1.1  (start of version history tracking)\n#  1.2\tCommented-out the use of 'match_nap_availability' to ease script testing (tracking #19296)\n#\n\nrequire 'base_routing'\n\n# This script demonstrates how to peform a remapping of the Calling/Called NOA flexibly based on the routes.\n# It also routes calls in the most simple fashion. The routing behavior is the same as routing with 'SimpleRouting'.\n# \n# Routing is in the following order:\n# \n# * Matching: <tt>BaseRouting.route_match</tt>\n#   * <tt>:call_field_name => :called</tt> - Match the called number of the call to a route. \n#   * <tt>:call_field_name => :calling</tt> - Match the calling number of the call to a route. \n#   * <tt>:call_field_name => :nap</tt> - Match the nap of the call to a route. \n#   * <tt>:method => :match_nap_availability</tt> - Match the call to a route using BaseRouting.match_nap_availability. \n# * Remapping: <tt>BaseRouting.route_remap</tt>\n#   * <tt>:call_field_name => :called, :route_field_name => :remapped_called</tt> - Remap the called \n#     number for the outgoing call with the choosen route field named 'remapped_called'.\n#   * <tt>:call_field_name => :calling, :route_field_name => :remapped_calling</tt> - Remap the calling \n#     number for the outgoing call with the choosen route field named 'remapped_calling'.\n#   * <tt>:call_field_name => :nap, :route_field_name => :remapped_nap</tt> - Remap the nap for the outgoing call. \n#     This means setting the destination nap for the route.\n#   * <tt>:method => :remap_called_noa_npi</tt> - NOA and NPI remapping based on the Called number prefix.\n#\n#\n\nclass NoaNpiRemap < BaseRouting\n  route_match :call_field_name => :called\n  route_match :call_field_name => :calling\n  route_match :call_field_name => :nap\n  #route_match :method => :match_nap_availability\n  route_remap :call_field_name => :called, :route_field_name => :remapped_called\n  route_remap :call_field_name => :calling, :route_field_name => :remapped_calling\n  route_remap :call_field_name => :nap, :route_field_name => :remapped_nap\n  route_remap :method => :flexible_noa_remap\n  \n\n    def flexible_noa_remap  ( route, caf_call, nap_list, call_params )\n    #remapped_fields = {}\n    \n    #puts route.inspect\n   \n      #Take the valued from the colums in the routes\n      #remapped_fields[:calling_noa] = route[:calling_noa]\n      #remapped_fields[:calling_npi] = route[:calling_npi] \n      call_params[:called_noa] = route[:called_noa]\n      call_params[:calling_noa] = route[:calling_noa]\n      call_params\n   \n    end\n\n  # Initialization method\n  def init_flexible_noa_remap params\n  end\nend\n\n@@routing = NoaNpiRemap.new\n\ndef init_routes( routes )\n  @@routing.init routes\nend\n\ndef route( call, nap_list )\n  @@routing.route call, nap_list\nend\n"
        },
        "ha_db.rb" : {
          "load_on_startup" : false,
          "name" : "ha_db.rb",
          "script_type" : "util",
          "source" : "#\n# Script version 1.00\n#\n#  1.00 (start of version history tracking)\n#\n# High Available Database class.\n# This class connects to the primary and secondary databases,\n# and hides the complexity of switching and choosing the database connection.\n#\n# Primary database is always used if available.  (automatic fallback from 2nd to 1st)\n#\n# Poll:\n#  Database connection is being monitored/polled every HADB_STATUS_POLL_PERIOD_SEC.\n#  If connection is stale, it gets disconnected with an alarm being sent.\n#  A closed connection will get a connection retry, with an alarm being sent.\n#\n#  DNS string format:\n#    \"DBI:ODBC:DRIVER={MySQL};DATABASE=#{DATABASE NAME};SERVER=#{MYSQL SERVER};USER=#{USERNAME};PWD=#{PASSWORD};\"\n#    example:\n#    \"DBI:ODBC:DRIVER={MySQL};DATABASE=HaDB;SERVER=10.0.0.94;USER=tbdb;PWD=tbdbpw;\"\n#      database_name: HaDB\n#      database_ip:   10.0.0.94\n#      username:      tbdb\n#      password:      tbdbpw\n#\n# params:\n#   :pdb_dsn           - Primary database DSN string (See format above)\n#   :sdb_dsn           - Secondary database DSN string (See format above)\n#\n#    (:poll_frequency)   - At which frequence (in seconds) to verify the database status\n#\n#    (:snmp_oid)         - Which OID to use for traps\n#    (:snmp_host_ip)     - Where to send SNMP traps\n#    (:snmp_community)   - SNMP community to use for traps\n#\nrequire 'rubygems'\nrequire 'dbi'\nrequire 'snmp'\n\nclass HaDb\n  DEFAULT_HADB_STATUS_POLL_PERIOD_SEC = 1*60   # Poll timeout in seconds\n\n  def initialize params\n    @dbs = [\n      { :name => \"PRIMARY_DATABASE\",    :dsn => params.fetch(:pdb_dsn) },\n      { :name => \"SECONDARY_DATABASE\",  :dsn => params.fetch(:sdb_dsn) }\n    ]\n    @poll_frequency = params[:poll_frequency] || DEFAULT_HADB_STATUS_POLL_PERIOD_SEC\n    @poll_time = Time.now + @poll_frequency\n    @current_db = nil\n    @query_cnt = 0\n    @query_fail = 0\n    @db_alarm = DbAlarm.new(params) if params[:snmp_oid]\n    poll_db_states   # poll db state will trigger connection opening\n  end\n\n  #\n  # Connect to database using ODBC\n  # If connection fails, send an alert.\n  #\n  def connect db\n    dbh = nil\n    begin\n      # connect to the server using ODBC\n      dbh = DBI.connect( db[:dsn], \"\", \"\")\n    rescue DBI::DatabaseError => e\n      send_notification :db_conn_failed, :db => db, :reason => e\n      dbh = nil\n    end\n    dbh\n  end\n\n\n  #\n  # Switch current db connection from Primary to Secondary\n  # The connection being switched out gets disconnected.\n  # The poll_db_states will handle the cleanup and reconnection on next its\n  # poll intervall\n  #\n  def switch_db reason\n    return false if @current_db[:name] != \"PRIMARY_DATABASE\"\n    \n    if @dbs[0][:dbh] && @dbs[0][:dbh].connected? \n      begin\n        @dbs[0][:dbh].disconnect\n      rescue\n        send_notification :db_conn_down, :db => @dbs[0]\n        @dbs[0][:dbh] = nil\n      end\n    end\n\n    if @dbs[1][:dbh] && @dbs[1][:dbh].connected? # if were using db0 and db1 is available, switch to db1\n      send_notification :db_conn_switch, :db=> @current_db, :reason => reason\n      \n      @current_db = @dbs[1]\n      @current_dbh = @current_db[:dbh]\n      true\n    else\n      false\n    end\n  end\n\n  #\n  # Poll Database and connection states every @poll_frequency period\n  # 1 - If connection is stale, close it\n  # 2 - try to reopen closed conenctions\n  # 3 - Fall back to primary DB when available\n  #\n  # Alarms are being sent on state changes\n  #\n  def poll_db_states\n    @poll_time = Time.now + @poll_frequency\n\n    # close stale connections\n    @dbs.each do |db|\n      next if db[:dbh].nil?\n      if !db[:dbh].poll # if poll fail then disconnect\n        begin\n          db[:dbh].disconnect\n        rescue\n        ensure\n          send_notification :db_conn_down, :db => db\n          db[:dbh] = nil\n        end\n      end\n    end\n\n    # try to reconnect\n    @dbs.each do |db|\n      if db[:dbh].nil?\n        db[:dbh] = connect db\n        if db[:dbh]\n          send_notification :db_conn_up, :db => db\n        end\n      end\n    end\n\n    # Fall back to primary database if available\n    if @dbs[0][:dbh]\n      send_notification( :db_conn_switch, :db => @dbs[0], :reason => 'Primary Database now available' ) if  @current_db != @dbs[0]\n      @current_db = @dbs[0]\n      @current_dbh = @current_db[:dbh]\n    elsif @dbs[1][:dbh]\n      @current_db = @dbs[1]\n      @current_dbh = @current_db[:dbh]\n    else\n      send_notification :db_outage, :reason => 'No database access is available'\n    end\n\n    #puts \"#{@query_cnt}/#{@query_fail}:Current Db: #{@current_db?@current_db[:name]:'none'}\"\nend\n\n  #\n  # Database connection agnostic SQL query execute\n  # Will execute the query on the currently prefered database connection.\n  # If the query fails, the function will retry after forcing a database\n  # connection switch.\n  #\n  def execute stmnt\n    poll_db_states if Time.now >= @poll_time # periodically poll for database states\n    sth = nil\n    @query_cnt += 1\n    begin\n      sth = @current_dbh.execute stmnt\n    rescue\n      retry if switch_db($!)\n      @query_fail += 1\n    end\n    raise \"Database Outage\" unless sth\n    sth\n  end\n\n  # method responsable to send alerts to outside world\n  def send_notification alarm, params={}\n    e = case alarm\n      when :db_conn_up      then \"#{alarm}: #{params[:db][:name]}\"\n      when :db_conn_down    then \"#{alarm}: #{params[:db][:name]}: #{params[:reason]}\"\n      when :db_conn_switch  then \"#{alarm}: #{params[:db][:name]}: #{params[:reason]}\"\n      when :db_conn_failed  then \"#{alarm}: #{params[:db][:name]}: #{params[:reason]}\"\n      when :db_outage       then \"#{alarm}: #{params[:reason]}\"\n    end\n    log_trace 4, e\n    @db_alarm.send alarm, e if @db_alarm\n  end\nend\n\n#\n# Database Alarming class\n#\n# SNMP trap\n#\n#  params:\n#    :snmp_host_ip     - Where to send SNMP traps\n#    :snmp_community   - SNMP community to use for traps\n#    :snmp_oid         - Which OID to use for traps\n#\nclass DbAlarm\n  SEVERITY = { #REFERENCE \"RFC 3164, Section 4.1 - syslog Message Parts\"\n    :emergency=> 1, #system is unusable\n    :alert    => 2, #action must be taken immediately\n    :critical => 3, #critical conditions\n    :warning  => 5, #warning conditions\n    :notice   => 6, #normal but significant condition\n  }\n\n  # Alarm definition with its severity\n  ALARMS = {\n      :db_conn_up =>      { :id => 1, :level => :notice },\n      :db_conn_down =>    { :id => 2, :level => :alert },\n      :db_conn_failed =>  { :id => 3, :level => :alert },\n      :db_conn_switch =>  { :id => 4, :level => :warning },\n      :db_outage =>       { :id => 5, :level => :emergency }\n  }\n\n  def initialize params\n    @snmp = SNMP::Manager.new(:Version => :SNMPv2c, :Host => params[:snmp_host_ip], :Community => params[:snmp_community] )\n    @start_time = Time.now\n  end\n\n  # send the alarm\n  def send event_id, event_string\n    send_snmp_trap( event_id, event_string ) unless defined? RAILS_ROOT\n  end\n\n  private\n  # send the alarm as a snmp trap\n  def send_snmp_trap event_id, event_string\n    raise \"Unsupported alarm/event #{event_id}\" unless ALARMS.include? event_id\n    up_time = Time.now - @start_time\n    @snmp.trap_v2 up_time.to_i, \"enterprises.21776\",\n      [\n        SNMP::VarBind.new(\"1.3.6.1.4.1.21776.2500.999.1.1.1\", SNMP::Integer.new(ALARMS[event_id][:id]) ),\n        SNMP::VarBind.new(\"1.3.6.1.4.1.21776.2500.999.1.2.1\", SNMP::Integer.new(SEVERITY[ALARMS[event_id][:level]]) ),\n        SNMP::VarBind.new(\"1.3.6.1.4.1.21776.2500.999.1.3.1\", SNMP::OctetString.new(event_string) )\n      ]\n  end\nend\n\n#\n# Added poll method to replace DBI ping method which does not work with ODBC.\n#\nmodule DBI\n  class DatabaseHandle < Handle\n    def poll\n      return false unless self.connected?\n      begin\n        sth = self.execute \"SELECT 1;\"\n        sth.finish\n        true\n      rescue\n        false\n      end\n    end\n  end\nend unless defined? RAILS_ROOT # Do not include this when running in emulation with webportal test button\n"
        },
        "iperbill_radius_auth_profile.rb" : {
          "load_on_startup" : false,
          "name" : "iperbill_radius_auth_profile.rb",
          "script_type" : "filter",
          "source" : "#\n# Script version 1.3\n#\n# Version history:\n#  1.0  First version of this script/profile.\n#  1.1  Added Iperbill return code table handling,\n#       partial h323-credit-time call duration support,\n#       partial h323-ivr-in suggested remapped_nap support.\n#  1.2  Added enhanced h323-ivr-in support for multiple NAPs with priorities.\n#  1.3  Added enhanced h323-ivr-in multiple NAPs: support for static or dynamic mode.\n#\n# This script is a profile for Iperbill radius authorization \n# This profile must be used in conjunction with radius_authorization.rb\n#\n# Authorization parameters were tuned to fit Iperbill needs\n#\n# Feature:\n#   - Iperbill authorization parameters\n#   - Iperbill h323-return-code handling\n#\n# Partially implemented:\n#   - h323-credit-time call duration\n#     => bridge[:max_call_duration]\n#   - h32h-ivr-in remapped_nap suggestion \n#     => call[ :auth_remapped_nap ]\n#\n# How to use:\n#   - Add the following require statements in the simple_routing.rb script:\n#       require 'radius_authorization'\n#       require 'iperbill_radius_auth_profile'\n#   - In the routing class, add the following statements:\n#       include RadiusAuthorization\n#       include IperbillRadiusAuthProfile\n#       before_filter :method => :radius_authorization\n#   - To use the h323-ivr-in attribute as\n#     * an outgoing NAP:\n#       Add the following in the simple_routing.rb script:\n#         route_match :call_field_name => :auth_remapped_nap, :route_field_name => :remapped_nap\n#     * Route mode with a list of outgoing NAPs with priorities\n#          ex. [(static|dynamic),]NAP1,prio1,NAP2,prio2,...\n#       static: using this method, all possible routes must have been created first.\n#       dynamic: using this method, the route will be dynamically built on each call based on the provided list.\n#       Add the following in the simple_routing.rb script:\n#         route_order :route_field_name => :prio\n#\n\nmodule IperbillRadiusAuthProfile\n  #\n  # Fill\n  #\n  def fill_authorization_attributes(params, auth)\n    call = params[:call]\n    auth[:\"User-Name\"] = call[:nap]\n    auth[:\"NAS-Identifier\"] = \"Gateway\"\n    auth[:\"Called-Station-Id\"] = call[:called]\n    auth[:\"Calling-Station-Id\"] = call[:calling]\n    # The conf-id is a 128-bit field. It is formatted as four 32-bit numbers\n    # printed in hexadecimal notation. The call[:leg_id] and call[:session_id]\n    # fields contain the necessary information to generate conf-ids that are\n    # also found in RADIUS accounting requests.\n    auth[:\"h323-incoming-conf-id\"] = \"#{call[:leg_id].to_i.to_s(16).rjust(8,\"0\")} 00000000 00000000 00000000\"\n    auth[:\"h323-conf-id\"] = call[:session_id].to_s\n    auth[:\"NAS-IP-Address\"] = \"auto\"\n  end\n\n  #\n  # Accept\n  #\n  def on_radius_authorization_accept(params, auth)\n    radius_fields = IperbillRadiusFields.new auth\n    params[ :bridge ] ||= {}\n    bridge = params[ :bridge ]\n    call = params[:call]\n\n    # Parse h323-return-code -> Radius Auth result\n    h323_return_code = radius_fields[ :\"h323-return-code\" ]\n    if h323_return_code\n      iperbill_return_code = IperbillReturnCode.new h323_return_code\n      if iperbill_return_code.action == :refuse\n        log_trace 2, \"Radius Authorization Refused: #{iperbill_return_code}\"\n        raise RoutingException, :call_rejected\n      end\n      log_trace 2, \"Radius Authorization Accepted: #{iperbill_return_code}\"\n    else\n      log_trace 2, \"Radius Authorization Refused: h323-return-code not present\"\n      raise RoutingException, :call_rejected\n    end\n    \n    # Parse h323-credit-time -> Number of seconds this call is allowed to last\n    h323_credit_time = auth[ :\"h323-credit-time\" ] # Numeric value\n    if h323_credit_time\n      bridge[:max_call_duration] = \"#{h323_credit_time}000\" # number of milliseconds\n      bridge[:call_duration_reason] = :normal\n      #bridge[:disconnect_tone] = \"0000.wav\"\n      log_trace 2, \"Radius Authorization Credit: #{h323_credit_time} seconds\"\n    end\n\n    # Parse h323-ivr-in -> Destination NAP(s)\n    h323_ivr_in = radius_fields[ :\"h323-ivr-in\" ]\n    if h323_ivr_in\n      nap_list = h323_ivr_in.split(',')\n      if nap_list.size == 1\n        # Single NAP specified\n        mode = \"one\"\n      elsif (h323_ivr_in.include? \"dynamic\") || (h323_ivr_in.include? \"static\")\n        # Remove route mode; static or dynamic\n        mode = nap_list.slice!(0,1).first\n      else\n        # Default is static\n        mode = \"static\"\n      end\n\n      case mode\n      when \"one\"\n        call[ :auth_remapped_nap ] = h323_ivr_in\n        log_trace 2, \"Radius Authorization suggested NAP: #{call[ :auth_remapped_nap ]}\"\n      when \"dynamic\"\n        # Dynamically create the routing table override the configured static routing table\n        # Multiple NAPs following the format: NAP1,5,NAP2,1,NAP3,4,NAP4,9\n        routes_auth = []\n        nap = nap_list.slice!(0,2)\n        while (nap.size() > 1)\n          routes_auth << {:name=>nap[0],:remapped_nap=>nap[0],:prio=>nap[1]}\n          nap = nap_list.slice!(0,2)\n        end\n        \n        log_trace 2, \"Radius Authorization route table created : #{routes_auth.inspect}\"\n\n        # Update routes\n        params[:routes] = routes_auth\n      when \"static\"\n        # Static mode: a route to each destination NAP must be pre-configured\n        # Use the configured static table and add \"prio\" to each NAP in h323_ivr_in list\n        # Remove the routes that does not have its NAP define in h323_ivr_in list\n        naps = {}\n        routes = params[:routes]\n        \n        # Multiple NAPs following the format: NAP1,5,NAP2,1,NAP3,4,NAP4,9\n        nap = nap_list.slice!(0,2)\n        while (nap.size() > 1)\n          naps[nap[0]] = nap[1]\n          nap = nap_list.slice!(0,2)\n        end\n        \n        log_trace 2, \"Radius Authorization suggested NAP priorities: #{naps.inspect}\"\n        \n        # Clone the route list and add :prio from Attributes list\n        routes_auth = clone_routes(routes)\n        routes.each_index do |i|\n          routes_auth[i][:prio] = naps[routes[i][:remapped_nap]] != nil ? naps[routes[i][:remapped_nap]].to_i : -1\n        end\n        \n        # Delete routes not in Attributes list\n        routes_auth.each do |n|\n          routes_auth.delete(n) if n[:prio] == -1\n        end\n\n        # Update routes\n        params[:routes] = routes_auth\n      else\n        log_trace :error, \"Radius Authorization unknown mode #{mode}\"\n        raise RoutingException, :call_rejected\n      end\n    end\n  end\n\n  #\n  # Reject\n  #\n  def on_radius_authorization_reject(params, auth)\n    radius_fields = IperbillRadiusFields.new auth\n    \n    h323_return_code = radius_fields[ :\"h323-return-code\" ]\n    if h323_return_code\n      iperbill_return_code = IperbillReturnCode.new h323_return_code\n      log_trace 2, \"Radius Authorization Rejected: #{iperbill_return_code}\"\n    end\n    raise RoutingException, :call_rejected \n  end\n  \n  #\n  # Challenge\n  #\n  def on_radius_authorization_challenge(params, auth)\n    raise RoutingException, :call_rejected\n  end\n\n  #\n  # Timeout\n  #\n  def on_radius_authorization_timeout(params, auth)\n    raise RoutingException, :call_rejected\n  end\n  \n  #\n  # Iperbill 'string' AVP parsing helper\n  #\n  # This class helps parsing Iperbill RADIUS string attributes. It overrides the\n  # bracket operator to remove the attribute name part found in some values.\n  #\n  class IperbillRadiusFields\n    def initialize auth_params\n      @auth = auth_params\n    end\n  \n    def []( auth_item )\n      # Iperbill 'string' AVP format looks like h323-return-code=\"h323-return-code=8\"\n      if @auth[ auth_item ] && @auth[ auth_item ].include?(\"=\")\n        @auth[ auth_item ].split(\"=\")[1]\n      else\n        nil\n      end\n    end\n  end\n\n  #\n  # IPERBILL Return code definition\n  #\n  IPERBILL_RETURN_CODE = \n  {\n    '0' =>   { :desc => 'Authorization succeeded',    :action => :continue },\n    '1' =>   { :desc => 'Invalid account',            :action => :refuse },\n    '2' =>   { :desc => 'Invalid PIN',                :action => :refuse },\n    '3' =>   { :desc => 'Account in use',             :action => :refuse },\n    '4' =>   { :desc => 'Zero balance',               :action => :refuse },\n    '5' =>   { :desc => 'Card Expired',               :action => :refuse },\n    '6' =>   { :desc => 'Credit limit in post-paid',  :action => :refuse },\n    '7' =>   { :desc => 'User deny',                  :action => :refuse },\n    '8' =>   { :desc => 'Service not available',      :action => :refuse },\n    '9' =>   { :desc => 'Destination number blocked', :action => :refuse },\n    '10' =>  { :desc => 'Number of retries exceeded', :action => :refuse },\n    '11' =>  { :desc => 'Invalid RADIUS argument',    :action => :refuse },\n    '12' =>  { :desc => 'Insufficient balance',       :action => :refuse },\n    '13' =>  { :desc => 'Toll-free call',             :action => :continue },\n    '14' =>  { :desc => 'Invalid card number',        :action => :refuse },\n    '21' =>  { :desc => 'Invalid destination number', :action => :refuse },\n    '95' =>  { :desc => 'Database related errors 95', :action => :refuse },\n    '98' =>  { :desc => 'Database related errors 98', :action => :refuse },\n    '99' =>  { :desc => 'Database related errors 99', :action => :refuse },\n    '101' => { :desc => 'ANI related errors 101',     :action => :refuse },\n    '102' => { :desc => 'ANI related errors 102',     :action => :refuse },\n    '103' => { :desc => 'ANI related errors 103',     :action => :refuse },\n    '104' => { :desc => 'ANI related errors 104',     :action => :refuse },\n    '105' => { :desc => 'ANI related errors 105',     :action => :refuse },\n    '111' => { :desc => 'Account related errors 111', :action => :refuse },\n    '112' => { :desc => 'Account related errors 112', :action => :refuse },\n    '113' => { :desc => 'Account related errors 113', :action => :refuse },\n    '114' => { :desc => 'Account related errors 114', :action => :refuse },\n    '115' => { :desc => 'Account related errors 115', :action => :refuse }\n  }\n\n  #\n  # Iperbill return_code class\n  #\n  # This class wraps an Iperbill-specific return code that may be found in the\n  # IPERBILL_RETURN_CODE table. It provides a to_s method to print the return\n  # code description.\n  #\n  class IperbillReturnCode\n    def initialize( return_code )\n      @return_code = return_code\n    end\n    \n    # Returns definition of Iperbill return code\n    def to_s\n      if IPERBILL_RETURN_CODE.include? @return_code\n        IPERBILL_RETURN_CODE[ @return_code ][:desc]\n      else\n        \"Unk return_code #{@return_code}\"\n      end\n    end\n    \n    # Returns action associated with Iperbill return code\n    def action\n      if IPERBILL_RETURN_CODE.include? @return_code\n        IPERBILL_RETURN_CODE[ @return_code ][:action]\n      else\n        :refuse\n      end\n    end\n  end\nend\n\n"
        },
        "lawful_intercept.rb" : {
          "load_on_startup" : false,
          "name" : "lawful_intercept.rb",
          "script_type" : "filter",
          "source" : "#\n# Script version 2.4\n#\n# Version history:\n#  2.4  Now built to be inserted as a after_remap filter.\n#       This allows fixing problem intercepting calls that have numbers remapped using a regex\n#  2.3  Simplified code. Using \"log_trace\" (level 1) to print parsed CSV files\n#  2.2  Now supporting remapped calling/called number\n#  2.1  Now skipping SQL results with empty agency or filename\n#  2.0  First final version of this script, supporting Lawful Interception controlled by csv file\n#  1.0  Beta and incomplete version of this script.\n#\n# This after-matching filter is used to enable lawful intercept on some calls.\n# For more information, please refer to on-line documentation about using lawful intercept.\n#\n# Lawful Interception summary:\n#   When a call is intercepted, outgoing calls are made toward the Law Enforcement Agency, on the\n#   specified NAP, carrying a copy of the audio of one leg of the intercepted call.\n#   One outgoing interception call is made for each call leg of the intercepted call.\n#   Call information is also sent (as files, using FTP protocol) to the Law Enforcement Agency.\n#\n# What this script does:\n#   Each Law Enforcement Agency defined in the Web Portal configuration is assigned a csv\n#   (coma-separated values) file that contains a list of targets to intercept.\n#   This script is reading that csv file, upon configuration reload, and building a hash of\n#   numbers to intercept.\n#   Whenever a calling or called number matches the number to intercept within the start/end\n#   time defined by the csv file, this script instructs to perform lawful interception on the call,\n#   by filling-out required script output parameters:\n#     params[:bridge][:intercepted_targets]\n#   The value for this parameter must be a JSON-encoded string that contains:\n#     - Array of agencies. Each agency has a map of multiple parameters:\n#       - agency: The name of the law enforcement agency that needs to intercept this call.\n#       - liid:   Unique identifer for this interception subject for that agency (a text string)\n#       - number: The number that's been intercepted\n#   For example:\n#     [{agency:NYPD,liid:Joe Dalton,number:5556666},{agency:FBI,liid:Billy the kid,number:5551111}]\n#\n\n\nrequire 'rubygems'\nbegin\nrequire 'json'\nrescue LoadError\n  raise \"Sorry, a prerequisite is missing (json). Please execute 'gem install json' on your host\"\nend\nrequire 'date'\n   \nmodule LawfulIntercept\n\n  #\n  # Initialization of the scheduling remapper filter.  We need to read and parse all .csv files to be ready\n  # when incoming calls are received.\n  #\n  def init_enable_lawful_intercept params\n    @intercepted_agencies_hash = {}\n  \n    # Get all lawful interception target csv files that are used by all agencies of current configuration\n    statement = \n      \"SELECT tbgw_law_enforcement_targets.name file, tbgw_lawful_agencies.name agency FROM tbgw_law_enforcement_targets\n       JOIN tbgw_lawful_agencies ON tbgw_lawful_agencies.tbgw_law_enforcement_target_id = tbgw_law_enforcement_targets.id\n       JOIN tbgw_lawful_configurations ON tbgw_lawful_agencies.tbgw_lawful_configuration_id = tbgw_lawful_configurations.id\n       WHERE tbgw_lawful_configurations.tbgw_configuration_id = #{DbFile.tbgw_configuration_id}\"\n    \n    sql_result = sql_select_all statement\n    #raise \"sql_select_all failed (nil result) for #{statement}\" if sql_result.nil?\n    #raise \"sql_select_all failed for #{statement}\" if sql_result.empty?\n        \n    if sql_result\n      sql_result.each do |result|\n        agency, filename = result[\"agency\"], result[\"file\"]  \n        next if agency.empty? || filename.empty?\n        \n        # Read file from database\n        file = DbFile.get_law_enforcement_target_file filename\n        raise \"Failed to load Law Enforcement Target file #{filename} from File Db\" unless file\n        \n        # Run CSV parser on the file\n        file.csv_parse do |col_names, col_vals, row_idx|\n          next if row_idx == 0 # First row is column name definition\n          \n          # Load and validate the columns\n          \n          liid   = col_vals[\"liid\"]\n          raise \"Failed to find column 'liid' from Law Enforcement Target file from #{filename}\" unless liid\n          \n          number = col_vals[\"number\"]\n          raise \"Failed to find column 'number' from Law Enforcement Target file from #{filename}\" unless number\n\n          start_time_string = col_vals[\"start\"]\n          if start_time_string.nil? || start_time_string.empty?\n            start_time = DateTime.now\n          else\n            start_time = DateTime.strptime( start_time_string, \"%Y-%m-%dT%H:%M:%S%z\" )\n          end\n          \n          end_time_string   = col_vals[\"end\"]\n          if end_time_string.nil? || end_time_string.empty?\n            end_time = DateTime.new(2999)\n          else\n            end_time = DateTime.strptime( end_time_string, \"%Y-%m-%dT%H:%M:%S%z\" )\n          end\n                 \n          # Build hash of parameters\n          law_enforcement_target = { :agency => agency, :liid => liid, :start => start_time, :end => end_time }\n          \n          # Add to the global hash by number\n          @intercepted_agencies_hash[number] = [] unless @intercepted_agencies_hash[number]\n          @intercepted_agencies_hash[number] << law_enforcement_target\n       end\n     end\n    end\n    \n    trace_level = 1\n    log_trace trace_level, \"\\n\\n@intercepted_agencies_hash:\"\n    @intercepted_agencies_hash.each do |number,targets|\n      log_trace trace_level, \"  Number: #{number}\"\n      targets.each do |target|\n        log_trace trace_level, \"    Agency: #{target[:agency]}\"\n        log_trace trace_level, \"      liid: #{target[:liid]}\"\n        log_trace trace_level, \"      Start/end: #{target[:start].to_s} / #{target[:end].to_s}\"\n      end\n    end\n    log_trace trace_level, \"\\n\\n\\n\"\n  end\n\n  def add_to_interception_list( interception_list, number, intercepting_agencies )\n    return unless intercepting_agencies\n    \n    intercepting_agencies.each do | agency |\n      \n      # Add to interception list only if between date/time range\n      now = DateTime.now\n      if now >= agency[:start] && now <= agency[:end]\n        interception_list << { :agency => agency[:agency], :liid => agency[:liid], :number => number }\n      end      \n    end\n  end\n\n  #\n  # This function is the actual filter that can remap/replace custom column based on current time/date and\n  # the different pre-loaded schedules\n  #\n  def enable_lawful_intercept params\n    # Get references to appropriate parameters from the call\n    call              = params[ :call ]\n    fields            = [:called, :calling, :private_address]\n    interception_list = []\n    agencies_by_field = {}\n    tested_numbers    = {}\n    \n    # Test each field (called, calling, private) for list of agencies that\n    # need to intercept that number\n    fields.each do |field|\n      number = call[field]\n      \n      # Skip if already tested that number...\n      next if tested_numbers[number]\n      tested_numbers[number] = true\n      \n      # Search agencies that intercept this number\n      agencies = @intercepted_agencies_hash[ number ]\n      \n      # Remember for later, these agencies will also need to intercept the corresponding\n      # \"remapped\" number if appropriate (see below in current function)\n      agencies_by_field[field] = agencies\n      \n      # Add these agencies to the list we're building\n      add_to_interception_list( interception_list, number, agencies )\n    end\n    \n    if params[:out_calls]\n      params[:out_calls].each do |out_call|\n        fields.each do |field|\n          remapped_number = out_call[field]\n          \n          # Skip if already tested that number...\n          next if tested_numbers[remapped_number]\n          tested_numbers[remapped_number] = true\n\n          # Search agencies that intercept this number\n          agencies = @intercepted_agencies_hash[ remapped_number ]\n\n          # Add these agencies to the list we're building\n          add_to_interception_list( interception_list, remapped_number, agencies )\n          \n          # Also add the agencies that were intercepting the corresponding non-remapped number\n          # because they still want to intercept the call despite the remapping...\n          add_to_interception_list( interception_list, remapped_number, agencies_by_field[field] )\n        end\n      end\n\t\tend\n        \n    # Build json string that lists all found interception targets and their parameters.\n    # These are the parameters expected by Toolpack to proceed to the Lawful interception\n    # For example: [{\"agency\":\"FBI\",\"liid\":\"joeDalton\",\"number\":\"5550123\"},{\"agency\":\"Interpol\",\"liid\":\"James Bond\",\"number\":\"007\"}]\n    params[:bridge][:intercepted_targets] = interception_list.to_json unless interception_list.empty?\n\n    params\n\n  end\n\n\nprivate\n\n  \nend"
        },
        "least_cost_routing.rb" : {
          "load_on_startup" : false,
          "name" : "least_cost_routing.rb",
          "script_type" : "main",
          "source" : "#\n# Script version 1.1\n#\n# Version history:\n#  1.1  (start of version history tracking)\n#\n\nrequire 'base_routing'\n\n# This script routes calls according to the cost values (which may depend on the time) of the routes. This is useful\n# for cases when a route's popularity (cost) depends on the time of the day.\n#\n# Routing is in the following order:\n#\n# * Sorting: <tt>BaseRouting.route_order</tt>\n#   * <tt>:method => :order_by_cost</tt> - Sort the routes using LeastCostRouting.order_by_cost.\n# * Matching: <tt>BaseRouting.route_match</tt>\n#   * <tt>:call_field_name => :called</tt> - Match the called number of the call to a route.\n#   * <tt>:call_field_name => :calling</tt> - Match the calling number of the call to a route.\n#   * <tt>:call_field_name => :nap</tt> - Match the nap of the call to a route.\n#   * <tt>:method => :match_nap_availability</tt> - Match the call to a route using BaseRouting.match_nap_availability.\n# * Remapping: <tt>BaseRouting.route_remap</tt>\n#   * <tt>:call_field_name => :called, :route_field_name => :remapped_called</tt> - Remap the called\n#     number for the outgoing call.\n#   * <tt>:call_field_name => :calling, :route_field_name => :remapped_calling</tt> - Remap the calling\n#     number for the outgoing call.\n#   * <tt>:call_field_name => :nap, :route_field_name => :remapped_nap</tt> - Remap the nap for the outgoing call.\n#     This means setting the destination nap for the route.\n#\nclass LeastCostRouting < BaseRouting\n  route_match :call_field_name => :called\n  route_match :call_field_name => :calling\n  route_match :call_field_name => :nap\n#  route_match :method => :match_nap_availability\n  route_remap :call_field_name => :called, :route_field_name => :remapped_called\n  route_remap :call_field_name => :calling, :route_field_name => :remapped_calling\n  route_remap :call_field_name => :nap, :route_field_name => :remapped_nap\n\n  route_order :method => :order_by_cost\n\n  # A sorting method for route cost. It will order routes giving priority to routes\n  # who's cost is lower. Three dynamic route attributes are required:\n  #\n  # * <tt>cost_0_6</tt>: Cost of the route between 0-6 o'clock.\n  # * <tt>cost_7_9</tt>: Cost of the route between 7-9 o'clock.\n  # * <tt>cost</tt>: Cost of the route for all other time.\n  #\n  def order_by_cost( routes, nap_list )\n    sorted_routes = routes.sort do | route_a, route_b |\n      case Time.now.hour\n      when 0..6\n        route_a[:cost_0_6].to_i <=> route_b[:cost_0_6].to_i\n      when 7..9\n        route_a[:cost_7_9].to_i <=> route_b[:cost_7_9].to_i\n      else\n        route_a[:cost].to_i <=> route_b[:cost].to_i\n      end\n    end\n\n    sorted_routes\n  end\nend\n\n@@routing = LeastCostRouting.new\n\ndef init_routes( routes )\n  @@routing.init routes\nend\n\ndef route( call, nap_list )\n  @@routing.route call, nap_list\nend\n"
        },
        "limit_call_per_nap.rb" : {
          "load_on_startup" : false,
          "name" : "limit_call_per_nap.rb",
          "script_type" : "main",
          "source" : "#\n# Script version 1.1\n#\n# Version history:\n#  1.0  First verison of this script\n#  1.1\tCommented-out the use of 'match_nap_availability' to ease script testing (tracking #19296)\n#\n\nrequire 'base_routing'\nrequire 'nap_call_limiter'\n\n# This script is doing basic routing and applies NAP call limitation.  The operator can limit the number of\n# incoming calls, outgoing calls or both for a source and/or destination NAP.  If the NAP usage goes beyond\n# the configured thresholds, the route (or the incoming call) is dropped.\n#\n# This filter requires three NAP custom columns\n# * <tt>max_call</tt>: Maximum number of calls allowed on a NAP.  Field type is <integer> and default value should be left empty.\n# * <tt>max_incoming_call</tt>: Maximum number of incoming calls allowed on a source NAP.  Field type is <integer> and default value should be left empty.\n# * <tt>max_outgoing_call</tt>: Maximum number of outgoing calls allowed on a destination NAP.  Field type is <integer> and default value should be left empty.\n\n\nclass SimpleRouting < BaseRouting\n  include NapCallLimiter\n\n  route_match :call_field_name => :called\n  route_match :call_field_name => :calling\n  route_match :call_field_name => :nap\n  #route_match :method => :match_nap_availability\n  route_remap :call_field_name => :called, :route_field_name => :remapped_called\n  route_remap :call_field_name => :calling, :route_field_name => :remapped_calling\n  route_remap :call_field_name => :nap, :route_field_name => :remapped_nap\n\n  # This is where the filtering according to NAP usage is done\n  after_filter :method => :filter_nap_call_limit\nend\n\n\n@@routing = SimpleRouting.new\n\ndef init_routes( routes )\n  @@routing.init routes\nend\n\ndef route( call, nap_list )\n  @@routing.route call, nap_list\nend\n"
        },
        "nap_call_limiter.rb" : {
          "load_on_startup" : false,
          "name" : "nap_call_limiter.rb",
          "script_type" : "filter",
          "source" : "#\n# Script version 1.1\n#\n# Version history:\n#  1.2  Added check for NULL remapped NAP\n#  1.1  Added new :max_call_block_dir parameter to specify what direction gets blocked when max_call is reached: incoming, outgoing or both\n#  1.0  First version of this script\n#\n# This after-matching filter is used to remove routes from the matching list if the source or destination\n# NAP have reached a configured limit.  If all routes are removed through this filter, an error code\n# will be returned to the incoming call leg.\n#\n# This ordering method requires three NAP custom columns\n# * <tt>max_call</tt>: Maximum number of calls allowed on a NAP.  Field type is <integer> and default value should be left empty.\n# * <tt>max_incoming_call</tt>: Maximum number of incoming calls allowed on a source NAP.  Field type is <integer> and default value should be left empty.\n# * <tt>max_outgoing_call</tt>: Maximum number of outgoing calls allowed on a destination NAP.  Field type is <integer> and default value should be left empty.\n#\n\nmodule NapCallLimiter\n\n  MAX_CALL_BLOCK_DIR_VALUES = [ :incoming, :outgoing, :both ]\n  def init_filter_nap_call_limit params\n    @max_call_block_dir = params[:max_call_block_dir] || :both\n    raise \"Allowed values for :max_call_block_dir are #{MAX_CALL_BLOCK_DIR_VALUES.inspect}\" unless MAX_CALL_BLOCK_DIR_VALUES.include? @max_call_block_dir\n  end\n\n  def filter_nap_call_limit params\n\n    # In this filter, we only alter the list of routes, not the routes themselves.  Since the routes are really \"pointers\" to the\n    # global routes of the system, we are not allowed to modify them.   However, here, we are only affecting the array of\n    # routes to remove item from it.    So, we can use the original routes without cloning them.\n    routes = params[:routes]\n    nap_list = params[:naps]\n    reduced_routes = []\n\n    # Are there any routes to process?  If not, we exit right away since we don't want to return an error code\n    if (routes.length == 0)\n        return params\n    end\n\n    # This blocks incoming calls\n    # Have we reached the maximum number of incoming call on the source NAP?\n    nap_src = nap_list[params[:call][:nap].to_sym]\n\n    if (nap_src[:max_incoming_call] && !nap_src[:max_incoming_call].empty?)\n      if (nap_src[:inst_incoming_call_cnt].to_i >= nap_src[:max_incoming_call].to_i)\n        # We reached the limit.  Erase all routes\n        routes = []\n      end\n    end\n\n    # This blocks incoming calls\n    # Have we reached the maximum number of incoming OR outgoing call on the source NAP?\n    if (nap_src[:max_call] && !nap_src[:max_call].empty?)\n      if ((nap_src[:inst_incoming_call_cnt].to_i + nap_src[:inst_outgoing_call_cnt].to_i) >= nap_src[:max_call].to_i)\n        # We reached the limit.  Erase all routes\n        routes = []\n      end\n    end unless @max_call_block_dir == :outgoing\n\n    # For each route, verify that it does not exceed its quota of calls on the destination NAP\n    routes.each do | route |\n      # Assume the route is good for now\n      fKeepRoute = true\n\n      # This blocks outgoing calls\n      # Have we reached the maximum number of outgoing call on the destination NAP?\n      nap_dst = nap_list[route[:remapped_nap].intern]\n      if (nap_dst && nap_dst[:max_outgoing_call] && !nap_dst[:max_outgoing_call].empty?)\n        if (nap_dst[:inst_outgoing_call_cnt].to_i >= nap_dst[:max_outgoing_call].to_i)\n          # We reached the limit.  This route is no longer valid\n          fKeepRoute = false\n        end\n      end\n\n      # This blocks outgoing calls\n      # Have we reached the maximum number of incoming OR outgoing call on the destination NAP?\n      if (nap_dst[:max_call] && !nap_dst[:max_call].empty?)\n        if ((nap_dst[:inst_incoming_call_cnt].to_i + nap_dst[:inst_outgoing_call_cnt].to_i) >= nap_dst[:max_call].to_i)\n          # We reached the limit.  This route is no longer valid\n          fKeepRoute = false\n        end\n      end unless @max_call_block_dir == :incoming\n\n      # Keep this route if is satisfies all the call quotas\n      reduced_routes << route if (fKeepRoute  == true)\n    end\n\n    # Replace the system's list of routes by our filtered list of routes!\n    params[:routes] = reduced_routes\n\n    # Are there any routes left ?\n    if (reduced_routes.length == 0)\n      # No, then return an explicit error code\n      raise RoutingException, :call_rejected\n    end\n\n    params\n  end\n\nend\n"
        },
        "nap_group_weight_retry_with_overload.rb" : {
          "load_on_startup" : false,
          "name" : "nap_group_weight_retry_with_overload.rb",
          "script_type" : "main",
          "source" : "#\n# Script version 1.2\n#\n# Version history:\n#  1.1  (start of version history tracking)\n#  1.2\tCommented-out the use of 'match_nap_availability' to ease script testing (tracking #19296)\n#\nrequire 'base_routing'\nrequire 'nap_group_weight_load_balancer'\n\n# This script is an example for a location-based traffic distribution.  It is used to group NAPs together with a\n# NAP custom column (called 'group') and a relative weight of each NAP within a single group.  The weight is taken\n# out of the NAP custom column called 'weight'.  A typical application would be in situations where calls need to be\n# routed toward multiple physical sites (one main and other are backups) but each site having different destinations\n# that need to have the traffic load-distributed according to pre-defined percentages (e.g. 20%, 35% 45%).  In case\n# a destination at one site goes down, the traffic should be load-distributed to remaining destination appropriately.\n# If an entire site goes down, the next site should be used as destination (with probably other load distribution\n# values).   However, since Toolpack also supports \"route retry\" in case of call failure on a NAP, this script\n# will use the different NAP to try reaching the predefined percentage but still using other matching NAPs\n# as backup solutions.\n#\n# This routing scenario is basically a \"percentage routing\" script where the percentage varies according to the\n# availability of the different destinations.   This scenario also includes the notion of \"backup\" NAPs.\n#\n# Scenario example:\n#    NAP_DEST_A = group 0 (highest), weight 50\n#    NAP_DEST_B = group 0 (highest), weight 100\n#    NAP_DEST_C = group 1, weight 100\n#    NAP_DEST_D = group 1, weight 100\n#    NAP_SOURCE = whatever\n#    Assuming that one static route exists from NAP_SOURCE to each of the destination NAPs\n#\n#    Since NAP_DEST_A and NAP_DEST_B are part of the same group 0, which has an higher priority than other\n#    NAP groups, the matching routes from group 0 will be tried first.  Note that all other matching criterias\n#    still apply (e.g. NAP source, called, calling, etc).   The weight field is used to calculate the percentage\n#    of active calls each NAPs should have within a group.  With the current values, NAP_DEST_A\n#    will have 33% (50/(50+100)) of the calls while NAP_DEST_B will have 66% (100/(50+100)) of the calls.\n#    If either NAP is down, the remaining NAP will get 100% of the calls.  If both group 0 NAPs are down (or if\n#    a unsuccessful calls is made on both), the routing will be done toward NAPs from group 1 (NAP_DEST_C\n#    and NAP_DEST_D).  In the later case, each NAPs will have the same % of calls (50%) since they have the\n#    same weigth.\n#\n# As a new addition to this script, it also supports that route can overload the \"group\" and \"weight\" parameters.\n# This allows a set of route to dynamically change the NAP group or weight on a per-call basis.  The route parameter\n# always have precedence over the remapped NAP parameters.  The user only needs to create two custom route columns\n# with the same names and characteristics as in the NAP.  However, the default value of these columns MUST be left\n# empty.\n#\nclass NapGroupAndWeightRouting < BaseRouting\n  include NapGroupAndWeightLoadBalancer\n\n  route_match :call_field_name => :called\n  route_match :call_field_name => :calling\n  route_match :call_field_name => :nap\n  #route_match :method => :match_nap_availability\n  route_remap :call_field_name => :called, :route_field_name => :remapped_called\n  route_remap :call_field_name => :calling, :route_field_name => :remapped_calling\n  route_remap :call_field_name => :nap, :route_field_name => :remapped_nap\n\n  # This is where the routes are being sorted according to destination NAP groups and weight\n  after_filter :method => :filter_by_group_and_weight\n\nend\n\n\n@@routing = NapGroupAndWeightRouting.new\n\ndef init_routes( routes )\n  @@routing.init routes\nend\n\ndef route( call, nap_list )\n  @@routing.route call, nap_list\nend\n"
        },
        "nap_priority_routing.rb" : {
          "load_on_startup" : false,
          "name" : "nap_priority_routing.rb",
          "script_type" : "main",
          "source" : "#\n# Script version 1.2\n#\n# Version history:\n#  1.2  (start of version history tracking)\n#\nrequire 'base_routing'\n\n# This script routes calls according to a priority of outgoing NAPs\n#\n# Routing is in the following order:\n#\n# * Sorting: <tt>BaseRouting.route_order</tt>\n#   * <tt>:method => :order_by_prio</tt> - Sort the routes using PriorityRouting.order_by_prio.\n# * Matching: <tt>BaseRouting.route_match</tt>\n#   * <tt>:call_field_name => :called</tt> - Match the called number of the call to a route.\n#   * <tt>:call_field_name => :calling</tt> - Match the calling number of the call to a route.\n#   * <tt>:call_field_name => :nap</tt> - Match the nap of the call to a route.\n#   * <tt>:method => :match_nap_availability</tt> - Match the call to a route using BaseRouting.match_nap_availability.\n# * Remapping: <tt>BaseRouting.route_remap</tt>\n#   * <tt>:call_field_name => :called, :route_field_name => :remapped_called</tt> - Remap the called\n#     number for the outgoing call.\n#   * <tt>:call_field_name => :calling, :route_field_name => :remapped_calling</tt> - Remap the calling\n#     number for the outgoing call.\n#   * <tt>:call_field_name => :nap, :route_field_name => :remapped_nap</tt> - Remap the nap for the outgoing call.\n#     This means setting the destination nap for the route.\n#\nclass NapPriorityRouting < BaseRouting\n  route_match :call_field_name => :called\n  route_match :call_field_name => :calling\n  route_match :call_field_name => :nap\n#  route_match :method => :match_nap_availability\n  route_remap :call_field_name => :called, :route_field_name => :remapped_called\n  route_remap :call_field_name => :calling, :route_field_name => :remapped_calling\n  route_remap :call_field_name => :nap, :route_field_name => :remapped_nap\n\n  route_order :method => :order_by_prio\n\n  # A sorting method for NAP priority. It will order routes according to the highest\n  # priority (i.e. lowest number) of the remapped NAP :\n  #\n  # * <tt>prio</tt>: Priority of the NAP\n  #\n  def order_by_prio( routes, nap_list )\n    sorted_routes = routes.sort do | route_a, route_b |\n       nap_list[route_a[:remapped_nap].to_sym][:prio].to_i <=> nap_list[route_b[:remapped_nap].to_sym][:prio].to_i\n    end\n    sorted_routes\n  end\nend\n\n@@routing = NapPriorityRouting.new\n\ndef init_routes( routes )\n  @@routing.init routes\nend\n\ndef route( call, nap_list )\n  @@routing.route call, nap_list\nend\n\n"
        },
        "noa_npi_remap.rb" : {
          "load_on_startup" : false,
          "name" : "noa_npi_remap.rb",
          "script_type" : "main",
          "source" : "#\n# Script version 1.2\n#\n# Version history:\n#  1.2  (start of version history tracking)\n#\nrequire 'base_routing'\n\n# This script demonstrates how to peform a remapping of the Called NOA and NPI based on the Called number prefix (001 for international).\n# It also routes calls in the most simple fashion. The routing behavior is the same as routing with 'SimpleRouting'.\n#\n# Routing is in the following order:\n#\n# * Matching: <tt>BaseRouting.route_match</tt>\n#   * <tt>:call_field_name => :called</tt> - Match the called number of the call to a route.\n#   * <tt>:call_field_name => :calling</tt> - Match the calling number of the call to a route.\n#   * <tt>:call_field_name => :nap</tt> - Match the nap of the call to a route.\n#   * <tt>:method => :match_nap_availability</tt> - Match the call to a route using BaseRouting.match_nap_availability.\n# * Remapping: <tt>BaseRouting.route_remap</tt>\n#   * <tt>:call_field_name => :called, :route_field_name => :remapped_called</tt> - Remap the called\n#     number for the outgoing call with the choosen route field named 'remapped_called'.\n#   * <tt>:call_field_name => :calling, :route_field_name => :remapped_calling</tt> - Remap the calling\n#     number for the outgoing call with the choosen route field named 'remapped_calling'.\n#   * <tt>:call_field_name => :nap, :route_field_name => :remapped_nap</tt> - Remap the nap for the outgoing call.\n#     This means setting the destination nap for the route.\n#   * <tt>:method => :remap_called_noa_npi</tt> - NOA and NPI remapping based on the Called number prefix.\n#\n#\n\n\nclass NoaNpiRemap < BaseRouting\n  route_match :call_field_name => :called\n  route_match :call_field_name => :calling\n  route_match :call_field_name => :nap\n#  route_match :method => :match_nap_availability\n  route_remap :method => :remap_called_noa_npi\n  route_remap :call_field_name => :called, :route_field_name => :remapped_called\n  route_remap :call_field_name => :calling, :route_field_name => :remapped_calling\n  route_remap :call_field_name => :nap, :route_field_name => :remapped_nap\n\n  # The route_remap method allow easy cutomization of outgoing call parameters remapping.\n  #\n  # This function takes the following arguments as parameters:\n  # * <tt>route </tt> - Hash of route parameters. Chosen route during the 'matching' phase of the BaseRouting::route function.\n  # * <tt>caf_call </tt> - Caf call object.\n  # * <tt>nap_list </tt> - Hash of NAP statuses and custom fields (Create New NAP Column).\n  # * <tt>call_params </tt> - Hash of call parameters. This hash contains all the call parameters. The remapped call parameter must be\n  # set in this hash. This hash also contains all previously remapped call parameter by other 'route_remap' functions.\n  # In this example, the 'remap_called_noa_npi' method is called before the called, calling and nap remapping.\n  #\n  # This function must return the remapped call parameters in the call_param hash.\n  #\n  # This method remap the Called number NOA and NPI based on a prefix rules regex.\n  # * If the called number match '001', the NOA is remapped to 'international_number'.\n  # * Otherwise, the NOA is remapped to 'national_number'\n  # * The NPI is always remapped to 'isdn'.\n  #\n  # NOA values for the <tt>called_noa</tt> or <tt>calling_noa</tt> remapped call parameter are:\n  # * <tt>unknown_number</tt>\n  # * <tt>international_number</tt>\n  # * <tt>national_number</tt>\n  # * <tt>subscriber_number</tt>\n  # * <tt>network_specific</tt>\n  # * <tt>network_routing_national_format</tt>\n  # * <tt>network_routing_international_format</tt>\n  # * <tt>abbreviated_number</tt>\n  # * <tt>subscriber_number_operator_requested</tt>\n  # * <tt>national_number_operator_requested</tt>\n  # * <tt>international_number_operator_requested</tt>\n  # * <tt>no_number_present_operator_requested</tt>\n  # * <tt>no_number_present_cut_through_call_to_carrier</tt>\n  # * <tt>test_line_test_code</tt>\n  # * <tt>non_unique_subscriber_number</tt>\n  # * <tt>non_unique_national_number</tt>\n  # * <tt>non_unique_international_number</tt>\n  # * <tt>call_950_number</tt>\n  #\n  # Those values will be remapped to the protocol specific NOA value. To provide protocol specific value:\n  # * <tt>remapped_fields[:called_noa] = 0x70</tt>\n  # or\n  # * <tt>remapped_fields[:called_noa] = 112</tt>\n  #\n  # NPI values for the <tt>called_npi</tt> or <tt>calling_npi</tt> remmapped call parameter are:\n  # * <tt>unknown_number</tt>\n  # * <tt>isdn</tt>\n  # * <tt>telephony</tt>\n  # * <tt>private</tt>\n  # * <tt>data</tt>\n  # * <tt>telex</tt>\n  # * <tt>national</tt>\n  #\n  # Calling Display Information Type possible remapped values (<tt>calling_display_type</tt>):\n  # * <tt>unspecified</tt> => Type is unspecified.\n  # * <tt>calling_party_name</tt> => Type is 0xB1.\n  #\n  # Those values will be remapped to the protocol specific  Display Information Type value. To provide protocol specific value:\n  # * <tt>remapped_fields[:calling_display_type] = 0xB1</tt>\n  # or\n  # * <tt>remapped_fields[:calling_display_type] = 177</tt>\n  #\n  # Calling Display Information can be set to any string value (<tt>calling_display</tt>):\n  # * <tt>remapped_fields[:calling_display] = \"Roger Fluffy\"</tt>\n  #\n  # Calling Presentation possible remapped values (<tt>calling_presentation</tt>):\n  # * <tt>unspecified</tt>\n  # * <tt>not_available</tt>\n  # * <tt>allowed</tt>\n  # * <tt>restricted</tt>\n  # * <tt>addr_restricted</tt>\n  # * <tt>name_restricted</tt>\n  #\n  # Calling Number Screening possible remapped values (<tt>calling_screening</tt>):\n  # * <tt>unspecified</tt>\n  # * <tt>no</tt>\n  # * <tt>pass</tt>\n  # * <tt>fail</tt>\n  # * <tt>network_provided</tt>\n  #\n  def remap_called_noa_npi( route, caf_call, nap_list, call_params )\n\n    # If the called number have previously been remapped before the call to this function, use it.\n    # Otherwise, use the call called number.\n    called = call_params[:called]\n\n    if @international_regex.match(called)\n      call_params[:called_noa] = :international_number\n    else\n      call_params[:called_noa] = :national_number\n    end\n\n    call_params[:called_npi] = :isdn\n\n    call_params\n  end\n\n  # Class initialization. Create a class instance variable <tt>regex</tt> hash used by remap_called_noa_npi.\n  # * <tt>international_regex</tt> - Math an international called number prefixed with '001'\n  #\n  def initialize\n    @international_regex = Regexp.new(/^001.*/)\n  end\n\nend\n\n@@routing = NoaNpiRemap.new\n\ndef init_routes( routes )\n  @@routing.init routes\nend\n\ndef route( call, nap_list )\n  @@routing.route call, nap_list\nend\n"
        },
        "numbers_prompts.rb" : {
          "load_on_startup" : false,
          "name" : "numbers_prompts.rb",
          "script_type" : "util",
          "source" : "#\n# Script version 1.0\n#\n# Version history:\n#  1.0  First version of this script\n#\n#\n######################\n# Overview:\n######################\n#\n# Utility module to generate a list of prompts for numeric values like \n# numbers, dates, times, or phone numbers.\n#\n \nmodule NumbersToPrompts\n \n  # Convert a number into sequence of prompts that pronounce the number for current language\n  def self.number_to_prompts( number, lang = :en, type = nil )\n    self.send(\"number_to_prompts_#{lang}\", number, type)\n  end\n \n  \n  # Convert a number into sequence of prompts that pronounce the number in english\n  # Parameters:\n  #   number:   Integer. The number to convert to prompts\n  #   type:     Symbol that indicates the type of number:\n  #               nil     Standard number\n  #               :minute The minute of the hour (5 -> \"o-five\", 0 -> \"oClock\")\n  #               :nth    The \"nth\" format (1 -> \"first\", 5 -> \"fifth\", 34 -> \"thirdy-fifth\")\n  def self.number_to_prompts_en( number, type = nil )\n    prompts = []\n \n    if( type == :minute )\n      if( number == 0 )\n        # Minute 0 is pronounced \"oclock\"\n        prompts <<  \"oclock\"\n        return prompts\n      elsif( number <= 9 )\n        # Minute between 1 and 9, we say \"o\" before the number\n        prompts <<  \"o\"\n      end\n    end\n \n    # Append the prompts to play the billions, millions, thousands, hundreds\n    groups = [ 1000000000, 1000000, 1000, 100 ]\n    groups.each do |group|\n      if( number >= group )\n        tmp_prompts, number = number_to_prompts_en_partial( number, type, group )\n        prompts = prompts.concat( tmp_prompts )\n        return prompts if number == 0\n      end\n    end\n \n    if( number >= 20 )\n      # Numbers above 20 are \"broken\" in tens and units\n \n      # Append the prompt that pronounces the tens\n      th = \"\"\n      th = \"th\" if( (type == :nth) && ((number % 10) == 0 ) )\n      prompts << ( (number / 10 * 10).to_s + th )\n \n      # Keep the units and continue (if a non-zero unit to pronounce)\n      number = number % 10\n      return prompts if( number == 0)\n    end\n \n    # There is one separate audio file for each number below 20\n    number = number.to_s\n    number += \"th\" if type == :nth && number != 0\n    \n    #Use a separate prompt for phone numbers. \n    number += \"-slow\" if type == :phone\n    \n    prompts << number\n \n    return prompts\n  end\n  \n  # Convert a phone number into a sequence of prompts that pronounce the phone number\n  # Supports characters 0-9, a-f (A-F), * and #\n  def self.phone_number_to_prompts( phone_number, lang = :en )\n    final_prompts = []\n    phone_number.split(\"\").each do | char |\n      prompts = []\n      case char\n        when '0'..'9'\n          prompts = number_to_prompts( char.to_i, lang, :phone )\n        when 'a'..'f'\n          prompts << char.to_s\n        when 'A'..'F'\n          prompts << char.downcase.to_s\n        when '*'\n          prompts << \"star\"\n        when '#'\n          prompts << \"pound\"\n      end\n \n      final_prompts.concat( prompts )\n    end\n \n    return final_prompts\n  end\n \n  # Convert a date into a sequence of prompts that pronounce the month/day\n  # Supports only month (1 to 12) and day (1 to 31)\n  def self.date_to_prompts( month, day, lang = :en )\n    prompts = []\n \n    if( month >= 1 && month <= 12 )\n      prompts << sprintf( \"month_%02u\", month )\n      if( day >= 1 && day <= 31 )\n        prompts.concat( number_to_prompts( day, lang, :nth ) )\n      end\n    end\n \n    return prompts\n  end\n \n  # Convert a time into a sequence of prompts that pronounce the current time (hour and minute)\n  def self.time_to_prompts( hour, minute, lang = :en )\n    prompts = []\n \n    if( hour >= 0 && hour <= 23 )\n      minute = 0 if( minute < 0 || minute > 59 )\n      prompts.concat( number_to_prompts( hour, lang ) )\n      prompts.concat( number_to_prompts( minute, lang, :minute ) )\n    end\n \n    return prompts\n  end\n  \n  private\n  \n  # Partial playback of a number (thousands or hundreds only)\n  def self.number_to_prompts_en_partial( number, type, unit )\n    # Append the prompt tht pronounces the number of (billions,millions,thousands,tens)\n    prompts = number_to_prompts_en( number/unit, nil )\n    \n    # Append the prompt that pronounces the (billions,millions,thousands,tens)\n    th = \"\"\n    th = \"th\" if( (type == :nth) && ((number % unit) == 0 ) )\n    prompts << ( unit.to_s + th )\n\n    # returns the prompts and the remainder\n    return prompts, number % unit\n  end\n\n  \nend\n \n"
        },
        "percentage_routing.rb" : {
          "load_on_startup" : false,
          "name" : "percentage_routing.rb",
          "script_type" : "main",
          "source" : "#\n# Script version 1.3\n#\n# Version history:\n#  1.3  (start of version history tracking)\n#\nrequire 'base_routing'\n\n# This script routes calls according to a percentage for each nap. This could be useful to envenly route call to\n# different providers.\n# The calculation of percentage for each nap is done by using the cumulative number of outgoing calls made to\n# each nap.\n# A variation of this script could be to use the current number of calls on each nap, in order to load-balance instananeous call count rather than cumulative count.\n# Another variation of this script could be to take into account incoming and outgoing call count on each NAP, rather than just outgoing calls.\n# These modifications could easily be made by modifying function order_by_percent_routing to base calculation on nap variables other than :total_outgoing_call_cnt.\n#\n# Routing is in the following order:\n#\n# * Sorting: <tt>BaseRouting.route_order</tt>\n#   * <tt>:method => :order_by_percent_routing</tt> - Sort the routes using PercentageRouting.order_by_percent_routing.\n# * Matching: <tt>BaseRouting.route_match</tt>\n#   * <tt>:call_field_name => :called</tt> - Match the called number of the call to a route.\n#   * <tt>:call_field_name => :calling</tt> - Match the calling number of the call to a route.\n#   * <tt>:call_field_name => :nap</tt> - Match the nap of the call to a route.\n#   * <tt>:method => :match_nap_availability</tt> - Match the call to a route using BaseRouting.match_nap_availability.\n# * Remapping: <tt>BaseRouting.route_remap</tt>\n#   * <tt>:call_field_name => :called, :route_field_name => :remapped_called</tt> - Remap the called\n#     number for the outgoing call.\n#   * <tt>:call_field_name => :calling, :route_field_name => :remapped_calling</tt> - Remap the calling\n#     number for the outgoing call.\n#   * <tt>:call_field_name => :nap, :route_field_name => :remapped_nap</tt> - Remap the nap for the outgoing call.\n#     This means setting the destination nap for the route.\n#\nclass PercentageRouting < BaseRouting\n  route_match :call_field_name => :called\n  route_match :call_field_name => :calling\n  route_match :call_field_name => :nap\n#  route_match :method => :match_nap_availability\n  route_remap :call_field_name => :called, :route_field_name => :remapped_called\n  route_remap :call_field_name => :calling, :route_field_name => :remapped_calling\n  route_remap :call_field_name => :nap, :route_field_name => :remapped_nap\n\n  route_order :method => :order_by_percent_routing\n\n  # A sorting method for nap usage. It will order routes giving priority to routes\n  # who's destination nap usage is farther from the desired usage. One dynamic nap attribute is required:\n  #\n  # * <tt>percent_target</tt>: Target usage for the nap (from 0-100).\n  #\n  def order_by_percent_routing( routes, nap_list )\n    global_call_cnt = 0\n\n    nap_list.each do |nap_name, nap|\n      if nap[:percent_target].to_i > 0 &&  nap[:percent_target].to_i <= 100\n        total_out_call = nap[:total_outgoing_call_cnt]\n\n        if total_out_call\n          global_call_cnt += nap[:total_outgoing_call_cnt].to_i\n        else\n          puts 'order_by_percent_routing: Nap status did not provide \\'total_outgoing_call_cnt\\'.'\n        end\n      end\n    end\n\n    routes.sort do |route_a, route_b|\n      nap_a = nap_list[route_a[:remapped_nap].intern]\n      if nap_a.nil?\n        puts \"order_by_percent_routing: Missing Nap status (#{route_a[:remapped_nap]})\"\n      end\n      nap_b = nap_list[route_b[:remapped_nap].intern]\n      if nap_b.nil?\n        puts \"order_by_percent_routing: Missing Nap status (#{route_b[:remapped_nap]})\"\n      end\n      percent_a_diff = 0\n      percent_b_diff = 0\n\n      if nap_a && nap_b\n        nap_a_percent_current = 0\n        nap_b_percent_current = 0\n        if global_call_cnt > 0\n          if nap_a[:percent_target].to_i > 0 &&  nap_a[:percent_target].to_i <= 100\n            nap_a_percent_current = (nap_a[:total_outgoing_call_cnt].to_i*100)/global_call_cnt\n          end\n          if nap_b[:percent_target].to_i > 0 &&  nap_b[:percent_target].to_i <= 100\n            nap_b_percent_current = (nap_b[:total_outgoing_call_cnt].to_i*100)/global_call_cnt\n          end\n        end\n\n        nap_a_percent_target  = nap_a[:percent_target].to_i\n        nap_b_percent_target  = nap_b[:percent_target].to_i\n\n        percent_a_diff = nap_a_percent_target - nap_a_percent_current\n        percent_b_diff = nap_b_percent_target - nap_b_percent_current\n\n        percent_a_diff = 0 if percent_a_diff < 0\n        percent_b_diff = 0 if percent_b_diff < 0\n      end\n\n      percent_b_diff <=> percent_a_diff\n    end\n  end\nend\n\n\n@@routing = PercentageRouting.new\n\ndef init_routes( routes )\n  @@routing.init routes\nend\n\ndef route( call, nap_list )\n  @@routing.route call, nap_list\nend\n"
        },
        "post_digit_remapper.rb" : {
          "load_on_startup" : false,
          "name" : "post_digit_remapper.rb",
          "script_type" : "filter",
          "source" : "# Script version 1.2\n#\n# Version history:\n#  1.3  Added check for NULL remapped NAP\n#  1.2  (start of version history tracking)\n#\n# Filter module that post-remap the called and/or calling number based on a custom column found in the destination NAP of the route.   If a route\n# already has remapped called or calling, then those have precedence over the NAP's.\n#\n# This filter method requires two NAP custom columns\n# * <tt>post_digit_called_remap</tt>: String value that can contain either a fixed string or a regular expression for remapping the called number.  The string \"NULL\" \n#                                     leave the called number untouched.\n# * <tt>post_digit_calling_remap</tt>: String value that can contain either a fixed string or a regular expression for remapping the called number.  The string \"NULL\" \n#                                     leave the calling number untouched.\nmodule PostDigitRemap\n\n  # After-filter to apply ANI/DNIS digit remapping on all chosen routes\n  # After-filter Initialization method to precompile all regular expression upon configuration reload\n  def init_filter_post_digit_remap params\n     called_match_field = {}\n     calling_match_field = {}\n     called_remap_field = {}\n     calling_remap_field = {}\n  \n     # Findout the hash that will contain the precompiled regex for called and calling\n     @match_fields.each { |match_field|\n       if (match_field[:route_field_name] == :called)\n         called_match_field = match_field\n         match_field[:route_field_regex] ||= {}\n       end\n       if (match_field[:route_field_name] == :calling)\n         calling_match_field = match_field\n         match_field[:route_field_regex] ||= {}\n       end\n     }\n     @remap_fields.each { |remap_field|\n       if (remap_field[:route_field_name] == :remapped_called)\n         called_remap_field = remap_field\n         remap_field[:route_field_regex] ||= {}\n       end\n       if (remap_field[:route_field_name] == :remapped_calling)\n         calling_remap_field = remap_field\n         remap_field[:route_field_regex] ||= {}\n       end\n     }\n    \n     # Cycle all the NAPs to pre-compile the regex\n     naps = params[:naps]\n     naps.each {|key,nap|\n     \n        # First, lets check the post_digit_called_remap\n        if (nap[:post_digit_called_remap] && called_match_field)\n        \n           remap_value = nap[:post_digit_called_remap].to_s\n           \n           # Is it a regex ?\n           if ((remap_value) && (remap_value =~ /^\\s*\\/(.*)\\/\\s*$/))\n           \n              # Yes... then compile the matching portion.  Store it in the base_routing.rb array of\n              # precompiled regular expressions.\n              called_match_field[:route_field_regex][remap_value] = { :regex => Regexp.new($1) }\n              \n              # Then compile the replacing portion.  Store it in the base_routing.rb array of \n              # precompiled regular expressions.\n              remap_value_split = remap_value.split('/')\n              if remap_value_split.length >= 3\n                called_remap_field[:route_field_regex][remap_value] = { :regex => Regexp.new(remap_value_split[1]), :replace => remap_value_split[2] }\n              end              \n           end\n        end \n      \n        # Then, lets check the post_digit_calling_remap\n        if (nap[:post_digit_calling_remap] && calling_match_field)\n        \n           remap_value = nap[:post_digit_calling_remap].to_s\n           \n           # Is it a regex ?\n           if ((remap_value) && (remap_value =~ /^\\s*\\/(.*)\\/\\s*$/))\n           \n              # Yes... then compile the matching portion.  Store it in the base_routing.rb array of\n              # precompiled regular expressions.\n              calling_match_field[:route_field_regex][remap_value] = { :regex => Regexp.new($1) }\n              \n              # Then compile the replacing portion.  Store it in the base_routing.rb array of \n              # precompiled regular expressions.\n              remap_value_split = remap_value.split('/')\n              if remap_value_split.length >= 3\n                calling_remap_field[:route_field_regex][remap_value] = { :regex => Regexp.new(remap_value_split[1]), :replace => remap_value_split[2] }\n              end              \n           end\n        end       \n     }  \n  \n  end\n  \n  # Apply ANI/DNIS digit remapping on all chosen routes\n  # This filter method requires two NAP custom columns\n  # * <tt>post_digit_called_remap</tt>: String value that can contain either a fixed string or a regular expression for remapping the called number.  The string \"NULL\" \n  #                                     leave the called number untouched.\n  # * <tt>post_digit_calling_remap</tt>: String value that can contain either a fixed string or a regular expression for remapping the called number.  The string \"NULL\" \n  #                                     leave the calling number untouched.\n  #\n  def filter_post_digit_remap params\n  \n     # Retrieve the incoming nap\n     naps = params[:naps]\n     routes = clone_routes params[:routes]\n     new_routes = []\n     \n     # Scan each selected routes and see if there is a post_NAP remapping\n     routes.each { |route|\n       # Format a copy of the current route\n       new_route = {}\n       new_route.replace route\n       \n       # Check if the destination route has replacement strings\n       # If the route has a remapping, it has precedence over the NAP remapping\n       destination_nap = naps[route[:remapped_nap].to_sym]\n       if destination_nap\n         if (new_route[:remapped_called].nil?)\n            if (destination_nap[:post_digit_called_remap] != \"NULL\")\n              new_route[:remapped_called] = destination_nap[:post_digit_called_remap].to_s\n            end\n         end\n         if (new_route[:remapped_calling].nil?)\n            if (destination_nap[:post_digit_calling_remap] != \"NULL\")\n              new_route[:remapped_calling] = destination_nap[:post_digit_calling_remap].to_s\n            end\n          end\n       end\n              \n       # Store the new route\n       new_routes << new_route\n     }\n     \n     # Replace all previous routes with the modified ones\n     routes.replace new_routes\n     params[:routes] = routes  \n     params\n  end  \n  \nend\n"
        },
        "post_digit_remapping.rb" : {
          "load_on_startup" : false,
          "name" : "post_digit_remapping.rb",
          "script_type" : "main",
          "source" : "# Script version 1.3\n#\n# Version history:\n#  1.2  (start of version history tracking)\n#  1.3\tCommented-out the use of 'match_nap_availability' to ease script testing (tracking #19296)\n#\n# Example script that makes use of a pre-defined fileter module that post-remap the called and/or calling number based on a custom column found \n# in the destination NAP of the route.   If a route already has remapped called or calling, then those have precedence over the NAP's.  \n# This script is an example for post-digit remapping.  After all routing is done, the operator might want to apply some digit\n# remapping to the called or calling number (e.g. add a '1' to the called number if it does not already have one) depending on\n# which outgong NAP the call might be routed to.  The digit remapping is typically a regular expression.\n#\n# This example requires two NAP custom columns\n# * <tt>post_digit_called_remap</tt>: String value that can contain either a fixed string or a regular expression for remapping the called number.  The string \"NULL\" \n#                                     leave the called number untouched.\n# * <tt>post_digit_calling_remap</tt>: String value that can contain either a fixed string or a regular expression for remapping the called number.  The string \"NULL\" \n#                                     leave the calling number untouched.\nrequire 'base_routing'\nrequire 'post_digit_remapped'\n\nclass TestRouting < BaseRouting\n  include PostDigitRemap\n  \n  route_match :call_field_name => :called\n  route_match :call_field_name => :calling\n  route_match :call_field_name => :nap\n  #route_match :method => :match_nap_availability\n  route_remap :call_field_name => :called, :route_field_name => :remapped_called\n  route_remap :call_field_name => :calling, :route_field_name => :remapped_calling\n  route_remap :call_field_name => :nap, :route_field_name => :remapped_nap\n  after_filter :method => :filter_post_digit_remap\n  \nend\n\n@@routing = TestRouting.new\n\ndef init_routes( routes )\n  @@routing.init routes\nend\n\ndef route( call, nap_list )\n  @@routing.route call, nap_list\nend"
        },
        "prefix_private_noa.rb" : {
          "load_on_startup" : false,
          "name" : "prefix_private_noa.rb",
          "script_type" : "main",
          "source" : "#\n# Script version 1.3\n#\n# Version history:\n#  1.2  (start of version history tracking)\n#  1.3  (NOA according to the prefix of the calling/called number, manipulations on both calling/called number. For calling number, if privacy exist, use that as the calling number to do the NOA and manipulation ) \n#\nrequire 'base_routing'\n\n# This script demonstrates how to peform a remapping of the Called NOA and NPI based on the Called number prefix (001 for international).\n# It also routes calls in the most simple fashion. The routing behavior is the same as routing with 'SimpleRouting'.\n#\n# Routing is in the following order:\n#\n# * Matching: <tt>BaseRouting.route_match</tt>\n#   * <tt>:call_field_name => :called</tt> - Match the called number of the call to a route.\n#   * <tt>:call_field_name => :calling</tt> - Match the calling number of the call to a route.\n#   * <tt>:call_field_name => :nap</tt> - Match the nap of the call to a route.\n#   * <tt>:method => :match_nap_availability</tt> - Match the call to a route using BaseRouting.match_nap_availability.\n# * Remapping: <tt>BaseRouting.route_remap</tt>\n#   * <tt>:call_field_name => :called, :route_field_name => :remapped_called</tt> - Remap the called\n#     number for the outgoing call with the choosen route field named 'remapped_called'.\n#   * <tt>:call_field_name => :calling, :route_field_name => :remapped_calling</tt> - Remap the calling\n#     number for the outgoing call with the choosen route field named 'remapped_calling'.\n#   * <tt>:call_field_name => :nap, :route_field_name => :remapped_nap</tt> - Remap the nap for the outgoing call.\n#     This means setting the destination nap for the route.\n#   * <tt>:method => :remap_called_noa_npi</tt> - NOA and NPI remapping based on the Called number prefix.\n#\n#\n\n\nclass NoaNpiRemap < BaseRouting\n  route_match :call_field_name => :called\n  route_match :call_field_name => :calling\n  route_match :call_field_name => :nap\n  #  route_match :method => :match_nap_availability\n  route_remap :method => :prefix_private_noa\n  route_remap :call_field_name => :called, :route_field_name => :remapped_called\n  route_remap :call_field_name => :calling, :route_field_name => :remapped_calling\n  route_remap :call_field_name => :nap, :route_field_name => :remapped_nap\n\n  # The route_remap method allow easy cutomization of outgoing call parameters remapping.\n  #\n  # This function takes the following arguments as parameters:\n  # * <tt>route </tt> - Hash of route parameters. Chosen route during the 'matching' phase of the BaseRouting::route function.\n  # * <tt>caf_call </tt> - Caf call object.\n  # * <tt>nap_list </tt> - Hash of NAP statuses and custom fields (Create New NAP Column).\n  # * <tt>call_params </tt> - Hash of call parameters. This hash contains all the call parameters. The remapped call parameter must be\n  # set in this hash. This hash also contains all previously remapped call parameter by other 'route_remap' functions.\n  # In this example, the 'remap_called_noa_npi' method is called before the called, calling and nap remapping.\n  #\n  # This function must return the remapped call parameters in the call_param hash.\n  #\n  # This method remap the Called number NOA and NPI based on a prefix rules regex.\n  # * If the called number match '001', the NOA is remapped to 'international_number'.\n  # * Otherwise, the NOA is remapped to 'national_number'\n  # * The NPI is always remapped to 'isdn'.\n  #\n  # NOA values for the <tt>called_noa</tt> or <tt>calling_noa</tt> remapped call parameter are:\n  # * <tt>unknown_number</tt>\n  # * <tt>international_number</tt>\n  # * <tt>national_number</tt>\n  # * <tt>subscriber_number</tt>\n  # * <tt>network_specific</tt>\n  # * <tt>network_routing_national_format</tt>\n  # * <tt>network_routing_international_format</tt>\n  # * <tt>abbreviated_number</tt>\n  # * <tt>subscriber_number_operator_requested</tt>\n  # * <tt>national_number_operator_requested</tt>\n  # * <tt>international_number_operator_requested</tt>\n  # * <tt>no_number_present_operator_requested</tt>\n  # * <tt>no_number_present_cut_through_call_to_carrier</tt>\n  # * <tt>test_line_test_code</tt>\n  # * <tt>non_unique_subscriber_number</tt>\n  # * <tt>non_unique_national_number</tt>\n  # * <tt>non_unique_international_number</tt>\n  # * <tt>call_950_number</tt>\n  #\n  # Those values will be remapped to the protocol specific NOA value. To provide protocol specific value:\n  # * <tt>remapped_fields[:called_noa] = 0x70</tt>\n  # or\n  # * <tt>remapped_fields[:called_noa] = 112</tt>\n  #\n  # NPI values for the <tt>called_npi</tt> or <tt>calling_npi</tt> remmapped call parameter are:\n  # * <tt>unknown_number</tt>\n  # * <tt>isdn</tt>\n  # * <tt>telephony</tt>\n  # * <tt>private</tt>\n  # * <tt>data</tt>\n  # * <tt>telex</tt>\n  # * <tt>national</tt>\n  #\n  # Calling Display Information Type possible remapped values (<tt>calling_display_type</tt>):\n  # * <tt>unspecified</tt> => Type is unspecified.\n  # * <tt>calling_party_name</tt> => Type is 0xB1.\n  #\n  # Those values will be remapped to the protocol specific  Display Information Type value. To provide protocol specific value:\n  # * <tt>remapped_fields[:calling_display_type] = 0xB1</tt>\n  # or\n  # * <tt>remapped_fields[:calling_display_type] = 177</tt>\n  #\n  # Calling Display Information can be set to any string value (<tt>calling_display</tt>):\n  # * <tt>remapped_fields[:calling_display] = \"Roger Fluffy\"</tt>\n  #\n  # Calling Presentation possible remapped values (<tt>calling_presentation</tt>):\n  # * <tt>unspecified</tt>\n  # * <tt>not_available</tt>\n  # * <tt>allowed</tt>\n  # * <tt>restricted</tt>\n  # * <tt>addr_restricted</tt>\n  # * <tt>name_restricted</tt>\n  #\n  # Calling Number Screening possible remapped values (<tt>calling_screening</tt>):\n  # * <tt>unspecified</tt>\n  # * <tt>no</tt>\n  # * <tt>pass</tt>\n  # * <tt>fail</tt>\n  # * <tt>network_provided</tt>\n  #\n  def prefix_private_noa( route, caf_call, nap_list, call_params )\n\n    # If the called number have previously been remapped before the call to this function, use it.\n    # Otherwise, use the call called number.\n    called = call_params[:called]\n    calling = call_params[:calling]\n    private_address = call_params[:private_address]\n    # calling_name = call_params[:private_display]\n    # log_trace 2, \"Incoming call info =#{call_params.inspect}\"\n   # puts calling, private_address, calling_name\n   # NOA and manipulation for called number\n   if @national_regex.match(called)\n      call_params[:called_noa] = :national_number\n      call_params[:called] = $2 + \"F\"\n   else\n      call_params[:called_noa] = :international_number\n      call_params[:called] = call_params[:called] + \"F\"     \n   end\n   \n   # NOA and manipulation for calling number\n   # If private_address, then use it as the calling number\n   if private_address && @valid_private_address.match(private_address)\n      calling = $1\n      #log_trace 2, \"calling use private_address: calling=#{calling}\"\n   end\n   \n    if @national_regex.match(calling)\n      call_params[:calling_noa] = :national_number\n      call_params[:calling] = $2\n    else\n      call_params[:calling_noa] = :international_number\n    end\n  \n    #log_trace 2, \"Incoming leg called=#{called}, calling=#{calling}, private_address=#{private_address}\"\n  \n    call_params[:called_npi] = :isdn\n    call_params\n  end\n\n  # Class initialization. Create a class instance variable <tt>regex</tt> hash used by remap_called_noa_npi.\n  # * <tt>international_regex</tt> - Math an international called number prefixed with '001'\n  #\n  def initialize\n    @national_regex = Regexp.new(/^(49)([0-9]+)$/)\n    #@valid_number_regex = Regexp.new(/^[0-9]+$/)\n  #@@request_uri_regex = /sip:(\\d*)(;.*)?@.*/\n  @valid_private_address = Regexp.new(/sip:(.*)@.*/)\n  end\n\nend\n\n@@routing = NoaNpiRemap.new\n\ndef init_routes( routes )\n  @@routing.init routes\nend\n\ndef route( call, nap_list )\n  @@routing.route call, nap_list\nend"
        },
        "priority_and_weight_load_balancer.rb" : {
          "load_on_startup" : true,
          "name" : "priority_and_weight_load_balancer.rb",
          "script_type" : "filter",
          "source" : "#\n# Script version 1.2\n#\n# Version history:\n#  1.3  Added check for NULL remapped NAP\n#  1.2  Added :call_attempts_round_robin mode for load balancing\n#  1.1  Fixed infinite loop in function order_routes_within_priority_call_attempts if routes have weight 0\n#  1.0  Initial version of this script, based on older script nap_group_weight_load_balancer.rb.\n#\n# This filter is used to:\n#   - Order routes by priority\n#   - Assign a relative weight amongst routes with the same priority\n#     (load-sharing among routes of the same priority according to relative weights)\n#\n# Route/NAP attribute that controls this filter:\n# priority          An positive integer, 0 being the highest possible priority\n# weight            An positive integer. The higher value meaning higher proportion of calls on a route.\n#                   A value of 0 means that a route will never be used unless other routes of the same priority are down.\n# load_share_mode   Defines how load distribution among routes of the same priority should be applied:\n#                     :alternate_exclusive  -> Select a single route for a given priority, discard other routes\n#                     :alternate_backup     -> Select the first route for a given priority, keep other routes\n#                                              as well for eventual route retry if selected route fails\n#\n# In the WEB Portal, \"priority\"/\"weight\" attributes are found:\n#   - Under \"Routes\" section of the web portal (then under \"Custom Parameters\").\n#   - Under \"NAP Columns\" section of the web portal.\n#\n# Route priority and weight are always taken either from the route or,\n# if not specified in the route, from the outgoing (remapped) NAP parameters.\n#\n# Route priority and weight for \"dynamic routes\" (registered users) is always taken from the outgoing NAP\n#\n# Global options for this filter script:\n#   :weight_by                  Comparison method to apply the weight for routes of the same priority:\n#                                 :load                       -> Weighted with current number of calls on each outgoing NAP\n#                                 :load_outgoing_only         -> Weighted with current number of outgoing calls on each outgoing NAP\n#                                 :call_attempts              -> Weighted statistically (regardless of current number of calls)\n#                                 :call_attempts_round_robin  -> Weighted statistically (regardless of current number of calls) in a round-robin mannner\n#   :default_load_share_mode    A default value for load_share_mode (see above for description of load_share_mode)\n#   :max_priority_delta         Maximum priority delta between highest and lowest matching route priority.\n#                               All routes too far from highest priority route will be discarded.\n#                               This can be used for a route to have absolute precedence over others.\n#                               Default (-1) means there is no limit in the priority delta.\n#\n\nmodule PriorityAndWeightLoadBalancer\n\n  # Constants that define valid values for script options\n  WEIGHT_BY_VALUES  = [ :load, :load_outgoing_only, :call_attempts, :call_attempts_round_robin ]\n  LS_MODE_VALUES    = [ :alternate_exclusive, :alternate_backup ]\n\n\n  @@available_sets = { }\n\n  # This function is automatically called at script intialization (called once when configuration is applied)\n  def init_filter_by_priority_and_weight params\n    @weight_by                  = params[ :weight_by ]                  || :load\n    @default_load_share_mode    = params[ :default_load_share_mode ]    || :alternate_backup\n    @max_priority_delta         = params[ :max_priority_delta ].to_i    || -1\n\n    # Clears memory\n    @@available_sets = { }\n\n    # Validate that :weight_by option is a known value\n    raise \"#{@weight_by} is invalid: Allowed values for :weight_by are #{WEIGHT_BY_VALUES.inspect}\" unless WEIGHT_BY_VALUES.include? @weight_by\n\n    validate_load_share_mode( params[ :routes ] )\n\n\n\n    # Print a trace indicating that this script is used\n    log_trace :always, \"Using PriorityAndWeightLoadBalancer\" + ( (@max_priority_delta > 0) ? \" (max_priority_delta: #{@max_priority_delta})\" : \"\" )\n  end\n\n  # This function is called for every incoming call to route.\n  # It will re-order routes according to \"priority\", and within the same priority in a load-sharing manner according to \"weight\"\n  def filter_by_priority_and_weight params\n\n    # Don't waste precious CPU cycles if we only have one matching route!\n    return params if params[ :routes ].size == 1\n\n    # Clone the matching routes array (normally just a few routes), since we cannot modify the original copy\n    cloned_routes               = clone_routes params[ :routes ]\n    routes_per_priority         = {}\n    final_routes                = []\n\n    # For routes that don't have a priority or weight defined, take the value from the remapped NAP.\n    cloned_routes.each do | route |\n      get_effective_route_attribute( route, params[ :naps ], :priority )\n      get_effective_route_attribute( route, params[ :naps ], :weight )\n    end\n\n    # Sort the route in ascending priority (highest priority first, i.e. lowest integer value first)\n    sorted_routes = cloned_routes.sort do | route_a, route_b |\n      if route_a[ :priority ] != route_b[ :priority ]\n        # Order the routes per priority\n        route_a[ :priority ] <=> route_b[ :priority ]\n      else\n        # For convenience, for same priority let's class by name\n        route_a[ :route_name ] <=> route_b[ :route_name ]\n      end\n    end\n\n    # Print ordered routes by priority and weight\n    #log_trace 1, \"Sorted routes by priority:\\n  -\" + sorted_routes.collect{ |route| \"#{route[:route_name]} prio:#{route[:priority].to_i}, weight:#{route[:weight].to_i}\" }.join( \"\\n  -\" )\n\n    # Create an array routes per priority:\n    #  routes_per_priority: Hash (by priority) of all routes for each priority\n    #  used_priorities:     Ordered array of priorities that have routes (required because a hash like routes_per_priority is unordered)\n    used_priorities   = []\n    highest_priority  = nil\n    sorted_routes.each do | sorted_route |\n      priority = sorted_route[ :priority ].to_i\n\n      # Check if maxium priority delta has been passed\n      if @max_priority_delta > 0\n        highest_priority = priority if highest_priority.nil?\n        if priority - highest_priority > @max_priority_delta\n          break\n        end\n      end\n\n      unless routes_per_priority[ priority ]\n        routes_per_priority[ priority ] = []\n        used_priorities << priority\n      end\n      routes_per_priority[ priority ] << sorted_route\n    end\n\n    # Let's apply relative weight of each route within the same priority\n    used_priorities.each do | priority |\n\n      # Find all the routes for that given priority\n      routes_same_prio = routes_per_priority[ priority ]\n\n      if routes_same_prio.size == 1\n\n        # No sorting needed here, only one route for this priority\n        final_routes << routes_same_prio.first\n\n      elsif routes_same_prio.size > 1\n        dynamic_routes = []\n        dynamic_routes_index = nil\n        dummy_dynamic_route = nil\n        dummy_dynamic_route_name = \"$dummy_dynamic_route$\"\n\n        i = 0\n        routes_same_prio.each do |route|\n          if(route[ :route_type ] == :dynamic)\n            # Keeping track of the dynamic routes found. Dynamic routes will be considered\n            # as one route, even if there are many\n            dynamic_routes.push( route )\n            dynamic_routes_index ||= i\n          end\n          i = i + 1\n        end\n\n        # Delete dynamic routes in routes_same_prio to replace them by one entity\n        dynamic_routes.each do |route|\n          routes_same_prio.delete( route )\n        end\n\n        # Inserting single dynamic route entity where dynamic routes where located\n        if( dynamic_routes_index != nil)\n          dummy_dynamic_route = dynamic_routes[ 0 ].dup\n          dummy_dynamic_route[ :route_name ] = dummy_dynamic_route_name\n          routes_same_prio.insert( dynamic_routes_index, dummy_dynamic_route )\n        end\n\n\n        # Let's order the routes now!\n        case @weight_by\n          when :load, :load_outgoing_only\n            routes_same_prio = order_routes_within_priority_current_calls( params[ :naps ], routes_same_prio )\n          when :call_attempts\n            routes_same_prio = order_routes_within_priority_call_attempts( params[ :naps ], routes_same_prio )\n          when :call_attempts_round_robin\n            routes_same_prio = order_routes_within_priority_call_attempts_round_robin( params[ :naps ], routes_same_prio )\n        end\n\n        # Reinsert the original dynamic routes where de  dummy route is now placed.\n        # This ensures that the dynamic routes are not reordered between themselves\n        i = 0\n        if( dynamic_routes_index != nil)\n          dynamic_routes_index = routes_same_prio.index( dummy_dynamic_route )\n          routes_same_prio.delete( dummy_dynamic_route )\n          dynamic_routes.each do |route|\n            routes_same_prio.insert( dynamic_routes_index + i, route)\n            i = i + 1\n          end\n        end\n\n        # Now put either first route or all routes from this priority back into the final_routes array\n        load_share_mode = routes_same_prio.first[ :load_share_mode ] ? routes_same_prio.first[ :load_share_mode ].to_sym : @default_load_share_mode\n        case load_share_mode\n          when :alternate_exclusive\n            final_routes << routes_same_prio.first\n          when :alternate_backup\n            routes_same_prio.each { | route | final_routes << route }\n        end\n\n      end\n\n    end\n\n    # Print the final routes order after applying routes weights\n    log_trace 2, \"Final routes order (priority and weight applied): \" + final_routes.collect{ |route| route[:route_name] }.join( \", \" )\n\n    # Replace the system's list of routes by our sorted list of routes!\n    params[:routes] = final_routes\n    params\n  end\n\n  # This function return the sumation of call based on @weight_by selection\n  def get_nb_calls( incoming, outgoing )\n    sum =\n    case @weight_by\n      when :load\n        sum = incoming + outgoing\n      when :load_outgoing_only\n        sum = outgoing\n      end\n    sum\n  end\n\n  # This function do the actual sorting of routes according to the relative weight of each destination NAP within a same priority\n  def order_routes_within_priority_current_calls( nap_list, routes_same_prio )\n    global_call_cnt = 0\n    global_weight   = 0\n\n    # Let's calculate the total number of call in the NAPs for the selected routes\n    routes_same_prio.each do | route |\n      remapped_nap = nap_list[ route[:remapped_nap].to_sym ]\n      if remapped_nap && route[ :weight ].to_i != 0\n        global_call_cnt   += get_nb_calls(\n          remapped_nap[ :inst_incoming_call_cnt ].to_i,\n          remapped_nap[ :inst_outgoing_call_cnt ].to_i\n        )\n        global_weight += route[ :weight ].to_i\n      end\n    end\n\n    # We want to sort the route list according to the weight of each NAP.  The weight is use to determine\n    # how many percentage of total call each destination is supposed to have.\n    routes_same_prio.sort! do |route_a, route_b|\n\n      # Retrieve NAP both nap to compare\n      nap_a                   = nap_list[ route_a[:remapped_nap].intern ]\n      nap_b                   = nap_list[ route_b[:remapped_nap].intern ]\n      weight_a                = route_a[ :weight ].to_i\n      weight_b                = route_b[ :weight ].to_i\n      traffic_fraction_a_diff = 0\n      traffic_fraction_b_diff = 0\n\n      # Calculate each NAP current percentage of calls compared to their target percentage (based on their relative weight)\n      if( nap_a && nap_b && (global_weight > 0) )\n\n        # First calculate the current percentage based on the number of active calls\n        nap_a_traffic_fraction_current = 0\n        nap_b_traffic_fraction_current = 0\n        if global_call_cnt > 0\n          if weight_a != 0\n            current_nb_call_in_nap          = get_nb_calls( nap_a[:inst_incoming_call_cnt].to_i, nap_a[:inst_outgoing_call_cnt].to_i )\n            nap_a_traffic_fraction_current  = (current_nb_call_in_nap*10000) / global_call_cnt\n          end\n          if weight_b != 0\n            current_nb_call_in_nap          = get_nb_calls( nap_b[:inst_incoming_call_cnt].to_i, nap_b[:inst_outgoing_call_cnt].to_i )\n            nap_b_traffic_fraction_current  = (current_nb_call_in_nap*10000) / global_call_cnt\n          end\n        end\n\n        # Let's calculate the target of each NAP based on their weight\n        nap_a_traffic_fraction_target  = weight_a * 10000 / global_weight\n        nap_b_traffic_fraction_target  = weight_b * 10000 / global_weight\n\n        # Then let's calculate the difference between the current and target level of each NAP\n        traffic_fraction_a_diff = nap_a_traffic_fraction_target - nap_a_traffic_fraction_current\n        traffic_fraction_b_diff = nap_b_traffic_fraction_target - nap_b_traffic_fraction_current\n\n        # A difference of 0 is reserved for route with an effective_weight 0; If the target is negative, set to 1\n        traffic_fraction_a_diff = 1 if traffic_fraction_a_diff <= 0 && weight_a != 0\n        traffic_fraction_b_diff = 1 if traffic_fraction_b_diff <= 0 && weight_b != 0\n\n      end\n\n      # Use the previous calculation to order the two NAPs\n      traffic_fraction_b_diff <=> traffic_fraction_a_diff\n    end\n\n    # Returned the ordered array of routes\n    routes_same_prio\n  end\n\n  # This function do the actual sorting of routes according to the relative weight of each destination NAP within a same priority\n  # This function will loop in a round-robin manner into the routes provided and select them a number of times according to the\n  # weight of each of them. For example, if we have:\n  #               Route1    Route2    Route3\n  #     weight:   1         3         4\n  #\n  # Through many calls of this function, the routes will be reordered this way:\n  #        Call 1: Route1 - Route2 - Route3  (Route1 selected)\n  #        Call 2: Route2 - Route3 - Route1  (Route2 selected)\n  #        Call 3: Route2 - Route3 - Route1  (Route2 selected)\n  #        Call 4: Route2 - Route3 - Route1  (Route2 selected)\n  #        Call 5: Route3 - Route1 - Route2  (Route3 selected)\n  #        Call 6: Route3 - Route2 - Route1  (Route3 selected)\n  #        Call 7: Route3 - Route2 - Route1  (Route3 selected)\n  #        Call 8: Route3 - Route2 - Route1  (Route3 selected)\n  #        Call 9: Route1 - Route2 - Route3  (Route1 selected)\n  #        Call 10: Route2 - Route3 - Route1  (Route2 selected)\n  #\n  # For routes with same weight, this function will select routes in a completely round-robin manner (one after the other)\n  #\n  #\n\n  def order_routes_within_priority_call_attempts_round_robin( nap_list, routes_same_prio )\n    routes_reordered = []\n    routes_remaining = routes_same_prio.dup\n    subsets_remaining = 5\n\n    # Recursive calls of the function \"select_route_round_robin\". This allows\n    # for round robin selection of each entry of the routes_reordered array.\n    # Since each call of \"select_route_round_robin\" with different routes\n    # creates an hash table (takes a lot of memory), we limit the number of\n    # calls to 5 per set of routes.\n    while( ( subsets_remaining != 0 ) && ( routes_remaining.size > 1 ))\n      \n      route_selected = select_route_round_robin( routes_remaining )\n\n      routes_reordered.push( route_selected )\n      routes_remaining.delete( route_selected )\n\n      subsets_remaining -= 1;\n    end\n\n    if(routes_remaining.size == 1)\n      routes_reordered = routes_reordered + routes_remaining\n    else\n      # the routes that remains after the fifth loop are reordered randomly\n      routes_reordered = routes_reordered + order_routes_within_priority_call_attempts( nap_list, routes_remaining )\n    end\n\n    return routes_reordered\n\n  end\n\n  def select_route_round_robin( routes_same_prio )\n    selected_route  = []\n    found_route = nil\n    routes_same_prio_id = \" \"\n    found_route_index = nil\n\n    # This function needs a memory of the preceeding attempts to each route in order\n    # to attempt them in a round-robin manner. Each route set is saved in memory and\n    # tagged with a unique ID built from route names\n\n    # Building ID for route set\n    i = 0\n    routes_same_prio.each do |route|\n      # The ID of the input set\n      routes_same_prio_id = routes_same_prio_id + route[ :route_name ]\n    end\n\n    # If this route set is not already in memory (unkown set)\n    @@available_sets[ routes_same_prio_id ] ||= Array.new(routes_same_prio.size, 0 )\n    selected_set = @@available_sets[ routes_same_prio_id ]\n\n    # Calculates the ratio attempts/weight of the first route in the set\n    if( routes_same_prio[ 0 ][ :weight ] != 0 )\n      first_route_ratio = selected_set[ 0 ].to_f / routes_same_prio[ 0 ][ :weight ].to_f\n    else\n      first_route_ratio = 1.0/0.0 # first_route_ratio = infinity ( route will never be selected )\n    end\n    \n\n    # looping through input set's routes\n    i = 0\n    while( i < routes_same_prio.size )\n\n      # Calculates the pound ratio of  the next route in the set\n      if( routes_same_prio[ i ][ :weight ] != 0 )\n        next_route_ratio = selected_set[ i ].to_f / routes_same_prio[ i ][ :weight ].to_f\n      else\n        next_route_ratio = 1.0/0.0 # next_route_ratio = infinity ( route will never be selected )\n      end\n\n      # if the next route ratio is smaller than the first, then have a candidate\n      if( next_route_ratio < first_route_ratio )\n        # Push the candidate in the sorted routes\n        selected_route.push( routes_same_prio[ i ] )\n\n        if(found_route == nil)\n          found_route = routes_same_prio[ i ]\n          found_route_index = i\n        end\n        \n        i = routes_same_prio.size # Exit the while loop\n      else\n        i = i + 1\n      end\n    end\n\n    # If no candidate was found, the first route is the selected one\n    if(found_route == nil)\n      found_route = routes_same_prio[ 0 ]\n      found_route_index = 0\n    end\n\n    # Increase the attempts count for the selected route\n    selected_set[ found_route_index ] = selected_set[ found_route_index ].to_i + 1\n\n    return found_route\n  end\n\n  # This function do the actual sorting of routes according to the relative weight of each destination NAP within a same priority\n  # This function will randomly select routes the following way:\n  #\n  # Repeat the following algorithm for routes remaining to order:\n  #   Randomly select a route by a random number between 0 and sum of weight of remaining routes\n  #               +---------------------------------------------------------------------------------------+\n  #               + Route 1 weight 20 | Route 2 weight 40                       | Route 3 weight 25       |\n  #   Random val:                           |\n  #   => Route 2 chosen, and removed from \"remaining routes\n  #               +---------------------------------------------+\n  #               + Route 1 weight 20 | Route 3 weight 25       |\n  #   Random val:             |\n  #   => Route 1 chosen\n  #   => Route 3 remains last\n  #   => Final route order:   Route 2, Route 1, Route 3\n  #\n  def order_routes_within_priority_call_attempts( nap_list, routes_same_prio )\n    global_weight       = 0\n    route_to_sort       = {}\n    route_remaining     = {}\n    sorted_route_names  = []\n\n    # Prepare some arrays/hashs for this algorithm\n    routes_same_prio.each do | route |\n      # Hash of routes by name (we'll need to retrieve them by name later)\n      route_to_sort[ route[ :route_name ] ]   = route\n\n      # Hash of routes remaining to randomize weight for\n      route_remaining[ route[ :route_name ] ] = route[ :weight ].to_i\n\n      # Calculate the total weight of all routes\n      global_weight                           += route[ :weight ].to_i\n    end\n\n    while( route_remaining.size > 1 )\n\n      # Randomly choose one of remaining routes\n      random_val = rand( global_weight ).to_i\n      found_route_name = nil\n      route_remaining.each do | route_name, weight |\n        if random_val < weight\n          # Choose this route, consume it\n          found_route_name  = route_name\n          global_weight     = global_weight - weight\n          break\n        end\n\n        # Do not consume this route, check if random value is within next route\n        random_val = random_val - weight\n      end\n      if found_route_name.nil?\n        # Did not find route with suitable weight -> Let's take first route\n        found_route_name = route_remaining.keys.first\n      end\n      sorted_route_names  << found_route_name\n      route_remaining.delete( found_route_name )\n    end\n    # Add last route\n    sorted_route_names << route_remaining.keys[0]\n\n    # Build and return the array of sorted routes\n    ordered_routes = []\n    routes_same_prio = []\n    sorted_route_names.each do | route_name |\n      routes_same_prio << route_to_sort[ route_name ]\n    end\n\n    return routes_same_prio\n  end\n\n  def get_effective_route_attribute( route, nap_list, attribute )\n    # Check if the route has the attribute already\n    if( route[ attribute ] && !route[ attribute ].empty? )\n      route[ attribute ] = route[ attribute ].to_i\n    else\n      remapped_nap = nap_list[ route[:remapped_nap].to_sym ]\n      if remapped_nap && remapped_nap[ attribute ]\n        # Take the attribute from the remapped NAP\n        route[ attribute ] = remapped_nap[ attribute ].to_i\n      else\n        route[ attribute ] = 0\n      end\n    end\n  end\n\n  # Validate load_share_mode values on each routes\n  def validate_load_share_mode( routes )\n    ls_mode_per_priority = {}\n    routes.each do |route|\n      load_share_mode = route[:load_share_mode] ? route[:load_share_mode].to_sym : @default_load_share_mode\n\n      # Validate that the value is correct (known)\n      unless LS_MODE_VALUES.include? load_share_mode\n        raise \"Invalid load_share_mode '#{route['load_share_mode']}' on route '#{route[:name]}'. Allowed values are #{LS_MODE_VALUES.join(' or ')}\"\n      end\n\n      # Make sure that all routes from the priority have the same load_share_mode\n      ls_mode_per_priority[route[:priority]] ||= load_share_mode\n      if ls_mode_per_priority[route[:priority]] != load_share_mode\n        raise \"Inconsistent load_share_mode '#{ls_mode_per_priority[route[:priority]]}' vs '#{load_share_mode}' for routes with priority '#{route[:priority]}' (all routes of the same priority must have the same load_share_mode)\"\n      end\n    end\n  end\n\nend"
        },
        "query_on_release.rb" : {
          "load_on_startup" : false,
          "name" : "query_on_release.rb",
          "script_type" : "filter",
          "source" : "#\n# Script version 1.0\n#\n# Version history:\n#  1.0  First version of this script\n#\n# This before-matching filter is used to handle Query-on-Release (QoR)\n# redirection requests.\n#\n# A QoR request is typically a redirection request without a redirection\n# contact associated with it.\n#\n#\n# In order to use this module, follow the steps below:\n# 1- Add the \"require 'query_on_release'\" statement at the top of the main\n#    script.\n# 2- Add the \"include QueryOnRelease\" statement in the main routing class.\n# 3- Add the filter \"before_filter :method => :query_on_release\" in the main\n#    routing class.\n# 4- Add your own code in the query method found below.\n#\n#\n# Contacts are obtained from the routing engine in the following format:\n# - params[:contacts][:list] contains the contact log. Each contact within the\n#   list has the following fields:\n#   - :called_number - the called number\n#   - :is_number_ported - if the called number has been ported\n#   - :ported_number - the called number that was ported (if available)\n#   - :sip_uri - the SIP URI of the contact (if available)\n#   - :raw_data - the raw data representing the contact in the signaling\n#       protocol\n#   - :priority - the priority of the contact [0-1000]\n#   - :expiration - the expiration time in seconds of the contact\n# - params[:contacts][:index] contains the index of the contact that is\n#   currently being routed.\n# - params[:contacts][:source_indexes] contains a comma-separated list of\n#   indexes from params[:contacts][:list]. Each index represents the contact\n#   from which the contact in the list was obtained from. For instance:\n#   \"nil,0,0,2\" describes the following example contact hierarchy:\n#   * 1234 <sip:1234@test.com>\n#      |- 1234 <sip:1234@other.com>\n#      `- 5555 <sip:5555@test.com>\n#          `- 5555 <sip:5555@final.com>\n#\n\n\nmodule QueryOnRelease\n  \n  def query(params, contact)\n    ##########################################################################\n    ###################### ADD YOUR OWN CODE HERE! ###########################\n    ##########################################################################\n    # The code could be a SQL database query, HTTP request, RADIUS request,\n    # text file search, etc.\n    # The called number that must be searched for is contact[:called_number].\n    \n    # To test the feature, we're just going to change the called number here.\n    call = params[:call]\n    call[:called] = \"1234567\"\n    log_trace :always, \"QoR redirection request: using called number 1234567\"\n    \n    params\n  end\n  \n  def init_query_on_release(params)\n    params\n  end\n  \n  def query_on_release(params)\n    # Verify if the called number is empty\n    call = params[:call]\n    called = call[:called]\n    called_is_empty = (called.nil? || called == \"\")\n    \n    # Verify if the call is redirected\n    contacts = params[:contacts]\n    contact_index = contacts[:index].to_i\n    call_is_redirected = (contact_index > 0)\n    \n    if called_is_empty && call_is_redirected\n      # Find the contact from which the QoR request was obtained from\n      source_indexes = contacts[:source_indexes].split(\",\")\n      qor_contact_index = source_indexes[contact_index].to_i\n      qor_contact = contacts[:list][qor_contact_index]\n      \n      # Update the call parameters \n      params = query(params, qor_contact)\n    end\n  \n    params\n  end\nend"
        },
        "radius_authorization.rb" : {
          "load_on_startup" : false,
          "name" : "radius_authorization.rb",
          "script_type" : "filter",
          "source" : "#\n# Script version 1.0\n#\n# Version history:\n#  1.0  First version of this script\n#\n# This before-matching filter is used to do RADIUS authentication and\n# authorization before routing takes place.\n#\n#\n# In order to use this module, follow the steps below:\n# 1- Add the \"require 'radius_authorization'\" statement at the top of the\n#    main script.\n# 2- Add the \"include RadiusAuthorization\" statement in the main routing\n#    class.\n# 3- Add the filter \"before_filter :method => :radius_authorization\" in the\n#    main routing class.\n# 4- (optional) Customize the RADIUS implementation in the main routing class:\n#   a. Implement \"requires_radius_authorization?\" to reduce the scope of the\n#      authorization (e.g. per NAP, called number prefix). This method must\n#      return true to proceed with authorization, or false to prevent it.\n#   b. Implement \"fill_authorization_attributes\" to specify in detail what\n#      authorization attributes to send in the Access-Request message.\n#   c. Implement the following event handlers:\n#      - \"on_radius_authorization_accept\": called when the Access-Accept\n#        message is received;\n#      - \"on_radius_authorization_challenge\": called when the Access-Challenge\n#        message is received;\n#      - \"on_radius_authorization_reject\": called when the Access-Reject\n#        message is received;\n#      - \"on_radius_authorization_timeout\": called when no response were\n#        received for an Access-Request message.\n#\n# Default implementations of the methods in point 4 are included in this\n# module.\n#\n#\n# The contents of the Access-Request message are collected from the key-value\n# pairs found in the params[:authorization] hash when a RoutingException\n# exception is raised along with symbol :authorization_required.\n#\n# Once the RADIUS request is completed (e.g. a response was received or a\n# timeout occurred), the call is routed again with params[:authorization]\n# filled with the RADIUS attributes found in the response. A special field\n# params[:authorization][:result] is set to indicate the result of the RADIUS\n# authorization (accept, reject, challenge or timeout).\n#\n\nmodule RadiusAuthorization\n\n  def init_radius_authorization params\n    # nothing to do\n  end\n\n  def radius_authorization params\n    return params unless requires_radius_authorization?(params)\n\n    auth = params[:authorization]\n\n    # Verify if this is the first time we're called, or if we received an authorization response already.\n    if auth.nil? then\n      # This is the first time we're called. Generate an authorization hash for our RADIUS attributes.\n      auth = {}\n\n      # Insert the RADIUS attributes as key-value pairs.\n      # The keys will be compared with the RADIUS dictionary contents. Make sure the keys are found in the dictionary!\n      # If the User-Password or Old-Password attributes are used, they will be correctly encoded.\n      # If the NAS-IP-Address attribute is used and its value is \"auto\", it will be replaced by the RADIUS client IP address.\n      fill_authorization_attributes params, auth\n      params[:authorization] = auth\n      \n      # Request the authorization to proceed.\n      raise RoutingException, :authorization_required\n      \n    else\n      # Verify the authorization result (using a special field, all other fields will be RADIUS attributes).\n      # The field has 4 possible values:\n      # - \"accept\", indicating that Access-Accept has been received;\n      # - \"reject\", indicating that Access-Reject has been received;\n      # - \"challenge\", indicating that Access-Challenge has been received;\n      # - \"timeout\", indicating that no response was received (consistently with the RADIUS client timeout settings).\n      result = auth.delete(:result)\n      case result\n        when \"accept\"    then on_radius_authorization_accept(params, auth)\n        when \"reject\"    then on_radius_authorization_reject(params, auth)\n        when \"challenge\" then on_radius_authorization_challenge(params, auth)\n        when \"timeout\"   then on_radius_authorization_timeout(params, auth)\n        else                  raise RoutingException, :call_rejected\n      end\n    end\n\n    params\n  end\n  \n  def requires_radius_authorization?(params)\n    true\n  end unless defined?(requires_radius_authorization?)\n  \n  def fill_authorization_attributes(params, auth)\n    call = params[:call]\n    auth[:\"User-Name\"] = call[:nap]\n    auth[:\"NAS-IP-Address\"] = \"auto\"\n    auth[:\"Called-Station-Id\"] = call[:called]\n    auth[:\"Calling-Station-Id\"] = call[:calling]\n  end unless defined?(fill_authorization_attributes)\n  \n  def on_radius_authorization_accept(params, auth)\n    # Parse RADIUS attributes here. Collect any information needed.\n    # Example to print all attributes:\n    #   log_trace :always, \"All attributes: #{auth.inspect}\"\n    # Example using a vendor-specific attribute:\n    #   attr_name = :\"MyVendor-NewCallingNumber\"\n    #   new_calling = auth[attr_name]\n    #   if new_calling\n    #     log_trace :always, \"Changing calling number from '#{params[:call][:calling]}' to '#{new_calling}'\"\n    #     params[:call][:calling] = new_calling\n    #   else\n    #     log_trace :always, \"#{attr_name} not found in authorization response\"\n    #   end\n  end unless defined?(on_radius_authorization_accept)\n  \n  def on_radius_authorization_reject(params, auth)\n    raise RoutingException, :call_rejected\n  end unless defined?(on_radius_authorization_reject)\n  \n  def on_radius_authorization_challenge(params, auth)\n    raise RoutingException, :call_rejected\n  end unless defined?(on_radius_authorization_challenge)\n  \n  def on_radius_authorization_timeout(params, auth)\n    raise RoutingException, :call_rejected\n  end unless defined?(on_radius_authorization_timeout)\n  \nend\n"
        },
        "route_priority_routing.rb" : {
          "load_on_startup" : false,
          "name" : "route_priority_routing.rb",
          "script_type" : "main",
          "source" : "#\n# Script version 1.1\n#\n# Version history:\n#  1.1  (start of version history tracking)\n#\n\nrequire 'base_routing'\n\n# This script routes calls according to the priority values of the routes.\n#\n# Routing is in the following order:\n#\n# * Sorting: <tt>BaseRouting.route_order</tt>\n#   * <tt>:route_field_name => :prio</tt> - Sort the routes using the 'prio' route attribute (lowest priority will be first).\n# * Matching: <tt>BaseRouting.route_match</tt>\n#   * <tt>:call_field_name => :called</tt> - Match the called number of the call to a route.\n#   * <tt>:call_field_name => :calling</tt> - Match the calling number of the call to a route.\n#   * <tt>:call_field_name => :nap</tt> - Match the nap of the call to a route.\n#   * <tt>:method => :match_nap_availability</tt> - Match the call to a route using BaseRouting.match_nap_availability.\n# * Remapping: <tt>BaseRouting.route_remap</tt>\n#   * <tt>:call_field_name => :called, :route_field_name => :remapped_called</tt> - Remap the called\n#     number for the outgoing call.\n#   * <tt>:call_field_name => :calling, :route_field_name => :remapped_calling</tt> - Remap the calling\n#     number for the outgoing call.\n#   * <tt>:call_field_name => :nap, :route_field_name => :remapped_nap</tt> - Remap the nap for the outgoing call.\n#     This means setting the destination nap for the route.\n#\nclass RoutePriorityRouting < BaseRouting\n  route_match :call_field_name => :called\n  route_match :call_field_name => :calling\n  route_match :call_field_name => :nap\n#  route_match :method => :match_nap_availability\n  route_remap :call_field_name => :called, :route_field_name => :remapped_called\n  route_remap :call_field_name => :calling, :route_field_name => :remapped_calling\n  route_remap :call_field_name => :nap, :route_field_name => :remapped_nap\n  route_order :route_field_name => :prio\nend\n\n@@routing = RoutePriorityRouting.new\n\ndef init_routes( routes )\n  @@routing.init routes\nend\n\ndef route( call, nap_list )\n  @@routing.route call, nap_list\nend\n"
        },
        "routesets_digit_analyzer.rb" : {
          "load_on_startup" : false,
          "name" : "routesets_digit_analyzer.rb",
          "script_type" : "filter",
          "source" : "#\n# Script version 1.9\n#\n# Version history:\n#  1.9: - Add a Pre-manipulation of the calling or called previous to the search in trie without modifying the real numbers.\n#       The regex :routesets_calling and :routesets_called in NAP columns defines the behaviour per inbound NAP.\n#       Also add the compiled regex in the remap_field[:route_field_name]. This would allow a remap function or after-filter\n#       to use the Pre-manipulation regex for remapping purpose.\n#       - Fix get_trie_path :called_calling and :calling_called wrong suffix.\n#  1.8: Emulation mode with webportal test button: fix ruby called_calling and calling_called search \n#       method implementation to be the same as C++ CTBCAFRubyRoutesetsTrie. Added the same check for\n#       digitmap duplicate entries when building the ruby 'Trie' tree.\n#  1.7: Restore emulation mode with webportal test button\n#  1.6: Added support for C++ based 'Trie' algorithm CTBCAFRubyRoutesetsTrie\n#       for improved performance (50x) for loading configuration with large number of digit maps\n#  1.5  (start of version history tracking)\n#\n\nrequire 'trie'\n\n# == Digit Analyzer\n#\n# The digit analyzer is a pre-routing algorithm. It uses a non-linear searching algorithm to find the optimal set of routes\n# with which routing will take place. All routes not required for routing the destination telephone number are therefore screened out.\n# This greatly enhance performances and allows to support virtually any number of routes, since routing is always\n# done on a limited set of routes, matching the destination telephone number.\n#\n# == routeset_definition\n#\n# Each NAP in the system must be assigned a routeset_definition CSV file.\n# A routeset_definition file gives the association between nap and route_set_name.\n# Each routeset_name can also be given optional attributes.\n#\n# * One mandatory column: route_set_name\n# * Supplementary columns are used to provide optional and custom attributes to dynamically created routes\n#\n#   For example:\n#     routeset_name, cost,    custom_1\n#     \"Calgary, AB\", \"0.24\",  \"custom_val\"\n#\n# For each routeset_name<->NAP association, a dynamic route entry gets created.\n# Moreover, in this example, the route gets created with custom attributes found in the second and third column.\n# Each custom attributes must be defined by adding a custom column in the gateway routing table.\n#\n# The NAP gets assigned a routeset_definition by setting the filename of the routeset_definition file in the NAP\n# dynamic columns named 'routeset_definition'.  More than one NAP can point to the same 'routeset_definition' file.\n# This file must be imported in the database using the File Db view.\n#\n# == routeset_digitmap\n#\n# Each NAP in the system can be assigned a routeset_digitmap CSV file.\n# It is usual that all NAP uses the same routeset_digitmap file.\n# A routeset_digitmap file gives the association between number and route_set_name.\n#\n# * Three mandatory columns: route_set_name, called and calling\n#\n#   For example:\n#     called,calling,routeset_name\n#     403200,       ,\"Calgary, AB\"\n#     514543,       ,\"Montreal, Qc\"\n#\n# == Filter\n#\n# Filter module that analyzes called/calling number and narrows down the routes from thousands to tens.\n# The narrowed array of routes, the routeset, contains solely routes for the destination.\n# The maximum number of routes matching a destination is equal to the number of NAPs in the system.\n#\n# The first step that the filter does is to manipulate the calling or called based on :routesets_calling\n# or :routesets_called regex, if defined in the NAPs column. If no regex is provided or the column does not\n# exist, no manipulation are done prior to the search algorithm. The original calling and called \n# of the call are not modified.\n#\n# The filter then looks in the trie to find the longest prefix match for the previously modified (or not) \n# called/calling numbers.\n#\n# The output of the trie is a routeset_name for the called/calling number. This routeset_name is then used a hash key\n# to find the array of routes for the given routeset_name.\n#\n# The digit analyzer supports three kind of searching method:\n#\n# * <tt>:called </tt> Longest match search is done on the called number.\n# * <tt>:calling </tt> Longest match search is done on the calling number.\n# * <tt>:called_calling </tt> Longest match is done on the called number first, followed by the calling number.\n# * <tt>:calling_called </tt> Longest match is done on the calling number first, followed by the called number.\n#\n#\n#\n\nmodule RoutesetsDigitAnalyzer\n\n  # Initialization method\n  #\n  def init_routesets_digit_analyzer params\n    @routesets = {}\n    @called_remap_field = {}\n    @calling_remap_field = {}\n\n    @trie = nil\n    @trie_order = params[ :trie_order ]\n    @naps       = params[ :naps ]\n    routes      = params[ :routes ]\n\n    if defined? RAILS_ROOT # Do not include this when running in emulation with webportal test button\n      # Fill the trie with all the digits and routesets associations\n      @trie = Trie.new\n      routesets_digitmap_filenames.each do |filename|\n\n        file = DbFile.get_routeset_digitmap_file filename\n        file.csv_parse do |col_names, col_vals, row_idx|\n          next if row_idx == 0 # First row is column name definition\n\n          calling = col_vals[\"calling\"]\n          if calling\n            calling = calling.strip   # remove white spaces\n            if calling.size == 0\n              calling = nil\n            end\n          end\n          called = col_vals[\"called\"]\n          if called\n            called = called.strip     # remove white spaces\n            if called.size == 0\n              called = nil\n            end\n          end \n          \n          trie_path = get_trie_path called, calling\n          \n          # If prefix, insert in trie\n          if trie_path[:prefix] != \"\"\n            @trie.push trie_path[:prefix], \"TRY_PREFIX\"\n          end\n          \n          @trie.push trie_path[:full], col_vals[\"routeset_name\"]\n        end\n      end\n    else\n      # Initialize the Trie longest match search object\n      CTBCAFRubyRoutesetsTrie.set_param( @trie_order )\n    end\n\n    # Create the list of routes hashed by routesets\n    routes.each do |route|\n      if route[ :routeset_name ].nil?\n        @routesets[ :global ] ||= {}\n        @routesets[ :global ][ :routes ] ||= []\n        @routesets[ :global ][ :routes ] << route\n      else\n        @routesets[ route[ :routeset_name ] ] ||= {}\n        @routesets[ route[ :routeset_name ] ][ :routes ] ||= []\n        @routesets[ route[ :routeset_name ] ][ :routes ] << route\n      end\n    end\n    \n    # Findout the hash that will contain the precompiled regex for called and calling\n    @remap_fields.each { |remap_field|\n      if (remap_field[:route_field_name] == :remapped_called)\n        @called_remap_field = remap_field\n        remap_field[:route_field_regex] ||= {}\n      end\n      if (remap_field[:route_field_name] == :remapped_calling)\n        @calling_remap_field = remap_field\n        remap_field[:route_field_regex] ||= {}\n      end\n    }\n    \n    # Compile regex for each nap if routesets_called or routesets_calling column is present\n    params[ :naps ].each do |nap_name,nap_attr|\n      if nap_attr[ :routesets_calling ] && @calling_remap_field\n        regex_value = nap_attr[ :routesets_calling ].to_s\n        remap_split = regex_value.split('/')\n        if remap_split.length >= 3\n          @calling_remap_field[:route_field_regex][regex_value] = {\n            :regex   => Regexp.new(remap_split[1]),\n            :replace => remap_split[2]\n          }\n        end\n      end\n      if nap_attr[ :routesets_called ] && @called_remap_field\n        regex_value = nap_attr[ :routesets_called ].to_s\n        remap_split = regex_value.split('/')\n        if remap_split.length >= 3\n          @called_remap_field[:route_field_regex][regex_value] = {\n            :regex   => Regexp.new(remap_split[1]),\n            :replace => remap_split[2]\n          }\n        end\n      end\n    end\n  end\n\n  # call-seq:\n  #   routesets_digit_analyzer( params ) -> modified params\n  #\n  # params[ :routes ] gets modified to include only useful routes.\n  #\n  def routesets_digit_analyzer params\n    naps = params[ :naps ]\n    call = params[ :call ]\n    params[ :routes ] = []\n    routeset_name = \"\"\n    remap_called = remap_calling = nil\n    nap = naps[ call[:nap].to_sym ]\n    if nap.nil?\n      puts \"WARNING: can't find #{call[:nap]} in the naps list! Add the nap to the test nap_list ...\"\n    end\n    remap_called = @called_remap_field[:route_field_regex][ nap[:routesets_called] ] if nap\n    remap_calling = @calling_remap_field[:route_field_regex][ nap[:routesets_calling] ] if nap\n    called = call[ :called ].dup ; called.gsub!( remap_called[ :regex ], remap_called[ :replace ] ) if remap_called\n    calling = call[ :calling ].dup ; calling.gsub!( remap_calling[ :regex ], remap_calling[ :replace ] ) if remap_calling\n\n    if defined? RAILS_ROOT # Do not include this when running in emulation with webportal test button\n      trie_path = get_trie_path called, calling\n      \n      puts \"1. Search in digitmap tree ...\"\n      puts \"- key order=#{@trie_order}\"\n      puts \"- calling = #{call[ :calling ]}; called = #{call[ :called ]}\"\n      if nap && nap[:routesets_called] && !nap[:routesets_called].empty?\n        puts \"-- #{nap[:name]} routesets_called = \\\"#{nap[:routesets_called]}\\\" => routeset_remap_called = #{called}\"\n      end\n      if nap && nap[:routesets_calling] && !nap[:routesets_calling].empty?\n        puts \"-- #{nap[:name]} routesets_calling = \\\"#{nap[:routesets_calling]}\\\" => routeset_remap_calling = #{calling}\"\n      end\n      puts \"- prefix key=#{trie_path[:prefix]}; suffix key=#{trie_path[:suffix]}\"\n      puts \"-- Start search ...\"\n      prefix = trie_path[:prefix]\n      if prefix == \"\"\n        longest_match = @trie.longest_prefix trie_path[:full]\n        routeset_name = @trie[ longest_match ]\n        puts \"--- Longest match for key #{trie_path[:full]} => #{longest_match} : \\\"#{routeset_name}\\\"\"\n      else\n        prefix_value = @trie.longest_prefix prefix\n        begin\n          path = prefix_value + trie_path[:suffix]\n          longest_match = @trie.longest_prefix path\n          match_value = @trie[ longest_match ]\n          if longest_match && match_value != \"TRY_PREFIX\"\n            routeset_name = match_value\n            puts \"--- Longest match for key '#{path}' => #{longest_match} : \\\"#{routeset_name}\\\"\"\n            break\n          else\n            puts \"--- Longest match for key '#{path}' => \\\"\\\"\"\n          end\n        end until prefix_value.chop!.empty?\n      end\n      puts \"2. Search in routes for \\\"#{routeset_name}\\\"\"\n    else\n      routeset_name = CTBCAFRubyRoutesetsTrie.longest_match( called, calling )\n    end\n\n    if @routesets[routeset_name] then\n      params[ :routes ] += @routesets[routeset_name][ :routes ]\n    end\n    if @routesets[:global]\n      params[ :routes ] += @routesets[:global][ :routes ]\n    end\n    params\n  end\n\n\nprivate\n  def routesets_digitmap_filenames\n    routesets_digitmap_filenames = @naps.collect { |nap| nap[1][:routesets_digitmap] }\n    routesets_digitmap_filenames.uniq!\n    routesets_digitmap_filenames = routesets_digitmap_filenames.select { |filename| !filename.nil? && !filename.empty? }\n    routesets_digitmap_filenames\n  end\n\n  def get_trie_path called, calling\n    trie_path = {}\n    trie_path[:prefix] = \"\"\n    trie_path[:suffix] = \"\"\n    trie_path[:full] = \"\"\n    case @trie_order\n    when :called\n      trie_path[:suffix] = \"#{called}\"\n    when :calling\n      trie_path[:suffix] = \"#{calling}\"\n    when :called_calling\n      trie_path[:prefix] = \"#{called}\"   if called\n      trie_path[:suffix] = \"&\"\n      trie_path[:suffix] += \"#{calling}\" if calling\n    when :calling_called\n      trie_path[:prefix] = \"#{calling}\"  if calling\n      trie_path[:suffix] = \"&\"\n      trie_path[:suffix] += \"#{called}\"  if called\n    else\n      raise \"Invalid trie_order parameter => #{@trie_order}\"\n    end\n    trie_path[:full] = trie_path[:prefix] + trie_path[:suffix]\n    trie_path\n  end\n\nend\n\n"
        },
        "routesets_routing.rb" : {
          "load_on_startup" : false,
          "name" : "routesets_routing.rb",
          "script_type" : "main",
          "source" : "#\n# Script version 1.1\n#\n# Version history:\n#  1.1  (start of version history tracking)\n#\n\nrequire 'base_routing'\nrequire 'routesets_digit_analyzer'\nrequire 'called_pre_remap'\n\n# This script is a modified version of SimpleRouting, but with the added usage of before_filter.\n# For more details, see individual filter documentation:\n# * RoutesetsDigitAnalyzer: Filter module that analyzes called/calling number and narrows down the routes from thousands to tens.\n# * CalledPreRemap: Filter module that pre-remap the called number, before routing takes place\n#\n# Routing is in the following order:\n#\n# * Filter: <tt>BaseRouting.before_filter</tt>\n# * Matching: <tt>BaseRouting.route_match</tt>\n# * Remapping: <tt>BaseRouting.route_remap</tt>\n# * Filter: <tt>BaseRouting.after_filter</tt>\n#\n\nclass RoutesetsRouting < BaseRouting\n  include RoutesetsDigitAnalyzer\n  include CalledPreRemap\n\n  before_filter :method => :called_pre_remap\n  before_filter :method => :routesets_digit_analyzer, :trie_order => :called_calling\n\n  route_match :call_field_name => :called\n  route_match :call_field_name => :calling\n  route_match :call_field_name => :nap\n#  route_match :method => :match_nap_availability\n  route_remap :call_field_name => :called, :route_field_name => :remapped_called\n  route_remap :call_field_name => :calling, :route_field_name => :remapped_calling\n  route_remap :call_field_name => :nap, :route_field_name => :remapped_nap\nend\n\n@@routing = RoutesetsRouting.new\n\ndef init_routes( routes )\n  @@routing.init routes\nend\n\ndef route( call, nap_list )\n  @@routing.route call, nap_list\nend\n"
        },
        "schedule_remapper.rb" : {
          "load_on_startup" : false,
          "name" : "schedule_remapper.rb",
          "script_type" : "filter",
          "source" : "#\n# Script version 2.0\n#\n# Version history:\n#  1.0  First version of this script\n#  2.0  Fix for Tracking#18440 - scheduler_remapper.rb is not working with regular expression\n#\n# This after-matching filter is used to remap/replace some of the routes columns (or custom columns) based\n# on the actual time/date and a schedule file associated to the route.\n#  \n# This remapping filter requires one route custom column \n# * <tt>custom_files</tt>: Filename of the .csv (comma separated value) file taken from the FileDB (in the \n#                          custom_files section).  Field type is <dbfile> and default value should be left\n#                          empty.\n#\n# The format of the .csv file should be the following:\n#     First line (title):   start,end,<column name 1>,{column name 2},...,{column name N}\n#     Subsequent lines  :   <Start time/date string>,<End time/date string>,<column 1 remap value>,...\n#\n#  A <start/end time/date string> is coded as below:\n#     Format1  -->   <Month>:<1-31>/<Hour 0-23>:<Minute 0-59>:<Second 0-59>\n#     Format2  -->   <WeekDay>/<Hour 0-23>:<Minute 0-59>:<Second 0-59>\n#     Format3  -->   default/<Timezone>\n#     Format4  -->   daylightsaving:<TimeZoneOffset>/<Month>:<1-31>/<Hour 0-23>:<Minute 0-59>:<Second 0-59>\n#\n#  Note that all entries must be entered as if daylightsaving is OFF (i.e. with the normal timezone).  They will be\n#  automatically adjusted if a \"daylightsaving\" entry is present.\n#\n#     <Timezone> is the time zone adjustment UTC formated +/-HHMM\n#     <TimezoneOffset> is the time offset (formated +/-HHMM) to be applied during daylightsaving\n#     <Month> is coded JA,FB,MR,AP,JU,JL,AU,SE,OC,NO,DE\n#     <WeekDay> is coded SU,MO,TU,WE,TH,FR,SA (where SUNDAY is the first day of the week)\n#     Format #3 can only be used once within a single file to indicate a default value.  It MUST be the first line\n#     Format #4 can only be used once within a single file to indicate the start and end date of the \n#               daylight saving period (if used).  This will affect all other entries of the schedule to\n#               adjust them automatically.  This line is optional but MUST be second if present.\n#\n#  Here is an example of a schedule.cvs file:\n#     \n#     start,end,cost\n#     default/-0500,default/-0500,1         <--- Timezone is EasternTime (-5h) and cost is '1' always except if another line matches\n#     daylightsaving:+0100/MR:13/02:00:00, daylightsaving:+0100/NO:06/02:00:00  <--- All schedule entries offset will have +1h from April 14th to Aug 28th.\n#     JA:01/00:00:00,JA:04/23:59:59,99999   <--- cost is '99999' between Jan 1st (00:00) and Jan 4th (23:59)\n#     DE:23/17:00:00,DE:31/23:59:59,99999   <--- cost is '99999' between Dec 23rd (17:00) and Dec 31th (23:59)\n#     SU/00:00:00,MO/09:00:00,99999         <--- cost is '99999' between Sunday (00:00) and Monday (09:00)\n#     FR/17:00:00,SA/23:59:59,99999         <--- cost is '99999' between Friday (17:00) and Saturday (23:59)\n#     FR/17:00:00,SA/23:59:59,99999         <--- cost is '99999' between Friday (17:00) and Saturday (23:59)  \n#\n# Ranges (from start to end) MUST be in chronological order using SUNDAY as the first day of the week.  This means that a \n# range with its start time on Saturday and end time on Sunday is invalid.   Any invalid schedule entry will be ignored.\n#\n# The schedule .csv file must be imported before it can be assigned to a route.  Use the \"Import custom files\"\n# from the \"File DB\" to input new schedule files.  Then, you can assign it to a route by populating the \"custom_files\"\n# column in the \"Routes\" tab.\n#\n\n# Constants  \n# Initialize hash to match the textual token description\nMONTHS = Hash[ \"JA\",1,\"FB\",2,\"MR\",3,\"AP\",4,\"MY\",5,\"JU\",6,\"JL\",7,\"AU\",8,\"SE\",9,\"OC\",10,\"NO\",11,\"DE\",12 ]\nWEEKDAYS = Hash[\"SU\",0,\"MO\",1,\"TU\",2,\"WE\",3,\"TH\",4,\"FR\",5,\"SA\",6]\n    \nmodule ScheduleRemap\n\n  #\n  # Initialization of the scheduling remapper filter.  We need to read and parse all .csv files to be ready\n  # when incoming calls are received.\n  #\n  def init_filter_schedule_remap params\n  \n    # Initialize member variables\n    @schedules = {}\n    @schedules_routes = params[ :routes ]\n\n    # Scan each unique schedule files\n    schedule_filenames.each do |filename|\n      newschedule = nil      \n      # Parse the .csv file\n      file = DbFile.get_file filename    \n      file.csv_parse do |col_names, col_vals, row_idx|\n        # Parse each of the row within the file\n        if (row_idx == 1)\n          # Create a new schedule based on the first row \n          newschedule = Schedule.new col_names, filename, @remap_fields\n          break if !newschedule.is_valid\n        end\n        \n        # Feed the schedule with each row\n        newschedule << col_vals      \n      end\n      \n      # Keep the schedule if it is valid\n      (@schedules[filename] = newschedule) if ((newschedule.is_valid) && (newschedule.length > 0))\n    end\n  end\n\n  #\n  # This function is the actual filter that can remap/replace custom column based on current time/date and\n  # the different pre-loaded schedules\n  #\n  def filter_schedule_remap params\n  \n    # Take current time\n    rightnow = TimeWithOffset.now.to_i\n    \n    # To test..  This make the current date to be x\n    #rightnow = TimeWithOffset.utc(2011, 3, 13, 1, 0, 0, \"-0500\")\n    #puts \"Right now = #{rightnow }\"\n    #rightnow  = rightnow.to_i\n  \n    # We are going to alter the routes (not only their ordering).  Since params[:route] is the system\n    # route list, it must be kept intact for subsequent incoming call.  Thus, we make a copy\n    new_routes = clone_routes params[:routes]\n    \n    # For each route, we try to apply their schedule\n    new_routes.each do | route |\n\n      # Is there a schedule associated with this route\n      if (route[:custom_files])\n        schedule = @schedules[route[:custom_files]]\n        schedule.remap(route, rightnow) if schedule \n      end\n    end\n  \n    # Return the new routes\n    params[:routes] = new_routes\n    params\n  end\n\n\nprivate\n\n  #\n  # This function find out all schedule DB unique filename we need to load\n  #\n  def schedule_filenames\n    name = @schedules_routes.collect{ |route| route[:custom_files] }\n    name.uniq!\n    name = name.select { |filename| !filename.nil? && !filename.empty? }\n    name \n  end\n  \nend\n\n\n\n#\n# This class represents a schedule containing a list of schedule entries\n#\nclass Schedule\n\n  # Init function\n  def initialize col_names, filename, remap_fields\n    # Initialize member variables\n    @column_names = []  \n    @schedule_entries = []\n    @default_entry = nil\n    @daylightsaving_entry = nil\n    @filename = filename\n    @remap_fields = remap_fields\n    \n    # Keep the column names \n    col_names.each do | col |\n      @column_names << col.to_s\n    end\n    \n    # Initialize our \"next update\" timestamps to zero so that the first remap request triggers an update\n    @week_limit_timestamp = 0\n    @month_limit_timestamp = 0\n    @daylightsaving_limit_timestamp = 0\n  end\n  \n  # Entry adding function\n  def << col_values\n  \n    # Create a new entry\n    zone_offset = 0\n    zone_offset = @default_entry.get_timezone_offset if @default_entry\n    daylightsaving_offset = 0\n    daylightsaving_offset = @daylightsaving_entry.get_daylightsaving_offset if @daylightsaving_entry\n    entry = Schedule_entry.new(col_values, TimeWithOffset.now.to_i, zone_offset, daylightsaving_offset, @filename)\n   \n    # Make sure the default entry is the first one\n    if (!@default_entry && !entry.is_default)\n      # Found entries BEFORE the default entry.  Dropping\n      puts \"#{@filename}: Dropping entry located BEFORE the 'default'...\"\n      \n    # Make sure that if the daylight saving entry is present, it is the second one\n    elsif (entry.is_daylightsaving && @schedule_entries.length > 1)\n      # Found other entries BEFORE the daylight saving entry.  Dropping it.\n      puts \"#{@filename}: Dropping mislocated daylight saving entry...\"\n   \n    # Keep it only if it is valid\n    elsif entry.is_valid\n      (@schedule_entries << entry ) \n      if (entry.is_default)\n        @default_entry = entry\n      else       \n        # Save the daylightsaving entry\n        if (entry.is_daylightsaving)\n          @daylightsaving_entry = entry\n        end\n      end\n    end\n  end\n  \n  # To validate if the schedule is valid\n  def is_valid\n    # Is the schedule derived from a valid CSV file?\n    if (@column_names[0] && (@column_names[0] == \"start\") && @column_names[1] && (@column_names[1] == \"end\"))\n      return true\n    end\n    return false\n  end\n  \n  # Return the number of valid entries in the schedule\n  def length \n    return @schedule_entries.length\n  end\n  \n  # This function remaps non-empty column values of a route if it matches one of its schedule entries.  \n  # First match wins\n  def remap route,current_time\n   \n    # Check if we need to re-update the timestamps of our entry list\n    if ((@week_limit_timestamp <= current_time) || (@month_limit_timestamp <= current_time) || (@daylightsaving_limit_timestamp <= current_time))\n\n      # Ajust time according to daylightsaving\n      daylight_saving_active = false;\n      daylight_saving_active = @daylightsaving_entry.is_daylight_saving_active(current_time) if @daylightsaving_entry\n    \n      # Cycle through each entries to recalculate new boundaries\n      @schedule_entries.each do |entry|\n        entry.update_timestamps(current_time, daylight_saving_active)\n      end\n      \n      # Calculate next week's boundary (i.e. next sunday)\n      curday = TimeWithOffset.at(current_time)\n      curday.change_utc_offsetInt(@default_entry.get_timezone_offset)\n      @week_limit_timestamp = curday.get_nextweek.to_i\n      \n      # Calculate next month's boundary (i.e. next 1st)\n      @month_limit_timestamp = curday.get_nextmonth.to_i\n      \n      # Calculate the next daylightsaving boundary\n      if (@daylightsaving_entry)\n         if (@daylightsaving_entry.get_start_timestamp > current_time)\n           @daylightsaving_limit_timestamp = @daylightsaving_entry.get_start_timestamp\n         elsif (@daylightsaving_entry.get_end_timestamp > current_time)\n           @daylightsaving_limit_timestamp = @daylightsaving_entry.get_end_timestamp\n         else\n           @daylightsaving_limit_timestamp = @month_limit_timestamp\n         end\n      end\n    end\n  \n    # Go through every entries to find a match amoungst regular entry\n    matching_entry = nil\n    @schedule_entries.each do |entry|\n      # Is this entry matching?\n      if (!entry.is_default && !entry.is_daylightsaving && entry.has_match( current_time ))\n        matching_entry = entry\n        break\n      end\n    end\n    \n    # If no match is found, then use default (if available)\n    if (!matching_entry && @default_entry)\n      matching_entry = @default_entry\n    end\n      \n    # Did we found a match?\n    if (matching_entry)\n      \n      # Assign each values to the route     \n      values = matching_entry.get_values\n      values.each do |name, value|\n        # Skip the start and end column\n        next if ((name == \"start\") || (name == \"end\"))\n          \n        # Is there a value to set\n        if (value && name)\n          set_route_value( route, name, value)\n        end\n      end\n\n    end\n  end\n  \n  def set_route_value route,name,value\n    @remap_fields.each { |remap_field|\n      next if (remap_field[:route_field_name] != name.to_sym)\n      remap_field[:route_field_regex] ||= {}\n      route_field_value = value\n      route_field_value_split = route_field_value.split('/')\n      if route_field_value_split.length >= 3\n        remap_field[:route_field_regex][route_field_value] = { :regex => Regexp.new(route_field_value_split[1]), :replace => route_field_value_split[2] }\n      end\n    } \n    route[name.to_sym] = value\n  end\n  \n\nend\n\n#\n# This class represents a schedule entry that contains the start and end time associated with the\n# different values to set if a match occurs.\n#\nclass Schedule_entry\n\n  # Init function\n  def initialize col_values, current_time, timezone_offset, daylightsaving_offset, filename\n    # Keep a copy (not a reference) of the range description strings + custom column values\n    @entry_values = col_values.dup\n    \n    # No absolute time calculated yet\n    @start_timestamp = 0\n    @end_timestamp = 0\n    \n    # Keep other parameters\n    @daylightsavingOffset = daylightsaving_offset\n    @timezone_offset = timezone_offset\n    @filename = filename\n        \n    # Not know if this is a default entry or daylight saving entry\n    @is_default = false\n    @is_daylightsaving = false\n\n    # Let's update them now (to have the \"valid\" function returns a good value)\n    update_timestamps(current_time, false)\n\n  end\n\n  # Return an array of values associated with the entry\n  def get_values\n    return @entry_values\n  end    \n\n  # To validate if the entry is valid\n  def is_valid\n    if (@is_default == true) || ((@start_timestamp > 0) && (@end_timestamp > 0) && (@start_timestamp < @end_timestamp))\n      return true\n    end\n    return false\n  end\n  \n  # To validate if the entry is a default entry\n  def is_default\n    return @is_default\n  end  \n  \n  # To validate if the entry is a daylight saving entry\n  def is_daylightsaving\n    return @is_daylightsaving\n  end\n  \n  # Is the daylight saving active or not\n  def is_daylight_saving_active current_time\n    if (is_daylightsaving && has_match(current_time))\n      return true\n    end\n    return false\n  end\n  \n  def get_start_timestamp\n    return @start_timestamp\n  end\n  \n  def get_end_timestamp\n    return @end_timestamp\n  end  \n  \n  # Return the time offset in sec\n  def get_daylightsaving_offset\n    @daylightsavingOffset\n  end\n  \n  # Return the timezone offset (read from the default entry)\n  def get_timezone_offset\n    @timezone_offset\n  end\n  \n  # To validate time entry matches the current time\n  def has_match current_time\n    if ((@start_timestamp <= current_time) && (@end_timestamp >= current_time))\n      return true\n    end\n    return false\n  end  \n\n  # Update the entry absolute timestamps calculated from the description strings\n  def update_timestamps current_time, daylight_saving_active\n     begin\n       @start_timestamp = parse_time_description_str( @entry_values[\"start\"], current_time, daylight_saving_active)\n     rescue\n       @start_timestamp = 0\n       puts \"#{@filename} - Invalid start description string: #{@entry_values[\"start\"]}\"\n     end\n\n     begin\n       @end_timestamp = parse_time_description_str( @entry_values[\"end\"], current_time, daylight_saving_active)\n     rescue\n       @end_timestamp = 0\n       puts \"#{@filename} - Invalid end description string: #{@entry_values[\"end\"]}\"\n     end\n     \n     # Validation check to make sure the range is valid\n     if (!@is_default) && (@start_timestamp >= @end_timestamp)\n       puts \"#{@filename} - Invalid range in schedule (start=#{@entry_values[\"start\"]}, end=#{@entry_values[\"end\"]})\"\n     end\n  end\n\nprivate\n\n  # Calculate the a timestamp value based on current timeand a description string.\n  # It returns 0 in case of parsing error or the absolute timestamp (in sec) since Epoch\n  def parse_time_description_str desc_str,current_time, daylight_saving_active\n  \n    # Assume no time value for now\n    absolute_time = 0\n  \n    # Make sure we have valid data\n    return 0 if (!desc_str)\n    \n    # Default entry does not need any calculation\n    return 0 if (@is_default == true)\n    \n    # Adjust timezone offset if the daylight saving is activated\n    if (daylight_saving_active)\n      effective_timezone_offset = @timezone_offset + @daylightsavingOffset \n    else\n      effective_timezone_offset = @timezone_offset \n    end\n    \n    # Check if the string is in format 1, 2 or 3\n    tokens = desc_str.split( '/' )\n    if (tokens[0] == \"default\")\n      #\n      # This string represents a default entry format\n      #\n      @is_default = true \n      absolute_time = 0\n      t = TimeWithOffset.new\n      t.change_utc_offsetStr(tokens[1])\n      @timezone_offset = t.utc_offset\n      \n    elsif (tokens[0][0,14] == \"daylightsaving\")\n      #\n      # This string represents a daylight entry format\n      #\n      @is_daylightsaving = true \n\n      # Retrieve the current year      \n      thisday = TimeWithOffset.at(current_time)\n      thisday.change_utc_offsetInt(effective_timezone_offset)\n      curYear = thisday.year      \n\n      # Retrieve other fields (HH:MM:SS)    \n      offsetStrings = tokens[0].split(':')\n      dateStrings = tokens[1].split(':')\n      timeStrings = tokens[2].split(':')      \n      curSec = timeStrings[2].to_i\n      curMin = timeStrings[1].to_i\n      curHour = timeStrings[0].to_i        \n      \n      # Parse the daylight saving time offset\n      @daylightsavingOffset = (((offsetStrings[1].to_i/ 100).to_i) * 3600) + (((offsetStrings[1].to_i% 100).to_i) * 60)         \n      \n      # Retrieve the day and month values\n      curDay = dateStrings[1].to_i\n      curMonth = MONTHS[dateStrings[0].to_str]\n      if (!curMonth)\n        puts \"#{@filename} - Format DLS: Invalid month string #{dateStrings[0].to_str}\"\n        absolute_time = 0\n      else\n        absolute_time = TimeWithOffset.utc_intoffset(curYear, curMonth, curDay, curHour, curMin, curSec, effective_timezone_offset)\n        absolute_time = absolute_time.to_i\n      end\n        \n    else \n   \n      #\n      # Format 1 and 2 have some parameters in common\n      #\n      thisday  = TimeWithOffset.at(current_time)\n      thisday.change_utc_offsetInt(effective_timezone_offset)\n      \n      curYear = thisday.year        \n      dateStrings = tokens[0].split(':')\n      timeStrings = tokens[1].split(':')\n      curSec = timeStrings[2].to_i\n      curMin = timeStrings[1].to_i\n      curHour = timeStrings[0].to_i    \n\n      # Check in which format the string is\n      if (dateStrings.length > 1)\n        #\n        #  Format1  -->   <TimeZone>/<Month>:<1-31>/<Hour 0-23>:<Minute 0-59>:<Second 0-59>      \n        #\n        curDay = dateStrings[1].to_i\n        curMonth = MONTHS[dateStrings[0].to_str]\n        if (!curMonth)\n          puts \"#{@filename} - Format1: Invalid month string #{dateStrings[0].to_str}\"\n          absolute_time = 0\n        else\n          absolute_time = TimeWithOffset.utc_intoffset(curYear, curMonth, curDay, curHour, curMin, curSec, effective_timezone_offset)\n          absolute_time = absolute_time.to_i\n        end\n      else\n        #\n        # Format2  -->   <TimeZone>/<WeekDay>/<Hour 0-23>:<Minute 0-59>:<Second 0-59>\n        #\n\n        # Found out the start of the week (i.e. sunday)\n        startofweek = thisday.get_startofweek\n        \n        # Then, search until we find the specified day in the current week\n        if (WEEKDAYS[dateStrings[0]])\n          specifiedDay = startofweek \n          until (specifiedDay.wday == WEEKDAYS[dateStrings[0]])\n            specifiedDay = specifiedDay.get_nextday\n          end\n          curDay = specifiedDay.day\n          curMonth = specifiedDay.month\n          absolute_time = TimeWithOffset.utc_intoffset(curYear, curMonth, curDay, curHour, curMin, curSec, effective_timezone_offset)\n          absolute_time = absolute_time.to_i\n        else\n          # Invalid week day string\n          puts \"#{@filename} - Format2: Invalid weekday string ${dateStrings[0].to_str}\"\n          absolute_time = 0\n        end\n      end\n\n    end\n    \n    return absolute_time  \n  end  \nend \n  \n\n#\n# This class is used to add timezone change functionality to the Ruby 1.8.6 Time class which\n# lacks the options to interpret the Epoch internal timestamps to different timezone in regards\n# to weekday and months functions.\n#\nclass TimeWithOffset\n\n  def initialize\n    @offset = 0\n    @timevalue = nil\n  end\n  \n  def create_with_time_with_integer_offset( year, month, day, hour, minute, sec, offsetInt )\n    @offset = offsetInt \n    @timevalue = Time.utc(year, month, day, hour, minute, sec) - @offset\n  end  \n\n  def create_with_time( year, month, day, hour, minute, sec, offsetStr )\n    numerical_offset = (((offsetStr.to_i/ 100).to_i) * 3600) + (((offsetStr.to_i % 100).to_i) * 60)\n    create_with_time_with_integer_offset(year, month, day, hour, minute, sec, numerical_offset)\n  end\n\n  # Creates a time with a number of seconds since Epoch (UTC)\n  def create_with_epoch( nbSecSinceEpoch )\n    @offset = 0\n    @timevalue = Time.at(nbSecSinceEpoch ).getutc\n  end\n  \n  # Change the UTC offset in integer format\n  def change_utc_offsetInt( offsetInt )\n    @offset = offsetInt \n  end  \n\n  # Change the UTC offset in string format\n  def change_utc_offsetStr( offsetStr )\n    numerical_offset = (((offsetStr.to_i/ 100).to_i) * 3600) + (((offsetStr.to_i % 100).to_i) * 60)\n    change_utc_offsetInt( numerical_offset )\n  end    \n  \n  # Emulate the Time.utc() function\n  def self.utc(year, month, day, hour, minute, sec, offsetStr)\n    t = TimeWithOffset.new\n    t.create_with_time(year, month, day, hour, minute, sec, offsetStr)\n    t\n  end  \n\n  # Emulate the Time.utc() but with the integer value of the timezone offset function\n  def self.utc_intoffset(year, month, day, hour, minute, sec, offsetInt)\n    t = TimeWithOffset.new\n    t.create_with_time_with_integer_offset(year, month, day, hour, minute, sec, offsetInt)\n    t\n  end  \n\n  # Emulate the Time.at() function\n  def self.at(nbSecSinceEpoch)\n    t = TimeWithOffset.new\n    t.create_with_epoch nbSecSinceEpoch\n    t\n  end\n  \n  # Emulate the Time.now() function\n  def self.now()\n    TimeWithOffset.at(Time.now.to_i)\n  end\n  \n  # Convert to absolute timestamp\n  def to_i\n    @timevalue.to_i  \n  end\n  \n  # Convert to string\n  def to_s\n    @timevalue.localtime.to_s\n  end\n  \n  # Returns the UTC offset\n  def utc_offset\n    @offset\n  end\n \n  # Returns the day in the current timezone (not UTC)\n  def day\n    t = @timevalue + @offset\n    t.day\n  end\n  \n  # Returns the week day in the current timezone (not UTC)\n  def wday\n    t = @timevalue + @offset\n    t.wday\n  end  \n  \n  # Returns the month in the current timezone (not UTC)\n  def month\n    t = @timevalue + @offset\n    t.month\n  end\n  \n  # Returns the yearin the current timezone (not UTC)\n  def year\n    t = @timevalue + @offset\n    t.year\n  end \n  \n  # Returns an object at the start of this day in the current timezone (not UTC)\n  def get_startofday\n    t = TimeWithOffset.utc_intoffset(self.year, self.month, self.day, 0, 0, 0, @offset)\n    t\n  end\n  \n  # Returns an object at the start of this month in the current timezone (not UTC)\n  def get_startofmonth\n    t = TimeWithOffset.utc_intoffset(self.year, self.month, 1, 0, 0, 0, @offset)\n    t  \n  end  \n  \n  # Returns an object at the start of the previous day in the current timezone (not UTC)\n  def get_previousday\n    t = TimeWithOffset.at((self.to_i - (24 * 3600)))\n    t.change_utc_offsetInt @offset\n    t = t.get_startofday\n    t\n  end\n  \n  # Returns an object at the start of the week (sunday) (at the beginning of that day) in the current timezone (not UTC)\n  def get_startofweek\n    t = self.get_startofday\n    until (t.wday == WEEKDAYS[\"SU\"])\n      t = t.get_previousday\n    end\n    t\n  end\n  \n  # Returns an object at the start of the previous week (sunday) (at the beginning of that day) in the current timezone (not UTC)\n  def get_previousweek\n    t = self.get_startofweek.get_previousday\n    t = t.get_startofweek\n    t\n  end  \n\n  # Returns an object at the start of the next day (at the beginning of that day) in the current timezone (not UTC)\n  def get_nextday\n    t = TimeWithOffset.at((self.to_i + (24 * 3600)))\n    t.change_utc_offsetInt @offset\n    t = t.get_startofday\n    t  \n  end\n  \n  # Returns an object at the start of the next week (at the beginning of sunday) in the current timezone (not UTC)\n  def get_nextweek\n    t = self.get_startofday\n    t = t.get_nextday if (t.wday == WEEKDAYS[\"SU\"])\n    until (t.wday == WEEKDAYS[\"SU\"])\n      t = t.get_nextday\n    end\n    t\n  end\n  \n  # Returns an object at the start of the next month (at the beginning of 1st) in the current timezone (not UTC)\n  def get_nextmonth\n    t = self.get_startofday\n    curmonth = t.month\n    until (t.month != curmonth )\n      t = t.get_nextday\n    end \n    t   \n  end  \n\nend\n  \n "
        },
        "schedule_remapping.rb" : {
          "load_on_startup" : false,
          "name" : "schedule_remapping.rb",
          "script_type" : "main",
          "source" : "#\n# Script version 1.0\n#\n# Version history:\n#  1.0  First version of this script\n#\n# This example makes use of a \"after_filter\" to remap/replace some of the routes columns (or custom columns)\n# based on the actual time/date and a schedule file associated to the route.\n#  \n# This example requires one route custom column\n# * <tt>custom_files</tt>: Filename of the .csv (comma separated value) file taken from the FileDB (in the \n#                          custom_files section).  Field type is <dbfile> and default value should be left\n#                          empty.\n#\n# Here is an example of a schedule.cvs file:\n#     start,end,cost\n#     default/-0500,default/-0500,1         <--- Timezone is EasternTime (-5h) and cost is '1' always except if another line matches\n#     daylightsaving:+0100/MR:13/02:00:00, daylightsaving:+0100/NO:06/02:00:00  <--- All schedule entries offset will have +1h from April 14th to Aug 28th.\n#     JA:01/00:00:00,JA:04/23:59:59,25      <--- cost is '25' between Jan 1st (00:00) and Jan 4th (23:59:59)\n#     DE:23/17:00:00,DE:31/23:59:59,99999   <--- cost is '99999' between Dec 23rd (17:00) and Dec 31th (23:59:59)\n#     SU/00:00:00,MO/09:00:00,,             <--- cost is unaltered between Sunday (00:00) and Monday (09:00:00)\n#     FR/17:00:00,SA/23:59:59,3             <--- cost is '3' between Friday (17:00) and Saturday (23:59:59)\n#     FR/17:00:00,SA/23:59:59,50            <--- cost is '50' between Friday (17:00) and Saturday (23:59:59)\n#\n# Refer to the description in the filter to get a more precise .csv file format and coding rules.\n#\nrequire 'base_routing'\nrequire 'schedule_remapper'\n\n# This class implements basic routing\nclass SimpleRouting< BaseRouting\n  include ScheduleRemap\n    \n  route_match :call_field_name => :called\n  route_match :call_field_name => :calling\n  route_match :call_field_name => :nap\n  route_remap :call_field_name => :called, :route_field_name => :remapped_called\n  route_remap :call_field_name => :calling, :route_field_name => :remapped_calling\n  route_remap :call_field_name => :nap, :route_field_name => :remapped_nap\n\n  # This filter remaps column(s) of a route according to the current time and a defined schedule\n  after_filter :method => :filter_schedule_remap\n  \nend\n\n@@routing = SimpleRouting.new\n\ndef init_routes( routes )\n  @@routing.init routes\nend\n\ndef route( call, nap_list )\n  @@routing.route call, nap_list\nend\n"
        },
        "simple_least_cost_routing.rb" : {
          "load_on_startup" : false,
          "name" : "simple_least_cost_routing.rb",
          "script_type" : "main",
          "source" : "#\n# Script version 1.1\n#\n# Version history:\n#  1.1  (start of version history tracking)\n#\nrequire 'base_routing'\n\n# This script routes calls according to the cost values of the routes.\n#\n# Routing is in the following order:\n#\n# * Sorting: <tt>BaseRouting.route_order</tt>\n#   * <tt>:route_field_name => :cost</tt> - Sort the routes using the 'cost' route attribute.\n# * Matching: <tt>BaseRouting.route_match</tt>\n#   * <tt>:call_field_name => :called</tt> - Match the called number of the call to a route.\n#   * <tt>:call_field_name => :calling</tt> - Match the calling number of the call to a route.\n#   * <tt>:call_field_name => :nap</tt> - Match the nap of the call to a route.\n#   * <tt>:method => :match_nap_availability</tt> - Match the call to a route using BaseRouting.match_nap_availability.\n# * Remapping: <tt>BaseRouting.route_remap</tt>\n#   * <tt>:call_field_name => :called, :route_field_name => :remapped_called</tt> - Remap the called\n#     number for the outgoing call.\n#   * <tt>:call_field_name => :calling, :route_field_name => :remapped_calling</tt> - Remap the calling\n#     number for the outgoing call.\n#   * <tt>:call_field_name => :nap, :route_field_name => :remapped_nap</tt> - Remap the nap for the outgoing call.\n#     This means setting the destination nap for the route.\n#\nclass SimpleLeastCostRouting < BaseRouting\n  route_match :call_field_name => :called\n  route_match :call_field_name => :calling\n  route_match :call_field_name => :nap\n#  route_match :method => :match_nap_availability\n  route_remap :call_field_name => :called, :route_field_name => :remapped_called\n  route_remap :call_field_name => :calling, :route_field_name => :remapped_calling\n  route_remap :call_field_name => :nap, :route_field_name => :remapped_nap\n  route_order :route_field_name => :cost\nend\n\n@@routing = SimpleLeastCostRouting.new\n\ndef init_routes( routes )\n  @@routing.init routes\nend\n\ndef route( call, nap_list )\n  @@routing.route call, nap_list\nend\n"
        },
        "simple_routing.rb" : {
          "load_on_startup" : true,
          "name" : "simple_routing.rb",
          "script_type" : "main",
          "source" : "#\n# Script version 1.3\n#\n# Version history:\n#  1.1  (start of version history tracking)\n#  1.2  Add dynamic routing, used to call registered users\n#  1.3  Removed dynamic routing, now that it's build into base_routing\n#\n\nrequire 'base_routing'\nrequire 'priority_and_weight_load_balancer' unless defined?(PriorityAndWeightLoadBalancer)\n\n# Play wav file when not empty and calling 5551234\nWAV_FILE = \"\"\n\n# This script routes calls in the most simple fashion. The behavior is the same as routing without ruby scripts\n# with the addition of nap availability.\n#\n# Routing is in the following order:\n#\n# * Matching: <tt>BaseRouting.route_match</tt>\n#   * <tt>:call_field_name => :called</tt> - Match the called number of the call to a route.\n#   * <tt>:call_field_name => :calling</tt> - Match the calling number of the call to a route.\n#   * <tt>:call_field_name => :nap</tt> - Match the nap of the call to a route.\n#   * <tt>:method => :match_nap_availability</tt> - Match the call to a route using BaseRouting.match_nap_availability.\n# * Remapping: <tt>BaseRouting.route_remap</tt>\n#   * <tt>:call_field_name => :called, :route_field_name => :remapped_called</tt> - Remap the called\n#     number for the outgoing call.\n#   * <tt>:call_field_name => :calling, :route_field_name => :remapped_calling</tt> - Remap the calling\n#     number for the outgoing call.\n#   * <tt>:call_field_name => :nap, :route_field_name => :remapped_nap</tt> - Remap the nap for the outgoing call.\n#     This means setting the destination nap for the route.\n#\n\n\nclass SimpleRouting < BaseRouting\n  include PriorityAndWeightLoadBalancer\n  \n  # These will filter routes by called, or calling number (as defined in the 'called' or 'calling' route attribute).\n  # (keep only routes matching the called number, or routes that don't have a called number defined)\n  # EXAMPLE: A route with :called attribute '5553333' will only match a call with called number '5553333'\n  #\n  # The route value (called or calling) can be a regular expression for advanced matching.\n  # EXAMPLE: A route with :called attribute /^555.*/ will only match any call with called number starting with '555'\n  #\n  # Optionally, SIP host and port can be filtered too, by using the format 'number@host:port'\n  # Each of 'number', 'host' or 'port' may be empty (and will \"always match\")\n  # EXAMPLE: A route with :called attribute '5553333@10.0.0.10:5060' will only match calls with called number\n  #          '5553333', only with SIP host '10.0.0.10' on port '5060'.\n  # EXAMPLE: A route with :called attribute 'alice@abc.com' will only match all calls trying to reach\n  #          'alice' at domain 'abc.com' (any port).\n  # EXAMPLE: A route with :called attribute '@abc.com' will only match all calls trying to reach\n  #          domain 'abc.com' (any user or port).\n  # \n  route_match :call_field_name => :called\n  \n  route_match :call_field_name => :calling\n  \n  route_match :call_field_name => :private_address\n  \n  # This will filter routes by incoming NAP\n  # (keep only routes matching the incoming NAP, or routes that don't have an incoming NAP defined)\n  route_match :call_field_name => :nap\n  \n  # This will filter routes by outgoing NAP availability\n  # (keep only routes which the outgoing NAP is available)\n  # Note: Enabling this route_match will prevent any CDR logs to be generated for unavailable NAPs.\n  #       For this reason, we disable this route_match so CDR logs are generated for outgoing call\n  #       attempts to unavailable NAPs (no actual outgoing call will be made anyways)\n  #route_match :method => :match_nap_availability\n  \n  # This will remap the called number if the route provides a non-empty \"remapped_called\" parameter\n  route_remap :call_field_name => :called, :route_field_name => :remapped_called\n\n  # This will remap the calling number if the route provides a non-empty \"remapped_calling\" parameter\n  route_remap :call_field_name => :calling, :route_field_name => :remapped_calling\n  \n  # This will remap the calling number if the route provides a non-empty \"remapped_private_address\" parameter\n  route_remap :call_field_name => :private_address, :route_field_name => :remapped_private_address\n\n  # This will select the outgoing NAP for this call according to the \"remapped_nap\" route parameter\n  route_remap :call_field_name => :nap, :route_field_name => :remapped_nap\n  \n  # This method will reorder routes according to priority and weight\n  # See priority_and_weight_load_balancer.rb for more information about the options\n  after_filter :method => :filter_by_priority_and_weight,\n               :weight_by                 => :call_attempts,\n               :default_load_share_mode   => :alternate_backup,\n               :max_priority_delta        => -1\n\n  after_filter :method => :play_demo\n\n  def init_play_demo( params )\n  end\n\n  def play_demo( params )\n\n    if WAV_FILE.length > 0  \n      if params[:call][:called] == \"5551234\"\n        log_trace :always, \"play_demo: Playing #{WAV_FILE}\"\n        params[:bridge][:announcement_tone ] = WAV_FILE\n        params[:bridge][:announcement_tone_answer] = \"yes\"\n        # End routing script now, play the prompt\n        raise RoutingException, :ok\n      end\n    end\n    params\n  end\nend\n\n@@routing = SimpleRouting.new\n\ndef init_routes( routes )\n  @@routing.init( routes )\nend\n\ndef route( call, nap_list )\n  @@routing.route( call, nap_list )\nend\n\n"
        },
        "simple_routing_selector.rb" : {
          "load_on_startup" : false,
          "name" : "simple_routing_selector.rb",
          "script_type" : "main",
          "source" : "#\n# Script version 1.0\n#\n# Version history:\n#  1.0  First version of this script\n#\n\nrequire 'base_routing'\nrequire 'simple_least_cost_routing'\nrequire 'least_cost_routing'\n\n# This script routes calls using different routing scrips. Effectively this means it changes the routing algorithm\n# depending on the incoming call. The algorithm choice is done by SimpleRoutingSelector.choose_routing_algo\n#\n#\nclass SimpleRoutingSelector < BaseRoutingSelector\n  def initialize\n    @slcr = SimpleLeastCostRouting.new\n    @lcr = LeastCostRouting.new\n  end\n\n  def init( routes )\n    slcr_routes = []\n    lcr_routes = []\n    routes.each do |route|\n        if route[:algo] == 'slcr'\n          slcr_routes.push route\n        else\n          lcr_routes.push route\n        end\n    end\n\n    @slcr.init slcr_routes\n    @lcr.init lcr_routes\n  end\n\n  # Choose the routing alogitmh depending on the call attributes:\n  #\n  # * If the incoming nap is 'FIDO' it will use LeastCostRouting.\n  # * If the incoming nap is 'BELL' it will use SimpleLeastCostRouting\n  #\n  def choose_routing_algo( call )\n    case call.get(:nap)\n    when 'FIDO'\n      @lcr\n    when 'BELL'\n      @slcr\n    end\n  end\nend\n\n@@routing_selector = SimpleRoutingSelector.new\n\ndef init_routes( routes )\n  @@routing_selector.init routes\nend\n\ndef route( call, nap_list )\n  routing = @@routing_selector.choose_routing_algo call\n  routing.route call, nap_list if routing\nend\n"
        },
        "tcap_cnam.rb" : {
          "load_on_startup" : false,
          "name" : "tcap_cnam.rb",
          "script_type" : "filter",
          "source" : "#\n# Script version 1.0\n#\n# Version history:\n#  1.0  Initial version\n#\n# \n#\n# This before-matching filter is used to tell Gateway to return to routing script after receiving the response of the CNAM query.\n# When the routing script receive the CNAM response then it replace the 'calling_display' with the receive response.\n#\n#\nmodule TcapCNAM\n  def init_tcap_cnam_remap( params )\n    params\n  end\n  \n  def tcap_cnam_remap( params )\n    if !params[:telephony_services].nil?\n      params[:telephony_services].each do |service|\n        \n        if( service[:type] == \"CNAM Request\" && service[:enabled] )     \n          serviceParams = service[:params]\n          if( serviceParams[:return_to_script] )\n          \n            # We have received a response from the CNAM service\n            response = service[:response]\n            if( response[:success] )\n              # Overwrite the caller name \n              call = params[ :call ]\n              call[:calling_display_type] = :unspecified\n              call[:calling_display] = response[ :caller_name ]\n              params[ :call ] = call\n            \n            end\n          \n            # Make sure the script is not called again\n            serviceParams[:return_to_script] = false\n\n            # Don't want to do the query again\n            service[:enabled] = false\n            \n          else\n            # The script is being called prior to the CNAM request.  Indicate to\n            # the script to make the query\n            serviceParams[:return_to_script] = true\n          \n          end\n        \n        end\n      \n      end\n   \n    end\n    \n    params\n    \n  end\n    \nend"
        },
        "tcap_cnam_remapping.rb" : {
          "load_on_startup" : false,
          "name" : "tcap_cnam_remapping.rb",
          "script_type" : "main",
          "source" : "#\n# Script version 1.0\n#\n# Version history:\n#  1.0  First version of this script\n#\n#\n# This example makes use of a \"before_filter\" to alter the caller name (aka the display name string) of an incoming call with the\n# generic name receive from a CNAM server.\n#\n#\nrequire 'base_routing'\nrequire 'tcap_cnam'\n\n\n# This class implements basic routing\nclass SimpleRouting< BaseRouting\n  include TcapCNAM\n\n  # This filter remaps the caller name after a query to the CNAM server using TCAP/SS7 protocol\n  before_filter :method => :tcap_cnam_remap\n\n  route_match :call_field_name => :called\n  route_match :call_field_name => :calling\n  route_match :call_field_name => :nap\n  route_remap :call_field_name => :called, :route_field_name => :remapped_called\n  route_remap :call_field_name => :calling, :route_field_name => :remapped_calling\n  route_remap :call_field_name => :nap, :route_field_name => :remapped_nap\n\nend\n\n@@routing = SimpleRouting.new\n\ndef init_routes( routes )\n  @@routing.init routes\nend\n\ndef route( call, nap_list )\n  @@routing.route call, nap_list\nend\n"
        },
        "toolpack_wrappers.rb" : {
          "load_on_startup" : false,
          "name" : "toolpack_wrappers.rb",
          "script_type" : "util",
          "source" : "# Toolpack Wrappers\n# The classes in this file are wrappers around Toolpack object.\n# There main goal is to optimize the access pattern to Toolpack Objects while\n# keeping normal Hash class interface\n\n# Call\n# This represents the current call (incoming)\nclass Call\n  # options:\n  #   attributes: Default content of the hash\n  def initialize(caf_call, options={})\n    @caf_call = caf_call\n    @attributes = options[:attributes] || {:reason => :ok}\n    @attributes[:caf_call] = @caf_call\n  end\n\n  # Hash get\n  def [](attribute)\n    return @attributes[attribute] if @attributes[attribute]\n\n    @attributes[attribute] = @caf_call.get(attribute)\n  end\n\n  # Hash set\n  def []=(attribute, new_value)\n    @attributes[attribute] = new_value\n\n    new_value\n  end\n\n  def merge(params={})\n    Call.new(@caf_call, :attributes => @attributes.merge(params))\n  end\n\n  # other_hash: Can be Call or Hash\n  def merge!(other_hash)\n    other_hash.to_hash.each {|key, value| self[key] = value}\n  end\n\n  def each(&block)\n    @caf_call.list_params.each(&block) if block_given?\n  end\n\n  def dup\n    attributes = {}\n    @attributes.each do |key, value|\n        attributes[key] = value.dup rescue value unless key == \"caf_call\"\n    end\n\n    Call.new(@caf_call, :attributes => attributes)\n  end\n\n  # Hash representation needed by Toolpack\n  def to_hash\n    # caf_call is a special case\n    @attributes.reject{|k,v| k.to_s == \"caf_call\"}\n  end\n\n  # Accept the call with the given route\n  def accept(route)\n    @caf_call.accept self.to_hash, route\n  end\n  \n  def inspect(*args)\n    load_params\n    super(*args)\n  end\n\n  def load_params \n    return @attributes if @attributes_loaded\n    @attributes_loaded = true\n    @caf_call.list_params.each { |k| @attributes[k] = @caf_call.get(k) unless @attributes[k]}\n    @attributes\n  end\n  \nend\n\nclass ScriptParams\n  # options:\n  #   attributes: Default content of the hash\n  def initialize(caf_call, options={})\n    @caf_call = caf_call\n    @attributes = options[:attributes] || {}\n    @attributes[:caf_call] = @caf_call\n  end\n\n  # Hash get\n  def [](attribute)\n    return @attributes[attribute] if @attributes[attribute]\n    load_script_params\n    @attributes[attribute]\n  end\n\n  # Hash set\n  def []=(attribute, new_value)\n    @attributes[attribute] = new_value\n    new_value\n  end\n\n  def each(&block)\n    load_script_params.each(&block) if block_given?\n  end\n\n  # Push all modified attributes to Toolpack\n  def push_all\n    @caf_call.push_script_params(@attributes)\n  end\n\n  def merge(params={})\n    ScriptParams.new(@caf_call, :attributes => @attributes.merge(params))\n  end\n  \n  private\n  # GetScriptParams and GetScriptParam take the same time,\n  # so we preload all the missing attributes when 1 is missing\n  def load_script_params\n    return @attributes if @attributes_loaded\n    @attributes = @caf_call.get_script_params.merge(@attributes)\n    @attributes_loaded = true\n    @attributes\n  end\nend\n\n\n"
        },
        "transit_network_selection.rb" : {
          "load_on_startup" : false,
          "name" : "transit_network_selection.rb",
          "script_type" : "filter",
          "source" : "#\n# Script version 1.3\n#\n# Version history:\n#  1.3  Changed release cause to invalid transit network when dropping a call because no network identification match\n#  1.2  Testing if call[:network_identification] is empty in addition to testing if nil?\n#  1.1  Removed debug information\n#  1.0  First version of this filter\n#\n# This transit network selection script is used to route calls according to the network identification when\n#  the IE TRANSIT_NETWORK_SELECTION is present, or, for Spirou protocol variant, when the Nature of Address is \n#  National (or International) significant number with transit network selection\n#\n# These filters require one ROUTES custom column  \n# * <tt>network_identification</tt>: \n#\n# To use these filters, add the following lines to simple_routing.rb\n#\n# require 'transit_network_selection'\n#\n#  include TransitNetworkSelection\n#  \n#  before_filter :method => :transit_network_selection_before_filter \n#  route_match :method => :transit_network_selection_route_match\n#  after_filter :method => :transit_network_selection_after_filter\n\nmodule TransitNetworkSelection\n\n  # Initialization methods\n  #\n  def init_transit_network_selection_before_filter params\n    #Keep track if a route was rejected because of transit network selection, to return the good release cause\n    @route_rejected = false\n  end\n  \n  def init_transit_network_selection_route_match params\n\n  end\n\n  def init_transit_network_selection_after_filter params\n\n  end\n\n  # Specific to Spirou, remaps called nature of address, removes selection prefix \n  # and uses it as the network_identification field\n  # This example will work for selection prefixes of 4 digits, modify .slice!(0..3) according to \n  # the length of the selection prefixes in your network\n def transit_network_selection_before_filter params\n   #Reset value to false\n   @route_rejected = false\n   \n   call = params[:call]\n   called_noa = call[:called_noa]\n\n   case called_noa\n     when 'international_number_with_transit_network_selection'\n       call[:called_noa] = 'international_number'\n       call[:network_identification] = call[:called].slice!(0..3)\n     when 'national_number_with_transit_network_selection'\n       call[:called_noa] = 'national_number'\n       call[:network_identification] = call[:called].slice!(0..3)\n   end\n\n   params\n\n end \n  \n  # Will only accept calls if no network identification is requested, or if \n  # the network identification of the route matches with the requested one\n def transit_network_selection_route_match( route, call, nap_list )\n    requested_network_id = call[:network_identification]\n\n    if requested_network_id.nil? || requested_network_id.empty?\n      return true\n    end\n\n    route_network_id = route[:network_identification]\n\n    if requested_network_id != route_network_id\n      #Keep track if a route was rejected because of transit network selection, to return the good release cause\n      @route_rejected = true\n      return false\n    end\n    \n    return true\n end\n\n #Test to send the good release reason if there is no route\n #Remove network identification, we do not want to transmit it in the outgoing call\n def transit_network_selection_after_filter params\n   routes = params[ :routes ]\n   #If no routes where found because routes were rejected for transit network selection, \n   #raise an exception with the correct error code \n   raise RoutingException, :invalid_transit_network if (routes.empty? && @route_rejected == true)\n   \n   #Remove network identification\n   call = params[:call]\n   call[:network_identification] = nil\n\n   params\n\n end\n\nend "
        },
        "trie.rb" : {
          "load_on_startup" : false,
          "name" : "trie.rb",
          "script_type" : "util",
          "source" : "#\n# Script version 1.0\n#\n# Version history:\n#  1.0  First version of this script\n#\n=begin rdoc\nA Trie is a data structure that stores key value pairs in a tree-like fashion. It allows\nO(m) lookup speed, where m is the length of the key searched, and has no chance of collisions,\nunlike hash tables. Because of its nature, search misses are quickly detected.\nTries are often used for longest prefix algorithms, wildcard matching, and can be used to\nimplement a radix sort.\n\nThis implemention is based on a Ternary Search Tree.\n=end\nclass Trie\n  # Create a new, empty Trie.\n  #\n  # t = Containers::Trie.new\n  # t[\"hello\"] = \"world\"\n  # t[\"hello] #=> \"world\"\n  def initialize\n    @root = nil\n  end\n\n  # Adds a key, value pair to the Trie, and returns the value if successful. The to_s method is\n  # called on the parameter to turn it into a string.\n  #\n  # Complexity: O(m)\n  #\n  # t = Containers::Trie.new\n  # t[\"hello\"] = \"world\"\n  # t.push(\"hello\", \"world\") # does the same thing\n  # t[\"hello\"] #=> \"world\"\n  # t[1] = 1\n  # t[1] #=> 1\n  def push(key, value)\n    key = key.to_s\n    return nil if key.empty?\n    @root = push_recursive(@root, key, 0, value)\n    value\n  end\n  alias_method :[]=, :push\n\n  # Returns true if the key is contained in the Trie.\n  #\n  # Complexity: O(m) worst case\n  #\n  def has_key?(key)\n    key = key.to_s\n    return false if key.empty?\n    !(get_recursive(@root, key, 0).nil?)\n  end\n\n  # Returns the value of the desired key, or nil if the key doesn't exist.\n  #\n  # Complexity: O(m) worst case\n  #\n  # t = Containers::Trie.new\n  # t.get(\"hello\") = \"world\"\n  # t.get(\"non-existant\") #=> nil\n  def get(key)\n    key = key.to_s\n    return nil if key.empty?\n    node = get_recursive(@root, key, 0)\n    node ? node.last : nil\n  end\n  alias_method :[], :get\n\n  # Returns the longest key that has a prefix in common with the parameter string. If\n  # no match is found, the blank string \"\" is returned.\n  #\n  # Complexity: O(m) worst case\n  #\n  # t = Containers::Trie.new\n  # t.push(\"Hello\", \"World\")\n  # t.push(\"Hello, brother\", \"World\")\n  # t.push(\"Hello, bob\", \"World\")\n  # t.longest_prefix(\"Hello, brandon\") #=> \"Hello\"\n  # t.longest_prefix(\"Hel\") #=> \"\"\n  # t.longest_prefix(\"Hello\") #=> \"Hello\"\n  def longest_prefix(string)\n    string = string.to_s\n    return nil if string.empty?\n    len = prefix_recursive(@root, string, 0)\n    string[0...len]\n  end\n\n  # Returns a sorted array containing strings that match the parameter string. The wildcard\n  # characters that match any character are '*' and '.' If no match is found, an empty\n  # array is returned.\n  #\n  # Complexity: O(n) worst case\n  #\n  # t = Containers::Trie.new\n  # t.push(\"Hello\", \"World\")\n  # t.push(\"Hilly\", \"World\")\n  # t.push(\"Hello, bob\", \"World\")\n  # t.wildcard(\"H*ll.\") #=> [\"Hello\", \"Hilly\"]\n  # t.wildcard(\"Hel\") #=> []\n  def wildcard(string)\n    string = string.to_s\n    return nil if string.empty?\n    ary = []\n    ary << wildcard_recursive(@root, string, 0, \"\")\n    ary.flatten.compact.sort\n  end\n\n  class Node # :nodoc: all\n    attr_accessor :left, :mid, :right, :char, :value, :end\n\n    def initialize(char, value)\n      @char = char\n      @value = value\n      @left = @mid = @right = nil\n      @end = false\n    end\n\n    def last?\n      @end == true\n    end\n  end\n\n  def wildcard_recursive(node, string, index, prefix)\n    return nil if node.nil? || index == string.length\n    arr = []\n    char = string[index]\n    if (char.chr == \"*\" || char.chr == \".\" || char < node.char)\n      arr << wildcard_recursive(node.left, string, index, prefix)\n    end\n    if (char.chr == \"*\" || char.chr == \".\" || char > node.char)\n      arr << wildcard_recursive(node.right, string, index, prefix)\n    end\n    if (char.chr == \"*\" || char.chr == \".\" || char == node.char)\n      arr << \"#{prefix}#{node.char.chr}\" if node.last?\n      arr << wildcard_recursive(node.mid, string, index+1, prefix + node.char.chr)\n    end\n    arr\n  end\n\n  def prefix_recursive(node, string, index)\n    return 0 if node.nil? || index == string.length\n    len = 0\n    rec_len = 0\n    char = string[index]\n    if (char < node.char)\n      rec_len = prefix_recursive(node.left, string, index)\n    elsif (char > node.char)\n      rec_len = prefix_recursive(node.right, string, index)\n    else\n      len = index+1 if node.last?\n      rec_len = prefix_recursive(node.mid, string, index+1)\n    end\n    len > rec_len ? len : rec_len\n  end\n\n  def push_recursive(node, string, index, value)\n    char = string[index]\n    node = Node.new(char, value) if node.nil?\n    if (char < node.char)\n      node.left = push_recursive(node.left, string, index, value)\n    elsif (char > node.char)\n      node.right = push_recursive(node.right, string, index, value)\n    elsif (index < string.length-1) # We're not at the end of the input string; add next char\n      node.mid = push_recursive(node.mid, string, index+1, value)\n    else\n      node.end = true\n      node.value = value\n    end\n    node\n  end\n\n  # Returns [char, value] if found\n  def get_recursive(node, string, index)\n    return nil if node.nil?\n    char = string[index]\n    if (char < node.char)\n      return get_recursive(node.left, string, index)\n    elsif (char > node.char)\n      return get_recursive(node.right, string, index)\n    elsif (index < string.length-1) # We're not at the end of the input string; add next char\n      return get_recursive(node.mid, string, index+1)\n    else\n      return node.last? ? [node.char, node.value] : nil\n    end\n  end\nend"
        },
        "utils.rb" : {
          "load_on_startup" : false,
          "name" : "utils.rb",
          "script_type" : "util",
          "source" : "#\n# Script version 1.2\n#\n# Version history:\n#  1.2  Remove the Cache class that was causing memory leak\n#  1.1  Add the Cache class\n#  1.0  First version of this script\n#\n\nmodule Utils\n  def self.deep_freeze( obj )\n    if obj.class.to_s == \"Array\"\n      obj.each { |element|\n        deep_freeze element\n      }\n    end\n    obj.freeze\n  end\nend\n\n"
        }
      }
    }
  },
  "sip_header_manipulation_cfgs" : {
    "sip register filtering" : {
      "enabled" : true,
      "name" : "sip register filtering",
      "inbound_rules" : {
        "default_scanner_protection" : {
          "cond_operator_id" : 2,
          "continue" : false,
          "enabled" : true,
          "method" : "REGISTER",
          "name" : "default_scanner_protection",
          "notes" : "Default rule to identify and drop register from scanner.  Includes User-Agent header values used commonly by scanners",
          "position" : 1,
          "actions" : {
            "default rule action drop" : {
              "action_param1" : "",
              "action_param2" : "",
              "action_type" : "Drop",
              "name" : "default rule action drop",
              "position" : 1
            }
          },
          "conditions" : {
            "default_rule_condition" : {
              "name" : "default_rule_condition",
              "operand" : "Header",
              "operand_param1" : "User-Agent",
              "operator" : "matches_regex",
              "position" : 1,
              "value" : "^(sipvicious|sipcli|sip-scan|sipsak|sundayddr|friendly-scanner|SIVuS|Gulp|sipv|smap|friendly-request|VaxIPUserAgent|VaxSIPUserAgent|siparmyknife|Test Agent).*"
            }
          }
        }
      },
      "outbound_rules" : {
      }
    }
  },
  "sip_registration_domains" : {
    "SIP_Registrar_FQDN" : {
      "advanced_parameters" : {
        "contact_expire_grace_time" : "1 minute"
      },
      "default_contact_expire" : "1 hour",
      "domain_name" : "sip1.chivigibi.net",
      "enabled" : true,
      "forwarding_parameters" : {
        "forward_after_switchover" : true,
        "forwarding_mode" : "Contact Remapping",
        "max_pending_register_forward" : 10000,
        "max_simultaneous_register_forward" : 10000,
        "min_registrar_expire" : "1 hour"
      },
      "max_contact_expire" : "1 hour",
      "max_registered_users" : 100000,
      "min_contact_expire" : "30 seconds",
      "name" : "SIP_Registrar_FQDN",
      "registration_method" : "Forwarding",
      "routing_method" : "Register Source",
      "unsubscribe_on_unregister" : true,
      "sip_registration_registrars" : {
        "SIP_Registrar_IP" : {
          "address" : "10.98.20.10",
          "enabled" : true,
          "name" : "SIP_Registrar_IP",
          "poll_registrar" : true,
          "polling_interval" : "5 seconds",
          "port" : 5060,
          "position" : 1,
          "response_timeout" : "12 seconds",
          "sip_transport_server" : "TS_LAN_00"
        }
      }
    }
  },
  "sip_stacks" : {
    "SIP_STACK" : {
      "dns_parameters" : {
        "cache_expire_time" : "12 hours",
        "enabled" : false,
        "local_dns_transport_server" : {
          "ip_interface" : "Host.LAN_00",
          "port" : 1024,
          "port_type" : "UDP"
        },
        "query_retry_count" : 3,
        "query_timer" : "20 seconds",
        "server_address" : "",
        "server_dns_port" : 53,
        "use_cache" : true,
        "use_enum" : false
      },
      "enabled" : true,
      "header_parameters_and_timers" : {
        "add_rport" : true,
        "allow_recurse" : false,
        "always_send_100" : true,
        "default_invite_expires" : "2 minutes",
        "default_register_expires" : "30 seconds",
        "default_session_timer_value" : "30 minutes",
        "domain_name" : "",
        "insert_accept" : true,
        "insert_allow" : true,
        "insert_date" : true,
        "insert_expires" : true,
        "insert_supported" : true,
        "insert_timestamp" : true,
        "max_forward" : 70,
        "min_se_value" : "90 seconds",
        "organization" : "",
        "provisional_response_type" : "Supported",
        "subject" : "",
        "t1_timer" : "500 milliseconds",
        "t2_timer" : "4 seconds",
        "t4_timer" : "5 seconds",
        "use_compact_hdr" : false,
        "use_fqdn_contact" : false,
        "use_session_timer" : true,
        "user_agent" : ""
      },
      "name" : "SIP_STACK",
      "quirks_and_advanced_parameters" : {
        "add_angle_brackets_around_sip_uri" : true,
        "advanced_transport_servers_configuration" : false,
        "clear_subscriptions_on_call_complete" : false,
        "etsi_ts102_0272_2006_compliant" : false,
        "generate_space_after_colon" : false,
        "refuse_initial_invite_with_to_tag" : false,
        "support_latin_characters" : false,
        "support_method_update" : true,
        "support_pound_and_backslash_in_user_info" : true,
        "support_quotes_in_quoted_string" : false,
        "support_spiral_call" : false
      },
      "unit" : "HOST",
      "transport_servers" : {
        "TS_LAN_00" : {
          "ip_header_tos" : 0,
          "ip_interface" : "Host.LAN_00",
          "name" : "TS_LAN_00",
          "port" : 5060,
          "port_type" : "UDP"
        },
        "TS_WAN_00" : {
          "ip_header_tos" : 0,
          "ip_interface" : "Host.WAN_00",
          "name" : "TS_WAN_00",
          "port" : 5060,
          "port_type" : "UDP"
        }
      }
    }
  },
  "snmp_services" : {
    "configuration" : {
      "advanced_parameters" : {
        "ip_header_tos" : 0,
        "system_name" : "",
        "trap_polling_delay" : "1 minute"
      },
      "enabled" : true,
      "ip_port" : 161,
      "system_contact" : "",
      "system_description" : "",
      "system_location" : "",
      "system_object_id" : "1",
      "use_system_names" : true,
      "snmp_v1_v2_communities" : {
        "private" : {
          "name" : "private",
          "write_operation_allowed" : true
        },
        "public" : {
          "name" : "public",
          "write_operation_allowed" : false
        }
      },
      "snmpv3_users" : {
      },
      "trap_destinations" : {
      }
    }
  },
  "ssh_services" : {
    "default_service" : {
      "ip_header_tos" : 0,
      "ip_interfaces" : [
        "ANY"
      ],
      "managed_by_web" : true,
      "port" : 22
    }
  },
  "telephony_services" : {
  },
  "user_tone_definition_profiles" : {
  },
  "***meta***" : {
    "src_path" : "",
    "version" : "3.0.107"
  }
}