quaff 0.5.4 → 0.6.0

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