--
-- (C) 2014-25 - ntop.org
--

if(pragma_once_lua_utils_get == true) then
   -- io.write(debug.traceback().."\n")
   -- avoid multiple inclusions
   return
end

pragma_once_lua_utils_get = true

require "ntop_utils"
require "label_utils"
require "check_redis_prefs"

local clock_start = os.clock()

-- ##############################################

function getInterfaceUrl(ifid)
   if (not ifid) then
      return ("")
   end

   return ntop.getHttpPrefix() .. "/lua/if_stats.lua?ifid=" .. ifid
end

-- ##############################################

function getFirstInterfaceId()
   local ifid = interface.getFirstInterfaceId()

   if ifid ~= nil then
      return ifid, getInterfaceName(ifid)
   end

   return -1, ""
end

-- ##############################################

local function urlencode(str)
   str = string.gsub(str, "\r?\n", "\r\n")
   str = string.gsub(str, "([^%w%-%.%_%~ ])", function(c)
			return string.format("%%%02X", string.byte(c))
   end)
   str = string.gsub(str, " ", "+")
   return str
end

-- ##############################################

function getPageUrl(base_url, params)
   if table.empty(params) then
      return base_url
   end

   local encoded = {}

   for k, v in pairs(params) do
      encoded[k] = urlencode(v)
   end

   local delim = "&"
   if not string.find(base_url, "?") then
      delim = "?"
   end

   return base_url .. delim .. table.tconcat(encoded, "=", "&")
end

-- ##############################################

function getProbesName(flowdevs, show_vlan, shorten_len)
   local probes_list = {}
   for interface, devices in pairs(flowdevs or {}) do
      local device_list = {} 
      if table.len(devices or {}) > 0 then
	 for id, device_info in pairsByKeys(devices or {}, asc) do
	    device_list[device_info.exporter_ip] = getProbeName(device_info.exporter_ip, show_vlan, shorten_len)
	 end
	 probes_list[interface] = device_list
      end
   end

   return probes_list
end

-- ##############################################

function getProbeName(exporter_ip, show_vlan, shorten_len)
   local cached_device_name
   local snmp_cached_dev
   local probe_alias = getFlowDevAlias(exporter_ip, true)

   -- In case an alias is set to the flow exporter, directly use the alias
   if not isEmptyString(probe_alias) and probe_alias ~= exporter_ip then
      return probe_alias
   end

   -- No alias set, let's try with the SNMP
   if ntop.isPro() then
      local dirs = ntop.getDirs()
      package.path = dirs.installdir .. "/pro/scripts/lua/modules/?.lua;" .. package.path
      snmp_cached_dev = require "snmp_cached_dev"
   end

   if snmp_cached_dev then
      cached_device_name = snmp_cached_dev:create(exporter_ip)
   end

   if cached_device_name then
      cached_device_name = cached_device_name["name"]
   else
      local hinfo = hostkey2hostinfo(exporter_ip)
      local exporter_label = hostinfo2label(hinfo, show_vlan, shorten_len)

      if not isEmptyString(exporter_label) then
	 cached_device_name = exporter_label
      end
   end

   return cached_device_name
end

-- ##############################################

function getCategoriesWithProtocols()
   local protocol_categories = interface.getnDPICategories()

   for k, v in pairsByKeys(protocol_categories) do
      protocol_categories[k] = {
	 id = v,
	 protos = interface.getnDPIProtocols(tonumber(v)),
	 count = 0
      }

      for proto, _ in pairs(protocol_categories[k].protos) do
	 protocol_categories[k].count = protocol_categories[k].count + 1
      end
   end

   return protocol_categories
end

-- ##############################################

-- Return the first 'howmany' hosts
function getTopInterfaceHosts(howmany, localHostsOnly)
   hosts_stats = interface.getHostsInfo()
   hosts_stats = hosts_stats["hosts"]
   ret = {}
   sortTable = {}
   n = 0
   for k, v in pairs(hosts_stats) do
      if ((not localHostsOnly) or ((v["localhost"] == true) and (v["ip"] ~= nil))) then
	 sortTable[v["bytes.sent"] + v["bytes.rcvd"] + n] = k
	 n = n + 0.01
      end
   end

   n = 0
   for _v, k in pairsByKeys(sortTable, rev) do
      if (n < howmany) then
	 ret[k] = hosts_stats[k]
	 n = n + 1
      else
	 break
      end
   end

   return (ret)
