모듈:IP

-- IP library
-- This library contains classes for working with IP addresses and IP ranges.
-- Load modules
require('strict')
local bit32 = require('bit32')
local libraryUtil = require('libraryUtil')
local checkType = libraryUtil.checkType
local checkTypeMulti = libraryUtil.checkTypeMulti
local makeCheckSelfFunction = libraryUtil.makeCheckSelfFunction
-- Constants
local V4 = 'IPv4'
local V6 = 'IPv6'
--------------------------------------------------------------------------------
-- Helper functions
--------------------------------------------------------------------------------
local function makeValidationFunction(className, isObjectFunc)
	-- Make a function for validating a specific object.
	return function (methodName, argIdx, arg)
		if not isObjectFunc(arg) then
			error(string.format(
				"bad argument #%d to '%s' (not a valid %s object)",
				argIdx, methodName, className
			), 3)
		end
	end
end
--------------------------------------------------------------------------------
-- Collection class
-- This is a table used to hold items.
--------------------------------------------------------------------------------
local Collection = {}
Collection.__index = Collection
function Collection:add(item)
	if item ~= nil then
		self.n = self.n + 1
		selfself.n = item
	end
end
function Collection:join(sep)
	return table.concat(self, sep)
end
function Collection:remove(pos)
	if self.n > 0 and (pos == nil or (0  0 and s:match('^0') then
						-- A redundant leading zero is for an IP in octal.
						return nil
					end
					octetsi = num
				else
					return nil
				end
			else
				return nil
			end
		end
		local parts = Collection.new()
		for i = 1, 3, 2 do
			parts:add(octetsi * 256 + octetsi+1)
		end
		return setmetatable(parts, RawIP)
	end
	return nil
end
function RawIP.newFromIPv6(ipStr)
	-- Return a RawIP object if ipStr is a valid IPv6 string. Otherwise,
	-- return nil.
	ipStr = ipStr:match('^%s*(.-)%s*$')
	local _, n = ipStr:gsub(':', ':')
	if n = 0x10000 then
			carry = previous and 0x10000 or 1
			sum = sum - 0x10000
		else
			carry = previous and 0xffff or 0
		end
		resulti = sum
	end
	return setmetatable(result, RawIP)
end
function RawIP:getPrefix(bitLength)
	-- Return a RawIP object for the prefix of the current IP Address with a
	-- bit length of bitLength.
	local result = Collection.new()
	result.n = self.n
	for i = 1, self.n do
		if bitLength > 0 then
			if bitLength >= 16 then
				resulti = selfi
				bitLength = bitLength - 16
			else
				resulti = bit32.replace(selfi, 0, 0, 16 - bitLength)
				bitLength = 0
			end
		else
			resulti = 0
		end
	end
	return setmetatable(result, RawIP)
end
function RawIP:getHighestHost(bitLength)
	-- Return a RawIP object for the highest IP with the prefix of length
	-- bitLength. In other words, the network (the most-significant bits)
	-- is the same as the current IP's, but the host bits (the
	-- least-significant bits) are all set to 1.
	local bits = self.n * 16
	local width
	if bitLength = bits then
		width = 0
	else
		width = bits - bitLength
	end
	local result = Collection.new()
	result.n = self.n
	for i = self.n, 1, -1 do
		if width > 0 then
			if width >= 16 then
				resulti = 0xffff
				width = width - 16
			else
				resulti = bit32.replace(selfi, 0xffff, 0, width)
				width = 0
			end
		else
			resulti = selfi
		end
	end
	return setmetatable(result, RawIP)
end
function RawIP:_makeIPv6String()
	-- Return an IPv6 string representation of the object. Behavior is
	-- undefined if the current object is IPv4.
	local z1, z2  -- indices of run of zeroes to be displayed as "::"
	local zstart, zcount
	for i = 1, 9 do
		-- Find left-most occurrence of longest run of two or more zeroes.
		if i  1 then
				if not z1 or zcount > z2 - z1 + 1 then
					z1 = zstart
					z2 = zstart + zcount - 1
				end
			end
			zstart = nil
			zcount = nil
		end
	end
	local parts = Collection.new()
	for i = 1, 8 do
		if z1 and z1  data.rawIP:getBitLength()
				or bitLength ~= math.floor(bitLength)
			then
				error(string.format(
					"bad argument #1 to 'getSubnet' (must be an integer between 0 and %d)",
					data.rawIP:getBitLength()
				), 2)
			end
			return makeSubnetFromRaw(data.rawIP, bitLength)
		end
		function obj:getNextIP()
			checkSelf(self, 'getNextIP')
			return makeIPAddressFromRaw(data.rawIP:getAdjacent())
		end
		function obj:getPreviousIP()
			checkSelf(self, 'getPreviousIP')
			return makeIPAddressFromRaw(data.rawIP:getAdjacent(true))
		end
		-- Metamethods
		return setmetatable(obj, {
			__eq = ipEquals,
			__lt = ipLessThan,
			__concat = concatIP,
			__tostring = ipToString,
			__index = function (self, key)
				-- If any code knows the unique data key, allow it to access
				-- the data table.
				if key == dataKey then
					return data
				end
			end,
			__metatable = false, -- don't allow access to the metatable
		})
	end
	makeIPAddress = function (ip)
		local rawIP = RawIP.newFromIP(ip)
		if not rawIP then
			error(string.format("'%s' is an invalid IP address", ip), 3)
		end
		return makeIPAddressFromRaw(rawIP)
	end
	function IPAddress.new(ip)
		checkType('IPAddress.new', 1, ip, 'string')
		return makeIPAddress(ip)
	end
end
--------------------------------------------------------------------------------
-- Subnet class
-- Represents a block of IPv4 or IPv6 addresses.
--------------------------------------------------------------------------------
local Subnet = {}
do
	-- uniqueKey is a unique, private key used to test whether a given object
	-- is a Subnet object.
	local uniqueKey = {}
	-- Metatable
	local mt = {
		__index = function (self, key)
			if key == uniqueKey then
				return true
			end
		end,
		__eq = function (self, obj)
			return self:getCIDR() == obj:getCIDR()
		end,
		__concat = function (self, obj)
			return tostring(self) .. tostring(obj)
		end,
		__tostring = function (self)
			return self:getCIDR()
		end,
		__metatable = false
	}
	-- Private static methods
	local function isSubnetObject(val)
		-- Return true if val is a Subnet object, and false otherwise.
		return type(val) == 'table' and valuniqueKey ~= nil
	end
	-- Function to validate subnet objects.
	-- Params:
	-- methodName (string) - the name of the method being validated
	-- argIdx (number) - the position of the argument in the argument list
	-- arg - the argument to be validated
	validateSubnet = makeValidationFunction('Subnet', isSubnetObject)
	-- Constructors
	makeSubnetFromRaw = function (rawIP, bitLength)
		-- Set up structure
		local obj = setmetatable({}, mt)
		local data = {
			rawIP = rawIP,
			bitLength = bitLength,
		}
		-- A function to check whether methods are called with a valid self
		-- parameter.
		local checkSelf = makeCheckSelfFunction(
			'IP',
			'subnet',
			obj,
			'Subnet object'
		)
		-- Public methods
		function obj:getPrefix()
			checkSelf(self, 'getPrefix')
			if not data.prefix then
				data.prefix = makeIPAddressFromRaw(
					data.rawIP:getPrefix(data.bitLength)
				)
			end
			return data.prefix
		end
		function obj:getHighestIP()
			checkSelf(self, 'getHighestIP')
			if not data.highestIP then
				data.highestIP = makeIPAddressFromRaw(
					data.rawIP:getHighestHost(data.bitLength)
				)
			end
			return data.highestIP
		end
		function obj:getBitLength()
			checkSelf(self, 'getBitLength')
			return data.bitLength
		end
		function obj:getCIDR()
			checkSelf(self, 'getCIDR')
			return string.format(
				'%s/%d',
				tostring(self:getPrefix()), self:getBitLength()
			)
		end
		function obj:getVersion()
			checkSelf(self, 'getVersion')
			return data.rawIP:getVersion()
		end
		function obj:isIPv4()
			checkSelf(self, 'isIPv4')
			return data.rawIP:isIPv4()
		end
		function obj:isIPv6()
			checkSelf(self, 'isIPv6')
			return data.rawIP:isIPv6()
		end
		function obj:containsIP(ip)
			checkSelf(self, 'containsIP')
			local tp = type(ip)
			if tp == 'string' then
				ip = makeIPAddress(ip)
			elseif tp == 'table' then
				validateIPAddress('containsIP', 1, ip)
			else
				checkTypeMulti('containsIP', 1, ip, {'string', 'table'})
			end
			if self:getVersion() == ip:getVersion() then
				return self:getPrefix() = self:getPrefix() and
					subnet:getPrefix()  ip2 then
			error('The first IP must be less than or equal to the second', 2)
		end
	end
	Collection.add(self, {ip1, ip2 or ip1})
end
function Ranges:merge()
	self:sort(
		function (lhs, rhs)
			-- Sort by second value, then first.
			if lhs2 == rhs2 then
				return lhs1  1 do
		for i = pos - 1, 1, -1 do
			local ip1 = selfi2
			local ip2 = ip1:getNextIP()
			if ip2  ip2 then
				break
			end
			ip1 = selfi1
			ip2 = selfpos1
			selfi = {ip1 > ip2 and ip2 or ip1, selfpos2}
			self:remove(pos)
			pos = pos - 1
			if pos ?%_', ' ')
			for position, hit in line:gmatch('()(%S+)') do
				local ip = hit:match('^(:%x+)/?%d*$')
				if ip then
					local _, n = ip:gsub(':', ':')
					if n >= 2 then
						self:_store(hit, position == 1)
					end
				end
			end
		end
		return self
	end
end
return {
	IPAddress = IPAddress,
	Subnet = Subnet,
	IPv4Collection = IPv4Collection,
	IPv6Collection = IPv6Collection,
}