unbound 0.0.2 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 41262867ad30ae0615f3a7669517863964d4b0e5
4
- data.tar.gz: 6bde21f3af9105f0e41e3c91dc6db1ebc847fc82
3
+ metadata.gz: b9e071138d458da3ab632fd55f7208e686e55679
4
+ data.tar.gz: ffebd0663a8fe538b933c21cb35bd9f876ec64eb
5
5
  SHA512:
6
- metadata.gz: 450c506ec33c36d41205ab8a0f58d746803ff1a211dac1ebf6c42758a9837a4f1bedb611bdeb992a48b7489bf2f46b2b20e4945444c88bb600bf30507904a8b4
7
- data.tar.gz: bdcd31bd6a883049f0b80271daea7d5fc6d37880faf47a808c64440a852997b4e44673b291a158e3150e93fea8f4e499e7fcf69184b3de30d08407bb777d5d82
6
+ metadata.gz: 3190a931ff0e491a2c772056b486cad1c35b0e54ade6c6b6cf9429dfdd154157921fac796e62b34d0ea6ae699056a968b02776723179224c337bad716de2809d
7
+ data.tar.gz: c539edfe6095cf3dba1c51411782ee6dcfc5f8c73e611270ebcaaa39e855dd61256c650d1f79ca6f058133c3bdadba7f08d4f75271d0317fbf5643d2d71ed15a
data/README.md CHANGED
@@ -6,42 +6,7 @@ These bindings allow for asynchronous and synchronous name resolution. You reall
6
6
 
7
7
  ## Code Example
8
8
 
9
- ```
10
- require 'unbound/context'
11
- require 'unbound/result'
12
-
13
- ctx = Unbound::Context.new
14
-
15
- CB = Proc.new do |mydata, err, result_ptr|
16
- if (err == 0)
17
- result = Unbound::Result.new(result_ptr)
18
- puts "Got response rcode: #{result[:rcode]}, qname: #{result[:qname]}"
19
- end
20
- end
21
-
22
- google_id = ctx.resolve_async("www.google.com", 1, 1, CB)
23
- yahoo_id = ctx.resolve_async("www.yahoo.com", 1, 1, CB)
24
- github_id = ctx.resolve_async("www.github.com", 1, 1, CB)
25
-
26
- io = ctx.io
27
-
28
- # Keep looping until we haven't gotten any
29
- while (::IO.select([io], nil, nil, 5))
30
- ctx.process()
31
- end
32
-
33
- ctx.close()
34
- puts "all done!"
35
- ```
36
-
37
- outputs:
38
- ```
39
- Got response rcode: 0, qname: www.google.com
40
- Got response rcode: 0, qname: www.github.com
41
- Got response rcode: 0, qname: www.yahoo.com
42
- all done!
43
- ```
44
-
9
+ See the examples directory.
45
10
 
46
11
  ## Motivation
47
12
 
data/Rakefile CHANGED
@@ -20,6 +20,7 @@ Jeweler::Tasks.new do |gem|
20
20
  gem.email = "falter@gmail.com"
21
21
  gem.authors = ["Mike Ryan"]
22
22
  gem.files = Dir.glob("lib/**/*.rb") +
23
+ Dir.glob("examples/*") +
23
24
  Dir.glob("spec/{*.rb}") +
24
25
  Dir.glob("spec/conf/{*.conf}") +
25
26
  %w(LICENSE.txt Gemfile README.md Rakefile VERSION)
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.2
1
+ 1.0.0
@@ -0,0 +1,47 @@
1
+ # Example that resolves three hostnames in an aynchronous fashion.
2
+ #
3
+ # Outputs:
4
+ # Got result for www.google.com!
5
+ # Got result for github.com!
6
+ # Got result for www.yahoo.com!
7
+ # all done!
8
+ #
9
+ require 'unbound'
10
+
11
+ ctx = Unbound::Context.new
12
+ resolver = Unbound::Resolver.new(ctx)
13
+
14
+ at_exit do
15
+ resolver.close unless resolver.closed?
16
+ end
17
+
18
+ # Add some callbacks to the resolver
19
+ resolver.on_answer do |q, result|
20
+ puts "Got result for #{q.name} : rcode: #{result[:rcode]}!"
21
+ end
22
+ resolver.on_cancel do |q|
23
+ puts "Query canceled: #{q.name}!"
24
+ end
25
+ resolver.on_error do |q, error_code|
26
+ puts "Query had error: #{q.name} #{error_code}!"
27
+ end
28
+
29
+ names = ["www.google.com", "www.yahoo.com", "github.com", "notadomain"]
30
+ names.each do |name|
31
+ query = Unbound::Query.new(name, 1, 1)
32
+ query.on_answer do |query, result|
33
+ puts "query-level callback for on_answer: #{name}"
34
+ end
35
+ resolver.send_query(query)
36
+ end
37
+
38
+ io = resolver.io
39
+
40
+ # Keep looping until we haven't gotten any
41
+ while resolver.outstanding_queries? && (::IO.select([io], nil, nil, 5))
42
+ ctx.process()
43
+ end
44
+
45
+ resolver.close()
46
+ puts "all done!"
47
+
@@ -1,3 +1,7 @@
1
1
  module Unbound
2
2
  require 'unbound/bindings'
3
+ require 'unbound/result'
4
+ require 'unbound/query'
5
+ require 'unbound/context'
6
+ require 'unbound/resolver'
3
7
  end
@@ -1,15 +1,46 @@
1
1
  require 'ffi'
2
+ require 'unbound/result'
2
3
  module Unbound
3
4
  module Bindings
4
5
  extend FFI::Library
5
6
 
6
7
  LIBNAMES = ['unbound', 'libunbound.so', 'libunbound.so.2']
7
8
  if ENV['LIBUNBOUND']
9
+ # :nocov:
8
10
  LIBNAMES.unshift(ENV['LIBUNBOUND'])
11
+ # :nocov:
9
12
  end
10
13
 
11
14
  ffi_lib LIBNAMES
12
15
 
