iplogic 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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