Module:IP: Difference between revisions

From the Croc Wiki, the Croc encyclopedia
Jump to navigationJump to search
5,841 bytes added ,  7 years ago
need to use __index to have a unique key which is really private - if outside code gets uniqueKey with getmetatable then they can set it in another table's __eq metamethod, thus foiling isSubnetObject
m (comment and variable tweaks)
(need to use __index to have a unique key which is really private - if outside code gets uniqueKey with getmetatable then they can set it in another table's __eq metamethod, thus foiling isSubnetObject)
 
(31 intermediate revisions by 2 users not shown)
Line 3:
 
-- Load modules
require('Module:No globals')
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
 
--------------------------------------------------------------------------------
Line 45 ⟶ 64:
-- Constructors
function RawIP.newFromIPv4(ipStr)
-- IfReturn a RawIP object if ipStr is a valid IPv4 string. Otherwise, return a collection of its parts.
-- Otherwise, return nil.
-- This representation is for compatibility with IPv6 addresses.
local octets = Collection()
Line 60 ⟶ 79:
if num > 0 and s:match('^0') then
-- A redundant leading zero is for an IP in octal.
return falsenil
end
octets[i] = num
else
return falsenil
end
else
return falsenil
end
end
Line 78 ⟶ 97:
return nil
end
 
function RawIP.newFromIPv6(ipStr)
-- IfReturn a RawIP object if ipStr is a valid IPv6 string. Otherwise, return a collection of its parts.
-- Otherwise, return nil.
ipStr = ipStr:match('^%s*(.-)%s*$')
local _, n = ipStr:gsub(':', ':')
Line 96 ⟶ 115:
parts[i] = 0
else
local num = tonumber('0x's, .. s16)
if num and 0 <= num and num <= 65535 then
parts[i] = num
else
return falsenil
end
end
Line 107 ⟶ 126:
end
return nil
end
 
function RawIP.newFromIP(ipStr)
-- Return a new RawIP object from either an IPv4 string or an IPv6
-- string. If ipStr is not a valid IPv4 or IPv6 string, then return
-- nil.
return RawIP.newFromIPv4(ipStr) or RawIP.newFromIPv6(ipStr)
end
 
-- Methods
function RawIP:getVersion()
-- Return a string with the version of the IP protocol we are using.
return self.n == 2 and V4 or V6
end
 
function RawIP:isIPv4()
-- Return true if this is an IPv4 representation, and false otherwise.
return self.n == 2
end
 
function RawIP:isIPv6()
-- Return true if this is an IPv6 representation, and false otherwise.
return self.n == 8
end
 
function RawIP:getAdjacent(previous)
-- Return a RawIP object for an adjacent IP address. If previous is true
Line 120 ⟶ 161:
local result = Collection()
result.n = self.n
local carry = downprevious and 0xffff or 1
for i = self.n, 1, -1 do
local sum = self[i] + carry
if sum >= 0x10000 then
carry = downprevious and 0x10000 or 1
sum = sum - 0x10000
else
carry = downprevious and 0xffff or 0
end
result[i] = sum
Line 145 ⟶ 186:
bitLength = bitLength - 16
else
result[i] = bit32.bandreplace(self[i], 0, 0, 16 - bitLength)
bit32.arshift(0xffff8000, bitLength - 1))
bitLength = 0
end
Line 178 ⟶ 218:
width = width - 16
else
result[i] = bit32.replace(self[i], 0xffff, width - 10, width)
width = 0
end
Line 185 ⟶ 225:
end
end
return setmetatable(result, rawIPRawIP)
end
 
function RawIP:_makeIPv6String()
-- Return an IPv6 string representation of the object. Behavior is undefined
-- undefined if the current object is IPv4.
local z1, z2 -- indices of run of zeros to be displayed as "::"
local zstart, zcount
Line 234 ⟶ 274:
 
function RawIP:_makeIPv4String()
-- Return an IPv4 string representation of the object. Behavior is undefined
-- undefined if the current object is IPv6.
local parts = Collection()
for i = 1, 2 do
Line 278 ⟶ 318:
end
end
 
--------------------------------------------------------------------------------
-- Initialize private methods available to IPAddress and Subnet
--------------------------------------------------------------------------------
 
-- Both IPAddress and Subnet need access to each others' private constructor
-- functions. IPAddress must be able to make Subnet objects from CIDR strings
-- and from RawIP objects, and Subnet must be able to make IPAddress objects
-- from IP strings and from RawIP objects. These constructors must all be
-- private to ensure correct error levels and to stop other modules from having
-- to worry about RawIP objects. Because they are private, they must be
-- initialized here.
local makeIPAddress, makeIPAddressFromRaw, makeSubnet, makeSubnetFromRaw
 