16
+ enum :error_code,
17
+ [
18
+ # /** no error */
19
+ :noerror , 0,
20
+ # /** socket operation. Set to -1, so that if an error from _fd() is
21
+ # * passed (-1) it gives a socket error. */
22
+ :socket , -1,
23
+ # /** alloc failure */
24
+ :nomem , -2,
25
+ # /** syntax error */
26
+ :syntax , -3,
27
+ # /** DNS service failed */
28
+ :servfail , -4,
29
+ # /** fork() failed */
30
+ :forkfail , -5,
31
+ # /** cfg change after finalize() */
32
+ :afterfinal , -6,
33
+ # /** initialization failed (bad settings) */
34
+ :initfail , -7,
35
+ # /** error in pipe communication with async bg worker */
36
+ :pipe , -8,
37
+ # /** error reading from file (resolv.conf) */
38
+ :readfile , -9,
39
+ # /** error async_id does not exist or result already been delivered */
40
+ :noid , -10
41
+ ]
42
+
43
+
13
44
 
14
45
  typedef :pointer, :mydata
15
46
  typedef :pointer, :ub_ctx_ptr
@@ -22,43 +53,43 @@ module Unbound
22
53
  attach_function :ub_ctx_create, [], :ub_ctx_ptr
23
54
  attach_function :ub_ctx_delete, [:ub_ctx_ptr], :void
24
55
 
25
- attach_function :ub_ctx_set_option, [:ub_ctx_ptr, :string, :string], :int
26
- attach_function :ub_ctx_get_option, [:ub_ctx_ptr, :string, :pointer], :int
27
- attach_function :ub_ctx_config, [:ub_ctx_ptr, :string], :int
56
+ attach_function :ub_ctx_set_option, [:ub_ctx_ptr, :string, :string], :error_code
57
+ attach_function :ub_ctx_get_option, [:ub_ctx_ptr, :string, :pointer], :error_code
58
+ attach_function :ub_ctx_config, [:ub_ctx_ptr, :string], :error_code
28
59
 
29
- attach_function :ub_ctx_set_fwd, [:ub_ctx_ptr, :string], :int
30
- attach_function :ub_ctx_resolvconf, [:ub_ctx_ptr, :string], :int
31
- attach_function :ub_ctx_hosts, [:ub_ctx_ptr, :string], :int
60
+ attach_function :ub_ctx_set_fwd, [:ub_ctx_ptr, :string], :error_code
61
+ attach_function :ub_ctx_resolvconf, [:ub_ctx_ptr, :string], :error_code
62
+ attach_function :ub_ctx_hosts, [:ub_ctx_ptr, :string], :error_code
32
63
 
33
- attach_function :ub_ctx_add_ta, [:ub_ctx_ptr, :string], :int
34
- attach_function :ub_ctx_add_ta_file, [:ub_ctx_ptr, :string], :int
64
+ attach_function :ub_ctx_add_ta, [:ub_ctx_ptr, :string], :error_code
65
+ attach_function :ub_ctx_add_ta_file, [:ub_ctx_ptr, :string], :error_code
35
66
 
36
- attach_function :ub_ctx_trustedkeys, [:ub_ctx_ptr, :string], :int
67
+ attach_function :ub_ctx_trustedkeys, [:ub_ctx_ptr, :string], :error_code
37
68
 
38
- attach_function :ub_ctx_debugout, [:ub_ctx_ptr, :pointer], :int # TODO: FILE pointer
39
- attach_function :ub_ctx_debuglevel, [:ub_ctx_ptr, :int], :int
69
+ attach_function :ub_ctx_debugout, [:ub_ctx_ptr, :pointer], :error_code # TODO: FILE pointer
70
+ attach_function :ub_ctx_debuglevel, [:ub_ctx_ptr, :int], :error_code
40
71
 
41
- attach_function :ub_ctx_async, [:ub_ctx_ptr, :int], :int
72
+ attach_function :ub_ctx_async, [:ub_ctx_ptr, :int], :error_code
42
73
 
43
- attach_function :ub_poll, [:ub_ctx_ptr], :int
44
- attach_function :ub_wait, [:ub_ctx_ptr], :int, :blocking => true
45
- attach_function :ub_fd, [:ub_ctx_ptr], :int
46
- attach_function :ub_process, [:ub_ctx_ptr], :int, :blocking => true
74
+ attach_function :ub_poll, [:ub_ctx_ptr], :error_code
75
+ attach_function :ub_wait, [:ub_ctx_ptr], :error_code, :blocking => true
76
+ attach_function :ub_fd, [:ub_ctx_ptr], :error_code
77
+ attach_function :ub_process, [:ub_ctx_ptr], :error_code, :blocking => true
47
78
 
48
- attach_function :ub_resolve, [:ub_ctx_ptr, :string, :int, :int, :ub_result_ptrptr], :int, :blocking => true
49
- attach_function :ub_resolve_async, [:ub_ctx_ptr, :string, :int, :int, :mydata, :ub_callback_t, :pointer], :int
50
- attach_function :ub_cancel, [:ub_ctx_ptr, :int], :int
79
+ attach_function :ub_resolve, [:ub_ctx_ptr, :string, :int, :int, :ub_result_ptrptr], :error_code, :blocking => true
80
+ attach_function :ub_resolve_async, [:ub_ctx_ptr, :string, :int, :int, :mydata, :ub_callback_t, :pointer], :error_code
81
+ attach_function :ub_cancel, [:ub_ctx_ptr, :int], :error_code
51
82
 
52
83
  attach_function :ub_resolve_free, [:ub_result_ptr], :void
53
- attach_function :ub_strerror, [:int], :string
84
+ attach_function :ub_strerror, [:error_code], :string
54
85
 
55
- attach_function :ub_ctx_print_local_zones, [:ub_ctx_ptr], :int
86
+ attach_function :ub_ctx_print_local_zones, [:ub_ctx_ptr], :error_code
56
87
 