end

-- #################################

function getIpUrl(ip)
   if isIPv6(ip) then
      -- https://www.ietf.org/rfc/rfc2732.txt
      return "[" .. ip .. "]"
   end
   return ip
end

-- #################################

function getApplicationIcon(name)
   local icon = ""
   if (name == nil) then
      name = ""
   end

   if (findString(name, "Skype")) then
      icon = '<i class=\'fab fa-skype\'></i>'
   elseif (findString(name, "Unknown")) then
      icon = '<i class=\'fas fa-question\'></i>'
   elseif (findString(name, "Twitter")) then
      icon = '<i class=\'fab fa-twitter\'></i>'
   elseif (findString(name, "DropBox")) then
      icon = '<i class=\'fab fa-dropbox\'></i>'
   elseif (findString(name, "Spotify")) then
      icon = '<i class=\'fab fa-spotify\'></i>'
   elseif (findString(name, "Apple")) then
      icon = '<i class=\'fab fa-apple\'></i>'
   elseif (findString(name, "Google") or findString(name, "Chrome")) then
      icon = '<i class=\'fab fa-google-plus-g\'></i>'
   elseif (findString(name, "FaceBook")) then
      icon = '<i class=\'fab fa-facebook\'></i>'
   elseif (findString(name, "Youtube")) then
      icon = '<i class=\'fab fa-youtube\'></i>'
   elseif (findString(name, "thunderbird")) then
      icon = '<i class=\'fas fa-paper-plane\'></i>'
   end

   return (icon)
end

-- #################################

function getApplicationLabel(name, maxlen)
   local icon = getApplicationIcon(name)

   if (maxlen == nil) then
      maxlen = 12
   end

   -- Do not convert to upper case, keep the nDPI case
   -- name = name:gsub("^%l", string.upper)

   if (icon == "") then
      return (shortenString(name, maxlen))
   else
      return (icon .. " " .. shortenString(name, maxlen))
   end
end

-- #################################

function getCategoryLabel(cat_name, cat_id)
   local categories_utils = require 'categories_utils'
   return categories_utils.getCustomCategoryName(cat_id, cat_name)
end

-- ###########################################

function getItemsNumber(n)
   tot = 0
   for k, v in pairs(n) do
      -- io.write(k.."\n")
      tot = tot + 1
   end

   -- io.write(tot.."\n")
   return (tot)
end

-- ###########################################

function getHostCommaSeparatedList(p_hosts)
   hosts = {}
   hosts_size = 0
   for i, host in pairs(split(p_hosts, ",")) do
      hosts[i] = host
      hosts_size = hosts_size + 1
   end
   return hosts, hosts_size
end

-- ##############################################

function getFirstIpFromMac(host_addr)
   local mac_resolved = host_addr
   -- Transforming the MAC into the ip address
   local mac_hosts = interface.getMacHosts(mac_resolved) or {}

   -- Mapping mac with ip and setting up the right href
   for _, h in pairsByKeys(mac_hosts, rev) do
      if (h.broadcast_domain_host) or (table.len(mac_hosts) == 1) then
	 mac_resolved = h.ip
	 break
      end
   end

   return mac_resolved
end

-- ##############################################

function getHostNotesKey(host_key)
   if (host_key == nil) then
      return (nil)
   end
   return "ntopng.cache.host_notes." .. host_key
end

-- ##############################################

function getHostNotes(host_info)
   local host_key

   if type(host_info) == "table" then
      host_key = host_info["host"]
   else
      host_key = host_info
   end

   local notes = ntop.getCache(getHostNotesKey(host_key))

   if isEmptyString(notes) and type(host_info) == "table" and host_info["vlan"] then
      -- Check if there is an alias for the host@vlan
      host_key = hostinfo2hostkey(host_info)
      notes = ntop.getCache(getHostNotesKey(host_key))
   end

   if not isEmptyString(notes) then
      notes = string.lower(notes)
   end

   return notes
end

-- ##############################################

