quaff 0.5.4 → 0.6.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 8d866d33ec2df619a80a00ee7aaaf04d70643370
4
+ data.tar.gz: f321221c8da956aef23e74104f1fd48abdd2dc02
5
+ SHA512:
6
+ metadata.gz: 761d0fc337cf7e29d4e3c10b1c83e126cb9f95e750312f08f5cd37f21205fb01dd953bb51b6c87370d9f2f10cfb565895b9e626fd9fea42fa34be71b8f700c27
7
+ data.tar.gz: edccac0984f874d1590efdf2ec8bbf6e9142d9ffdd90531fa11f2ee40f3c19887aa38cf14fd68ba7d2c30e0be64fbb78e22dfa43e35092f34fbafa6926e214f7
data/lib/auth.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  require 'base64'
2
2
 
3
3
  module Quaff
4
- module Auth
4
+ module Auth #:nodoc:
5
5
  def Auth.gen_nonce auth_pairs, username, passwd, method, sip_uri
6
6
  a1 = username + ":" + auth_pairs["realm"] + ":" + passwd
7
7
  a2 = method + ":" + sip_uri
data/lib/call.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # -*- coding: us-ascii -*-
2
2
  require 'securerandom'
3
+ require 'timeout'
3
4
  require_relative './utils.rb'
4
5
  require_relative './sources.rb'
5
6
  require_relative './auth.rb'
@@ -29,7 +30,7 @@ class Call
29
30
  def initialize(cxn,
30
31
  cid,
31
32
  instance_id=nil,
32
- uri="sip:5557777888@#{Utils::local_ip}",
33
+ uri=nil,
33
34
  destination=nil,
34
35
  target_uri=nil)
35
36
  @cxn = cxn
@@ -37,14 +38,11 @@ class Call
37
38
  @retrans = nil
38
39
  @t1, @t2 = 0.5, 32
39
40
  @instance_id = instance_id
40
- set_default_headers cid, uri, target_uri
41
- end
42
-
43
- def change_cid cid
44
41
  @cid = cid
45
- @cxn.add_call_id @cid
42
+ set_default_headers cid, uri, target_uri
46
43
  end
47
44
 
45
+ # Changes the branch parameter if the Via header, creating a new transaction
48
46
  def update_branch via_hdr=nil
49
47
  via_hdr ||= get_new_via_hdr
50
48
  @last_Via = via_hdr
@@ -56,10 +54,6 @@ class Call
56
54
  "SIP/2.0/#{@cxn.transport} #{Quaff::Utils.local_ip}:#{@cxn.local_port};rport;branch=#{Quaff::Utils::new_branch}"
57
55
  end
58
56
 
59
- def create_dialog msg
60
- set_callee msg.first_header("Contact")
61
- end
62
-
63
57
  def set_callee uri
64
58
  if /<(.*?)>/ =~ uri
65
59
  uri = $1
@@ -67,7 +61,39 @@ class Call
67
61
 
68
62
  @sip_destination = "#{uri}"
69
63
  end
64
+
65
+ def create_dialog msg
66
+ if @in_dialog
67
+ return
68
+ end
69
+
70
+ @in_dialog = true
71
+
72
+ uri = msg.first_header("Contact")
73
+
74
+ if /<(.*?)>/ =~ uri
75
+ uri = $1
76
+ end
77
+
78
+ @sip_destination = uri
79
+
80
+ unless msg.all_headers("Record-Route").nil?
81
+ if msg.type == :request
82
+ @routeset = msg.all_headers("Record-Route")
83
+ else
84
+ @routeset = msg.all_headers("Record-Route").reverse
85
+ end
86
+ end
87
+
88
+ end
70
89
 
90
+ # Sets the Source where messages in this call should be sent to by
91
+ # default.
92
+ #
93
+ # Options:
94
+ # :recv_from_this - if true, also listens for any incoming
95
+ # messages over this source's connection. (This is only
96
+ # meaningful for connection-oriented transports.)
71
97
  def setdest source, options={}
72
98
  @src = source
73
99
  if options[:recv_from_this] and source.sock
@@ -78,7 +104,7 @@ class Call
78
104
  def recv_request(method, dialog_creating=true)
79
105
  begin
80
106
  msg = recv_something
81
- rescue
107
+ rescue Timeout::Error
82
108
  raise "#{ @uri } timed out waiting for #{ method }"
