netaddr 1.5.1 → 2.0.5

Sign up to get free protection for your applications and to get access to all the features.
data/lib/mask128.rb ADDED
@@ -0,0 +1,70 @@
1
+ module NetAddr
2
+
3
+ #Mask128 represents a 128-bit netmask.
4
+ class Mask128
5
+ # mask is the Integer representation of this netmask
6
+ attr_reader :mask
7
+
8
+ # prefix_len is the Integer prefix length of this netmask
9
+ attr_reader :prefix_len
10
+
11
+ # Create a Mask128 from an Integer prefix length. Valid values are 0-128.
12
+ # Throws ValidationError on error.
13
+ def initialize(prefix_len)
14
+ if (!prefix_len.kind_of?(Integer))
15
+ raise ValidationError, "Expected an Integer for 'prefix_len' but got a #{prefix_len.class}."
16
+ elsif ( (prefix_len < 0) || (prefix_len > 128) )
17
+ raise ValidationError, "#{prefix_len} must be in the range of 0-128."
18
+ end
19
+ @prefix_len = prefix_len
20
+ @mask = NetAddr::F128 ^ (NetAddr::F128 >> @prefix_len)
21
+ end
22
+
23
+ # parse will create an Mask128 from its string representation.
24
+ # arguments:
25
+ # * mask - String representing an netmask (ie. "/64").
26
+ #
27
+ # Throws ValidationError on error.
28
+ def Mask128.parse(mask)
29
+ mask = mask.strip
30
+ if (mask.start_with?("/")) # cidr format
31
+ mask = mask[1..-1] # remove "/"
32
+ end
33
+ return Mask128.new(Integer(mask))
34
+ rescue ArgumentError
35
+ raise ValidationError, "#{mask} is not valid integer."
36
+ end
37
+
38
+ #cmp compares equality with another Mask128. Return:
39
+ #* 1 if this Mask128 is larger in capacity
40
+ #* 0 if the two are equal
41
+ #* -1 if this Mask128 is smaller in capacity
42
+ def cmp(other)
43
+ if (!other.kind_of?(Mask128))
44
+ raise ArgumentError, "Expected an Mask128 object for 'other' but got a #{other.class}."
45
+ end
46
+ if (self.prefix_len < other.prefix_len)
47
+ return 1
48
+ elsif (self.prefix_len > other.prefix_len)
49
+ return -1
50
+ end
51
+ return 0
52
+ end
53
+
54
+ #len returns the number of IP addresses in this network. This is only useful if you have a subnet
55
+ # smaller than a /64 as it will always return 0 for prefixes <= 64.
56
+ def len()
57
+ if (self.prefix_len <= 64)
58
+ return 0
59
+ end
60
+ return (self.mask ^ NetAddr::F128) + 1 # bit flip the netmask and add 1
61
+ end
62
+
63
+ # to_s returns the Mask128 as a String
64
+ def to_s()
65
+ return "/#{@prefix_len}"
66
+ end
67
+
68
+ end # end class Mask128
69
+
70
+ end # end module
data/lib/mask32.rb ADDED
@@ -0,0 +1,105 @@
1
+ module NetAddr
2
+
3
+ #Mask32 represents a 32-bit netmask.
4
+ class Mask32
5
+ # mask is the Integer representation of this netmask
6
+ attr_reader :mask
7
+
8
+ # prefix_len is the Integer prefix length of this netmask
9
+ attr_reader :prefix_len
10
+
11
+ # Create a Mask32 from an Integer prefix length. Valid values are 0-32.
12
+ # Throws ValidationError on error.
13
+ def initialize(prefix_len)
14
+ if (!prefix_len.kind_of?(Integer))
15
+ raise ValidationError, "Expected an Integer for 'prefix_len' but got a #{prefix_len.class}."
16
+ elsif ( (prefix_len < 0) || (prefix_len > 32) )
17
+ raise ValidationError, "#{prefix_len} must be in the range of 0-32."
18
+ end
19
+ @prefix_len = prefix_len
20
+ @mask = NetAddr::F32 ^ (NetAddr::F32 >> @prefix_len)
21
+ end
22
+
23
+ # parse will create an Mask32 from its string representation.
24
+ # arguments:
25
+ # * mask - String representing a netmask (ie. "/24" or "255.255.255.0").
26
+ #
27
+ # Throws ValidationError on error.
28
+ def Mask32.parse(mask)
29
+ mask = mask.strip
30
+ if (mask.start_with?("/")) # cidr format
31
+ mask = mask[1..-1] # remove "/"
32
+ end
33
+
34
+ if (!mask.include?("."))
35
+ begin
36
+ return Mask32.new(Integer(mask))
37
+ rescue ArgumentError
38
+ raise ValidationError, "#{mask} is not valid integer."
39
+ end
40
+ end
41
+
42
+ # for extended netmask
43
+ # determine length of netmask by cycling through bit by bit and looking
44
+ # for the first '1' bit, tracking the length as we go. we also want to verify
45
+ # that the mask is valid (ie. not something like 255.254.255.0). we do this
46
+ # by creating a hostmask which covers the '0' bits of the mask. once we have
47
+ # separated the net vs host mask we xor them together. the result should be that
48
+ # all bits are now '1'. if not then we know we have an invalid netmask.
49
+ maskI = Util.parse_IPv4(mask)
50
+ prefix = 32
51
+ hostmask = 1
52
+ i = maskI
53
+ 32.downto(1) do
54
+ if (i&1 == 1)
55
+ hostmask = hostmask >> 1
56
+ if (maskI ^hostmask != NetAddr::F32)
57
+ raise ValidationError, "#{mask} is invalid. It contains '1' bits in its host portion."
58
+ end
59
+ break
60
+ end
61
+ hostmask = (hostmask << 1) | 1
62
+ i = i>>1
63
+ prefix -= 1
64
+ end
65
+ return Mask32.new(prefix)
66
+
67
+ end
68
+
69
+ # extended returns the Mask32 in extended format (eg. x.x.x.x)
70
+ def extended()
71
+ Util.int_to_IPv4(@mask)
72
+ end
73
+
74
+ #cmp compares equality with another Mask32. Return:
75
+ #* 1 if this Mask128 is larger in capacity
76
+ #* 0 if the two are equal
77
+ #* -1 if this Mask128 is smaller in capacity
78
+ def cmp(other)
79
+ if (!other.kind_of?(Mask32))
80
+ raise ArgumentError, "Expected an Mask32 object for 'other' but got a #{other.class}."
81
+ end
82
+ if (self.prefix_len < other.prefix_len)
83
+ return 1
84
+ elsif (self.prefix_len > other.prefix_len)
85
+ return -1
86
+ end
87
+ return 0
88
+ end
89
+
90
+ #len returns the number of IP addresses in this network. It will always return 0 for /0 networks.
91
+ def len()
92
+ if (self.prefix_len == 0)
93
+ return 0
94
+ end
95
+ return (self.mask ^ NetAddr::F32) + 1 # bit flip the netmask and add 1
96
+ end
97
+
98
+ # to_s returns the Mask32 as a String
99
+ def to_s()
100
+ return "/#{@prefix_len}"
101
+ end
102
+
103
+ end # end class Mask32
104
+
105
+ end # end module
data/lib/netaddr.rb CHANGED
@@ -1,30 +1,126 @@
1
- =begin rdoc
2
- Copyleft (c) 2006 Dustin Spinhirne
3
-
4
- Licensed under the same terms as Ruby, No Warranty is provided.
5
- =end
6
-
7
- require 'time'
8
- require 'digest/sha1'
9
- require File.join(File.dirname(__FILE__), 'validation_shortcuts.rb')
10
- require File.join(File.dirname(__FILE__), 'ip_math.rb')
11
- require File.join(File.dirname(__FILE__), 'cidr_shortcuts.rb')
12
- require File.join(File.dirname(__FILE__), 'methods.rb')
13
- require File.join(File.dirname(__FILE__), 'cidr.rb')
14
- require File.join(File.dirname(__FILE__), 'tree.rb')
15
- require File.join(File.dirname(__FILE__), 'eui.rb')
1
+ require_relative "eui48.rb"
2
+ require_relative "eui64.rb"
3
+ require_relative "ipv4.rb"
4
+ require_relative "ipv4net.rb"
5
+ require_relative "ipv6.rb"
6
+ require_relative "ipv6net.rb"
7
+ require_relative "mask32.rb"
8
+ require_relative "mask128.rb"
9
+ require_relative "util.rb"
16
10
 
