iplogic 0.1.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ # MIT license. See http://www.opensource.org/licenses/mit-license.php
2
+
3
+ Copyright (c) 2010 Philotic, Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,11 @@
1
+ # Because it's just a 32-bit integer.
2
+
3
+ Usage:
4
+
5
+ require 'iplogic'
6
+ include IPLogic
7
+
8
+ ip = IP('11.22.33.44')
9
+ cidr = CIDR('11.22.32.00/20')
10
+
11
+ Look in `spec/ip_spec.rb` for a neat summary of al the methods available.
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+
4
+ begin
5
+ require 'spec/rake/spectask'
6
+ Spec::Rake::SpecTask.new('spec') do |t|
7
+ t.spec_files = FileList['spec/**/*_spec.rb']
8
+ t.ruby_opts = ["-r spec/spec_helper.rb"]
9
+ end
10
+ rescue LoadError
11
+ #pass. rspec is not required
12
+ end
data/iplogic.gemspec ADDED
@@ -0,0 +1,44 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'iplogic'
3
+ s.version = '0.1.0'
4
+
5
+ s.authors = ["Jay Adkisson"]
6
+ s.date = '2010-11-13'
7
+ s.description = <<-desc.strip
8
+ An IPv4 swiss-army chainsaw
9
+ desc
10
+
11
+ s.email = %q{jay@causes.com}
12
+ s.extra_rdoc_files = [
13
+ "LICENSE",
14
+ "README.md"
15
+ ]
16
+ s.files = %w(
17
+ Rakefile
18
+ LICENSE
19
+ README.md
20
+ iplogic.gemspec
21
+ lib/iplogic.rb
22
+ lib/core_ext.rb
23
+ lib/core_ext/fixnum.rb
24
+ lib/iplogic/cidr.rb
25
+ lib/iplogic/ip.rb
26
+ )
27
+
28
+ s.homepage = 'http://github.com/causes/iplogic'
29
+ s.require_paths = ["lib"]
30
+ s.rubygems_version = '1.3.7'
31
+
32
+ s.summary = <<-sum
33
+ Because it's just a 32-bit integer.
34
+ sum
35
+
36
+ s.test_files = %w(
37
+ spec/ip_spec.rb
38
+ spec/spec_helper.rb
39
+ spec/radix_spec.rb
40
+ )
41
+
42
+ s.add_development_dependency('rspec')
43
+ end
44
+
@@ -0,0 +1,14 @@
1
+ class Fixnum
2
+ def radix(rad)
3
+ raise ArgumentError if self < 0
4
+ return [] if zero?
5
+
6
+ i = self
7
+ digits = []
8
+ while i > 0
9
+ i, r = i.divmod(rad)
10
+ digits << r
11
+ end
12
+ digits.reverse
13
+ end unless 0.respond_to? :radix
14
+ end
data/lib/core_ext.rb ADDED
@@ -0,0 +1,3 @@
1
+ Dir.glob(File.join(File.dirname(__FILE__), 'core_ext/*.rb')).each do |f|
2
+ require f
3
+ end
@@ -0,0 +1,108 @@
1
+ module IPLogic
2
+ class CIDR
3
+ include Enumerable
4
+
5
+ class << self
6
+ def wrap(*args)
7
+ return args.first if args.first.is_a? CIDR
8
+ if args.size == 2
9
+ ip, bits = args
10
+
11
+ bits = case bits
12
+ when Integer
13
+ bits
14
+ when String
15
+ netmask_to_bits(IP.wrap(bits))
16
+ when IP
17
+ netmask_to_bits(bits)
18
+ else
19
+ bits.to_i
20
+ end
21
+
22
+ new(ip, bits)
23
+ elsif args.size == 1
24
+ arg = args.first
25
+ if arg =~ /(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\/(\d+)/
26
+ new($1, $2)
27
+ end
28
+ end
29
+ end
30
+ private
31
+ def netmask_to_bits(netmask)
32
+ # TODO: there's probably a clever mathy way to do this,
33
+ # but this works just fine.
34
+ netmask.to_i.to_s(2) =~ /^(1*)0*$/
35
+ $1.length
36
+ end
37
+ end
38
+
39
+ attr_reader :ip, :bits
40
+ alias prefix_length bits
41
+
42
+ def initialize(ip, bits)
43
+ @bits = bits.to_i
44
+ @ip = IP.wrap(ip)
45
+ end
46
+
47
+ ALL = self.new(0,0)
48
+ def self.all
49
+ ALL
50
+ end
51
+
52
+ def inv_bits
53
+ 32 - bits
54
+ end
55
+
56
+ def inspect
57
+ "#<CIDR [ #{ip}/#{bits} ]>"
58
+ end
59
+
60
+ def netmask
61
+ @netmask ||= IP.wrap(
62
+ ((1 << bits) - 1) << (32 - bits)
63
+ )
64
+ end
65
+
66
+ def size
67
+ @size ||= (1 << inv_bits)
68
+ end
69
+
70
+ def min
71
+ @min ||= IP.wrap(
72
+ (ip.to_i >> inv_bits) << inv_bits
73
+ )
74
+ end
75
+ alias :begin :min
76
+ alias first min
77
+ alias prefix min
78
+
79
+ def max
80
+ @max ||= min + (size - 1)
81
+ end
82
+ alias :end :max
83
+ alias last max
84
+
85
+ def rest_field
86
+ @rest_field ||= ip - min
87
+ end
88
+ alias rest rest_field
89
+
90
+ def significant_octets
91
+ 4 - (bits / 8)
92
+ end
93
+
94
+ def zone
95
+ ip.parts[0..-(1+significant_octets)].reverse.join('.')
96
+ end
97
+
98
+ def each(&blk)
99
+ (min..max).each(&blk)
100
+ end
101
+ end
102
+
103
+ def CIDR(*args)
104
+ return CIDR if args.empty?
105
+
106
+ CIDR.wrap(*args)
107
+ end
108
+ end
data/lib/iplogic/ip.rb ADDED
@@ -0,0 +1,148 @@
1
+ module IPLogic
2
+ class IP
3
+ class << self
4
+ # Basic monad wrapper for IP addresses.
5
+ #
6
+ # You can pass it:
7
+ #
8
+ # a String
9
+ # >> IP('11.22.33.44')
10
+ # => #<IP [ 11.22.33.44 ]>
11
+ #
12
+ # an Integer
13
+ # >> IP(0xFFFFFFFF)
14
+ # => #<IP [ 255.255.255.255 ]>
15
+ #
16
+ # an Array of int-ish objects
17
+ # >> IP(['11', 22, 33, '44'])
18
+ # => #<IP [ 11.22.33.44 ]>
19
+ #
20
+ # nil
21
+ # >> IP(nil)
22
+ # => #<IP [ 0.0.0.0 ]>
23
+ #
24
+ # an IP
25
+ # >> ip = IP('11.22.33.44')
26
+ # => #<IP [ 11.22.33.44 ]>
27
+ # >> IP(ip)
28
+ # => #<IP [ 11.22.33.44 ]>
29
+ # >> ip.object_id == IP(ip).object_id
30
+ # => true
31
+ #
32
+ def wrap(arg)
33
+ return arg if arg.is_a? IP
34
+
35
+ int = case arg
36
+ when Array
37
+ parts_to_int(arg)
38
+ when String
39
+ parts_to_int(arg.split('.'))
40
+ when Fixnum
41
+ arg
42
+ when nil
43
+ 0
44
+ else
45
+ raise ArgumentError, <<-msg.strip
46
+ Couldn't parse #{arg.inspect} to an IP.
47
+ msg
48
+ end
49
+
50
+ return new(int)
51
+ end
52
+
53
+ private
54
+ def parts_to_int(parts)
55
+ r = 0
56
+ parts.reverse.each_with_index do |part, i|
57
+ r += (part.to_i << 8*i)
58
+ end
59
+ r
60
+ end
61
+ end
62
+
63
+ # -*- instance methods -*-
64
+ attr_reader :int
65
+ alias to_i int
66
+ alias to_int int
67
+
68
+ def initialize(int)
69
+ @int = int
70
+ end
71
+
72
+ # 255.255.255.255
73
+ MAXIP = self.new(0xFFFFFFFF)
74
+ def self.max
75
+ MAXIP
76
+ end
77
+
78
+ def parts
79
+ @parts ||= begin
80
+ rad = int.radix(256)
81
+ [0]*([4-rad.size,0].max) + rad
82
+ end
83
+ end
84
+
85
+ def to_s
86
+ parts.join('.')
87
+ end
88
+ alias to_str to_s
89
+
90
+ def inspect
91
+ "#<IP [ #{self} ]>"
92
+ end
93
+
94
+ def eql?(other)
95
+ self.int == IP(other).int
96
+ end
97
+ alias == eql?
98
+
99
+ include Comparable
100
+ def <=>(other)
101
+ self.int <=> other.int
102
+ end
103
+
104
+ def +(int_ish)
105
+ IP(int + int_ish.to_i)
106
+ end
107
+
108
+ def succ
109
+ self + 1
110
+ end
111
+
112
+ def -(int_ish)
113
+ self + (-int_ish.to_i)
114
+ end
115
+
116
+ def prefix(netmask)
117
+ CIDR.wrap(self, netmask).min
118
+ end
119
+ alias min prefix
120
+
121
+ def max(netmask)
122
+ CIDR.wrap(self, netmask).max
123
+ end
124
+
125
+ def rest_field(netmask)
126
+ CIDR.wrap(self, netmask).rest_field
127
+ end
128
+ alias rest rest_field
129
+
130
+ def netmask?
131
+ maxint32 = 0xFFFFFFFF
132
+ (0..32).any? do |i|
133
+ (int-1) + (1 << i) == maxint32
134
+ end
135
+ end
136
+
137
+ def assert_netmask!
138
+ raise ArgumentError, <<-msg.strip unless netmask?
139
+ #{self.inspect} is not a valid netmask.
140
+ msg
141
+ end
142
+ end
143
+
144
+ def IP(*args)
145
+ return IP if args.empty?
146
+ IP.wrap(args.first)
147
+ end
148
+ end
data/lib/iplogic.rb ADDED
@@ -0,0 +1,9 @@
1
+ module IPLogic
2
+ VERSION = '0.1.0'
3
+
4
+ LIB = File.expand_path(File.dirname(__FILE__))
5
+ end
6
+
7
+ require File.join(IPLogic::LIB, 'core_ext')
8
+ require File.join(IPLogic::LIB, 'iplogic', 'ip')
9
+ require File.join(IPLogic::LIB, 'iplogic', 'cidr')
data/spec/ip_spec.rb ADDED
@@ -0,0 +1,222 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ describe IP do
4
+ it "parses a string" do
5
+ ip = IP('11.22.33.44')
6
+ ip.should be_a IP
7
+ ip.inspect.should include '11.22.33.44'
8
+ end
9
+
10
+ it "parses an integer" do
11
+ ip = IP(255)
12
+ ip.should be_a IP
13
+ ip.inspect.should include '0.0.0.255'
14
+
15
+ ip = IP(0xA00A10FF)
16
+ ip.should be_a IP
17
+ ip.inspect.should include '160.10.16.255'
18
+ end
19
+
20
+ it "parses an array of int-ish objects" do
21
+ four = Class.new do
22
+ def to_i
23
+ 4
24
+ end
25
+ end.new
26
+
27
+ ip = IP([7, '42', four, nil])
28
+ ip.should be_a IP
29
+ ip.inspect.should include '7.42.4.0'
30
+ end
31
+
32
+ it "parses nil" do
33
+ ip = IP(nil)
34
+ ip.should be_a IP
35
+ ip.inspect.should include '0.0.0.0'
36
+ end
37
+
38
+ it "parses an IP" do
39
+ ip = IP('11.22.33.44')
40
+ IP(ip).should be_a IP
41
+ IP(ip).object_id.should == ip.object_id
42
+ end
43
+
44
+ it "knows its integer representation" do
45
+ i = rand(0xFFFFFFFF)
46
+ IP(i).to_i.should == i
47
+ end
48
+
49
+ it "knows its string representation" do
50
+ IP('11.22.33.44').to_s.should == '11.22.33.44'
51
+ end
52
+
53
+ it "knows its parts" do
54
+ IP('44.33.22.11').parts.should == [44, 33, 22, 11]
55
+ end
56
+
57
+ it "knows the max" do
58
+ IP::MAXIP.to_i.should == 0xFFFFFFFF
59
+ IP.max.to_i.should == 0xFFFFFFFF
60
+ IP.max.to_s.should == '255.255.255.255'
61
+ end
62
+
63
+ it "is comparable" do
64
+ IP.included_modules.should include Comparable
65
+ IP.public_methods.should include '<=>'
66
+
67
+ i1, i2 = rand(0xFFFFFFFF), rand(0xFFFFFFFF)
68
+ (IP(i1) <=> IP(i2)).should == (i1 <=> i2)
69
+ end
70
+
71
+ it "can add, subtract, and succ" do
72
+ i1, i2 = rand(0xFFFFFFF), rand(0xFFFFFFF)
73
+ ip1 = IP(i1)
74
+ ipsum = IP(i1) + i2
75
+ ipsum.should be_a IP
76
+ ipsum.to_i.should == i1 + i2
77
+
78
+ ip1.succ.to_i.should == i1 + 1
79
+ (ipsum - i2).to_i.should == i1
80
+ (ipsum - ip1).to_i.should == i2
81
+ end
82
+
83
+ it "knows its prefix and rest field given a netmask" do
84
+ ip = IP('11.22.33.44')
85
+
86
+ ip.prefix('255.255.255.0').should == IP('11.22.33.00')
87
+ ip.rest_field('255.255.255.0').should == IP('0.0.0.44')
88
+ (ip.prefix('255.255.255.0') + ip.rest_field('255.255.255.0')).
89
+ should == ip
90
+
91
+ ip.prefix('255.255.240.0').should == IP('11.22.32.00')
92
+ ip.rest_field('255.255.240.0').should == IP('0.0.1.44')
93
+ (ip.prefix('255.255.240.0') + ip.rest_field('255.255.240.0')).
94
+ should == ip
95
+ end
96
+
97
+ it "knows whether it's a netmask" do
98
+ zero_bits = rand(32)
99
+ (0..32).each do |bits|
100
+ IP((0xFFFFFFFF >> bits) << bits).should be_netmask
101
+ end
102
+
103
+ IP('1.2.3.4').should_not be_netmask
104
+ IP('0.255.0.0').should_not be_netmask
105
+ end
106
+
107
+ describe CIDR do
108
+ it "parses an IP-ish and netmask-ish" do
109
+ r = CIDR('4.33.222.111', '255.255.240.0')
110
+ r.should be_a CIDR
111
+ r.inspect.should include '4.33.222.111/20'
112
+
113
+ r = CIDR(IP('4.33.222.111'), 20)
114
+ r.should be_a CIDR
115
+ r.inspect.should include '4.33.222.111/20'
116
+
117
+ r = CIDR('4.33.222.111', IP('255.255.240.0'))
118
+ r.should be_a CIDR
119
+ r.inspect.should include '4.33.222.111/20'
120
+ end
121
+
122
+ it "parses slash notation" do
123
+ r = CIDR('11.22.33.44/8')
124
+ r.should be_a CIDR
125
+ r.inspect.should include '11.22.33.44/8'
126
+ end
127
+
128
+ it "knows its bits" do
129
+ i = rand(33)
130
+ CIDR("1.1.1.1/#{i}").bits.
131
+ should == i
132
+ end
133
+
134
+ it "knows its ip" do
135
+ CIDR('11.22.33.44/20').ip.
136
+ should == IP('11.22.33.44')
137
+ end
138
+
139
+ it "knows its netmask" do
140
+ CIDR('11.22.33.44/20').netmask.
141
+ should == IP('255.255.240.0')
142
+
143
+ CIDR('11.22.33.44/8').netmask.
144
+ should == IP('255.0.0.0')
145
+
146
+ CIDR('11.22.33.44/32').netmask.
147
+ should == IP('255.255.255.255')
148
+
149
+ CIDR('1.1.1.1/0').netmask.
150
+ should == IP('0.0.0.0')
151
+ end
152
+
153
+ it "knows its min" do
154
+ CIDR('11.22.33.44/20').min.
155
+ should == IP('11.22.32.0')
156
+
157
+ CIDR('11.22.33.44/8').min.
158
+ should == IP('11.0.0.0')
159
+
160
+ CIDR('11.22.33.44/32').min.
161
+ should == IP('11.22.33.44')
162
+
163
+ CIDR('11.22.33.44/0').min.
164
+ should == IP('0.0.0.0')
165
+ end
166
+
167
+ it "knows its max" do
168
+ CIDR('11.22.33.44/20').max.
169
+ should == IP('11.22.47.255')
170
+
171
+ CIDR('11.22.33.44/8').max.
172
+ should == IP('11.255.255.255')
173
+
174
+ CIDR('11.22.33.44/32').max.
175
+ should == IP('11.22.33.44')
176
+
177
+ CIDR('11.22.33.44/0').max.
178
+ should == IP('255.255.255.255')
179
+ end
180
+
181
+ it "knows its rest field" do
182
+ CIDR('11.22.33.44/20').rest_field.
183
+ should == IP('0.0.1.44')
184
+
185
+ CIDR('11.22.33.44/8').rest_field.
186
+ should == IP('0.22.33.44')
187
+
188
+ CIDR('11.22.33.44/32').rest_field.
189
+ should == IP('0.0.0.0')
190
+
191
+ CIDR('11.22.33.44/0').rest_field.
192
+ should == IP('11.22.33.44')
193
+ end
194
+
195
+ it "knows its size" do
196
+ CIDR('11.22.33.44/20').size.
197
+ should == 0x1000
198
+
199
+ CIDR('11.22.33.44/8').size.
200
+ should == 0x1000000
201
+
202
+ CIDR('11.22.33.44/32').size.
203
+ should == 1
204
+
205
+ CIDR('11.22.33.44/0').size.
206
+ should == 0x100000000
207
+ end
208
+
209
+ it "is enumerable" do
210
+ r = CIDR('11.22.33.44/24')
211
+ r.should respond_to :each
212
+ CIDR.included_modules.should include Enumerable
213
+
214
+ a = r.to_a
215
+
216
+ a.size.should == r.size
217
+ a.first.should == r.first
218
+ a.last.should == r.last
219
+ end
220
+
221
+ end
222
+ end
@@ -0,0 +1,14 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Radix" do
4
+ before :each do
5
+ @lim = 50 + rand(100)
6
+ @rad = 2 + rand(9)
7
+ end
8
+
9
+ it "calculates radix" do
10
+ (1..@lim).each do |i|
11
+ i.radix(@rad).join.should == i.to_s(@rad)
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,11 @@
1
+ require 'rubygems'
2
+ require 'spec'
3
+
4
+ require File.expand_path(File.join(
5
+ File.dirname(__FILE__),
6
+ '..',
7
+ 'lib',
8
+ 'iplogic'
9
+ ))
10
+
11
+ include IPLogic
metadata ADDED
@@ -0,0 +1,94 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: iplogic
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
11
+ platform: ruby
12
+ authors:
13
+ - Jay Adkisson
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-11-13 00:00:00 -08:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: rspec
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 3
30
+ segments:
31
+ - 0
32
+ version: "0"
33
+ type: :development
34
+ version_requirements: *id001
35
+ description: An IPv4 swiss-army chainsaw
36
+ email: jay@causes.com
37
+ executables: []
38
+
39
+ extensions: []
40
+
41
+ extra_rdoc_files:
42
+ - LICENSE
43
+ - README.md
44
+ files:
45
+ - Rakefile
46
+ - LICENSE
47
+ - README.md
48
+ - iplogic.gemspec
49
+ - lib/iplogic.rb
50
+ - lib/core_ext.rb
51
+ - lib/core_ext/fixnum.rb
52
+ - lib/iplogic/cidr.rb
53
+ - lib/iplogic/ip.rb
54
+ - spec/ip_spec.rb
55
+ - spec/spec_helper.rb
56
+ - spec/radix_spec.rb
57
+ has_rdoc: true
58
+ homepage: http://github.com/causes/iplogic
59
+ licenses: []
60
+
61
+ post_install_message:
62
+ rdoc_options: []
63
+
64
+ require_paths:
65
+ - lib
66
+ required_ruby_version: !ruby/object:Gem::Requirement
67
+ none: false
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ hash: 3
72
+ segments:
73
+ - 0
74
+ version: "0"
75
+ required_rubygems_version: !ruby/object:Gem::Requirement
76
+ none: false
77
+ requirements:
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ hash: 3
81
+ segments:
82
+ - 0
83
+ version: "0"
84
+ requirements: []
85
+
86
+ rubyforge_project:
87
+ rubygems_version: 1.3.7
88
+ signing_key:
89
+ specification_version: 3
90
+ summary: Because it's just a 32-bit integer.
91
+ test_files:
92
+ - spec/ip_spec.rb
93
+ - spec/spec_helper.rb
94
+ - spec/radix_spec.rb