83
109
  end
84
110
 
@@ -97,20 +123,85 @@ class Call
97
123
  end
98
124
 
99
125
  if dialog_creating
100
- @in_dialog = true
126
+ create_dialog msg
127
+ end
128
+ msg
129
+ end
101
130
 
102
- set_callee msg.first_header("Contact")
103
- unless msg.all_headers("Record-Route").nil?
104
- @routeset = msg.all_headers("Record-Route")
131
+ # Waits until the next message comes in, and handles it if it is one
132
+ # of possible_messages.
133
+ #
134
+ # possible_messages is a list of things that can be received.
135
+ # Elements can be:
136
+ # * a string representing the SIP method, e.g. "INVITE"
137
+ # * a number representing the SIP status code, e.g. 200
138
+ # * a two-item list, containing one of the above and a boolean
139
+ # value, which indicates whether this message is dialog-creating. by
140
+ # default, requests are assumed to be dialog-creating and responses
141
+ # are not.
142
+ #
143
+ # For example, ["INVITE", 301, ["ACK", false], [200, true]] is a
144
+ # valid value for possible_messages.
145
+ def recv_any_of(possible_messages)
146
+ begin
147
+ msg = recv_something
148
+ rescue Timeout::Error
149
+ raise "#{ @uri } timed out waiting for one of these: #{possible_messages}"
150
+ end
151
+
152
+ found_match = false
153
+ dialog_creating = nil
154
+
155
+ possible_messages.each do
156
+ | what, this_dialog_creating |
157
+ type = if (what.class == String) then :request else :response end
158
+ if this_dialog_creating.nil?
159
+ this_dialog_creating = (type == :request)
160
+ end
161
+
162
+ found_match =
163
+ if type == :request
164
+ msg.type == :request and what == msg.method
165
+ else
166
+ msg.type == :response and what == msg.status_code
167
+ end
168
+
169
+ if found_match
170
+ dialog_creating = this_dialog_creating
171
+ break
105
172
  end
106
173
  end
174
+
175
+ unless found_match
176
+ raise((msg.to_s || "Message is nil!"))
177
+ end
178
+
179
+ if msg.type == :request
180
+ unless @has_To_tag
181
+ @has_To_tag = true
182
+ tospec = ToSpec.new
183
+ tospec.parse(msg.header("To"))
184
+ tospec.params['tag'] = generate_random_tag
185
+ @last_To = tospec.to_s
186
+ @last_From = msg.header("From")
187
+ end
188
+ else
189
+ if @in_dialog
190
+ @has_To_tag = true
191
+ @last_To = msg.header("To")
192
+ end
193
+ end
194
+
195
+ if dialog_creating
196
+ create_dialog msg
197
+ end
107
198
  msg
108
199
  end
109
200
 
110
201
  def recv_response(code, dialog_creating=false)
111
202
  begin
112
203
  msg = recv_something
113
- rescue
204
+ rescue Timeout::Error
114
205
  raise "#{ @uri } timed out waiting for #{ code }"
115
206
  end
116
207
  unless msg.type == :response \
@@ -119,11 +210,7 @@ class Call
119
210
  end
120
211
 
121
212
  if dialog_creating
122
- @in_dialog = true
123
- set_callee msg.first_header("Contact")
124
- unless msg.all_headers("Record-Route").nil?
125
- @routeset = msg.all_headers("Record-Route").reverse
126
- end
213
+ create_dialog msg
127
214
  end
128
215
 
129
216
  if @in_dialog
@@ -238,14 +325,13 @@ class Call
238
325
 
239
326
  def set_default_headers cid, uri, target_uri
240
327
  @cseq_number = 1
241
- change_cid cid
242
328
  @uri = uri
243
329
  @last_From = "<#{uri}>;tag=" + generate_random_tag
244
330
  @in_dialog = false
245
331
  @has_To_tag = false
246
332
  update_branch
247
333
  @last_To = "<#{target_uri}>"
248
- set_callee target_uri if target_uri
334
+ @sip_destination = target_uri
249
335
  @routeset = []
250
336
  end
251
337
 
data/lib/endpoint.rb CHANGED
@@ -3,7 +3,7 @@ require 'socket'
3
3
  require 'thread'
4
4
  require 'timeout'
5
5
  require 'resolv'