57
- attach_function :ub_ctx_zone_add, [:ub_ctx_ptr, :string, :string], :int
58
- attach_function :ub_ctx_zone_remove, [:ub_ctx_ptr, :string], :int
88
+ attach_function :ub_ctx_zone_add, [:ub_ctx_ptr, :string, :string], :error_code
89
+ attach_function :ub_ctx_zone_remove, [:ub_ctx_ptr, :string], :error_code
59
90
 
60
- attach_function :ub_ctx_data_add, [:ub_ctx_ptr, :string], :int
61
- attach_function :ub_ctx_data_remove, [:ub_ctx_ptr, :string], :int
91
+ attach_function :ub_ctx_data_add, [:ub_ctx_ptr, :string], :error_code
92
+ attach_function :ub_ctx_data_remove, [:ub_ctx_ptr, :string], :error_code
62
93
 
63
94
  ## This wasn't added until unbound 1.4.15
64
95
  # attach_function :ub_version, [], :string
@@ -0,0 +1,21 @@
1
+ module Unbound
2
+ class CallbackArray < Array
3
+ # Registers one or more callbacks
4
+ # @param [Array<#call>] cbs A splat of procs to register as callbacks
5
+ # @yield [] cb_block A block to register as a callback
6
+ # @raise [ArgumentError] if no callbacks are provided
7
+ def add_callback(*cbs, &cb_block)
8
+ cbs.push(cb_block) unless cb_block.nil?
9
+ raise(ArgumentError.new("Missing callback")) if cbs.empty?
10
+ concat(cbs)
11
+ end
12
+
13
+ # Calls each registered callback with the provided arguments
14
+ # @param [Array] args A splat of arguments to pass to each callback via #call
15
+ def call(*args)
16
+ each do |cb|
17
+ cb.call(*args)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,54 @@
1
+ require 'unbound/callback_array'
2
+ module Unbound
3
+ module CallbacksMixin
4
+ def init_callbacks
5
+ @callbacks_start = CallbackArray.new
6
+ @callbacks_answer = CallbackArray.new
7
+ @callbacks_error = CallbackArray.new
8
+ @callbacks_cancel = CallbackArray.new
9
+ @callbacks_finish = CallbackArray.new
10
+ end
11
+ private :init_callbacks
12
+
13
+ def clear_callbacks!
14
+ @callbacks_start.clear
15
+ @callbacks_answer.clear
16
+ @callbacks_error.clear
17
+ @callbacks_cancel.clear
18
+ @callbacks_finish.clear
19
+ end
20
+ private :clear_callbacks!
21
+
22
+ # Adds a callback that will be called just after the query is sent
23
+ # @param [Proc] cb
24
+ def on_start(*cbs, &cb_block)
25
+ @callbacks_start.add_callback(*cbs, &cb_block)
26
+ end
27
+
28
+ # Adds a callback that will be called when we receive an answer to the query
29
+ # @param [Proc] cb
30
+ def on_answer(*cbs, &cb_block)
31
+ @callbacks_answer.add_callback(*cbs, &cb_block)
32
+ end
33
+
34
+ # Adds a callback that will be called when an error internal to unbound occurs
35
+ # @param [Proc] cb
36
+ def on_error(*cbs, &cb_block)
37
+ @callbacks_error.add_callback(*cbs, &cb_block)
38
+ end
39
+
40
+ # Adds a callback that will be called if the query times out (dependant on
41
+ # the resolver)
42
+ # @param [Proc] cb
43
+ def on_cancel(*cbs, &cb_block)
44
+ @callbacks_cancel.add_callback(*cbs, &cb_block)
45
+ end
46
+
47
+ # Adds a callback will *always* be called when the query is finished
48
+ # whether answerfully, in error, or due to cancel.
49
+ # @param [Proc] cb
50
+ def on_finish(*cbs, &cb_block)
51
+ @callbacks_finish.add_callback(*cbs, &cb_block)
52
+ end
53
+ end
54
+ end
@@ -5,6 +5,15 @@ module Unbound
5
5
  class Context
6
6
  def initialize
7
7
  @ub_ctx = Unbound::Bindings.ub_ctx_create()
8
+ @raise_on_noid = false
9
+ end
10
+
11
+ def raise_on_noid=(b)
12
+ @raise_on_noid = (b == true)
13
+ end
14
+
15
+ def raise_on_noid?
16
+ @raise_on_noid
8
17
  end
9
18
 
10
19
  def check_closed!
@@ -14,10 +23,12 @@ module Unbound
14
23
  end
15
24
 
16
25
  def raise_if_error!(retval)
17
- if retval != 0
18
- raise APIError.new(retval)
26
+ return retval if retval == :noerror
27
+
28
+ if retval == :noid && @raise_on_noid == false
29
+ return retval
19
30
  end
20
- return retval
31
+ raise APIError.new(retval)
21
32
  end
22
33
 
23
34
  def closed?
@@ -23,4 +23,8 @@ module Unbound
23
23
  class ContextClosedError < MissingContextError
24
24
  end
25
25
 
26
+ # Indicates that an identical query object has already been submitted.
27
+ class QueryAlreadyStarted < Error
28
+ end
29
+
26
30
  end