-- A function to give a useful device name
function getDeviceName(device_mac, skip_manufacturer)
   local name = mac2label(device_mac)

   if name == device_mac then
      -- Not found, try with first host
      local info = interface.getHostsInfo(false, nil, 1, 0, nil, nil, nil, tonumber(vlan), nil, nil, device_mac)
      
      if (info ~= nil) then
	 for x, host in pairs(info.hosts) do
	    -- Make sure the IP is in the broadcast domain to avoid setting up names to MACs such as the gateway
	    if host.broadcast_domain_host and not isEmptyString(host.name) and host.name ~= host.ip and host.name ~=
	       "NoIP" then
	       name = host.name
	       elseif host.ip ~= "0.0.0.0" then
		  name = ip2label(host.ip)
		  if name == host.ip then
		     name = nil
		  end
	       end
	    break
	 end
      else
	 name = nil
      end
   end

   if(isEmptyString(name) and (device_mac ~= nil)) then
      if (not skip_manufacturer) then
	 name = get_symbolic_mac(device_mac, true)
      else
	 -- last resort
	 name = device_mac
      end
   end
   
   if isEmptyString(name) or name == device_mac then
      return ''
   end

   return name
end

-- ############################################
-- Redis Utils
-- ############################################

-- Inpur:     General prefix (i.e ntopng.pref)
-- Output:  User based prefix, if it exists
--
-- Examples:
--                With user:  ntopng.pref.user_name
--                Without:    ntopng.pref
function getRedisPrefix(str)
   if _SESSION and not (isEmptyString(_SESSION["user"])) then
      -- Login enabled
      return (str .. '.' .. _SESSION["user"])
   else
      -- Login disabled
      return (str)
   end
end

-----  End of Redis Utils  ------

-- ##############################################

-- Table preferences

function getDefaultTableSort(table_type)
   local table_key = getRedisPrefix("ntopng.sort.table")
   local value = nil

   if (table_type ~= nil) then
      value = ntop.getHashCache(table_key, "sort_" .. table_type)
   end
   if ((value == nil) or (value == "")) then
      value = 'column_'
   end
   return (value)
end

-- ##############################################

function getDefaultTableSortOrder(table_type, force_get)
   local table_key = getRedisPrefix("ntopng.sort.table")
   local value = nil

   if (table_type ~= nil) then
      value = ntop.getHashCache(table_key, "sort_order_" .. table_type)
   end
   if ((value == nil) or (value == "")) and (force_get ~= true) then
      value = 'desc'
   end
   return (value)
end

-- ##############################################

function getDefaultTableSize()
   table_key = getRedisPrefix("ntopng.sort.table")
   value = ntop.getHashCache(table_key, "rows_number")
   if ((value == nil) or (value == "")) then
      value = 10
   end
   return (tonumber(value))
end

-- ##############################################

function getInterfaceSpeed(ifid)
   local ifname = getInterfaceName(ifid)
   local ifspeed = ntop.getCache('ntopng.prefs.ifid_' .. tostring(ifid) .. '.speed')
   if not isEmptyString(ifspeed) and tonumber(ifspeed) ~= nil then
      ifspeed = tonumber(ifspeed)
   else
      ifspeed = interface.getMaxIfSpeed(ifid)
   end

   return ifspeed
end

-- ##############################################

function getCustomnDPIProtoCategoriesKey()
   return "ntop.prefs.custom_nDPI_proto_categories"
end

-- ##############################################

function setCustomnDPIProtoCategory(app_id, new_cat_id)
   ntop.setnDPIProtoCategory(app_id, new_cat_id)

   local key = getCustomnDPIProtoCategoriesKey()

   -- NOTE: when the ndpi struct changes, the custom associations are
   -- reloaded by Ntop::loadProtocolsAssociations
   ntop.setHashCache(key, tostring(app_id), tostring(new_cat_id));
end

-- ##############################################

function removeCustomnDPIProtoCategory(app_id)
   local key = getCustomnDPIProtoCategoriesKey()

   -- NOTE: when the ndpi struct changes, the custom associations are
   -- reloaded by Ntop::loadProtocolsAssociations
   ntop.delHashCache(key, tostring(app_id));
end

-- ##############################################

function getKeysSortedByValue(tbl, sortFunction)
   local keys = {}
   for key in pairs(tbl) do
      table.insert(keys, key)
   end

   table.sort(keys, function(a, b)
		 return sortFunction(tbl[a], tbl[b])
   end)

   return keys
end

-- ##############################################

function getKeys(t, col)
   local keys = {}
   for k, v in pairs(t) do
      keys[tonumber(v[col])] = k
   end
   return keys
end

-- ##############################################