17
11
  module NetAddr
12
+ # Constants
13
+
14
+ # 32 bits worth of '1'
15
+ F32 = 2**32-1
16
+
17
+ # 128 bits worth of '1'
18
+ F128 = 2**128-1
19
+
18
20
 
19
- class BoundaryError < StandardError #:nodoc:
20
- end
21
-
22
- class ValidationError < StandardError #:nodoc:
23
- end
24
-
25
- class VersionError < StandardError #:nodoc:
26
- end
27
-
28
- end # module NetAddr
29
-
30
- __END__
21
+ # ValidationError is thrown when a method fails a validation test.
22
+ class ValidationError < StandardError
23
+ end
24
+
25
+ # ipv4_prefix_len returns the prefix length needed to hold the number of IP addresses specified by "size".
26
+ def ipv4_prefix_len(size)
27
+ prefix_len = 32
28
+ 32.downto(0) do |i|
29
+ hostbits = 32 - prefix_len
30
+ max = 1 << hostbits
31
+ if (size <= max)
32
+ break
33
+ end
34
+ prefix_len -= 1
35
+ end
36
+ return prefix_len
37
+ end
38
+ module_function :ipv4_prefix_len
39
+
40
+ ## parse_ip parses a string into an IPv4 or IPv6
41
+ def parse_ip(ip)
42
+ if (ip.include?(":"))
43
+ return IPv6.parse(ip)
44
+ end
45
+ return IPv4.parse(ip)
46
+ end
47
+ module_function :parse_ip
48
+
49
+ ## parse_net parses a string into an IPv4Net or IPv6Net
50
+ def parse_net(net)
51
+ if (net.include?(".")) # ipv4
52
+ return IPv4Net.parse(net)
53
+ end
54
+ return IPv6Net.parse(net)
55
+ end
56
+ module_function :parse_net
57
+
58
+ # sort_IPv4 sorts a list of IPv4 objects in ascending order.
59
+ # It will return a new list with any non IPv4 objects removed.
60
+ def sort_IPv4(list)
61
+ if ( !list.kind_of?(Array) )
62
+ raise ArgumentError, "Expected an Array for 'list' but got a #{list.class}."
63
+ end
64
+ filtered = Util.filter_IPv4(list)
65
+ return Util.quick_sort(filtered)
66
+ end
67
+ module_function :sort_IPv4
68
+
69
+ # sort_IPv6 sorts a list of IPv6 objects in ascending order.
70
+ # It will return a new list with any non IPv6 objects removed.
71
+ def sort_IPv6(list)
72
+ if ( !list.kind_of?(Array) )
73
+ raise ArgumentError, "Expected an Array for 'list' but got a #{list.class}."
74
+ end
75
+ filtered = Util.filter_IPv6(list)
76
+ return Util.quick_sort(filtered)
77
+ end
78
+ module_function :sort_IPv6
79
+
80
+ # sort_IPv4Net sorts a list of IPv4Net objects in ascending order.
81
+ # It will return a new list with any non IPv4Net objects removed.
82
+ def sort_IPv4Net(list)
83
+ if ( !list.kind_of?(Array) )
84
+ raise ArgumentError, "Expected an Array for 'list' but got a #{list.class}."
85
+ end
86
+ filtered = Util.filter_IPv4Net(list)
87
+ return Util.quick_sort(filtered)
88
+ end
89
+ module_function :sort_IPv4Net
90
+
91
+ # sort_IPv6Net sorts a list of IPv6Net objects in ascending order.
92
+ # It will return a new list with any non IPv6Net objects removed.
93
+ def sort_IPv6Net(list)
94
+ if ( !list.kind_of?(Array) )
95
+ raise ArgumentError, "Expected an Array for 'list' but got a #{list.class}."
96
+ end
97
+ filtered = Util.filter_IPv6Net(list)
98
+ return Util.quick_sort(filtered)
99
+ end
100
+ module_function :sort_IPv6Net
101
+
102
+ # summ_IPv4Net summarizes a list of IPv4Net objects as much as possible.
103
+ # It will return a new list with any non IPv4Net objects removed.
104
+ def summ_IPv4Net(list)
105
+ list = Util.filter_IPv4Net(list)
106
+ if (list.length>1)
107
+ list = Util.discard_subnets(list)
108
+ return Util.summ_peers(list)
109
+ end
110
+ return [].concat(list)
111
+ end
112
+ module_function :summ_IPv4Net
113
+
114
+ # summ_IPv6Net summarizes a list of IPv6Net objects as much as possible.
115
+ # It will return a new list with any non IPv6Net objects removed.
116
+ def summ_IPv6Net(list)
117
+ list = Util.filter_IPv6Net(list)
118
+ if (list.length>1)
119
+ list = Util.discard_subnets(list)
120
+ return Util.summ_peers(list)
121
+ end
122
+ return [].concat(list)
123
+ end
124
+ module_function :summ_IPv6Net
125
+
126
+ end # end module
data/lib/util.rb ADDED
@@ -0,0 +1,373 @@
1
+ module NetAddr
2
+
3
+ # Contains various internal util functions
4
+ class Util
5
+ private
6
+
7
+ # backfill generates subnets between given IPv4Net/IPv6Net and the limit address.
8
+ # limit should be < ipnet. will create subnets up to and including limit.
9
+ def Util.backfill(ipnet,limit)
10
+ nets = []
11
+ cur = ipnet
12
+ while true do
13
+ net = cur.prev
14
+ if (net == nil || net.network.addr < limit)
15
+ break
16
+ end
17
+ nets.unshift(net)
18
+ cur = net
19
+ end
20
+ return nets
21
+ end
22
+
23
+
24
+ # discard_subnets returns a copy of the IPv4NetList with any entries which are subnets of other entries removed.
25
+ def Util.discard_subnets(list)
26
+ keepers = []
27
+ last = list[list.length-1]
28
+ keep_last = true
29
+ list.each do |net|
30
+ rel = last.rel(net)
31
+ if (!rel) # keep unrelated nets
32
+ keepers.push(net)
33
+ elsif (rel == -1) # keep supernets, but do not keep last
34
+ keepers.push(net)
35
+ keep_last = false
36
+ end
37
+ end
38
+
39
+ # recursively clean up keepers
40
+ if (keepers.length > 0)
41
+ keepers = discard_subnets(keepers)
42
+ end
43
+ if keep_last
44
+ keepers.unshift(last)
45
+ end
46
+ return keepers
47
+ end
48
+
49
+ # fill returns a copy of the given Array, stripped of any networks which are not subnets of ipnet
50
+ # and with any missing gaps filled in.
51
+ def Util.fill(ipnet,list)
52
+ # sort & get rid of non subnets
53
+ subs = []
54
+ discard_subnets(list).each do |sub|
55
+ r = ipnet.rel(sub)
56
+ if (r == 1)
57
+ subs.push(sub)
58
+ end
59
+ end
60
+ subs = quick_sort(subs)
61
+
62
+ filled = []
63
+ if (subs.length > 0)
64
+ # bottom fill if base missing
65
+ base = ipnet.network.addr
66
+ if (subs[0].network.addr != base)
67
+ filled = backfill(subs[0],base)
68
+ end
69
+
70
+ # fill gaps between subnets
71
+ 0.upto(subs.length-1) do |i|
72
+ sub = subs[i]
73
+ if (i+1 < subs.length)
74
+ filled.concat( fwdfill(sub,ipnet,subs[i+1]) )
75
+ else
76
+ filled.concat( fwdfill(sub,ipnet,nil) )
77
+ end
78
+ end
79
+ end
80
+ return filled
81
+ end
82
+
83
+ # filter_IPv4 returns a copy of list with only IPv4 objects
84
+ def Util.filter_IPv4(list)
85
+ filtered = []
86
+ list.each do |ip|
87
+ if (ip.kind_of?(IPv4))
88
+ filtered.push(ip)
89
+ end
90
+ end
91
+ return filtered
92
+ end
93
+
94
+ # filter_IPv4Net returns a copy of list with only IPv4Net objects
95
+ def Util.filter_IPv4Net(list)
96
+ filtered = []
97
+ list.each do |ip|
98
+ if (ip.kind_of?(IPv4Net))
99
+ filtered.push(ip)
100
+ end
101
+ end
102
+ return filtered
103
+ end
104
+
105
+ # filter_IPv6 returns a copy of list with only IPv6 objects
106
+ def Util.filter_IPv6(list)
107
+ filtered = []
108
+ list.each do |ip|
109
+ if (ip.kind_of?(IPv6))
110
+ filtered.push(ip)
111
+ end
112
+ end
113
+ return filtered
114
+ end
115
+
116
+ # filter_IPv6Net returns a copy of list with only IPv4Net objects
117
+ def Util.filter_IPv6Net(list)
118
+ filtered = []
119
+ list.each do |ip|
120
+ if (ip.kind_of?(IPv6Net))
121
+ filtered.push(ip)
122
+ end
123
+ end
124
+ return filtered
125
+ end
126
+
127
+ # fwdfill returns subnets between given IPv4Net/IPv6Nett and the limit address. limit should be > ipnet.
128
+ def Util.fwdfill(ipnet,supernet,limit)
129
+ nets = [ipnet]
130
+ cur = ipnet
131
+ if (limit != nil) # if limit, then fill gaps between net and limit
132
+ while true do
133
+ nextSub = cur.next()
134
+ # ensure we've not exceed the total address space
135
+ if (nextSub == nil)
136
+ break
137
+ end
138
+ # ensure we've not exceeded the address space of supernet
139
+ if (supernet.rel(nextSub) == nil)
140
+ break
141
+ end
142
+ # ensure we've not hit limit
143
+ if (nextSub.network.addr == limit.network.addr)
144
+ break
145
+ end
146
+
147
+ # check relationship to limit
148
+ if (nextSub.rel(limit) != nil) # if related, then nextSub must be a supernet of limit. we need to shrink it.
149
+ prefixLen = nextSub.netmask.prefix_len
150
+ while true do
151
+ prefixLen += 1
152
+ if (nextSub.kind_of?(IPv4Net))
153
+ nextSub = IPv4Net.new(nextSub.network, Mask32.new(prefixLen))
154
+ else
155
+ nextSub = IPv6Net.new(nextSub.network, Mask128.new(prefixLen))
156
+ end
157
+ if (nextSub.rel(limit) == nil) # stop when we no longer overlap with limit
158
+ break
159
+ end
160
+ end
161
+ else # otherwise, if unrelated then grow until we hit the limit
162
+ prefixLen = nextSub.netmask.prefix_len
163
+ mask = nextSub.netmask.mask
164
+ while true do
165
+ prefixLen -= 1
166
+ if (prefixLen == supernet.netmask.prefix_len) # break if we've hit the supernet boundary
167
+ break
168
+ end
169
+ mask = mask << 1
170
+ if (nextSub.network.addr|mask != mask) # break when bit boundary crossed (there are '1' bits in the host portion)
171
+ break
172
+ end
173
+ if (nextSub.kind_of?(IPv4Net))
174
+ grown = IPv4Net.new(nextSub.network, Mask32.new(prefixLen))
175
+ else
176
+ grown = IPv6Net.new(nextSub.network, Mask128.new(prefixLen))
177
+ end
178
+ if (grown.rel(limit) != nil) # if we've overlapped with limit in any way, then break
179
+ break
180
+ end
181
+ nextSub = grown
182
+ end
183
+ end
184
+ nets.push(nextSub)
185
+ cur = nextSub
186
+ end
187
+ else # if no limit, then get next largest sibs until we've exceeded supernet
188
+ while true do
189
+ nextSub = cur.next()
190
+ # ensure we've not exceed the total address space
191
+ if (nextSub == nil)
192
+ break
193
+ end
194
+ # ensure we've not exceeded the address space of supernet
195
+ if (supernet.rel(nextSub) == nil)
196
+ break
197
+ end
198
+ nets.push(nextSub)
199
+ cur = nextSub
200
+ end
201
+ end
202
+ return nets
203
+ end
204
+
205
+ # int_to_IPv4 converts an Integer into an IPv4 address String
206
+ def Util.int_to_IPv4(i)
207
+ octets = []
208
+ 3.downto(0) do |x|
209
+ octet = (i >> 8*x) & 0xFF
210
+ octets.push(octet.to_s)
211
+ end
212
+ return octets.join('.')
213
+ end
214
+
215
+ # parse_IPv4 parses an IPv4 address String into an Integer
216
+ def Util.parse_IPv4(ip)
217
+ # check that only valid characters are present
218
+ if (ip =~ /[^0-9\.]/)
219
+ raise ValidationError, "#{ip} contains invalid characters."
220
+ end
221
+
222
+ octets = ip.strip.split('.')
223
+ if (octets.length != 4)
224
+ raise ValidationError, "IPv4 requires (4) octets."
225
+ end
226
+
227
+ ipInt = 0
228
+ i = 4
229
+ octets.each do |octet|
230
+ octetI = octet.to_i()
231
+ if ( (octetI < 0) || (octetI >= 256) )
232
+ raise ValidationError, "#{ip} is out of bounds for IPv4."
233
+ end
234
+ i -= 1
235
+ ipInt = ipInt | (octetI << 8*i)
236
+ end
237
+ return ipInt
238
+ end
239
+
240
+ # parse_IPv6 parses an IPv6 address String into an Integer
241
+ def Util.parse_IPv6(ip)
242
+ # check that only valid characters are present
243
+ if (ip =~ /[^0-9a-fA-F\:.]/)
244
+ raise ValidationError, "#{ip} contains invalid characters."
245
+ end
246
+
247
+ ip = ip.strip
248
+ if (ip == "::")
249
+ return 0 # zero address
250
+ end
251
+ ipv4Int = nil
252
+ if (ip.include?(".")) # check for ipv4 embedded addresses
253
+ words = ip.split(":")
254
+ begin
255
+ ipv4Int = Util.parse_IPv4(words.last)
256
+ rescue
257
+ raise ValidationError, "IPv4-embedded IPv6 address is invalid."
258
+ end
259
+ ip = ip.sub(words.last,"0:0") # temporarily remove the ipv4 portion
260
+ end
261
+ words = []
262
+ if (ip.include?("::")) # short format
263
+ if (ip =~ /:{3,}/) # make sure only i dont have ":::"
264
+ raise ValidationError, "#{ip} contains invalid field separator."
265
+ end
266
+ if (ip.scan(/::/).length != 1)
267
+ raise ValidationError, "#{ip} contains multiple '::' sequences."
268
+ end
269
+
270
+ halves = ip.split("::")
271
+ if (halves[0] == nil) # cases such as ::1
272
+ halves[0] = "0"
273
+ end
274
+ if (halves[1] == nil) # cases such as 1::
275
+ halves[1] = "0"
276
+ end
277
+ upHalf = halves[0].split(":")
278
+ loHalf = halves[1].split(":")
279
+ numWords = upHalf.length + loHalf.length
280
+ if (numWords > 8)
281
+ raise ValidationError, "#{ip} is too long."
282
+ end
283
+ words = upHalf
284
+ (8-numWords).downto(1) do |i|
285
+ words.push("0")
286
+ end
287
+ words.concat(loHalf)
288
+ else
289
+ words = ip.split(":")
290
+ if (words.length > 8)
291
+ raise ValidationError, "#{ip} is too long."
292
+ elsif (words.length < 8)
293
+ raise ValidationError, "#{ip} is too short."
294
+ end
295
+ end
296
+ ipInt = 0
297
+ i = 8
298
+ words.each do |word|
299
+ i -= 1
300
+ word = word.to_i(16) << (16*i)
301
+ ipInt = ipInt | word
302
+ end
303
+ if ipv4Int # re-add ipv4 portion if present
304
+ ipInt = ipInt | ipv4Int
305
+ end
306
+ return ipInt
307
+ end
308
+
309
+ # quick_sort will return a sorted copy of the provided Array.
310
+ # The array must contain only objects which implement a cmp method and which are comparable to each other.
311
+ def Util.quick_sort(list)
312
+ if (list.length <= 1)
313
+ return [].concat(list)
314
+ end
315
+
316
+ final_list = []
317
+ lt_list = []
318
+ gt_list = []
319
+ eq_list = []
320
+ pivot = list[list.length-1]
321
+ list.each do |ip|
322
+ cmp = pivot.cmp(ip)
323
+ if (cmp == 1)
324
+ lt_list.push(ip)
325
+ elsif (cmp == -1)
326
+ gt_list.push(ip)
327
+ else
328
+ eq_list.push(ip)
329
+ end
330
+ end
331
+ final_list.concat( quick_sort(lt_list) )
332
+ final_list.concat(eq_list)
333
+ final_list.concat( quick_sort(gt_list) )
334
+ return final_list
335
+ end
336
+
337
+ # summ_peers returns a copy of the list with any merge-able subnets summ'd together.
338
+ def Util.summ_peers(list)
339
+ summd = quick_sort(list)
340
+ while true do
341
+ list_len = summd.length
342
+ last = list_len - 1
343
+ tmp_list = []
344
+ i = 0
345
+ while (i < list_len) do
346
+ net = summd[i]
347
+ next_net = i+1
348
+ if (i != last)
349
+ # if this net and next_net summarize then discard them & keep summary
350
+ new_net = net.summ(summd[next_net])
351
+ if (new_net) # can summ. keep summary
352
+ tmp_list.push(new_net)
353
+ i += 1 # skip next_net
354
+ else # cant summ. keep existing
355
+ tmp_list.push(net)
356
+ end
357
+ else
358
+ tmp_list.push(net) # keep last
359
+ end
360
+ i += 1
361
+ end
362
+
363
+ # stop when list stops getting shorter
364
+ if (tmp_list.length == list_len)
365
+ break
366
+ end
367
+ summd = tmp_list
368
+ end
369
+ return summd
370
+ end
371
+
372
+ end # end class
373
+ end # end module