@@ -0,0 +1,72 @@
1
+ require 'unbound/exceptions'
2
+ require 'unbound/callbacks_mixin'
3
+ module Unbound
4
+ # A representation of a query, as used by Unbound::Resolver
5
+ class Query
6
+ include CallbacksMixin
7
+ attr_reader :name, :rrtype, :rrclass, :async_id
8
+
9
+ STATE_INIT = 0
10
+ STATE_STARTED = 1
11
+ STATE_FINISHED = 100
12
+
13
+ # @param [String] name
14
+ # @param [Integer] rrtype
15
+ # @param [Integer] rrclass
16
+ def initialize(name, rrtype, rrclass)
17
+ @name = name
18
+ @rrtype = rrtype
19
+ @rrclass = rrclass
20
+ @async_id = nil
21
+
22
+ @state = STATE_INIT
23
+ init_callbacks
24
+ end
25
+
26
+ # @return [Boolean] whether the query has finished or not
27
+ def finished?
28
+ @state >= STATE_FINISHED
29
+ end
30
+
31
+ # @return [Boolean] whether the query has been started or not
32
+ def started?
33
+ @state >= STATE_STARTED
34
+ end
35
+
36
+ # Called by the resolver just after it has sent the query, and received an
37
+ # asynchronous ID.
38
+ def start!(async_id)
39
+ @state = STATE_STARTED
40
+ @async_id = async_id
41
+ @callbacks_start.call(self)
42
+ @callbacks_start.clear
43
+ end
44
+
45
+ # Called by the resolver when it has received an answer
46
+ # @param [Unbound::Result] result The result structure
47
+ def answer!(result)
48
+ @callbacks_answer.call(self, result)
49
+ finish!()
50
+ end
51
+
52
+ # Called by the resolver when it has encountered an internal unbound error
53
+ # @param [Unbound::Bindings::error_codes] error_code
54
+ def error!(error_code)
55
+ @callbacks_error.call(self, error_code)
56
+ finish!()
57
+ end
58
+
59
+ # Called by the resolver after a cancel has occurred.
60
+ def cancel!()
61
+ @callbacks_cancel.call(self)
62
+ finish!()
63
+ end
64
+
65
+ private
66
+ def finish!()
67
+ @state = STATE_FINISHED
68
+ @callbacks_finish.call(self)
69
+ clear_callbacks!
70
+ end
71
+ end
72
+ end
@@ -1,36 +1,115 @@
1
1
  require 'unbound/bindings'
2
2
  require 'unbound/result'
3
3
  require 'unbound/context'
4
+ require 'unbound/callbacks_mixin'
4
5
 
5
6
  module Unbound
7
+ # A simple asynchronous resolver
6
8
  class Resolver
7
- def initialize()
8
- @ctx = Unbound::Context.new
9
- @ctx.load_config("unbound/etc/unbound.conf")
9
+ include CallbacksMixin
10
10
 
11
- cb = Proc.new { |a,b,c| nil }
12
- self.query("localhost", 1, 1, cb)
13
- @ctx.wait()
11
+ # @param [Unbound::Context] ctx The context around which we will wrap this
12
+ # resolver.
13
+ def initialize(ctx)
14
+ @ctx = ctx
15
+ @queries = {}
16
+ @resolve_callback_func = FFI::Function.new(
17
+ :void, [:pointer, :int, :pointer],
18
+ self.method(:resolve_callback))
19
+
20
+ init_callbacks
21
+
22
+ on_cancel do |query|
23
+ if query.async_id
24
+ @ctx.cancel_async_query(query.async_id)
25
+ end
26
+ end
27
+ on_finish do |query|
28
+ @queries.delete(query.object_id)
29
+ end
30
+ end
31
+
32
+ # @return [Integer] the number of queries for which we are awaiting reply
33
+ def outstanding_queries
34
+ @queries.count
14
35
  end
15
36
 
37
+ # @return [Boolean] true if there are any queries for which we are awaiting
38
+ # reply
39
+ def outstanding_queries?
40
+ @queries.count > 0
41
+ end
42
+
43
+ def resolve_callback(oid_ptr, err, result_ptr)
44
+ return if oid_ptr.nil?
45
+ oid = oid_ptr.address
46
+ query = @queries[oid]
47
+ return if query.nil?
48
+
49
+ if err == 0
50
+ query.answer!(Unbound::Result.new(result_ptr))
51
+ else
52
+ # :nocov:
53
+ query.error!(err)
54
+ # :nocov:
55
+ end
56
+ ensure
57
+ if err == 0
58
+ # Always free the result pointer
59
+ Unbound::Bindings.ub_resolve_free(result_ptr)
60
+ end
61
+ end
62
+ private :resolve_callback
63
+
64
+ # Cancel all outstanding queries.
65
+ def cancel_all
66
+ @queries.each_value do |query|
67
+ query.cancel!
68
+ end
69
+ @queries.clear
70
+ end
71
+
72
+ def closed?
73
+ @ctx.closed?
74
+ end
75
+
76
+ # Cancel all queries and close the resolver down.
16
77
  def close
78
+ return if self.closed? == true
79
+ self.cancel_all
17
80
  @ctx.close
18
81
  end
19
82
 
20
- def fd
21
- @ctx.fd
83
+ # @see Unbound::Context#io
84
+ def io
85
+ @ctx.io
22
86
  end
23
87
 
88
+ # @see Unbound::Context#process
24
89
  def process
25
90
  @ctx.process
26
91
  end
27
92
 
28
- def cancel(async_id)
29
- @ctx.cancel_async_query(async_id)
30
- end
31
-
32
- def query(name, rrtype, rrclass, callback, private_data = nil)
33
- @ctx.resolve_async(name, rrtype, rrclass, callback, private_data)
93
+ # @param [Unbound::Query] query
94
+ def send_query(query)
95
+ if query.started?
96
+ raise QueryAlreadyStarted.new
97
+ end
98
+ @queries[query.object_id] = query
99
+ # Add all of our callbacks, if any have been registered.
100
+ query.on_start(*@callbacks_start) unless @callbacks_start.empty?
101
+ query.on_answer(*@callbacks_answer) unless @callbacks_answer.empty?
102
+ query.on_error(*@callbacks_error) unless @callbacks_error.empty?
103
+ query.on_cancel(*@callbacks_cancel)
104
+ query.on_finish(*@callbacks_finish)
105
+ oid_ptr = FFI::Pointer.new query.object_id
106
+ async_id = @ctx.resolve_async(
107
+ query.name,
108
+ query.rrtype,
109
+ query.rrclass,
110
+ @resolve_callback_func,
111
+ oid_ptr)
112
+ query.start!(async_id)
34
113
  end
35
114
  end
36
115
  end
@@ -50,15 +50,7 @@ module Unbound
50
50
  end
51
51
  end
52
52
 