-- IPAddress and Subnet also need to be able to validate Subnet and IPAddress
-- objects that they are passed as input, so initialize those functions here
-- as well.
local validateIPAddress, validateSubnet
 
--------------------------------------------------------------------------------
Line 285 ⟶ 343:
 
local IPAddress = {}
 
local newIPFromRaw
-- newIPFromRaw constructs a new IPAddress object from a rawIP object. It needs
-- to be accessible from the Subnet class but it should not be public.
 
do
local-- dataKey =is {} -- Aa unique key to access objects' internal data. This is needed
-- to access the RawIP objects contained in other IPAddress objects so that
-- they can be compared with the current object's RawIP object. This data
-- is not available to other classes or other modules.
local dataKey = {}
 
-- Private static methods
local function isIPAddressObject(val)
return type(val) == 'table' and val[dataKey] ~= nil
end
 
validateIPAddress = makeValidationFunction('IPAddress', isIPAddressObject)
 
-- Metamethods that don't need upvalues
Line 302 ⟶ 367:
end
 
local function concatIPsconcatIP(ip1ip, ip2val)
return ip1:getIPtostring(ip) .. ip2:getIPtostring(val)
end
 
Line 311 ⟶ 376:
 
-- Constructors
newIPFromRawmakeIPAddressFromRaw = function (rawIP)
-- Constructs a new IPAddress object from a rawIP object. This function
-- is for internal use; it is called by IPAddress.new and from other
Line 320 ⟶ 385:
-- Set up structure
local obj = {}
local data = {}
data.rawIP = rawIP,
 
version = rawIP.n == 2 and V4 or V6,
-- A function to check whether methods are called with a valid self
}
-- parameter.
local checkSelf = makeCheckSelfFunction(
'IP',
'ipAddress',
obj,
'IPAddress object'
)
 
-- Public methods
function obj:getIP()
checkSelf(self, 'getIP')
return tostring(data.rawIP)
end
 
function obj:getVersion()
checkSelf(self, 'getVersion')
return data.version
return data.rawIP:getVersion()
end
 
function obj:getHighestIP(bitLength)
return newIPFromRaw(setHostBits(data.rawIP, bitLength))
end
 
function obj:getPrefix(bitLength)
return newIPFromRaw(copyPrefix(data.rawIP, bitLength))
end
 
function obj:isIPv4()
checkSelf(self, 'isIPv4')
return data.version == V4
return data.rawIP:isIPv4()
end
 
function obj:isIPv6()
checkSelf(self, 'isIPv6')
return data.version == V6
return data.rawIP:isIPv6()
end
 
function obj:isInSubnet(subnet)
checkSelf(self, 'isInSubnet')
-- TODO Consider alternative of checking:
local tp = type(subnet)
-- (ipFirst <= self and self <= ipLast)
if self:getVersion()tp == subnet:getVersion()'string' then
local prefixsubnet = self:getPrefixmakeSubnet(subnet:getBitLength())
returnelseif prefixtp == subnet:getPrefix()'table' then
validateSubnet('isInSubnet', 1, subnet)
else
checkTypeMulti('isInSubnet', 1, subnet, {'string', 'table'})
end
return falsesubnet:containsIP(self)
end
 
function obj:getSubnet(bitLength)
checkSelf(self, 'getSubnet')
checkType('getSubnet', 1, bitLength, 'number')
return makeSubnetFromRaw(data.rawIP, bitLength)
end
 
function obj:getNextIP()
checkSelf(self, 'getNextIP')
return newIPFromRaw(data.rawIP:getAdjacent())
return makeIPAddressFromRaw(data.rawIP:getAdjacent())
end
 
function obj:getPreviousIP()
checkSelf(self, 'getPreviousIP')
return newIPFromRaw(data.rawIP:getAdjacent(true))
return makeIPAddressFromRaw(data.rawIP:getAdjacent(true))
end
 
Line 372 ⟶ 451:
__eq = ipEquals,
__lt = ipLessThan,
__concat = concatIPsconcatIP,
__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 IPAddress.new(ip)
local rawIP = RawIP.newFromIP(ip)
checkType('IPAddress.new', 1, ip, 'string')
local rawIP = RawIP.newFromIPv4String(ip) or RawIP.newFromIPv6String(ip)
if not rawIP then
error(string.format("'%s' is an invalid IP' address", ip), 23)
end
return newIPFromRawmakeIPAddressFromRaw(rawIP)
end
end
 
