<commit_before>--[[

LuCI ip calculation libarary
(c) 2008 <NAME> <<EMAIL>>
(c) 2008 <NAME> <<EMAIL>>

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

        http://www.apache.org/licenses/LICENSE-2.0

$Id$

]]--

module( "luci.ip", package.seeall )

require("bit")
require("luci.util")

LITTLE_ENDIAN = not luci.util.bigendian()
BIG_ENDIAN    = not LITTLE_ENDIAN

FAMILY_INET4  = 0x04
FAMILY_INET6  = 0x06


local function __bless(x)
	return setmetatable( x, {
		__index	= luci.ip.cidr,
		__add   = luci.ip.cidr.add,
		__lt	= luci.ip.cidr.lower,
		__eq	= luci.ip.cidr.equal,
		__le	=
			function(...)
				return luci.ip.cidr.equal(...) or luci.ip.cidr.lower(...)
			end
	} )
end

local function __mask16(bits)
	return bit.lshift(
		bit.rshift( 0xFFFF, 16 - bits % 16 ),
		16 - bits % 16
	)
end

-- htons(), htonl(), ntohs(), ntohl()

function htons(x)
	if LITTLE_ENDIAN then
		return bit.bor(
			bit.rshift( x, 8 ),
			bit.band( bit.lshift( x, 8 ), 0xFF00 )
		)
	else
		return x
	end
end

function htonl(x)
	if LITTLE_ENDIAN then
		return bit.bor(
			bit.lshift( htons( bit.band( x, 0xFFFF ) ), 16 ),
			htons( bit.rshift( x, 16 ) )
		)
	else
		return x
	end
end

ntohs = htons
ntohl = htonl


function IPv4(address, netmask)
	address = address or "0.0.0.0/0"

	local data = {}
	local prefix = address:match("/(.+)")

	if netmask then
		prefix = IPv4():prefix(netmask)
	elseif prefix then
		address = address:gsub("/.+","")
		prefix = tonumber(prefix)
		if not prefix or prefix < 0 or prefix > 32 then return nil end
	else
		prefix = 32
	end

	local b1, b2, b3, b4 = address:match("^(%d+)%.(%d+)%.(%d+)%.(%d+)$")

	b1 = tonumber(b1)
	b2 = tonumber(b2)
	b3 = tonumber(b3)
	b4 = tonumber(b4)

	if b1 and b1 <= 255 and
	   b2 and b2 <= 255 and
	   b3 and b3 <= 255 and
	   b4 and b4 <= 255
	then
		return __bless({
			FAMILY_INET4,
			{ b1 * 256 + b2, b3 * 256 + b4 },
			prefix
		})
	end
end