53
- class Result < FFI::ManagedStruct
54
- include ResultLayout
55
-
56
- def self.release(ptr)
57
- Unbound.ub_resolve_free(ptr)
58
- end
59
- end
60
-
61
- class ResultCast < FFI::Struct
53
+ class Result < FFI::Struct
62
54
  include ResultLayout
63
55
  end
64
56
  end
@@ -0,0 +1,71 @@
1
+ require 'spec_helper'
2
+
3
+ describe Unbound::CallbackArray do
4
+ before :each do
5
+ @cba = Unbound::CallbackArray.new
6
+ end
7
+
8
+ describe "#add_callback" do
9
+ it "should raise an ArgumentError if no callback is provided" do
10
+ expect {
11
+ @cba.add_callback()
12
+ }.to raise_error(ArgumentError)
13
+ end
14
+ it "should accept a callback as an argument" do
15
+ proc1 = Proc.new {}
16
+ @cba.add_callback(proc1)
17
+ expect(@cba).to eq([proc1])
18
+ end
19
+ it "should accept a callback as block" do
20
+ proc1 = Proc.new {}
21
+ @cba.add_callback(&proc1)
22
+ expect(@cba).to eq([proc1])
23
+ end
24
+ it "should accept multiple callbacks as arguments in order" do
25
+ proc1 = Proc.new {}
26
+ proc2 = Proc.new {}
27
+ proc3 = Proc.new {}
28
+ proc4 = Proc.new {}
29
+ proc5 = Proc.new {}
30
+ @cba.add_callback(proc1, proc2, proc3, proc4, proc5)
31
+ expect(@cba).to eq([proc1, proc2, proc3, proc4, proc5])
32
+ end
33
+ it "should preserve order" do
34
+ proc1 = Proc.new {}
35
+ proc2 = Proc.new {}
36
+ proc3 = Proc.new {}
37
+ proc4 = Proc.new {}
38
+ proc5 = Proc.new {}
39
+ @cba.add_callback(proc1)
40
+ @cba.add_callback(proc2)
41
+ @cba.add_callback(proc3)
42
+ @cba.add_callback(proc4)
43
+ @cba.add_callback(proc5)
44
+ expect(@cba).to eq([proc1, proc2, proc3, proc4, proc5])
45
+ end
46
+ it "should arguments and blocks at the same time" do
47
+ proc1 = Proc.new {}
48
+ proc2 = Proc.new {}
49
+ proc3 = Proc.new {}
50
+ proc4 = Proc.new {}
51
+ proc5 = Proc.new {}
52
+ @cba.add_callback(proc1, proc2, proc3, proc4, &proc5)
53
+ expect(@cba).to eq([proc1, proc2, proc3, proc4, proc5])
54
+ end
55
+ end
56
+ describe "#call" do
57
+ it "should arguments and blocks at the same time" do
58
+ expect { |cb1|
59
+ expect { |cb2|
60
+ expect { |cb3|
61
+ expect { |cb4|
62
+ @cba.add_callback(cb1.to_proc, cb2.to_proc, cb3.to_proc, cb4.to_proc)
63
+ @cba.call(1,2,3)
64
+ }.to yield_with_args(1,2,3)
65
+ }.to yield_with_args(1,2,3)
66
+ }.to yield_with_args(1,2,3)
67
+ }.to yield_with_args(1,2,3)
68
+ end
69
+ end
70
+ end
71
+
@@ -1,8 +1,4 @@
1
1
  require 'spec_helper'
2
- require 'unbound/context'
3
- require 'unbound/exceptions'
4
- require 'unbound/result'
5
- require 'pp'
6
2
 
7
3
  describe Unbound::Context do
8
4
  before :each do
@@ -13,6 +9,15 @@ describe Unbound::Context do
13
9
  @ctx.close unless @ctx.closed?
14
10
  end
15
11
 
12
+ subject { @ctx }
13
+ its(:raise_on_noid?) { should be_false }
14
+
15
+
16
+ it "should let me indicate that it should raise an error if the error code is :noid" do
17
+ @ctx.raise_on_noid = true
18
+ expect(@ctx.raise_on_noid?).to be_true
19
+ end
20
+
16
21
  describe "#closed?" do
17
22
  it "should indicate whether the context is closed or not" do
18
23
  expect(@ctx.closed?).to be_false
@@ -74,7 +79,6 @@ describe Unbound::Context do
74
79
  expect(1).to be(1)
75
80
 
76
81
  @cb_result_ptr = nil
77
- @cb_result = nil
78
82
  @cb_err = nil
79
83
  @cb_mydata = nil
80
84
 
@@ -82,9 +86,6 @@ describe Unbound::Context do
82
86
  @cb_mydata = mydata
83
87
  @cb_err = err
84
88
  @cb_result_ptr = result_ptr
85
- if (@cb_err == 0)
86
- @cb_result = Unbound::Result.new(@cb_result_ptr)
87
- end
88
89
  end
89
90
  end
90
91
 
@@ -120,14 +121,21 @@ describe Unbound::Context do
120
121
  end
121
122
 
122
123
  it "should cancel a query" do
123
- expect(@ctx.cancel_async_query(@async_id)).to be(0)
124
+ expect(@ctx.cancel_async_query(@async_id)).to be(:noerror)
124
125
  end
125
126
 
126
127
  it "should raise an APIError when canceling a non-existent query" do
128
+ @ctx.raise_on_noid = true
129
+ expect(@ctx.raise_on_noid?).to be_true
127
130
  expect(lambda do
128
131
  @ctx.cancel_async_query(@async_id + 1)
129
132
  end).to raise_error(Unbound::APIError)
130
133
  end
134
+
135
+ it "#raise_on_noid=false should raise an APIError when canceling a non-existent query" do
136
+ expect(@ctx.raise_on_noid?).to be_false
137
+ expect(@ctx.cancel_async_query(@async_id + 1)).to eq(:noid)
138
+ end
131
139
  end
132
140
  end
133
141
 
