diff options
Diffstat (limited to 'misc/freeswitch/scripts/dialplan/sip_call.lua')
-rw-r--r-- | misc/freeswitch/scripts/dialplan/sip_call.lua | 266 |
1 files changed, 266 insertions, 0 deletions
diff --git a/misc/freeswitch/scripts/dialplan/sip_call.lua b/misc/freeswitch/scripts/dialplan/sip_call.lua new file mode 100644 index 0000000..57f92c6 --- /dev/null +++ b/misc/freeswitch/scripts/dialplan/sip_call.lua @@ -0,0 +1,266 @@ +-- Gemeinschaft 5 module: sip call class +-- (c) AMOOMA GmbH 2012 +-- + +module(...,package.seeall); + +SipCall = {} + +-- Create SipCall object +function SipCall.new(self, arg) + arg = arg or {} + object = arg.object or {} + setmetatable(object, self); + self.__index = self; + self.log = arg.log; + self.session = arg.session; + self.record = arg.record; + self.database = arg.database; + self.domain = arg.domain; + self.caller = arg.caller; + self.on_answer = arg.on_answer; + self.calling_object = arg.calling_object; + return object; +end + + +function SipCall.wait_answer(self, caller_session, callee_session, timeout, start_time) + if caller_session:ready() and callee_session:ready() then + callee_session:waitForAnswer(caller_session); + end + + while true do + if not caller_session:ready() then + return 'ORIGINATOR_CANCEL'; + elseif not callee_session:ready() then + return 'UNSPECIFIED'; + elseif (os.time() - start_time) > timeout then + return 'NO_ANSWER'; + elseif callee_session:answered() then + return 'SUCCESS'; + end + + self.caller:sleep(500); + end +end + + +function SipCall.wait_hangup(self, caller_session, callee_session) + local hangup_on = { + CS_HANGUP = true, + CS_DESTROY = true, + } + + while true do + local state_caller = caller_session:getState(); + local state_callee = callee_session:getState(); + if hangup_on[state_caller] or hangup_on[state_callee] then + break; + end + caller_session:sleep(500); + end +end + + +function SipCall.call_waiting_busy(self, sip_account) + require 'common.str' + if common.str.to_b(sip_account.record.call_waiting) then + self.log:info('CALL_WAITING - status: enabled'); + return false; + else + local state = sip_account:call_state(); + self.log:info('CALL_WAITING - status: disabled, sip_account state: ', state); + return state; + end +end + + +function SipCall.fork(self, destinations, arg ) + local dial_strings = {} + + require 'common.sip_account' + local sip_account_class = common.sip_account.SipAccount:new{ log = self.log, database = self.database }; + + local call_result = { code = 404, phrase = 'No destination' }; + local some_destinations_busy = false; + + for index, destination in ipairs(destinations) do + local origination_variables = { 'gs_fork_index=' .. index } + + self.log:info('FORK ', index, '/', #destinations, ' - ', destination.type, '=', destination.id, '/', destination.gateway or destination.uuid, '@', destination.node_id, ', number: ', destination.number); + if not destination.node_local or destination.type == 'node' then + require 'common.node' + local node = nil; + if tonumber(destination.gateway) then + node = common.node.Node:new{ log = self.log, database = self.database }:find_by_id(tonumber(destination.gateway)); + else + node = common.node.Node:new{ log = self.log, database = self.database }:find_by_id(destination.node_id); + end + if node then + table.insert(origination_variables, 'sip_h_X-GS_node_id=' .. self.caller.local_node_id); + table.insert(dial_strings, '[' .. table.concat(origination_variables , ',') .. ']sofia/gateway/' .. node.record.name .. '/' .. destination.number); + end + elseif destination.type == 'sipaccount' then + local callee_id_params = ''; + local sip_account = sip_account_class:find_by_id(destination.id); + local call_waiting = self:call_waiting_busy(sip_account); + if not call_waiting then + destinations[index].numbers = sip_account:phone_numbers(); + + if not arg.callee_id_name then + table.insert(origination_variables, "effective_callee_id_name='" .. sip_account.record.caller_name .. "'"); + end + if not arg.callee_id_number then + table.insert(origination_variables, "effective_callee_id_number='" .. destination.number .. "'"); + end + if destination.alert_info then + table.insert(origination_variables, "alert_info='" .. destination.alert_info .. "'"); + end + table.insert(dial_strings, '[' .. table.concat(origination_variables , ',') .. ']user/' .. sip_account.record.auth_name); + else + some_destinations_busy = true; + call_result = { code = 486, phrase = 'User busy', disposition = 'USER_BUSY' }; + end + elseif destination.type == 'gateway' then + if destination.caller_id_number then + table.insert(origination_variables, "origination_caller_id_number='" .. destination.caller_id_number .. "'"); + end + if destination.caller_id_name then + table.insert(origination_variables, "origination_caller_id_name='" .. destination.caller_id_name .. "'"); + end + table.insert(dial_strings, '[' .. table.concat(origination_variables , ',') .. ']sofia/gateway/' .. destination.gateway .. '/' .. destination.number); + elseif destination.type == 'dial' then + if destination.caller_id_number then + table.insert(origination_variables, "origination_caller_id_number='" .. destination.caller_id_number .. "'"); + end + if destination.caller_id_name then + table.insert(origination_variables, "origination_caller_id_name='" .. destination.caller_id_name .. "'"); + end + table.insert(dial_strings, '[' .. table.concat(origination_variables , ',') .. ']' .. destination.number); + else + self.log:info('FORK ', index, '/', #destinations, ' - unhandled destination type: ', destination.type, ', number: ', destination.number); + end + end + + if #dial_strings == 0 then + self.log:notice('FORK - no active destinations - result: ', call_result.code, ' ', call_result.phrase); + return call_result; + end + + self.caller:set_callee_id(arg.callee_id_number, arg.callee_id_name); + self.caller:set_header('X-GS_account_uuid', self.caller.account_uuid); + self.caller:set_header('X-GS_account_type', self.caller.account_type); + self.caller:set_header('X-GS_auth_account_type', self.caller.auth_account_type); + self.caller:set_header('X-GS_auth_account_uuid', self.caller.auth_account_uuid); + self.caller:set_header('X-GS_loop_count', self.caller.loop_count); + + self.caller:set_variable('call_timeout', arg.timeout ); + self.log:info('FORK DIAL - destinations: ', #dial_strings, ', timeout: ', arg.timeout); + + if arg.send_ringing then + self.caller:execute('ring_ready'); + end + + local start_time = os.time(); + local session_callee = freeswitch.Session('{local_var_clobber=true}' .. table.concat(dial_strings, ','), self.caller.session); + self.log:debug('FORK SESSION_INIT - dial_time: ', os.time() - start_time); + local answer_result = self:wait_answer(self.caller.session, session_callee, arg.timeout, start_time); + local fork_index = nil; + self.log:info('FORK ANSWER - status: ', answer_result, ', dial_time: ', os.time() - start_time); + if answer_result == 'SUCCESS' then + session_callee:setAutoHangup(false); + fork_index = tonumber(session_callee:getVariable('gs_fork_index')) or 0; + local destination = destinations[fork_index]; + + if arg.bypass_media_network then + local callee_uuid = session_callee:get_uuid(); + + if callee_uuid and self.caller.uuid and freeswitch then + require 'common.ipcalc' + local callee_network_str = self.caller:to_s('bleg_network_addr'); + local caller_network_str = self.caller:to_s('network_addr'); + local callee_network_addr = common.ipcalc.ipv4_to_i(callee_network_str); + local caller_network_addr = common.ipcalc.ipv4_to_i(caller_network_str); + local network, netmask = common.ipcalc.ipv4_to_network_netmask(arg.bypass_media_network); + if network and netmask and callee_network_addr and caller_network_addr + and common.ipcalc.ipv4_in_network(callee_network_addr, network, netmask) + and common.ipcalc.ipv4_in_network(caller_network_addr, network, netmask) then + self.log:info('FORK ', fork_index, ' BYPASS_MEDIA - caller_ip: ', caller_network_str, + ', callee_ip: ', callee_network_str, + ', subnet: ', arg.bypass_media_network, + ', uuid: ', self.caller.uuid, ', bleg_uuid: ', callee_uuid); + freeswitch.API():execute('uuid_media', 'off ' .. self.caller.uuid); + freeswitch.API():execute('uuid_media', 'off ' .. callee_uuid); + end + end + end + + if self.on_answer then + self.on_answer(self.calling_object, destination); + end + + self.caller:set_variable('gs_destination_type', destination.type); + self.caller:set_variable('gs_destination_id', destination.id); + self.caller:set_variable('gs_destination_uuid', destination.uuid); + + self.log:info('FORK ', fork_index, + ' BRIDGE - destination: ', destination.type, '=', destination.id, '/', destination.uuid,'@', destination.node_id, + ', number: ', destination.number, + ', dial_time: ', os.time() - start_time); + freeswitch.bridge(self.caller.session, session_callee); + self:wait_hangup(self.caller.session, session_callee); + end + + -- if session_callee:ready() then + -- self.log:info('FORK - hangup destination channel'); + -- session_callee:hangup('ORIGINATOR_CANCEL'); + -- end + + call_result = {}; + call_result.disposition = session_callee:hangupCause(); + call_result.fork_index = fork_index; + + if some_destinations_busy and call_result.disposition == 'USER_NOT_REGISTERED' then + call_result.phrase = 'User busy'; + call_result.code = 486; + call_result.disposition = 'USER_BUSY'; + elseif call_result.disposition == 'USER_NOT_REGISTERED' then + call_result.phrase = 'User offline'; + call_result.code = 480; + elseif call_result.disposition == 'NO_ANSWER' then + call_result.phrase = 'No answer'; + call_result.code = 408; + elseif call_result.disposition == 'NORMAL_TEMPORARY_FAILURE' then + call_result.phrase = 'User offline'; + call_result.code = 480; + else + call_result.cause = self.caller:to_s('last_bridge_hangup_cause'); + call_result.code = self.caller:to_i('last_bridge_proto_specific_hangup_cause'); + call_result.phrase = self.caller:to_s('sip_hangup_phrase'); + end + + self.log:info('FORK EXIT - disposition: ', call_result.disposition, + ', cause: ', call_result.cause, + ', code: ', call_result.code, + ', phrase: ', call_result.phrase, + ', dial_time: ', os.time() - start_time); + + return call_result; +end + +-- Return call forwarding settngs +function SipCall.conditional_call_forwarding(self, cause, call_forwarding) + local condition_map = {USER_NOT_REGISTERED="offline", NO_ANSWER="noanswer", USER_BUSY="busy"} + local condition = condition_map[cause] + if call_forwarding and condition and call_forwarding[condition] then + log:debug('call forwarding on ' .. condition .. ' - destination: ' .. call_forwarding[condition].destination .. ', type: ' .. call_forwarding[condition].call_forwardable_type); + return call_forwarding[condition] + end +end + +function SipCall.set_callee_variables(self, sip_account) + self.session:setVariable("gs_callee_account_id", sip_account.id); + self.session:setVariable("gs_callee_account_type", "SipAccount"); + self.session:setVariable("gs_callee_account_owner_type", sip_account.sip_accountable_type); + self.session:setVariable("gs_callee_account_owner_id", sip_account.sip_accountable_id); +end |