function getFlag(country)
   if ((country == nil) or (country == "")) then
      return ("")
   else
      return (" <a href='" .. ntop.getHttpPrefix() .. "/lua/hosts_stats.lua?country=" .. country .. "'><img src='" ..
	      ntop.getHttpPrefix() .. "/dist/images/blank.gif' class='flag flag-" .. string.lower(country) ..
	      "'></a> ")
   end
end

-- ##############################################

function getUsernameInputPattern()
   -- maximum len must be kept in sync with MAX_PASSWORD_LEN
   return [[^[a-zA-Z0-9._@!-?]{3,30}$]]
end

-- ##############################################

function getPasswordInputPattern()
   -- maximum len must be kept in sync with MAX_PASSWORD_LEN
   return [[^[\w\$\\!\/\(\)= \?\^\*@_\-\u0000-\u0019\u0021-\u00ff]{5,128}$]]
end

-- ##############################################

-- NOTE: keep in sync with validateLicense()
function getLicensePattern()
   return [[^[a-zA-Z0-9\+/=]+$]]
end

-- ##############################################

function getIPv4Pattern()
   return "^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])$"
end

-- ##############################################

function getACLPattern()
   local ipv4 =
      "(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])"
   local netmask = "(\\/([0-9]|[1-2][0-9]|3[0-2]))"
   local cidr = ipv4 .. netmask
   local yesorno_cidr = "[\\+\\-]" .. cidr
   return "^" .. yesorno_cidr .. "(," .. yesorno_cidr .. ")*$"
end

-- ##############################################

function getMacPattern()
   return "^([0-9a-fA-F][0-9a-fA-F]:){5}[0-9a-fA-F]{2}$"
end

-- ##############################################

function getURLPattern()
   return "^https?://.+$"
end

-- ##############################################

function getURLPathPattern()
   return "^\\/lua\\/(?:[^\\/]+\\/)*[^\\/]+\\.lua(/?.*)?$"
end

-- ##############################################

function getRestUrl(script, is_pro, is_enterprise)
   if is_enterprise then
      return (ntop.getHttpPrefix() .. "/lua/enterprise/rest/v2/get/" .. script)
   elseif is_pro then
      return (ntop.getHttpPrefix() .. "/lua/pro/rest/v2/get/" .. script)
   else
      return (ntop.getHttpPrefix() .. "/lua/rest/v2/get/" .. script)
   end
end

-- ##############################################

-- get_mac_classification
function get_mac_classification(m, extended_name)
   local short_extended = ntop.getMacManufacturer(m) or {}

   if extended_name then
      return short_extended.extended or short_extended.short or m
   else
      return short_extended.short or m
   end

   return m
end

-- ##############################################

local magic_macs = {
   ["00:00:00:00:00:00"] = "",
   ["FF:FF:FF:FF:FF:FF"] = "Broadcast",
   ["01:00:0C:CC:CC:CC"] = "CDP",
   ["01:00:0C:CC:CC:CD"] = "CiscoSTP",
   ["01:80:C2:00:00:00"] = "STP",
   ["01:80:C2:00:00:00"] = "LLDP",
   ["01:80:C2:00:00:03"] = "LLDP",
   ["01:80:C2:00:00:0E"] = "LLDP",
   ["01:80:C2:00:00:08"] = "STP",
   ["01:1B:19:00:00:00"] = "PTP",
   ["01:80:C2:00:00:0E"] = "PTP"
}

local magic_short_macs = {
   ["01:00:5E"] = "IPv4mcast",
   ["33:33:"] = "IPv6mcast"
}

-- ###############################################

-- get_symbolic_mac
function get_symbolic_mac_rev(mac_address)
   if (magic_macs[mac_address] ~= nil) then
      return (magic_macs[mac_address])
   else
      local m = string.sub(mac_address, 1, 8)
      local s = get_mac_classification(m)

      if ((s ~= nil) and (s ~= m)) then
	 return(mac_address.. " [".. trimSpace(s) .."]")
      end
   end

   return(mac_address)
end