function IPv6(address, netmask)
	address = address or "::/0"

	local data = {}
	local prefix = address:match("/(.+)")

	if netmask then
		prefix = IPv6():prefix(netmask)
	elseif prefix then
		address = address:gsub("/.+","")
		prefix = tonumber(prefix)
		if not prefix or prefix < 0 or prefix > 128 then return nil end
	else
		prefix = 128
	end

	local borderl = address:sub(1, 1) == ":" and 2 or 1
	local borderh, zeroh, chunk, block

	if #address > 45 then return nil end

	repeat
		borderh = address:find(":", borderl, true)
		if not borderh then break end

		block = tonumber(address:sub(borderl, borderh - 1), 16)
		if block and block <= 65535 then
			table.insert(data, block)
		else
			if zeroh or borderh - borderl > 1 then return nil end
			zeroh = #data + 1
		end

		borderl = borderh + 1
	until #data == 7

	chunk = address:sub(borderl)
	if #chunk > 0 and #chunk <= 4 then
		block = tonumber(chunk, 16)
		if not block or block > 65535 then return nil end

		table.insert(data, block)
	elseif #chunk > 4 then
		if #data == 7 or #chunk > 15 then return nil end
		borderl = 1
		for i=1, 4 do
			borderh = chunk:find(".", borderl, true)
			if not borderh and i < 4 then return nil end
			borderh = borderh and borderh - 1

			block = tonumber(chunk:sub(borderl, borderh))
			if not block or block > 255 then return nil end

			if i == 1 or i == 3 then
				table.insert(data, block * 256)
			else
				data[#data] = data[#data] + block
			end

			borderl = borderh and borderh + 2
		end
	end

	if zeroh then
		if #data == 8 then return nil end
		while #data < 8 do
			table.insert(data, zeroh, 0)
		end
	end

	if #data == 8 then
		return __bless({ FAMILY_INET6, data, prefix })
	end
end


cidr = luci.util.class()

function cidr.is4( self )
	return self[1] == FAMILY_INET4
end

function cidr.is6( self )
	return self[1] == FAMILY_INET6
end

function cidr.string( self )
	local str
	if self:is4() then
		str = string.format(
			"%d.%d.%d.%d",
			bit.rshift(self[2][1], 8), bit.band(self[2][1], 0xFF),
			bit.rshift(self[2][2], 8), bit.band(self[2][2], 0xFF)
		)
		if self[3] < 32 then
			str = str .. "/" .. self[3]
		end
	elseif self:is6() then
		str = string.format( "%X:%X:%X:%X:%X:%X:%X:%X", unpack(self[2]) )
		if self[3] < 128 then
			str = str .. "/" .. self[3]
		end
	end
	return str
end

function cidr.lower( self, addr )
	assert( self[1] == addr[1], "Can't compare IPv4 and IPv6 addresses" )
	for i = 1, #self[2] do
		if self[2][i] ~= addr[2][i] then
			return self[2][i] < addr[2][i]
		end
	end
	return false
end

function cidr.higher( self, addr )
	assert( self[1] == addr[1], "Can't compare IPv4 and IPv6 addresses" )
	for i = 1, #self[2] do
		if self[2][i] ~= addr[2][i] then
			return self[2][i] > addr[2][i]
		end
	end
	return false
end

function cidr.equal( self, addr )
	assert( self[1] == addr[1], "Can't compare IPv4 and IPv6 addresses" )
	for i = 1, #self[2] do
		if self[2][i] ~= addr[2][i] then
			return false
		end
	end
	return true
end

function cidr.prefix( self, mask )
	local prefix = self[3]

	if mask then
		prefix = 0

		local obj = self:is4() and IPv4(mask) or IPv6(mask)
		if not obj then
			return nil
		end

		for i, block in ipairs(obj[2]) do
			local pos = bit.lshift(1, 15)
			for i=15, 0, -1 do
				if bit.band(block, pos) == pos then
					prefix = prefix + 1
				else
					return prefix
				end
				pos = bit.rshift(pos, 1)
			end
		end
	end

	return prefix
end

function cidr.network( self )
	local data = { }

	for i = 1, math.floor( self[3] / 16 ) do
		table.insert( data, self[2][i] )
	end

	table.insert( data, bit.band( self[2][1+#data], __mask16(self[3]) ) )

	for i = #data, #self[2] do
		table.insert( data, 0 )
	end

	return __bless({ self[1], data, self:is4() and 32 or 128 })
end

function cidr.host( self )
	return __bless({ self[1], data, self:is4() and 32 or 128 })
end

function cidr.mask( self, bits )
	local data = { }<commit_msg>lib/core: luci.ip: Added more sanity checks and optimizations<commit_after>--[[

LuCI ip calculation libarary
(c) 2008 <NAME> <<EMAIL>>
(c) 2008 <NAME> <<EMAIL>>

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

        http://www.apache.org/licenses/LICENSE-2.0

$Id$

]]--

module( "luci.ip", package.seeall )

require("bit")
require("luci.util")

LITTLE_ENDIAN = not luci.util.bigendian()
BIG_ENDIAN    = not LITTLE_ENDIAN

FAMILY_INET4  = 0x04
FAMILY_INET6  = 0x06


local function __bless(x)
	return setmetatable( x, {
		__index	= luci.ip.cidr,
		__add   = luci.ip.cidr.add,
		__lt	= luci.ip.cidr.lower,
		__eq	= luci.ip.cidr.equal,
		__le	=
			function(...)
				return luci.ip.cidr.equal(...) or luci.ip.cidr.lower(...)
			end
	} )
end

local function __mask16(bits)
	return bit.lshift(
		bit.rshift( 0xFFFF, 16 - bits % 16 ),
		16 - bits % 16
	)
end

-- htons(), htonl(), ntohs(), ntohl()

function htons(x)
	if LITTLE_ENDIAN then
		return bit.bor(
			bit.rshift( x, 8 ),
			bit.band( bit.lshift( x, 8 ), 0xFF00 )
		)
	else
		return x
	end
end

function htonl(x)
	if LITTLE_ENDIAN then
		return bit.bor(
			bit.lshift( htons( bit.band( x, 0xFFFF ) ), 16 ),
			htons( bit.rshift( x, 16 ) )
		)
	else
		return x
	end
end

ntohs = htons
ntohl = htonl


function IPv4(address, netmask)
	address = address or "0.0.0.0/0"

	local obj = __bless({ FAMILY_INET4 })

	local data = {}
	local prefix = address:match("/(.+)")

	if netmask then
		prefix = obj:prefix(netmask)
	elseif prefix then
		address = address:gsub("/.+","")
		prefix = tonumber(prefix)
		if not prefix or prefix < 0 or prefix > 32 then return nil end
	else
		prefix = 32
	end

	local b1, b2, b3, b4 = address:match("^(%d+)%.(%d+)%.(%d+)%.(%d+)$")

	b1 = tonumber(b1)
	b2 = tonumber(b2)
	b3 = tonumber(b3)
	b4 = tonumber(b4)

	if b1 and b1 <= 255 and
	   b2 and b2 <= 255 and
	   b3 and b3 <= 255 and
	   b4 and b4 <= 255 and
	   prefix
	then
		table.insert(obj, { b1 * 256 + b2, b3 * 256 + b4 })
		table.insert(obj, prefix)
		return obj
	end
end

function IPv6(address, netmask)
	address = address or "::/0"

	local obj = __bless({ FAMILY_INET6 })

	local data = {}
	local prefix = address:match("/(.+)")

	if netmask then
		prefix = obj:prefix(netmask)
	elseif prefix then
		address = address:gsub("/.+","")
		prefix = tonumber(prefix)
		if not prefix or prefix < 0 or prefix > 128 then return nil end
	else
		prefix = 128
	end

	local borderl = address:sub(1, 1) == ":" and 2 or 1
	local borderh, zeroh, chunk, block

	if #address > 45 then return nil end

	repeat
		borderh = address:find(":", borderl, true)
		if not borderh then break end

		block = tonumber(address:sub(borderl, borderh - 1), 16)
		if block and block <= 65535 then
			table.insert(data, block)
		else
			if zeroh or borderh - borderl > 1 then return nil end
			zeroh = #data + 1
		end

		borderl = borderh + 1
	until #data == 7

	chunk = address:sub(borderl)
	if #chunk > 0 and #chunk <= 4 then
		block = tonumber(chunk, 16)
		if not block or block > 65535 then return nil end

		table.insert(data, block)
	elseif #chunk > 4 then
		if #data == 7 or #chunk > 15 then return nil end
		borderl = 1
		for i=1, 4 do
			borderh = chunk:find(".", borderl, true)
			if not borderh and i < 4 then return nil end
			borderh = borderh and borderh - 1

			block = tonumber(chunk:sub(borderl, borderh))
			if not block or block > 255 then return nil end

			if i == 1 or i == 3 then
				table.insert(data, block * 256)
			else
				data[#data] = data[#data] + block
			end

			borderl = borderh and borderh + 2
		end
	end

	if zeroh then
		if #data == 8 then return nil end
		while #data < 8 do
			table.insert(data, zeroh, 0)
		end
	end

	if #data == 8 and prefix then
		table.insert(obj, data)
		table.insert(obj, prefix)
		return obj
	end
end


cidr = luci.util.class()

function cidr.is4( self )
	return self[1] == FAMILY_INET4
end

function cidr.is6( self )
	return self[1] == FAMILY_INET6
end

function cidr.string( self )
	local str
	if self:is4() then
		str = string.format(
			"%d.%d.%d.%d",
			bit.rshift(self[2][1], 8), bit.band(self[2][1], 0xFF),
			bit.rshift(self[2][2], 8), bit.band(self[2][2], 0xFF)
		)
		if self[3] < 32 then
			str = str .. "/" .. self[3]
		end
	elseif self:is6() then
		str = string.format( "%X:%X:%X:%X:%X:%X:%X:%X", unpack(self[2]) )
		if self[3] < 128 then
			str = str .. "/" .. self[3]
		end
	end
	return str
end

function cidr.lower( self, addr )
	assert( self[1] == addr[1], "Can't compare IPv4 and IPv6 addresses" )
	for i = 1, #self[2] do
		if self[2][i] ~= addr[2][i] then
			return self[2][i] < addr[2][i]
		end
	end
	return false
end

function cidr.higher( self, addr )
	assert( self[1] == addr[1], "Can't compare IPv4 and IPv6 addresses" )
	for i = 1, #self[2] do
		if self[2][i] ~= addr[2][i] then
			return self[2][i] > addr[2][i]
		end
	end
	return false
end

function cidr.equal( self, addr )
	assert( self[1] == addr[1], "Can't compare IPv4 and IPv6 addresses" )
	for i = 1, #self[2] do
		if self[2][i] ~= addr[2][i] then
			return false
		end
	end
	return true
end

function cidr.prefix( self, mask )
	local prefix = self[3]

	if mask then
		prefix = 0
		local stop = false
		local obj = self:is4() and IPv4(mask) or IPv6(mask)

		if not obj then
			return nil
		end

		for i, block in ipairs(obj[2]) do
			local pos = bit.lshift(1, 15)
			for i=15, 0, -1 do
				if bit.band(block, pos) == pos then
					if not stop then
						prefix = prefix + 1
					else
						return nil
					end
				else
					stop = true
				end
				pos = bit.rshift(pos, 1)
			end
		end
	end

	return prefix
end

function cidr.network( self )
	local data = { }

	for i = 1, math.floor( self[3] / 16 ) do
		table.insert( data, self[2][i] )
	end

	table.insert( data, bit.band( self[2][1+#data], __mask16(self[3]) ) )

	for i = #data, #self[2] do
		table.insert( data, 0 )
	end

	return __bless({ self[1], data, self:is4() and 32 or 128 })
end

function cidr.host( self )
	return __bless({ self[1], data, self:is4() and 32 or 128 })
end

function cidr.mask( self, bits )
	local data = { }
{'fraction_non_alphanumeric': 0.141874533852656, 'fraction_numerical': 0.04209828457777409, 'mean_word_length': 3.011968085106383, 'pattern_counts': {'":': 6, '<': 41, '<?xml version=': 0, '>': 35, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 2, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '202614', 'n_tokens_mistral': 5542, 'n_tokens_neox': 4795, 'n_words': 1512}