netaddr 1.5.3 → 2.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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,24 +1,126 @@
1
- require 'time'
2
- require 'digest/sha1'
3
- require File.join(File.dirname(__FILE__), 'validation_shortcuts.rb')
4
- require File.join(File.dirname(__FILE__), 'ip_math.rb')
5
- require File.join(File.dirname(__FILE__), 'cidr_shortcuts.rb')
6
- require File.join(File.dirname(__FILE__), 'methods.rb')
7
- require File.join(File.dirname(__FILE__), 'cidr.rb')
8
- require File.join(File.dirname(__FILE__), 'tree.rb')
9
- 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"
10
10
 
11
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
+
12
20
 
13
- class BoundaryError < StandardError #:nodoc:
21
+ # ValidationError is thrown when a method fails a validation test.
22
+ class ValidationError < StandardError
14
23
  end
15
-
16
- class ValidationError < StandardError #:nodoc:
17
- end
18
-
19
- class VersionError < StandardError #:nodoc:
20
- end
21
-
22
- end # module NetAddr
23
-
24
- __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?(":"))
52
+ return IPv6Net.parse(net)
53
+ end
54
+ return IPv4Net.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