-- get_symbolic_mac
function get_symbolic_mac(mac_address, no_href, add_extra_info)
   if (magic_macs[mac_address] ~= nil) then
      return (magic_macs[mac_address])
   else
      local m = string.sub(mac_address, 1, 8)
      local t = string.sub(mac_address, 10, 17)

      if (magic_short_macs[m] ~= nil) then
	 if (add_extra_info == true) then
	    return (magic_short_macs[m] .. "_" .. t .. " (" .. macInfo(mac_address) .. ")")
	 else
	    if no_href then
	       return (magic_short_macs[m] .. "_" .. t)
	    else
	       return (macInfoWithSymbName(mac_address, magic_short_macs[m] .. "_" .. t))
	    end
	 end
      else
	 local s = get_mac_classification(m)

	 if (m == s) then
	    if no_href then
	       return get_mac_classification(m) .. ":" .. t
	    else
	       return '<a href="' .. ntop.getHttpPrefix() .. '/lua/mac_details.lua?host=' .. mac_address .. '">' ..
		  get_mac_classification(m) .. ":" .. t .. '</a>'
	    end
	 else
	    local href = ""
	    local href_end = ""

	    if not no_href then
	       href = '<a href="' .. ntop.getHttpPrefix() .. '/lua/mac_details.lua?host=' .. mac_address .. '">'
	       href_end = "</a>"
	    end

	    if (add_extra_info == true) then
	       return (href .. get_mac_classification(m) .. "_" .. t .. " (" .. macInfo(mac_address) .. ")" ..
		       href_end)
	    else
	       return (href .. get_mac_classification(m) .. "_" .. t .. href_end)
	    end
	 end
      end
   end
end

-- ##############################################

function get_mac_url(mac)
   local m = get_symbolic_mac(mac, true)

   if isEmptyString(m) then
      return ""
   end

   local url = ntop.getHttpPrefix() .. "/lua/mac_details.lua?host=" .. mac

   return string.format('[ <a href=\"%s\">%s</a> ]', url, m)
end

-- ##############################################

function get_manufacturer_mac(mac_address)
   local m = string.sub(mac_address, 1, 8)
   local ret = get_mac_classification(m, true --[[ extended name --]] )

   if ret == m then
      ret = ""
   end

   if not isEmptyString(ret) then
      ret = ret:gsub("'", " ")
   end

   return ret
end

-- ##############################################

-- getservbyport
function getservbyport(port_num, proto)
   if not port_num then return '' end
   port_num = tonumber(port_num)
   if not port_num then return '' end

   if not proto then proto = "TCP" end
   proto = string.lower(proto)

   -- io.write(port_num.."@"..proto.."\n")
   return (ntop.getservbyport(port_num, proto))
end

-- ##############################################

function getFlowMaxRate(cli_max_rate, srv_max_rate)
   cli_max_rate = tonumber(cli_max_rate)
   srv_max_rate = tonumber(srv_max_rate)

   if ((cli_max_rate == 0) or (srv_max_rate == 0)) then
      max_rate = 0
   elseif ((cli_max_rate == -1) and (srv_max_rate > 0)) then
      max_rate = srv_max_rate
   elseif ((cli_max_rate > 0) and (srv_max_rate == -1)) then
      max_rate = cli_max_rate
   else
      max_rate = math.min(cli_max_rate, srv_max_rate)
   end

   return (max_rate)
end

-- ####################################################

-- Functions to set/get a device type of user choice

local function getCustomDeviceKey(mac)
   return "ntopng.prefs.device_types." .. string.upper(mac)
end

-- ##############################################

function getCustomDeviceType(mac)
   return tonumber(ntop.getPref(getCustomDeviceKey(mac)))
end

-- #############################################

function setCustomDeviceType(mac, device_type)
   ntop.setPref(getCustomDeviceKey(mac), tostring(device_type))
end

-- ##############################################

-- @brief Compute and return the difference, in seconds, between the local time of this instance and GMT
-- @return A positive or negative number corresponding to the seconds between local time and GMT
local function get_server_timezone_diff_seconds()
   if not server_timezone_diff_seconds then
      local tmp_time = os.time()
      local d1 = os.date("*t", tmp_time)
      local d2 = os.date("!*t", tmp_time)
      -- Forcefully set isdst to false otherwise difference won't work during DST
      d1.isdst = false
      -- Use a minus to have the difference between local time and GMT, rather than between GMT and loca ltime
      server_timezone_diff_seconds = -os.difftime(os.time(d1), os.time(d2))
   end

   return server_timezone_diff_seconds
end

-- ####################################################

-- @brief Get the frontend timezone offset in seconds
-- @return The offset of the frontend timezone
function getFrontendTzSeconds()
   local frontend_tz_offset = nil

   if _COOKIE and _COOKIE.tzoffset then
      -- The timezone offset can be passed from the client as a cookie.
      -- This allows to format the dates in the frontend timezone.
      frontend_tz_offset = tonumber(_COOKIE.tzoffset)
   end

   if frontend_tz_offset == nil then
      -- If timezone is not available in the client _COOKIE,
      -- server timezone is used as fallback
      return -get_server_timezone_diff_seconds()
   end

   return frontend_tz_offset