@@ -0,0 +1,210 @@
1
+ require 'spec_helper'
2
+
3
+ describe Unbound::Query do
4
+ context "a query for google.com IN A" do
5
+ before :each do
6
+ @rrtype = 1
7
+ @rrclass = 1
8
+ @name = "google.com"
9
+ @query = Unbound::Query.new(@name, @rrtype, @rrclass)
10
+ end
11
+
12
+ subject {@query}
13
+ its(:name) { should eq("google.com") }
14
+ its(:rrtype) { should eq(1) }
15
+ its(:rrclass) { should eq(1) }
16
+
17
+ describe "#finished?" do
18
+ it "should be false by default" do
19
+ expect(@query.finished?).to be_false
20
+ end
21
+ it "should be true after #cancel! is called" do
22
+ @query.cancel!
23
+ expect(@query.finished?).to be_true
24
+ end
25
+ it "should be true after #error! is called" do
26
+ @query.error!(1234)
27
+ expect(@query.finished?).to be_true
28
+ end
29
+ it "should be true after #answer! is called" do
30
+ result = double("Result")
31
+ @query.answer!(result)
32
+ expect(@query.finished?).to be_true
33
+ end
34
+ end
35
+
36
+ describe "#start!" do
37
+ it "should set the async_id" do
38
+ expect(@query.async_id).to be_nil
39
+ @query.start!(1234)
40
+ expect(@query.async_id).to eq(1234)
41
+ end
42
+
43
+ specify "the query should be started after calling" do
44
+ @query.start!(1234)
45
+ expect(@query).to be_started
46
+ end
47
+ end
48
+
49
+ describe "#started?" do
50
+ it "should be false by default" do
51
+ expect(@query.started?).to be_false
52
+ end
53
+ it "should be true after start! is called" do
54
+ @query.start!(1234)
55
+ expect(@query.started?).to be_true
56
+ end
57
+ end
58
+
59
+ describe "#on_start" do
60
+ it "should be called after start! is called" do
61
+ expect { |cb|
62
+ @query.on_start(cb.to_proc)
63
+ @query.start!(1234)
64
+ }.to yield_with_args(@query)
65
+ end
66
+ end
67
+
68
+ describe "#error!" do
69
+ it "should require an error code as an argument" do
70
+ expect {
71
+ @query.error!
72
+ }.to raise_error(ArgumentError)
73
+ expect {
74
+ @query.error!(1234)
75
+ }.to_not raise_error
76
+ end
77
+
78
+ specify "the query finished after being called" do
79
+ @query.error!(1234)
80
+ expect(@query).to be_finished
81
+ end
82
+ end
83
+
84
+ describe "#answer!" do
85
+ it "should require a result as an argument" do
86
+ expect {
87
+ @query.answer!
88
+ }.to raise_error(ArgumentError)
89
+ result = double("Result")
90
+ expect {
91
+ @query.answer!(result)
92
+ }.to_not raise_error
93
+ end
94
+
95
+ specify "the query finished after being called" do
96
+ result = double("Result")
97
+ @query.answer!(result)
98
+ expect(@query).to be_finished
99
+ end
100
+ end
101
+
102
+ describe "#cancel!" do
103
+ it "should require that no arguments be specified" do
104
+ expect {
105
+ @query.cancel!(1234)
106
+ }.to raise_error(ArgumentError)
107
+ expect {
108
+ @query.cancel!()
109
+ }.to_not raise_error
110
+ end
111
+
112
+ specify "the query finished after being called" do
113
+ @query.cancel!
114
+ expect(@query).to be_finished
115
+ end
116
+ end
117
+
118
+
119
+ describe "#on_error" do
120
+ specify "callbacks should be called upon #error!, but not others" do
121
+ expect { |answer_cb|
122
+ expect { |error_cb|
123
+ expect { |cancel_cb|
124
+ @query.on_answer(&answer_cb)
125
+ @query.on_error(&error_cb)
126
+ @query.on_cancel(&cancel_cb)
127
+ @query.error!(999)
128
+ }.to_not yield_control
129
+ }.to yield_control
130
+ }.to_not yield_control
131
+ end
132
+
133
+ specify "callbacks should be called with the query object and error code" do
134
+ expect { |error_cb|
135
+ @query.on_error(&error_cb)
136
+ @query.error!(999)
137
+ }.to yield_with_args(@query, 999)
138
+ end
139
+ end
140
+
141
+ describe "#on_answer" do
142
+ specify "callbacks should be called upon #answer!, but not others" do
143
+ result = double("Result")
144
+ expect { |answer_cb|
145
+ expect { |error_cb|
146
+ expect { |cancel_cb|
147
+ @query.on_answer(&answer_cb)
148
+ @query.on_error(&error_cb)
149
+ @query.on_cancel(&cancel_cb)
150
+ @query.answer!(result)
151
+ }.to_not yield_control
152
+ }.to_not yield_control
153
+ }.to yield_control
154
+ end
155
+
156
+ specify "callbacks should be called with the query object and result object" do
157
+ result = double("Result")
158
+ expect { |answer_cb|
159
+ @query.on_answer(&answer_cb)
160
+ @query.answer!(result)
161
+ }.to yield_with_args(@query, result)
162
+ end
163
+ end
164
+
165
+ describe "#on_cancel" do
166
+ specify "callbacks should be called upon #cancel!, but not others" do
167
+ expect { |answer_cb|
168
+ expect { |error_cb|
169
+ expect { |cancel_cb|
170
+ @query.on_answer(&answer_cb)
171
+ @query.on_error(&error_cb)
172
+ @query.on_cancel(&cancel_cb)
173
+ @query.cancel!()
174
+ }.to yield_control
175
+ }.to_not yield_control
176
+ }.to_not yield_control
177
+ end
178
+
179
+ specify "callbacks should be called with the query object" do
180
+ expect { |cancel_cb|
181
+ @query.on_cancel(&cancel_cb)
182
+ @query.cancel!()
183
+ }.to yield_with_args(@query)
184
+ end
185
+ end
186
+
187
+ describe "#on_finish" do
188
+ it "should be called after cancel! is called" do
189
+ expect { |cb|
190
+ @query.on_finish(cb.to_proc)
191
+ @query.cancel!()
192
+ }.to yield_with_args(@query)
193
+ end
194
+ it "should be called after answer! is called" do
195
+ result = double("Result")
196
+ expect { |cb|
197
+ @query.on_finish(cb.to_proc)
198
+ @query.answer!(result)
199
+ }.to yield_with_args(@query)
200
+ end
201
+ it "should be called after error! is called" do
202
+ expect { |cb|
203
+ @query.on_finish(cb.to_proc)
204
+ @query.error!(999)
205
+ }.to yield_with_args(@query)
206
+ end
207
+ end
208
+ end
209
+ end
210
+
@@ -0,0 +1,156 @@
1
+ require 'spec_helper'
2
+
3
+ describe Unbound::Resolver do
4
+ before :each do
5
+ @context = Unbound::Context.new
6
+ @resolver = Unbound::Resolver.new(@context)
7
+ end
8
+
9
+ after :each do
10
+ @resolver.close unless @resolver.closed?
11
+ end
12
+
13
+ describe "#cancel_all" do
14
+ it "should cancel all queries" do
15
+ query1 = Unbound::Query.new("localhost", 1, 1)
16
+ expect { |cb|
17
+ query1.on_cancel(cb.to_proc)
18
+ @resolver.send_query(query1)
19
+ @resolver.cancel_all
20
+ }.to yield_control
21
+ end
22
+ end
23
+
24
+ describe "#outstanding_queries" do
25
+ it "should return the number of outstanding queries" do
26
+ expect(@resolver.outstanding_queries).to eq(0)
27
+ 10.times do
28
+ query = Unbound::Query.new("localhost", 1, 1)
29
+ @resolver.send_query(query)
30
+ end
31
+ expect(@resolver.outstanding_queries).to eq(10)
32
+ @resolver.cancel_all
33
+ expect(@resolver.outstanding_queries).to eq(0)
34
+ end
35
+ end
36
+
37
+ describe "#outstanding_queries?" do
38
+ it "should be false if there are no outstanding queries" do
39
+ expect(@resolver.outstanding_queries?).to be_false
40
+ end
41
+
42
+ it "should be true if there are any outstanding queries" do
43
+ query = Unbound::Query.new("localhost", 1, 1)
44
+ @resolver.send_query(query)
45
+ expect(@resolver.outstanding_queries?).to be_true
46
+ end
47
+ end
48
+
49
+ describe "#closed?" do
50
+ it "should not be closed by default" do
51
+ expect(@resolver.closed?).to be_false
52
+ end
53
+ it "should be closed after calling #close" do
54
+ @resolver.close
55
+ expect(@resolver.closed?).to be_true
56
+ end
57
+ end
58
+
59
+ describe "#close" do
60
+ it "should call #cancel_all" do
61
+ @resolver.should_receive(:cancel_all).and_call_original
62
+ @resolver.close
63
+ end
64
+
65
+ it "should close the context" do
66
+ @context.should_receive(:close).and_call_original
67
+ @resolver.close
68
+ end
69
+ end
70
+
71
+ describe "#io" do
72
+ it "should return an IO object" do
73
+ expect(@resolver.io).to be_a(::IO)
74
+ end
75
+ end
76
+
77
+ describe "#process" do
78
+ it "should call our callback" do
79
+ query1 = Unbound::Query.new("localhost", 1, 1)
80
+ expect { |cb|
81
+ query1.on_answer(cb.to_proc)
82
+ @resolver.send_query(query1)
83
+ io = @resolver.io
84
+ expect(::IO.select([io], nil, nil, 5)).to_not be_nil
85
+ @resolver.process
86
+ }.to yield_control
87
+ end
88
+ end
89
+
90
+ describe "#send_query" do
91
+ it "should raise an exception if the same query object is submitted twice" do
92
+ query1 = Unbound::Query.new("localhost", 1, 1)
93
+ @resolver.send_query(query1)
94
+ expect {@resolver.send_query(query1)}.to raise_error
95
+ end
96
+ end
97
+
98
+ describe "#on_start" do
99
+ specify "callbacks should be called when send_query is called" do
100
+ query = Unbound::Query.new("localhost", 1, 1)
101
+ expect { |cb|
102
+ @resolver.on_start(&cb)
103
+ @resolver.send_query(query)
104
+ }.to yield_with_args(query)
105
+ end
106
+ end
107
+
108
+ describe "#on_answer" do
109
+ specify "callbacks should be called if the query has been answered" do
110
+ query = Unbound::Query.new("localhost", 1, 1)
111
+ result = double("Result")
112
+ expect { |cb|
113
+ @resolver.on_answer(&cb)
114
+ @resolver.send_query(query)
115
+ query.answer!(result)
116
+ }.to yield_with_args(query, result)
117
+ end
118
+ end
119
+
120
+ describe "#on_error" do
121
+ specify "callbacks should be called if the query has an error" do
122
+ query = Unbound::Query.new("localhost", 1, 1)
123
+ result = double("Result")
124
+ expect { |cb|
125
+ @resolver.on_error(&cb)
126
+ @resolver.send_query(query)
127
+ query.error!(1234)
128
+ }.to yield_with_args(query, 1234)
129
+ end
130
+ end
131
+
132
+ describe "#on_cancel" do
133
+ specify "callbacks should be called if the query has been canceled" do
134
+ query = Unbound::Query.new("localhost", 1, 1)
135
+ result = double("Result")
136
+ expect { |cb|
137
+ @resolver.on_cancel(&cb)
138
+ @resolver.send_query(query)
139
+ query.cancel!()
140
+ }.to yield_with_args(query)
141
+ end
142
+ end
143
+
144
+ describe "#on_finish" do
145
+ specify "callbacks should be called if the query is finished for any reason" do
146
+ query = Unbound::Query.new("localhost", 1, 1)
147
+ result = double("Result")
148
+ expect { |cb|
149
+ @resolver.on_finish(&cb)
150
+ @resolver.send_query(query)
151
+ query.cancel!()
152
+ }.to yield_with_args(query)
153
+ end
154
+ end
155
+ end
156
+
@@ -0,0 +1,21 @@
1
+ require 'spec_helper'
2
+
3
+ describe Unbound::Result do
4
+ extend UnboundHelper
5
+ UDP_RESPONSE = hex2bin("44438180000100060000000003777777057961686f6f03636f6d0000010001c00c0005000100000121000f0666642d667033037767310162c010c02b000500010000012100090664732d667033c032c046000500010000003100150e64732d616e792d6670332d6c666203776131c036c05b000500010000012100120f64732d616e792d6670332d7265616cc06ac07c00010001000000310004628bb718c07c00010001000000310004628bb495")
6
+
7
+ describe "#to_resolv" do
8
+ it "should return nil if there is no data" do
9
+ result = Unbound::Result.new
10
+ expect(result.to_resolv).to be_nil
11
+ end
12
+ it "should return a resolv object if there is data" do
13
+ result = Unbound::Result.new
14
+ packet_ptr = FFI::MemoryPointer.from_string(UDP_RESPONSE)
15
+ result[:answer_packet] = packet_ptr
16
+ result[:answer_len] = packet_ptr.size
17
+ expect(result.to_resolv).to be_a(Resolv::DNS::Message)
18
+ end
19
+ end
20
+ end
21
+
@@ -7,9 +7,24 @@ module UnboundHelper
7
7
  SPEC_ROOT = Pathname.new(__FILE__).dirname.expand_path