6
- require 'digest/md5'
6
+ require 'securerandom'
7
7
  #require 'milenage'
8
8
  require_relative './sip_parser.rb'
9
9
  require_relative './sources.rb'
@@ -11,53 +11,75 @@ require_relative './sources.rb'
11
11
  module Quaff
12
12
  class BaseEndpoint
13
13
  attr_accessor :msg_trace, :uri, :sdp_port, :sdp_socket, :instance_id
14
- attr_reader :msg_log
14
+ attr_reader :msg_log, :local_port
15
15
 
16
+ # Creates an SDP socket bound to an ephemeral port
16
17
  def setup_sdp
17
18
  @sdp_socket = UDPSocket.new
18
19
  @sdp_socket.bind('0.0.0.0', 0)
19
20
  @sdp_port = @sdp_socket.addr[1]
20
21
  end
21
22
 
22
- def generate_call_id
23
- digest = Digest::MD5.hexdigest(rand(60000).to_s)
24
- end
25
-
23
+ # Cleans up the endpoint - designed to be overriden by
24
+ # per-transport subclasses
26
25
  def terminate
27
26
  end
28
27
 
28
+ # Adds a socket connection to another UA - designed to be
29
+ # overriden by per-transport subclasses
29
30
  def add_sock sock
30
31
  end
31
32
 
32
- def incoming_call *args
33
- call_id ||= get_new_call_id
34
- puts "Call-Id for endpoint on #{@lport} is #{call_id}" if @msg_trace
35
- Call.new(self, call_id, @instance_id, *args)
33
+ # Retrieves the next unhandled call for this endpoint and returns
34
+ # a +Call+ object representing it
35
+ def incoming_call
36
+ begin
37
+ call_id = get_new_call_id
38
+ rescue Timeout::Error
39
+ raise "#{ @uri } timed out waiting for new incoming call"
40
+ end
41
+
42
+ puts "Call-Id for endpoint on #{@local_port} is #{call_id}" if @msg_trace
43
+ Call.new(self, call_id, @instance_id, @uri)
36
44
  end
37
45
 
46
+ # Creates a +Call+ object representing a new outbound call
38
47
  def outgoing_call to_uri
39
48
  call_id = generate_call_id
40
- puts "Call-Id for endpoint on #{@lport} is #{call_id}" if @msg_trace
49
+ puts "Call-Id for endpoint on #{@local_port} is #{call_id}" if @msg_trace
41
50
  Call.new(self, call_id, @instance_id, @uri, @outbound_connection, to_uri)
42
51
  end
43
52
 
44
- def create_client(uri, username, password, outbound_proxy, outbound_port=5060)
53
+ # Not yet ready for use
54
+ def create_client(uri, username, password, outbound_proxy, outbound_port=5060) # :nodoc:
45
55
  end
46
56
 
47
- def create_server(uri, local_port=5060, outbound_proxy=nil, outbound_port=5060)
48
-
57
+ # Not yet ready for use
58
+ def create_server(uri, local_port=5060, outbound_proxy=nil, outbound_port=5060) # :nodoc:
49
59
  end
50
60
 
51
- def create_aka_client(uri, username, key, op, outbound_proxy, outbound_port=5060)
61
+ # Not yet ready for use
62
+ def create_aka_client(uri, username, key, op, outbound_proxy, outbound_port=5060) # :nodoc:
52
63
  end
53
64
 
65
+ # Constructs a new endpoint
66
+ # Params:
67
+ # +uri+:: The SIP URI of this endpoint
68
+ # +username+:: The authentication username of this endpoint
69
+ # +password+:: The authentication password of this endpoint
70
+ # +local_port+:: The port this endpoint should bind to. Use
71
+ # ':anyport' to bind to an ephemeral port.
72
+ # +outbound_proxy+:: The outbound proxy where all requests should
73
+ # be directed. Optional, but it only makes sense to omit it when
74
+ # Quaff is emulating a server rather than a client.
75
+ # +outbound_port+:: The port of the outbound proxy
54
76
  def initialize(uri, username, password, local_port, outbound_proxy=nil, outbound_port=5060)
55
77
  @msg_log = Array.new
56
78
  @uri = uri
57
79
  @resolver = Resolv::DNS.new
58
80
  @username = username
59
81
  @password = password
60
- @lport = local_port
82
+ @local_port = local_port
61
83
  initialize_connection
