unbound 0.0.2 → 1.0.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.
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: []