end

-- ##############################################

function getTopFlowPeers(hostname_vlan, max_hits, detailed, other_options)
   local detailed = detailed or false

   local paginator_options = {
      sortColumn = "column_thpt",
      a2zSortOrder = false,
      detailedResults = detailed,
      maxHits = max_hits
   }

   if other_options ~= nil then
      paginator_options = table.merge(paginator_options, other_options)
   end

   local res = interface.getFlowsInfo(hostname_vlan, paginator_options)
   if ((res ~= nil) and (res.flows ~= nil)) then
      return res.flows
   else
      return {}
   end
end

-- ##############################################

function getSafeChildIcon()
   return ("&nbsp;<font color='#5cb85c'><i class='fas fa-lg fa-child' aria-hidden='true'></i></font>")
end

-- ###########################################

function getNtopngRelease(ntopng_info, verbose)
   local release

   if ntopng_info.oem or ntopng_info["version.nedge_edition"] then
      release = ""
   elseif (ntopng_info["version.enterprise_xl_edition"]) then
      release = "Enterprise XL"
   elseif (ntopng_info["version.enterprise_l_edition"]) then
      release = "Enterprise L"
   elseif (ntopng_info["version.enterprise_m_edition"]) then
      release = "Enterprise M"
   elseif (ntopng_info["version.enterprise_edition"]) or (ntopng_info["version.nedge_enterprise_edition"]) then
      release = "Enterprise"
   elseif (ntopng_info["pro.release"]) then
      release = "Professional"
   else
      release = "Community"
   end

   if not isEmptyString(release) and ntopng_info["version.embedded_edition"] then
      release = release .. " (Embedded)"
   end

   -- E.g., ntopng edge v.4.3.210112 (Ubuntu 16.04.6 LTS)
   local res = string.format("%s %s v.%s (%s)", ntopng_info.product, release, ntopng_info.version, ntopng_info.OS)

   if verbose and ntopng_info.revision then
      res = string.format("%s %s v.%s rev.%s (%s)", ntopng_info.product, release, ntopng_info.version,
			  ntopng_info.revision, ntopng_info.OS)
   end

   if not ntopng_info.oem then
      local vers = string.split(ntopng_info["version.git"], ":")

      if vers and vers[2] then
	 local ntopng_git_url = "<A HREF=\"https://github.com/ntop/ntopng/commit/" .. vers[2] ..
	    "\"><i class='fab fa-github'></i></A>"

	 res = string.format("%s | %s", res, ntopng_git_url)
      end
   end

   return res
end

-- ###########################################

-- A redis hash mac -> first_seen
function getDevicesHashMapKey(ifid)
   return "ntopng.checks.device_connection_disconnection.ifid_" .. ifid
end

-- ###########################################

function getHideFromTopSet(ifid)
   return "ntopng.prefs.iface_" .. ifid .. ".hide_from_top"
end

-- ###########################################

function getGwMacsSet(ifid)
   return "ntopng.prefs.iface_" .. ifid .. ".gw_macs"
end

-- ###########################################

function getDeviceProtocolPoliciesUrl(params_str)
   local url, sep

   if ntop.isnEdge() then
      url = "/lua/pro/nedge/admin/nf_edit_user.lua?page=device_protocols"
      sep = "&"
   else
      url = "/lua/admin/edit_device_protocols.lua"
      sep = "?"
   end

   if not isEmptyString(params_str) then
      return ntop.getHttpPrefix() .. url .. sep .. params_str
   end

   return ntop.getHttpPrefix() .. url
end

-- ###########################################