local function makeSubnetIPAddress.new(data, cidrStrip)
checkType('IPAddress.new', 1, ip, 'string')
-- If cidrStr is a valid IPv4 or IPv6 CIDR specification, store its
return makeIPAddress(ip)
-- information in data and return its version. Otherwise, return nil.
local lhs, rhs = cidrStr:match('^%s*(.-)/(%d+)%s*$')
if lhs then
local bits = lhs:find(':', 1, true) and 128 or 32
local n = tonumber(rhs)
if n and n <= bits then
local base = IPAddress.new(lhs)
local prefix = base:getPrefix(n)
if base == prefix then
data.bitLength = n
data.prefix = prefix
data.highestIP = base:getHighestIP(n)
return bits == 32 and V4 or V6
end
end
end
return nil
end
 
Line 421 ⟶ 486:
 
do
-- uniqueKey is a unique, private key used to test whether a given object
-- Initialize metatable
-- is a Subnet object.
local mt = {}
local uniqueKey = setmetatable({}, {__metatable = false})
 
-- Private static methods
-- Constructor
local function Subnet.newisSubnetObject(cidrval)
-- Return true if val is a Subnet object, and false otherwise.
return getmetatable(val) == uniqueKey
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)
 
-- Metamethods that don't need upvalues
local function subnetEquals(subnet1, subnet2)
return subnet1:getCIDR() == subnet2:getCIDR()
end
 
local function concatenateSubnets(subnet1, subnet2)
return tostring(subnet1) .. tostring(subnet2)
end
 
local function subnetToString(subnet)
return subnet:getCIDR()
end
 
-- 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', self:getPrefix(), self:getBitLength())
return string.format(
'%s/%d',
tostring(self:getPrefix()), self:getBitLength()
)
end
 
function obj:getVersion()
checkSelf(self, 'getVersion')
return data.version
return data.rawIP:getVersion()
end
 
function obj:isIPv4()
checkSelf(self, 'isIPv4')
return data.version == V4
return data.rawIP:isIPv4()
end
 
function obj:isIPv6()
checkSelf(self, 'isIPv6')
return data.version == V6
return data.rawIP:isIPv6()
end
 
function obj:containsIP(ip)
checkSelf(self, 'containsIP')
-- TODO See ip:isInSubnet(subnet); use this technique there?
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() <= ip and ip <= self:getHighestIP()
Line 468 ⟶ 600:
 
function obj:overlapsSubnet(subnet)
checkSelf(self, 'overlapsSubnet')
local tp = type(subnet)
if tp == 'string' then
subnet = makeSubnet(subnet)
elseif tp == 'table' then
validateSubnet('overlapsSubnet', 1, subnet)
else
checkTypeMulti('overlapsSubnet', 1, subnet, {'string', 'table'})
end
if self:getVersion() == subnet:getVersion() then
return (
Line 477 ⟶ 618:
end
 
function obj:walk()
-- Set initial values
checkSelf(self, 'walk')
checkType('Subnet.new', 1, cidr, 'string')
local started
data.version = makeSubnet(data, cidr)
local current = self:getPrefix()
if not data.version then
local highest = self:getHighestIP()
error('invalid CIDR', 2)
return function ()
if not started then
started = true
return current
end
if current < highest then
current = current:getNextIP()
return current
end
end
end
 
return setmetatable(obj, {
__index = function (self, key)
if key == uniqueKey then
return true
end
end,
__eq = subnetEquals,
__concat = concatenateSubnets,
__tostring = subnetToString,
__metatable = false,
})
end
 
makeSubnet = function (cidr)
-- Metamethods
-- Return a Subnet object from a CIDR string. If the CIDR string is
function mt:__eq(obj)
-- invalid, throw an error.
return self:getCIDR() == obj:getCIDR()
local lhs, rhs = cidr:match('^%s*(.-)/(%d+)%s*$')
if lhs then
local bits = lhs:find(':', 1, true) and 128 or 32
local n = tonumber(rhs)
if n and n <= bits and (n == 0 or not rhs:find('^0')) then
-- The right-hand side is a number between 0 and 32 (for IPv4)
-- or 0 and 128 (for IPv6) and doesn't have any leading zeroes.
local base = RawIP.newFromIP(lhs)
if base then
-- The left-hand side is a valid IP address.
local prefix = base:getPrefix(n)
if base == prefix then
-- The left-hand side is the lowest IP in the subnet.
return makeSubnetFromRaw(prefix, n)
end
end
end
end
error(string.format("'%s' is an invalid CIDR string", cidr), 3)
end
 
function mt:__tostringSubnet.new(cidr)
checkType('Subnet.new', 1, cidr, 'string')
return self:getCIDR()
return makeSubnet(cidr)
end
end
Cookies help us deliver our services. By using our services, you agree to our use of cookies.

Navigation menu