local active = redis.call('GET', KEYS[1]) if not active or active == '' then return { 'reject', 'CONFIG_MISSING', 'none', 'none', 'none', 'none', 'no_active_version', 'none', 'none', 'none', 'none', 'UNKNOWN', 'UNKNOWN', 'UNKNOWN', 'UNKNOWN', 'UNKNOWN', 'UNKNOWN', '' } end local source_ip = ARGV[1] local caller = ARGV[2] or '' local callee = ARGV[3] or '' local prefix = 'cfg:v:' .. active local function field(json, name) return string.match(json, '"' .. name .. '":"([^"]*)"') end local function normalize_callee(value) local digits = string.gsub(value or '', '[^0-9]', '') if string.sub(digits, 1, 4) == '0086' then digits = string.sub(digits, 5) elseif string.sub(digits, 1, 2) == '86' and string.len(digits) == 13 then digits = string.sub(digits, 3) end return digits end local function unknown_number(callee_digits) return { city_code = 'UNKNOWN', city_name = 'UNKNOWN', province_code = 'UNKNOWN', province_name = 'UNKNOWN', carrier = 'UNKNOWN', number_type = 'UNKNOWN', normalized = callee_digits or '' } end local function with_geo_city(info) if info.city_code == 'UNKNOWN' or info.province_code ~= 'UNKNOWN' then return info end local city_json = redis.call('GET', prefix .. ':geo_city:' .. info.city_code) if city_json then info.province_code = field(city_json, 'provinceCode') or info.province_code info.province_name = field(city_json, 'provinceName') or info.province_name info.city_name = field(city_json, 'cityName') or info.city_name end return info end local function resolve_carrier(digits, fallback) if string.len(digits) >= 4 then local prefix4_json = redis.call('GET', prefix .. ':carrier_prefix:' .. string.sub(digits, 1, 4)) if prefix4_json then return field(prefix4_json, 'carrier') or fallback end end if string.len(digits) >= 3 then local prefix3_json = redis.call('GET', prefix .. ':carrier_prefix:' .. string.sub(digits, 1, 3)) if prefix3_json then return field(prefix3_json, 'carrier') or fallback end end return fallback end local function resolve_number(value) local digits = normalize_callee(value) local info = unknown_number(digits) if string.match(digits, '^1%d%d%d%d%d%d%d%d%d%d$') then info.number_type = 'MOBILE' local segment_json = redis.call('GET', prefix .. ':phone_segment:' .. string.sub(digits, 1, 7)) if segment_json then info.city_code = field(segment_json, 'cityCode') or info.city_code info.city_name = field(segment_json, 'cityName') or info.city_name info.province_code = field(segment_json, 'provinceCode') or info.province_code info.province_name = field(segment_json, 'provinceName') or info.province_name info.carrier = field(segment_json, 'carrier') or info.carrier end info.carrier = resolve_carrier(digits, info.carrier) return with_geo_city(info) end if string.sub(digits, 1, 1) == '0' and string.len(digits) >= 3 then info.number_type = 'LANDLINE' local area_json = nil if string.len(digits) >= 4 then area_json = redis.call('GET', prefix .. ':area_code:' .. string.sub(digits, 1, 4)) end if not area_json and string.len(digits) >= 3 then area_json = redis.call('GET', prefix .. ':area_code:' .. string.sub(digits, 1, 3)) end if area_json then info.city_code = field(area_json, 'cityCode') or info.city_code info.city_name = field(area_json, 'cityName') or info.city_name info.province_code = field(area_json, 'provinceCode') or info.province_code info.province_name = field(area_json, 'provinceName') or info.province_name end return with_geo_city(info) end info.carrier = resolve_carrier(digits, info.carrier) return info end local number_info = resolve_number(callee) local function result(decision, reason, gateway_id, version, line_group_id, policy_id, customer_id, vendor_id, vendor_gateway_id, host, port) return { decision, reason, gateway_id, version, line_group_id, policy_id, customer_id, vendor_id, vendor_gateway_id, host, port, number_info.city_code, number_info.city_name, number_info.province_name, number_info.carrier, number_info.number_type, number_info.province_code, number_info.normalized } end local gateway_id = redis.call('GET', prefix .. ':auth:ip:' .. source_ip) if not gateway_id then return result('reject', 'AUTH_MISSING', 'none', active, 'none', 'none', 'no_auth_ip', 'none', 'none', 'none', 'none') end local gateway_json = redis.call('GET', prefix .. ':customer_gateway:' .. gateway_id) if not gateway_json then return result('reject', 'GATEWAY_MISSING', gateway_id, active, 'none', 'none', 'gateway_missing', 'none', 'none', 'none', 'none') end if not string.find(gateway_json, '"status":"ENABLED"', 1, true) then return result('reject', 'GATEWAY_DISABLED', gateway_id, active, 'none', 'none', 'gateway_disabled', 'none', 'none', 'none', 'none') end local customer_id = string.match(gateway_json, '"customerId":"([^"]+)"') if not customer_id then return result('reject', 'CUSTOMER_MISSING', gateway_id, active, 'none', 'none', 'customer_id_missing', 'none', 'none', 'none', 'none') end local customer_json = redis.call('GET', prefix .. ':customer:' .. customer_id) if not customer_json then return result('reject', 'CUSTOMER_MISSING', gateway_id, active, 'none', 'none', 'customer_missing', 'none', 'none', 'none', 'none') end if not string.find(customer_json, '"status":"ENABLED"', 1, true) then return result('reject', 'CUSTOMER_DISABLED', gateway_id, active, 'none', 'none', 'customer_disabled', 'none', 'none', 'none', 'none') end local function region_blocked(vendor_gateway_id) if number_info.city_code ~= 'UNKNOWN' then local city_blocked = redis.call('SISMEMBER', prefix .. ':vendor_gateway:' .. vendor_gateway_id .. ':blocked_city_codes', number_info.city_code) if city_blocked == 1 then return true end end if number_info.province_code ~= 'UNKNOWN' then local province_blocked = redis.call('SISMEMBER', prefix .. ':vendor_gateway:' .. vendor_gateway_id .. ':blocked_province_codes', number_info.province_code) if province_blocked == 1 then return true end end return false end local function first_vendor_route(line_group_id) local line_group_json = redis.call('GET', prefix .. ':line_group:' .. line_group_id) if not line_group_json then return nil, 'LINE_GROUP_MISSING' end if not string.find(line_group_json, '"status":"ENABLED"', 1, true) then return nil, 'LINE_GROUP_DISABLED' end local items = redis.call('LRANGE', prefix .. ':line_group:' .. line_group_id .. ':items', 0, -1) local skipped_region = false for _, item_json in ipairs(items) do if string.find(item_json, '"status":"ENABLED"', 1, true) then local vendor_gateway_id = string.match(item_json, '"vendorGatewayId":"([^"]+)"') if vendor_gateway_id then local vendor_gateway_json = redis.call('GET', prefix .. ':vendor_gateway:' .. vendor_gateway_id) if vendor_gateway_json and string.find(vendor_gateway_json, '"status":"ENABLED"', 1, true) then if region_blocked(vendor_gateway_id) then skipped_region = true else local vendor_id = string.match(vendor_gateway_json, '"vendorId":"([^"]+)"') or 'none' local host = string.match(vendor_gateway_json, '"host":"([^"]+)"') or 'none' local port = string.match(vendor_gateway_json, '"port":([0-9]+)') or '5060' return {vendor_id, vendor_gateway_id, host, port}, 'OK' end end end end end if skipped_region then return nil, 'NO_VENDOR_ROUTE_REGION_BLOCKED' end return nil, 'NO_VENDOR_ROUTE' end local policies = redis.call('LRANGE', prefix .. ':customer_gateway:' .. gateway_id .. ':policies', 0, -1) for _, policy_json in ipairs(policies) do if string.find(policy_json, '"status":"ENABLED"', 1, true) then local caller_mode = string.match(policy_json, '"callerMode":"([^"]+)"') or 'ANY' local caller_value = string.match(policy_json, '"callerValue":"([^"]*)"') or '' local callee_mode = string.match(policy_json, '"calleeMode":"([^"]+)"') or 'ANY' local callee_value = string.match(policy_json, '"calleeValue":"([^"]*)"') or '' local caller_ok = caller_mode == 'ANY' or (caller_mode == 'EQUALS' and caller == caller_value) or (caller_mode == 'PREFIX' and string.sub(caller, 1, string.len(caller_value)) == caller_value) local callee_ok = callee_mode == 'ANY' or (callee_mode == 'EQUALS' and callee == callee_value) or (callee_mode == 'PREFIX' and string.sub(callee, 1, string.len(callee_value)) == callee_value) if caller_ok and callee_ok then local policy_id = string.match(policy_json, '"id":"([^"]+)"') or 'none' local line_group_id = string.match(policy_json, '"lineGroupId":"([^"]+)"') or 'none' local vendor_route, route_reason = first_vendor_route(line_group_id) if vendor_route then return result('allow', 'OK', gateway_id, active, line_group_id, policy_id, customer_id, vendor_route[1], vendor_route[2], vendor_route[3], vendor_route[4]) end return result('reject', route_reason, gateway_id, active, line_group_id, policy_id, customer_id, 'none', 'none', 'none', 'none') end end end return result('reject', 'NO_POLICY', gateway_id, active, 'none', 'none', 'no_policy_match', 'none', 'none', 'none', 'none')