8
8
  PROJECT_ROOT = (SPEC_ROOT + '../').expand_path
9
9
  CONF_ROOT = SPEC_ROOT + 'conf'
10
- def self.config_file(name)
10
+
11
+ def config_file(name)
11
12
  (CONF_ROOT + name).to_s
12
13
  end
14
+ module_function :config_file
15
+
16
+ def hex2bin(hexstring)
17
+ ret = "\x00" * (hexstring.length / 2)
18
+ ret.force_encoding("BINARY")
19
+ offset = 0
20
+ while offset < hexstring.length
21
+ hex_byte = hexstring[offset..(offset+1)]
22
+ ret.setbyte(offset/2, hex_byte.to_i(16))
23
+ offset += 2
24
+ end
25
+ ret
26
+ end
27
+ module_function :hex2bin
13
28
  end
14
29
 
15
30
  require 'simplecov'
@@ -28,4 +43,4 @@ RSpec.configure do |config|
28
43
  end
29
44
  end
30
45
 
31
-
46
+ require 'unbound'
metadata CHANGED
@@ -1,57 +1,57 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: unbound
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Ryan
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-01-28 00:00:00.000000000 Z
11
+ date: 2014-01-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ffi
15
- version_requirements: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - '>='
18
- - !ruby/object:Gem::Version
19
- version: '0'
20
15
  requirement: !ruby/object:Gem::Requirement