-- Returns the size of a folder (size is in bytes)
-- ! @param path the path to compute the size for
-- ! @param timeout the maxium time to compute the size. If nil, it defaults to 15 seconds.
function getFolderSize(path, timeout)
   local os_utils = require "os_utils"
   local folder_size_key = "ntopng.cache.folder_size"
   local now = os.time()
   local expiration = 30 -- sec
   local size = nil

   if ntop.isWindows() then
      size = 0 -- TODO
   else
      local MAX_TIMEOUT = tonumber(timeout) or 15 -- default
      -- Check if timeout is present on the system to cap the execution time of the subsequent du,
      -- which may be very time consuming, especially when the number of files is high
      local has_timeout = ntop.getCache("ntopng.cache.has_gnu_timeout")

      if isEmptyString(has_timeout) then
	 -- Cache the timeout
	 -- Check timeout existence with which. If no timeout is found, command will return nil
	 has_timeout = (os_utils.execWithOutput("which timeout >/dev/null 2>&1") ~= nil)
	 ntop.setCache("ntopng.cache.has_gnu_timeout", tostring(has_timeout), 3600)
      else
	 has_timeout = has_timeout == "true"
      end

      -- Check the cache for a recent value
      local time_size = ntop.getHashCache(folder_size_key, path)
      if not isEmptyString(time_size) then
	 local values = split(time_size, ',')
	 if #values >= 2 and tonumber(values[1]) >= (now - expiration) then
	    size = tonumber(values[2])
	 end
      end

      if size == nil then
	 size = 0
	 -- Read disk utilization
	 local periodic_activities_utils = require "periodic_activities_utils"
	 if ntop.isdir(path) and not periodic_activities_utils.have_degraded_performance() then
	    local du_cmd = string.format("du -s %s 2>/dev/null", path)
	    if has_timeout then
	       du_cmd = string.format("timeout %u%s %s", MAX_TIMEOUT, "s", du_cmd)
	    end

	    -- use POSIXLY_CORRECT=1 to guarantee results is returned in 512-byte blocks
	    -- both on BSD and Linux
	    local line = os_utils.execWithOutput(string.format("POSIXLY_CORRECT=1 %s", du_cmd))
	    local values = split(line, '\t')
	    if #values >= 1 then
	       local used = tonumber(values[1])
	       if used ~= nil then
		  size = math.ceil(used * 512)

		  -- Cache disk utilization
		  ntop.setHashCache("ntopng.cache.folder_size", path, now .. "," .. size)
	       end
	    end
	 end
      end
   end

   return size
end

-- ###########################################

function getHttpUrlPrefix()
   if starts(_SERVER["HTTP_HOST"], 'https://') then
      return "https://"
   else
      return "http://"
   end
end

-- ##############################################

function getPoolName(pool_id)
   if isEmptyString(pool_id) or pool_id == "0" then
      return "Default"
   else
      local key = "ntopng.prefs.host_pools.details." .. pool_id

      return ntop.getHashCache(key, "name")
   end
end

-- ##############################################

function get_version_update_msg(info, latest_version)
   if info then
      local version_elems = split(info["version"], " ")
      local new_version = version2int(latest_version)
      local this_version = version2int(version_elems[1])

      if (new_version > this_version) then
	 return i18n("about.new_major_available", {
			product = info["product"],
			version = latest_version,
			url = "http://www.ntop.org/get-started/download/"
	 })
      end
   end

   return ""
end

-- ##############################################

function getFlowDevAliasKey()
   return "ntopng.flow_dev_aliases"
end

-- ##############################################

function getFlowDevAlias(flowdev_ip, add_id)
   local alias = ntop.getHashCache(getFlowDevAliasKey(), flowdev_ip)
   local ret

   if not isEmptyString(alias) then
      if (add_id == true) then
	 ret = flowdev_ip .. " [" .. alias .. "]"
      else
	 ret = alias
      end
   else
      ret = tostring(flowdev_ip)
   end

   return ret
end

-- ##############################################

function getFullFlowDevName(flowdev_ip, compact, add_id)
   local alias = getFlowDevAlias(flowdev_ip, add_id)

   if not isEmptyString(flowdev_ip) then
      if not isEmptyString(flowdev_ip) and alias ~= tostring(flowdev_ip) then
	 if compact then
	    alias = shortenString(alias)
	    return string.format("%s", alias)
	 else
	    return string.format("%s [%s]", flowdev_ip, alias)
	 end
      end
   end

   return flowdev_ip
end

-- ##############################################

function getObsPointAliasKey()
   return "ntopng.observation_point_aliases"
end

-- ##############################################

function getObsPointAlias(observation_point_id, add_id, add_href)
   local alias = ntop.getHashCache(getObsPointAliasKey(), observation_point_id)
   local ret

   if not isEmptyString(alias) then
      if (add_id == true) then
	 ret = observation_point_id .. " [" .. alias .. "]"
      else
	 ret = alias
      end
   else
      ret = tostring(observation_point_id)
   end

   if (add_href == true) then
      ret = "<A HREF=\"" .. ntop.getHttpPrefix() .. "/lua/pro/enterprise/observation_points.lua\">" .. ret .. "</A>"
   end

   return ret