62
84
  if outbound_proxy
63
85
  @outbound_connection = new_connection(outbound_proxy, outbound_port)
@@ -66,40 +88,26 @@ module Quaff
66
88
  start
67
89
  end
68
90
 
69
- def local_port
70
- @lport
71
- end
72
-
73
- def add_call_id cid
74
- @messages[cid] ||= Queue.new
75
- end
76
-
77
- def get_new_call_id time_limit=30
78
- Timeout::timeout(time_limit) { @call_ids.deq }
79
- end
80
-
81
- def get_new_message(cid, time_limit=30)
82
- Timeout::timeout(time_limit) { @messages[cid].deq }
83
- end
84
-
85
- def mark_call_dead(cid)
86
- @messages.delete cid
87
- now = Time.now
88
- @dead_calls[cid] = now + 30
89
- @dead_calls = @dead_calls.keep_if {|k, v| v > now}
90
- end
91
-
92
- def send_msg(data, source)
93
- @msg_log.push "Endpoint on #{@lport} sending:\n\n#{data.strip}\n\nto #{source.inspect}"
94
- puts "Endpoint on #{@lport} sending #{data} to #{source.inspect}" if @msg_trace
91
+ def send_msg(data, source) # :nodoc:
92
+ @msg_log.push "Endpoint on #{@local_port} sending:\n\n#{data.strip}\n\nto #{source.inspect}"
93
+ puts "Endpoint on #{@local_port} sending #{data} to #{source.inspect}" if @msg_trace
95
94
  source.send_msg(@cxn, data)
96
95
  end
97
96
 
98
- def set_aka_credentials key, op
97
+ # Not yet ready for use
98
+ def set_aka_credentials key, op # :nodoc:
99
99
  @kernel = Milenage.Kernel key
100
100
  @kernel.op = op
101
101
  end
102
102
 
103
+ # Utility method - handles a REGISTER/200 or
104
+ # REGISTER/401/REGISTER/200 flow to authenticate the subscriber.
105
+ # Currently only supports SIP Digest authentication. Re-REGISTERs
106
+ # are not handled; if you need long-running endpoints you should
107
+ # create a thread to re-REGISTER them yourself.
108
+ #
109
+ # Returns the +Message+ representing the 200 OK, or throws an
110
+ # exception on failure to authenticate successfully.
103
111
  def register expires="3600", aka=false
104
112
  @reg_call ||= outgoing_call(@uri)
105
113
  auth_hdr = Quaff::Auth.gen_empty_auth_header @username
@@ -125,7 +133,42 @@ module Quaff
125
133
  register 0
126
134
  end
127
135
 
136
+ # Only designed for use by the Call class. Retrieves a new message
137
+ # on a particular call. If no new message has been received,
138
+ # blocks for up to time_limit seconds waiting for one. If nothing
139
+ # arrives, raises a TimeoutError.
140
+ def get_new_message(cid, time_limit=30) # :nodoc:
141
+ Timeout::timeout(time_limit) { @messages[cid].deq }
142
+ end
143
+
144
+
145
+
146
+ # Flags that a particular call has ended, and any more messages
147
+ # using it shold be ignored.
148
+ def mark_call_dead(cid)
149
+ @messages.delete cid
150
+ now = Time.now
151
+ @dead_calls[cid] = now + 30
152
+ @dead_calls = @dead_calls.keep_if {|k, v| v > now}
153
+ end
128
154
  private
155
+
156
+ # Creates a random Call-ID
157
+ def generate_call_id
158
+ call_id = SecureRandom::hex
159
+ add_call_id call_id
160
+ return call_id
161
+ end
162
+
163
+ def get_new_call_id time_limit=30
164
+ Timeout::timeout(time_limit) { @call_ids.deq }
165
+ end
166
+
167
+ # Sets up the internal structures needed to handle calls for a new Call-ID.
168
+ def add_call_id cid
169
+ @messages[cid] ||= Queue.new
170
+ end
171
+
129
172
  def initialize_queues
130
173
  @messages = {}
131
174
  @call_ids = Queue.new
@@ -142,8 +185,8 @@ module Quaff
142
185
  end
143
186
 
144
187
  def queue_msg(msg, source)