21
16
  requirements:
22
17
  - - '>='
23
18
  - !ruby/object:Gem::Version
24
19
  version: '0'
25
- prerelease: false
26
20
  type: :runtime
27
- - !ruby/object:Gem::Dependency
28
- name: jeweler
21
+ prerelease: false
29
22
  version_requirements: !ruby/object:Gem::Requirement
30
23
  requirements:
31
24
  - - '>='
32
25
  - !ruby/object:Gem::Version
33
26
  version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: jeweler
34
29
  requirement: !ruby/object:Gem::Requirement
35
30
  requirements:
36
31
  - - '>='
37
32
  - !ruby/object:Gem::Version
38
33
  version: '0'
39
- prerelease: false
40
34
  type: :development
41
- - !ruby/object:Gem::Dependency
42
- name: rake
35
+ prerelease: false
43
36
  version_requirements: !ruby/object:Gem::Requirement
44
37
  requirements:
45
38
  - - '>='
46
39
  - !ruby/object:Gem::Version
47
40
  version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
48
43
  requirement: !ruby/object:Gem::Requirement
49
44
  requirements:
50
45
  - - '>='
51
46
  - !ruby/object:Gem::Version
52
47
  version: '0'
53
- prerelease: false
54
48
  type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
55
  description: Unbound DNS resolver bindings for Ruby
56
56
  email: falter@gmail.com
57
57
  executables: []
@@ -65,21 +65,29 @@ files:
65
65
  - README.md
66
66
  - Rakefile
67
67
  - VERSION
68
+ - examples/resolve_async.rb
68
69
  - lib/unbound.rb
69
70
  - lib/unbound/bindings.rb
71
+ - lib/unbound/callback_array.rb
72
+ - lib/unbound/callbacks_mixin.rb
70
73
  - lib/unbound/context.rb
71
74
  - lib/unbound/exceptions.rb
75
+ - lib/unbound/query.rb
72
76
  - lib/unbound/resolver.rb
73
77
  - lib/unbound/result.rb
78
+ - spec/callback_array_spec.rb
74
79
  - spec/conf/local_zone.conf
75
80
  - spec/conf/test_config.conf
76
81
  - spec/context_spec.rb
82
+ - spec/query_spec.rb
83
+ - spec/resolver_spec.rb
84
+ - spec/result_spec.rb
77
85
  - spec/spec_helper.rb
78
86
  homepage: http://github.com/justfalter/unbound-ruby
79
87
  licenses:
80
88
  - MIT
81
89
  metadata: {}
82
- post_install_message:
90
+ post_install_message:
83
91
  rdoc_options: []
84
92
  require_paths:
85
93
  - lib
@@ -94,9 +102,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
94
102
  - !ruby/object:Gem::Version
95
103
  version: '0'
96
104
  requirements: []
97
- rubyforge_project:
98
- rubygems_version: 2.1.9
99
- signing_key:
105
+ rubyforge_project:
106
+ rubygems_version: 2.0.14
107
+ signing_key:
100
108
  specification_version: 4
101
109
  summary: Unbound DNS resolver bindings for Ruby
102
110
  test_files: []