end

-- ##############################################

function getFullObsPointName(observation_point_id, compact, add_id)
   local alias = getObsPointAlias(observation_point_id, add_id)

   if not isEmptyString(observation_point_id) then
      if not isEmptyString(observation_point_id) and alias ~= tostring(observation_point_id) then
	 if compact then
	    alias = shortenString(alias)
	    return string.format("%s", alias)
	 else
	    return string.format("%u [%s]", observation_point_id, alias)
	 end
      end
   end

   return observation_point_id
end

-- ##############################################

function getExtraFlowInfoTLSIssuerDN(alert_json)
   if alert_json and alert_json["proto"] and alert_json["proto"]["tls"] and
      not isEmptyString(alert_json["proto"]["tls"]["issuerDN"]) then
      return alert_json["proto"]["tls"]["issuerDN"]
   end
   return nil
end

-- ##############################################

function getExtraFlowInfoServerName(alert_json)
   if alert_json then
      if alert_json["proto"] and alert_json["proto"]["http"] and
	 not isEmptyString(alert_json["proto"]["http"]["server_name"]) then
	 return alert_json["proto"]["http"]["server_name"]
      elseif alert_json["proto"] and alert_json["proto"]["dns"] and
	 not isEmptyString(alert_json["proto"]["dns"]["last_query"]) then
	 return alert_json["proto"]["dns"]["last_query"]
      elseif alert_json["proto"] and alert_json["proto"]["tls"] and
	 not isEmptyString(alert_json["proto"]["tls"]["client_requested_server_name"]) then
	 return alert_json["proto"]["tls"]["client_requested_server_name"]
      end
   end
   return nil
end

-- ##############################################

function getExtraFlowInfoURL(alert_json)
   if alert_json then
      if alert_json["proto"] and alert_json["proto"]["http"] and
	 not isEmptyString(alert_json["proto"]["http"]["last_url"]) then
	 return alert_json["proto"]["http"]["last_url"]
      end
   end
   return getExtraFlowInfoServerName(alert_json)
end

-- ##############################################

function get_badge(info)
   local badge = 'success'

   if info ~= true and info ~= 0 then
      badge = 'danger'
   end

   return badge
end

-- ##############################################

function get_confidence(confidence_id, shorten_string)
   local tag_utils = require "tag_utils"
   local confidence_name = confidence_id

   if confidence_id and tonumber(confidence_id) then
      confidence_id = tonumber(confidence_id)

      for _, confidence in pairs(tag_utils.confidence or {}) do
	 if confidence.id == confidence_id then
	    confidence_name = confidence.label

	    break
	 end
      end
   end

   return confidence_name
end

-- ##############################################

local services = nil

local function readServices()
   if(services == nil) then
      local file = io.open("/etc/services", "r")
      if not file then return services end
      
      services = {}
      
      for line in file:lines() do
	 if((line ~= "") and (not(string.starts(line, "#")))) then
	    line = line:gsub("\t", " ")
	    line = line:gsub("  ", " ")
	    local elems = split(line, " ")
	    local label = elems[1]
	    local value
	    local i = 2
	    
	    while(elems[i] ~= nil) do
	       if(elems[i] ~=  "") then
		  value = elems[i]
		  break
	       end

	       i = i + 1
	    end
	    
	    services[value] = label
	    -- print(label.." = "..value.."<br>\n")
	 end
      end
      
      file:close()
   end

   return(services)
end

-- Example mapServiceName(80, "tcp")
function mapServiceName(port, protocol)
   readServices()
   local key = tostring(port).."/"..protocol

   return(services[key])
end

-- ##############################################

function getExporterList()
   local flowdevs = interface.getFlowDevices()
   local unified_exporters = {}
   for interface_id, device_list in pairs(flowdevs or {}) do
      for device_id, exporter_info in pairs(device_list, asc) do
	 local exporter_ip = exporter_info.exporter_ip
	 if not unified_exporters[exporter_ip] then
	    unified_exporters[exporter_ip] = exporter_info
	 end
      end
   end

   return unified_exporters
end

-- ##############################################

if (trace_script_duration ~= nil) then
   io.write(debug.getinfo(1, 'S').source .. " executed in " .. (os.clock() - clock_start) * 1000 .. " ms\n")
end
