PageRenderTime 53ms CodeModel.GetById 27ms RepoModel.GetById 1ms app.codeStats 0ms

/vendor/gems/ip-0.2.2/lib/ip/address.rb

https://github.com/AndreiMedvedsky/Katoomba
Ruby | 407 lines | 171 code | 85 blank | 151 comment | 27 complexity | d47b29ac7f24ea1795aeba968cc516df MD5 | raw file
  1. #
  2. # IP::Address - base class for IP::Address::IPv4 and IP::Address::IPv6
  3. #
  4. class IP::Address
  5. #
  6. # This original IP Address you passed it, returned as a string.
  7. #
  8. attr_reader :ip_address
  9. #
  10. # This returns an Array of Integer which contains the octets of
  11. # the IP, in descending order.
  12. #
  13. attr_reader :octets
  14. #
  15. # Returns an octet given the proper index. The octets returned are
  16. # Integer types.
  17. #
  18. def [](num)
  19. if @octets[num].nil?
  20. raise IP::BoundaryException.new("Invalid octet")
  21. end
  22. return @octets[num]
  23. end
  24. #
  25. # See [].
  26. #
  27. alias_method :octet, :[]
  28. #
  29. # Returns a 128-bit integer representing the address.
  30. #
  31. def pack
  32. fail "This method is abstract."
  33. end
  34. end
  35. #
  36. # Support for IPv4
  37. #
  38. class IP::Address::IPv4 < IP::Address
  39. #
  40. # Parses an IP address and stores it as the current address
  41. #
  42. def IPv4.parse(ip_address)
  43. return IP::Address::IPv4.new(ip_address)
  44. end
  45. #
  46. # Constructs an IP::Address::IPv4 object.
  47. #
  48. # This can take two types of input. Either a string that contains
  49. # a dotted-quad formatted address, or an integer that contains the
  50. # data. This integer is expected to be constructed in the way that
  51. # IP::Address::Util.pack_ipv4 would generate such an integer.
  52. #
  53. # This constructor will throw IP::AddressException on any parse
  54. # errors.
  55. #
  56. def initialize(ip_address)
  57. if ip_address.kind_of? Integer
  58. # unpack to generate a string, and parse that.
  59. # overwrites 'ip_address'
  60. # horribly inefficient, but general.
  61. raw = IP::Address::Util.raw_unpack(ip_address)[0..1]
  62. octets = []
  63. 2.times do |x|
  64. octets.push(raw[x] & 0x00FF)
  65. octets.push((raw[x] & 0xFF00) >> 8)
  66. end
  67. ip_address = octets.reverse.join(".")
  68. end
  69. if ! ip_address.kind_of? String
  70. raise IP::AddressException.new("Fed IP address '#{ip_address}' is not String or Fixnum")
  71. end
  72. @ip_address = ip_address
  73. #
  74. # Unbeknowest by me, to_i will not throw an exception if the string
  75. # can't be converted cleanly - it just truncates, similar to atoi() and perl's int().
  76. #
  77. # Code below does a final sanity check.
  78. #
  79. octets = ip_address.split(/\./)
  80. octets_i = octets.collect { |x| x.to_i }
  81. 0.upto(octets.length - 1) do |octet|
  82. if octets[octet] != octets_i[octet].to_s
  83. raise IP::AddressException.new("Integer conversion failed")
  84. end
  85. end
  86. @octets = octets_i
  87. # I made a design decision to allow 0.0.0.0 here.
  88. if @octets.length != 4 or @octets.find_all { |x| x > 255 }.length > 0
  89. raise IP::AddressException.new("IP address is improperly formed")
  90. end
  91. end
  92. #
  93. # Returns a 128-bit integer representing the address.
  94. #
  95. def pack
  96. # this routine does relatively little. all it does is ensure
  97. # that the IP address is of a certain size and has certain numeric limits.
  98. myip = self.octets
  99. packval = [0] * 6
  100. #
  101. # this ensures that the octets are 8 bit, and combines the octets in order to
  102. # form two 16-bit integers suitable for pushing into the last places in 'packval'
  103. #
  104. (0..3).step(2) { |x| packval.push(((myip[x] & 0xFF) << 8) | (myip[x+1] & 0xFF)) }
  105. return IP::Address::Util.raw_pack(packval)
  106. end
  107. end
  108. class IP::Address::IPv6 < IP::Address
  109. #
  110. # Construct a new IP::Address::IPv6 object.
  111. #
  112. # It can be passed two different types for construction:
  113. #
  114. # * A string which contains a valid, RFC4291-compliant IPv6 address
  115. # (all forms are supported, including the
  116. # backwards-compatibility IPv4 methods)
  117. #
  118. # * A 128-bit integer which is a sum of all the octets, left-most
  119. # octet being the highest 32-bit portion (see IP::Address::Util
  120. # for help generating this value)
  121. #
  122. def initialize(ip_address)
  123. if ip_address.kind_of? Integer
  124. # unpack to generate a string, and parse that.
  125. # overwrites 'ip_address'
  126. # horribly inefficient, but general.
  127. raw = IP::Address::Util.raw_unpack(ip_address)
  128. ip_address = format_address(raw.reverse)
  129. end
  130. if ! ip_address.kind_of? String
  131. raise IP::AddressException.new("Fed IP address '#{ip_address}' is not String or Fixnum")
  132. end
  133. @ip_address = ip_address
  134. octets = parse_address(ip_address)
  135. if octets.length != 8
  136. raise IP::AddressException.new("IPv6 address '#{ip_address}' does not have 8 octets or a floating range specifier")
  137. end
  138. #
  139. # Now we check the contents of the address, to be sure we have
  140. # proper hexidecimal values
  141. #
  142. @octets = octets_atoi(octets)
  143. end
  144. #
  145. # parses an ip address and stores it as the current object.
  146. #
  147. def IPv6.parse(ip_address)
  148. return IP::Address::IPv6.new(ip_address)
  149. end
  150. #
  151. # returns an octet in its hexidecimal representation.
  152. #
  153. def octet_as_hex(index)
  154. return format_octet(self[index])
  155. end
  156. #
  157. # Returns an address with no floating range specifier.
  158. #
  159. # Ex:
  160. #
  161. # IP::Address::IPv6.new("DEAD::BEEF").long_address => "DEAD:0:0:0:0:0:0:BEEF"
  162. #
  163. def long_address
  164. return format_address
  165. end
  166. #
  167. # Returns a shortened address using the :: range specifier.
  168. #
  169. # This will replace any sequential octets that are equal to '0' with '::'.
  170. # It does this searching from right to left, looking for a sequence
  171. # of them. Per specification, only one sequence can be replaced in
  172. # this fashion. It will return a long address if it can't find
  173. # something suitable.
  174. #
  175. # Ex:
  176. #
  177. # "DEAD:0:0:0:BEEF:0:0:0" => "DEAD:0:0:0:BEEF::"
  178. def short_address
  179. octets = @octets.dup
  180. # short circuit: if less than 2 octets are equal to 0, don't
  181. # bother - return a long address.
  182. if octets.find_all { |x| x == 0 }.length < 2
  183. return format_address(octets)
  184. end
  185. filling = false
  186. left = []
  187. right = []
  188. 7.downto(0) do |x|
  189. if !filling and left.length == 0 and octets[x] == 0
  190. filling = true
  191. elsif filling
  192. if octets[x] != 0
  193. left.push(octets[x])
  194. filling = false
  195. end
  196. elsif left.length > 0
  197. left.push(octets[x])
  198. else
  199. right.push(octets[x])
  200. end
  201. end
  202. return format_address(left.reverse) + "::" + format_address(right.reverse)
  203. end
  204. #
  205. # Returns a 128-bit integer representing the address.
  206. #
  207. def pack
  208. return IP::Address::Util.raw_pack(self.octets.dup)
  209. end
  210. protected
  211. #
  212. # will parse a string address and return an 8 index array containing
  213. # all the octets. Should handle IPv4 to IPv6 and wildcards properly.
  214. #
  215. def parse_address(address)
  216. #
  217. # since IPv6 can have missing parts, we have to scan sequentially.
  218. # fill the LHS until we encounter '::', at which point we fill the
  219. # RHS.
  220. #
  221. # If the LHS is 8 octets, we're done. Otherwise, we take a
  222. # difference of 8 and the sum of the number of octets from the LHS
  223. # from the number of octets from the RHS. We then fill abs(num) at
  224. # the end of the LHS array with 0, and combine them to form the
  225. # address.
  226. #
  227. # There can only be one '::' in an address, so if we are filling
  228. # the RHS and encounter another '::', we throw AddressException.
  229. #
  230. # catches ::XXXX::
  231. if address.scan(/::/).length > 1
  232. raise IP::AddressException.new("IPv6 address '#{ip_address}' has more than one floating range ('::') specifier")
  233. end
  234. octets = address.split(":")
  235. if octets.length < 8
  236. if octets[-1].index(".").nil? and address.match(/::/)
  237. octets = handle_wildcard_in_address(octets)
  238. elsif octets[-1].index(".")
  239. # we have a dotted quad IPv4 compatibility address.
  240. # create an IPv4 object, get the raw value and stuff it into
  241. # the lower two octets.
  242. raw = IP::Address::IPv4.new(octets.pop).pack
  243. raw = raw & 0xFFFFFFFF
  244. low = raw & 0xFFFF
  245. high = (raw >> 16) & 0xFFFF
  246. octets = handle_wildcard_in_address(octets)[0..5] + ([high, low].collect { |x| format_octet(x) })
  247. else
  248. raise IP::AddressException.new("IPv6 address '#{address}' has less than 8 octets")
  249. end
  250. elsif octets.length > 8
  251. raise IP::AddressException.new("IPv6 address '#{address}' has more than 8 octets")
  252. end
  253. return octets
  254. end
  255. #
  256. # This handles :: addressing in IPv6 and generates a full set of
  257. # octets in response.
  258. #
  259. # The series of octets handed to this routine are expected to be the
  260. # result of splitting the address by ':'.
  261. #
  262. def handle_wildcard_in_address(octets)
  263. lhs = []
  264. rhs = []
  265. i = octets.index("") # find ::
  266. # easy out for xxxx:xxxx:: and so on
  267. if i.nil?
  268. lhs = octets.dup
  269. elsif i == 0
  270. # for some reason "::123:123".split(":") returns two empty
  271. # strings in the array, yet a trailing "::" doesn't.
  272. rhs = octets[2..-1]
  273. else
  274. lhs = octets[0..(i-1)]
  275. rhs = octets[(i+1)..-1]
  276. end
  277. unless rhs.index("").nil?
  278. raise IP::AddressException.new("IPv6 address '#{ip_address}' has more than one floating range ('::') specifier")
  279. end
  280. missing = (8 - (lhs.length + rhs.length))
  281. missing.times { lhs.push("0") }
  282. octets = lhs + rhs
  283. return octets
  284. end
  285. #
  286. # Converts (and checks) a series of octets from ascii to integer.
  287. #
  288. def octets_atoi(octets)
  289. new_octets = []
  290. octets.each do |x|
  291. if x.length > 4
  292. raise IP::AddressException.new("IPv6 address '#{ip_address}' has an octet that is larger than 32 bits")
  293. end
  294. octet = x.hex
  295. # normalize the octet to 4 places with leading zeroes, uppercase.
  296. x = ("0" * (4 - x.length)) + x.upcase
  297. unless ("%0.4X" % octet) == x
  298. raise IP::AddressException.new("IPv6 address '#{ip_address}' has octets that contain non-hexidecimal data")
  299. end
  300. new_octets.push(octet)
  301. end
  302. return new_octets
  303. end
  304. #
  305. # Formats the integer octets in ruby into their respective hex values.
  306. #
  307. def format_octet(octet)
  308. # this isn't required by the spec, but makes the output quite a bit
  309. # prettier.
  310. if octet < 10
  311. return octet.to_s
  312. end
  313. return "%0.4X" % octet
  314. end
  315. #
  316. # The same as +format_octet+, but processes an array (intended to be
  317. # a whole address).
  318. #
  319. def format_address(octets=@octets)
  320. return octets.collect { |x| format_octet(x) }.join(":")
  321. end
  322. end