145
- @msg_log.push "Endpoint on #{@lport} received:\n\n#{msg.to_s.strip}\n\nfrom #{source.inspect}"
146
- puts "Endpoint on #{@lport} received #{msg} from
188
+ @msg_log.push "Endpoint on #{@local_port} received:\n\n#{msg.to_s.strip}\n\nfrom #{source.inspect}"
189
+ puts "Endpoint on #{@local_port} received #{msg} from
147
190
  ##{source.inspect}" if @msg_trace
148
191
  msg.source = source
149
192
  cid = @parser.message_identifier msg
@@ -160,17 +203,6 @@ module Quaff
160
203
  class TCPSIPEndpoint < BaseEndpoint
161
204
  attr_accessor :sockets
162
205
 
163
- def initialize_connection
164
- if @lport != :anyport
165
- @cxn = TCPServer.new(@lport)
166
- else
167
- @cxn = TCPServer.new(0)
168
- @lport = @cxn.addr[1]
169
- end
170
- @parser = SipParser.new
171
- @sockets = []
172
- end
173
-
174
206
  def transport
175
207
  "TCP"
176
208
  end
@@ -194,7 +226,21 @@ module Quaff
194
226
 
195
227
 
196
228
  alias_method :new_connection, :new_source
229
+
197
230
  private
231
+
232
+ def initialize_connection
233
+ if @local_port != :anyport
234
+ @cxn = TCPServer.new(@local_port)
235
+ else
236
+ @cxn = TCPServer.new(0)
237
+ @local_port = @cxn.addr[1]
238
+ end
239
+ @parser = SipParser.new
240
+ @sockets = []
241
+ end
242
+
243
+
198
244
  def recv_msg
199
245
  select_response = IO.select(@sockets, [], [], 0) || [[]]
200
246
  readable = select_response[0]
@@ -239,13 +285,14 @@ module Quaff
239
285
  alias_method :new_connection, :new_source
240
286
 
241
287
  private
288
+
242
289
  def initialize_connection
243
290
  @cxn = UDPSocket.new
244
- if @lport != :anyport
245
- @cxn.bind('0.0.0.0', @lport)
291
+ if @local_port != :anyport
292
+ @cxn.bind('0.0.0.0', @local_port)
246
293
  else
247
294
  @cxn.bind('0.0.0.0', 0)
248
- @lport = @cxn.addr[1]
295
+ @local_port = @cxn.addr[1]
249
296
  end
250
297
  @sockets = []
251
298
  @parser = SipParser.new
data/lib/sip_parser.rb CHANGED
@@ -4,7 +4,7 @@ require 'abnf'
4
4
  require_relative './message.rb'
5
5
 
6
6
  module Quaff
7
- class SipParser
7
+ class SipParser # :nodoc:
8
8
  attr_reader :state
9
9
  def parse_start
10
10
  @buf = ""
@@ -99,7 +99,7 @@ module Quaff
99
99
 
100
100
  end
101
101
 
102
- class ABNFSipParser
102
+ class ABNFSipParser # :nodoc:
103
103
  include ABNF
104
104
 
105
105
  # Rules
@@ -227,7 +227,7 @@ module Quaff
227
227
  end
228
228
  end
229
229
 
230
- class ToSpec < ABNFSipParser
230
+ class ToSpec < ABNFSipParser # :nodoc:
231
231
  attr_accessor :params, :uri, :displayname, :is_nameaddr
232
232
  def initialize
233
233
  super
data/lib/sources.rb CHANGED
@@ -1,21 +1,15 @@
1
1
  require 'socket'
2
2
  module Quaff
3
+ # A Source is an abstraction representing an IP
4
+ # address/port/transport where a SIP message originates from or can
5
+ # be sent to. It allows users to abstract over TCP and UDP sockets.
3
6
  class Source
4
- def remote_ip
5
- @ip
6
- end
7
-
8
- def remote_port
9
- @port
10
- end
7
+ attr_reader :ip, :port, :transport, :sock
11
8
 
9
+ # Designed to be overriden by subclasses
12
10
  def close cxn
13
11
  end
14
12
 
15
- def sock
16
- nil
17
- end
18
-
19
13
  def to_s
20
14
  "#{@ip}:#{@port} (#{@transport})"
21
15
  end
@@ -41,7 +35,6 @@ module Quaff
41
35
 
42
36
 
43
37
  class TCPSource < Source
44
- attr_reader :sock
45
38
 
46
39
  def initialize ip, port
47
40
  @transport = "TCP"
data/lib/utils.rb CHANGED
@@ -3,7 +3,7 @@ require 'facter'
3
3
 
4
4
  module Quaff
5
5
 
6
- module Utils
6
+ module Utils #:nodoc:
7
7
  def Utils.local_ip
8
8
  Facter.value("ipaddress")
9
9
  end
metadata CHANGED
@@ -1,62 +1,55 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: quaff
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.4
5
- prerelease:
4
+ version: 0.6.0
6
5
  platform: ruby
7
6
  authors:
8
7
  - Rob Day
9
8
  autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2014-03-10 00:00:00.000000000 Z
11
+ date: 2014-06-22 00:00:00.000000000 Z
13
12
  dependencies:
14
13
  - !ruby/object:Gem::Dependency
15
14
  name: facter
16
15
  requirement: !ruby/object:Gem::Requirement
17
- none: false
18
16
  requirements:
19
- - - ! '>='
17
+ - - '>='
20
18
  - !ruby/object:Gem::Version
21
19
  version: 1.7.3
22
20
  type: :runtime
23
21
  prerelease: false
24
22
  version_requirements: !ruby/object:Gem::Requirement
25
- none: false
26
23
  requirements:
27
- - - ! '>='
24
+ - - '>='
28
25
  - !ruby/object:Gem::Version
29
26
  version: 1.7.3
30
27
  - !ruby/object:Gem::Dependency
31
28
  name: milenage
32
29
  requirement: !ruby/object:Gem::Requirement
33
- none: false
34
30
  requirements:
35
- - - ! '>='
31
+ - - '>='
36
32
  - !ruby/object:Gem::Version
37
33
  version: 0.1.0
38
34
  type: :runtime
39
35
  prerelease: false
40
36
  version_requirements: !ruby/object:Gem::Requirement
41
- none: false
42
37
  requirements:
43
- - - ! '>='
38
+ - - '>='
44
39
  - !ruby/object:Gem::Version
45
40
  version: 0.1.0
46
41
  - !ruby/object:Gem::Dependency
47
42
  name: abnf-parsing
48
43
  requirement: !ruby/object:Gem::Requirement
49
- none: false
50
44
  requirements:
51
- - - ! '>='
45
+ - - '>='
52
46
  - !ruby/object:Gem::Version
53
47
  version: 0.2.0
54
48
  type: :runtime
55
49
  prerelease: false
56
50
  version_requirements: !ruby/object:Gem::Requirement
57
- none: false
58
51
  requirements:
59
- - - ! '>='
52
+ - - '>='
60
53
  - !ruby/object:Gem::Version
61
54
  version: 0.2.0
62
55
  description: A Ruby library for writing SIP test scenarios
@@ -65,37 +58,36 @@ executables: []
65
58
  extensions: []
66
59
  extra_rdoc_files: []
67
60
  files:
68
- - lib/utils.rb
61
+ - lib/call.rb
69
62
  - lib/endpoint.rb
70
63
  - lib/message.rb
71
- - lib/call.rb
72
- - lib/auth.rb
64
+ - lib/quaff.rb
73
65
  - lib/sources.rb
66
+ - lib/utils.rb
74
67
  - lib/sip_parser.rb
75
- - lib/quaff.rb
68
+ - lib/auth.rb
76
69
  homepage: http://github.com/rkday/quaff
77
70
  licenses:
78
71
  - GPL3
72
+ metadata: {}
79
73
  post_install_message:
80
74
  rdoc_options: []
81
75
  require_paths:
82
76
  - lib
83
77
  required_ruby_version: !ruby/object:Gem::Requirement
84
- none: false
85
78
  requirements:
86
- - - ! '>='
79
+ - - '>='
87
80
  - !ruby/object:Gem::Version
88
81
  version: '0'
89
82
  required_rubygems_version: !ruby/object:Gem::Requirement
90
- none: false
91
83
  requirements:
92
- - - ! '>='
84
+ - - '>='
93
85
  - !ruby/object:Gem::Version
94
86
  version: '0'
95
87
  requirements: []
96
88
  rubyforge_project:
97
- rubygems_version: 1.8.25
89
+ rubygems_version: 2.1.11
98
90
  signing_key:
99
- specification_version: 3
91
+ specification_version: 4
100
92
  summary: Quaff
101
93
  test_files: []