ruby_home 0.1.2 → 0.1.3

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.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +13 -14
  3. data/README.md +1 -1
  4. data/bin/rubyhome +1 -1
  5. data/lib/ruby_home/accessory_info.rb +33 -72
  6. data/lib/ruby_home/device_id.rb +0 -2
  7. data/lib/ruby_home/dns/service.rb +1 -6
  8. data/lib/ruby_home/dns/text_record.rb +0 -2
  9. data/lib/ruby_home/factories/accessory_factory.rb +0 -5
  10. data/lib/ruby_home/factories/characteristic_factory.rb +0 -3
  11. data/lib/ruby_home/factories/templates/characteristic_template.rb +0 -1
  12. data/lib/ruby_home/factories/templates/service_template.rb +0 -2
  13. data/lib/ruby_home/hap/accessory.rb +2 -2
  14. data/lib/ruby_home/hap/characteristic.rb +2 -2
  15. data/lib/ruby_home/hap/crypto/chacha20poly1305.rb +0 -4
  16. data/lib/ruby_home/hap/crypto/hkdf.rb +0 -2
  17. data/lib/ruby_home/hap/http_decryption.rb +5 -6
  18. data/lib/ruby_home/hap/http_encryption.rb +21 -11
  19. data/lib/ruby_home/hap/service.rb +0 -3
  20. data/lib/ruby_home/hap/tlv.rb +28 -21
  21. data/lib/ruby_home/hex_helper.rb +15 -0
  22. data/lib/ruby_home/http/application.rb +24 -19
  23. data/lib/ruby_home/http/controllers/accessories_controller.rb +1 -2
  24. data/lib/ruby_home/http/controllers/application_controller.rb +60 -13
  25. data/lib/ruby_home/http/controllers/characteristics_controller.rb +2 -3
  26. data/lib/ruby_home/http/controllers/pair_setups_controller.rb +41 -78
  27. data/lib/ruby_home/http/controllers/pair_verifies_controller.rb +22 -24
  28. data/lib/ruby_home/http/controllers/pairings_controller.rb +8 -8
  29. data/lib/ruby_home/http/hap_request.rb +2 -6
  30. data/lib/ruby_home/http/hap_response.rb +2 -8
  31. data/lib/ruby_home/http/hap_server.rb +7 -12
  32. data/lib/ruby_home/http/serializers/object_serializer.rb +0 -2
  33. data/lib/ruby_home/http/services/start_srp_service.rb +46 -0
  34. data/lib/ruby_home/http/services/verify_srp_service.rb +55 -0
  35. data/lib/ruby_home/rack/handler/hap_server.rb +2 -7
  36. data/lib/ruby_home/version.rb +1 -1
  37. data/lib/ruby_home/yaml_record.rb +440 -0
  38. data/lib/ruby_home.rb +45 -6
  39. data/rubyhome.gemspec +4 -4
  40. metadata +44 -49
  41. data/lib/ruby_home/broadcast.rb +0 -31
  42. data/lib/ruby_home/hap/hex_pad.rb +0 -13
  43. data/lib/ruby_home/http/cache.rb +0 -30
@@ -1,10 +1,9 @@
1
1
  require_relative 'application_controller'
2
- require_relative '../serializers/characteristic_value_serializer'
3
2
 
4
3
  module RubyHome
5
4
  module HTTP
6
5
  class CharacteristicsController < ApplicationController
7
- get '/characteristics' do
6
+ get '/' do
8
7
  content_type 'application/hap+json'
9
8
 
10
9
  if cache[:controller_to_accessory_key] && cache[:accessory_to_controller_key]
@@ -21,7 +20,7 @@ module RubyHome
21
20
  end
22
21
  end
23
22
 
24
- put '/characteristics' do
23
+ put '/' do
25
24
  content_type 'application/hap+json'
26
25
 
27
26
  if cache[:controller_to_accessory_key] && cache[:accessory_to_controller_key]
@@ -1,81 +1,75 @@
1
- require 'hkdf'
2
- require 'openssl'
3
- require 'rbnacl/libsodium'
4
- require 'ruby_home-srp'
5
- require_relative '../../hap/hex_pad'
6
- require_relative '../../hap/tlv'
7
1
  require_relative 'application_controller'
8
2
 
9
3
  module RubyHome
10
4
  module HTTP
11
5
  class PairSetupsController < ApplicationController
12
- post '/pair-setup' do
6
+ post '/' do
13
7
  content_type 'application/pairing+tlv8'
14
8
 
15
- case unpack_request['kTLVType_State']
9
+ case unpack_request[:state]
16
10
  when 1
17
- srp_start_response
11
+ start
18
12
  when 3
19
- srp_verify_response
13
+ verify
20
14
  when 5
21
- exchange_response
15
+ exchange
22
16
  end
23
17
  end
24
18
 
25
19
  private
26
20
 
27
- def srp_start_response
28
- username = 'Pair-Setup'
29
- password = '031-45-154'
21
+ def pairing_failed
22
+ clear_cache
30
23
 
31
- auth = srp_verifier.generate_userauth(username, password)
24
+ tlv state: 4, error: 2
25
+ end
32
26
 
33
- verifier = auth[:verifier]
34
- salt = auth[:salt]
27
+ def start
28
+ start_srp = StartSRPService.new(
29
+ username: accessory_info.username,
30
+ password: accessory_info.password
31
+ )
35
32
 
36
- challenge_and_proof = srp_verifier.get_challenge_and_proof(username, verifier, salt)
37
- store_proof(challenge_and_proof[:proof])
33
+ cache[:srp_session] = start_srp.proof
38
34
 
39
- HAP::TLV.encode({
40
- 'kTLVType_Salt' => [challenge_and_proof[:challenge][:salt]].pack('H*'),
41
- 'kTLVType_PublicKey' => [challenge_and_proof[:challenge][:B]].pack('H*'),
42
- 'kTLVType_State' => 2
43
- })
35
+ tlv salt: start_srp.salt_bytes, public_key: start_srp.public_key_bytes, state: 2
44
36
  end
45
37
 
46
- def srp_verify_response
47
- proof = retrieve_proof.dup
48
- proof[:A] = unpack_request['kTLVType_PublicKey'].unpack1('H*')
49
-
50
- server_m2_proof = srp_verifier.verify_session(proof, unpack_request['kTLVType_Proof'].unpack1('H*'))
38
+ def verify
39
+ verify_srp = VerifySRPService.new(
40
+ device_proof: unpack_request[:proof],
41
+ srp_session: cache[:srp_session],
42
+ public_key: unpack_request[:public_key],
43
+ )
51
44
 
52
- store_session_key(srp_verifier.K)
53
- forget_proof!
45
+ if verify_srp.valid?
46
+ cache[:session_key] = verify_srp.session_key
47
+ cache.delete(:srp_session)
54
48
 
55
- HAP::TLV.encode({
56
- 'kTLVType_State' => 4,
57
- 'kTLVType_Proof' => [server_m2_proof].pack('H*')
58
- })
49
+ tlv state: 4, proof: verify_srp.server_proof
50
+ else
51
+ pairing_failed
52
+ end
59
53
  end
60
54
 
61
- def exchange_response
62
- encrypted_data = unpack_request['kTLVType_EncryptedData']
55
+ def exchange
56
+ encrypted_data = unpack_request[:encrypted_data]
63
57
 
64
58
  hkdf = HAP::Crypto::HKDF.new(info: 'Pair-Setup-Encrypt-Info', salt: 'Pair-Setup-Encrypt-Salt')
65
- key = hkdf.encrypt(session_key)
59
+ key = hkdf.encrypt(cache[:session_key])
66
60
 
67
61
  chacha20poly1305ietf = HAP::Crypto::ChaCha20Poly1305.new(key)
68
62
 
69
- nonce = HAP::HexPad.pad('PS-Msg05')
63
+ nonce = HexHelper.pad('PS-Msg05')
70
64
  decrypted_data = chacha20poly1305ietf.decrypt(nonce, encrypted_data)
71
65
  unpacked_decrypted_data = HAP::TLV.read(decrypted_data)
72
66
 
73
- iosdevicepairingid = unpacked_decrypted_data['kTLVType_Identifier']
74
- iosdevicesignature = unpacked_decrypted_data['kTLVType_Signature']
75
- iosdeviceltpk = unpacked_decrypted_data['kTLVType_PublicKey']
67
+ iosdevicepairingid = unpacked_decrypted_data[:identifier]
68
+ iosdevicesignature = unpacked_decrypted_data[:signature]
69
+ iosdeviceltpk = unpacked_decrypted_data[:public_key]
76
70
 
77
71
  hkdf = HAP::Crypto::HKDF.new(info: 'Pair-Setup-Controller-Sign-Info', salt: 'Pair-Setup-Controller-Sign-Salt')
78
- iosdevicex = hkdf.encrypt(session_key)
72
+ iosdevicex = hkdf.encrypt(cache[:session_key])
79
73
 
80
74
  iosdeviceinfo = [
81
75
  iosdevicex.unpack1('H*'),
@@ -86,7 +80,7 @@ module RubyHome
86
80
 
87
81
  if verify_key.verify(iosdevicesignature, [iosdeviceinfo].pack('H*'))
88
82
  hkdf = HAP::Crypto::HKDF.new(info: 'Pair-Setup-Accessory-Sign-Info', salt: 'Pair-Setup-Accessory-Sign-Salt')
89
- accessory_x = hkdf.encrypt(session_key)
83
+ accessory_x = hkdf.encrypt(cache[:session_key])
90
84
 
91
85
  signing_key = accessory_info.signing_key
92
86
  accessoryltpk = signing_key.verify_key.to_bytes
@@ -98,48 +92,17 @@ module RubyHome
98
92
 
99
93
  accessorysignature = signing_key.sign([accessoryinfo].pack('H*'))
100
94
 
101
- subtlv = HAP::TLV.encode({
102
- 'kTLVType_Identifier' => accessory_info.device_id,
103
- 'kTLVType_PublicKey' => accessoryltpk,
104
- 'kTLVType_Signature' => accessorysignature
105
- })
95
+ subtlv = tlv(identifier: accessory_info.device_id, public_key: accessoryltpk, signature: accessorysignature)
106
96
 
107
- nonce = HAP::HexPad.pad('PS-Msg06')
97
+ nonce = HexHelper.pad('PS-Msg06')
108
98
  encrypted_data = chacha20poly1305ietf.encrypt(nonce, subtlv)
109
99
 
110
100
  pairing_params = { admin: true, identifier: iosdevicepairingid, public_key: iosdeviceltpk.unpack1('H*') }
111
101
  accessory_info.add_paired_client pairing_params
112
102
 
113
- HAP::TLV.encode({
114
- 'kTLVType_State' => 6,
115
- 'kTLVType_EncryptedData' => encrypted_data
116
- })
103
+ tlv state: 6, encrypted_data: encrypted_data
117
104
  end
118
105
  end
119
-
120
- def srp_verifier
121
- @_verifier ||= RubyHome::SRP::Verifier.new
122
- end
123
-
124
- def store_proof(proof)
125
- cache[:proof] = proof
126
- end
127
-
128
- def retrieve_proof
129
- cache[:proof]
130
- end
131
-
132
- def forget_proof!
133
- cache[:proof] = nil
134
- end
135
-
136
- def store_session_key(key)
137
- cache[:session_key] = key
138
- end
139
-
140
- def session_key
141
- cache[:session_key]
142
- end
143
106
  end
144
107
  end
145
108
  end
@@ -1,16 +1,14 @@
1
- require 'x25519'
2
- require_relative '../../hap/crypto/hkdf'
3
- require_relative '../../hap/hex_pad'
4
- require_relative '../../hap/tlv'
5
1
  require_relative 'application_controller'
6
2
 
7
3
  module RubyHome
8
4
  module HTTP
9
5
  class PairVerifiesController < ApplicationController
10
- post '/pair-verify' do
6
+ post '/' do
11
7
  content_type 'application/pairing+tlv8'
12
8
 
13
- case unpack_request['kTLVType_State']
9
+ verify_accessory_paired
10
+
11
+ case unpack_request[:state]
14
12
  when 1
15
13
  verify_start_response
16
14
  when 3
@@ -21,10 +19,10 @@ module RubyHome
21
19
  private
22
20
 
23
21
  def verify_start_response
24
- secret_key = X25519::Scalar.generate
22
+ secret_key = RbNaCl::PrivateKey.generate
25
23
  public_key = secret_key.public_key.to_bytes
26
- client_public_key = X25519::MontgomeryU.new(unpack_request['kTLVType_PublicKey'])
27
- shared_secret = secret_key.multiply(client_public_key).to_bytes
24
+ client_public_key = RbNaCl::PublicKey.new(unpack_request[:public_key])
25
+ shared_secret = RbNaCl::GroupElement.new(client_public_key).mult(secret_key).to_bytes
28
26
  cache[:shared_secret] = shared_secret
29
27
 
30
28
  accessoryinfo = [
@@ -36,46 +34,46 @@ module RubyHome
36
34
  signing_key = accessory_info.signing_key
37
35
  accessorysignature = signing_key.sign([accessoryinfo].pack('H*'))
38
36
 
39
- subtlv = HAP::TLV.encode({
40
- 'kTLVType_Identifier' => accessory_info.device_id,
41
- 'kTLVType_Signature' => accessorysignature
42
- })
37
+ subtlv = tlv(identifier: accessory_info.device_id, signature: accessorysignature)
43
38
 
44
39
  hkdf = HAP::Crypto::HKDF.new(info: 'Pair-Verify-Encrypt-Info', salt: 'Pair-Verify-Encrypt-Salt')
45
40
  session_key = hkdf.encrypt(shared_secret)
46
41
  cache[:session_key] = session_key
47
42
 
48
43
  chacha20poly1305ietf = HAP::Crypto::ChaCha20Poly1305.new(session_key)
49
- nonce = HAP::HexPad.pad('PV-Msg02')
44
+ nonce = HexHelper.pad('PV-Msg02')
50
45
  encrypted_data = chacha20poly1305ietf.encrypt(nonce, subtlv)
51
46
 
52
- HAP::TLV.encode({
53
- 'kTLVType_State' => 2,
54
- 'kTLVType_PublicKey' => public_key,
55
- 'kTLVType_EncryptedData' => encrypted_data
56
- })
47
+ tlv state: 2, public_key: public_key, encrypted_data: encrypted_data
57
48
  end
58
49
 
59
50
  def verify_finish_response
60
- encrypted_data = unpack_request['kTLVType_EncryptedData']
51
+ encrypted_data = unpack_request[:encrypted_data]
61
52
 
62
53
  chacha20poly1305ietf = HAP::Crypto::ChaCha20Poly1305.new(cache[:session_key])
63
- nonce = HAP::HexPad.pad('PV-Msg03')
54
+ nonce = HexHelper.pad('PV-Msg03')
64
55
  decrypted_data = chacha20poly1305ietf.decrypt(nonce, encrypted_data)
65
56
  unpacked_decrypted_data = HAP::TLV.read(decrypted_data)
66
57
 
67
- if accessory_info.paired_clients.any? {|h| h[:identifier] == unpacked_decrypted_data['kTLVType_Identifier']}
58
+ if accessory_info.paired_clients.any? {|h| h[:identifier] == unpacked_decrypted_data[:identifier]}
68
59
  hkdf = HAP::Crypto::HKDF.new(info: 'Control-Write-Encryption-Key', salt: 'Control-Salt')
69
60
  cache[:controller_to_accessory_key] = hkdf.encrypt(cache[:shared_secret])
70
61
 
71
62
  hkdf = HAP::Crypto::HKDF.new(info: 'Control-Read-Encryption-Key', salt: 'Control-Salt')
72
63
  cache[:accessory_to_controller_key] = hkdf.encrypt(cache[:shared_secret])
73
64
 
74
- HAP::TLV.encode({'kTLVType_State' => 4})
65
+ cache.delete(:session_key)
66
+ cache.delete(:shared_secret)
67
+
68
+ tlv state: 4
75
69
  else
76
- HAP::TLV.encode({'kTLVType_State' => 4, 'kTLVType_Error' => 2})
70
+ tlv state: 4, error: 2
77
71
  end
78
72
  end
73
+
74
+ def verify_accessory_paired
75
+ halt 403 unless accessory_info.paired?
76
+ end
79
77
  end
80
78
  end
81
79
  end
@@ -3,10 +3,10 @@ require_relative 'application_controller'
3
3
  module RubyHome
4
4
  module HTTP
5
5
  class PairingsController < ApplicationController
6
- post '/pairings' do
6
+ post '/' do
7
7
  content_type 'application/pairing+tlv8'
8
8
 
9
- case unpack_request['kTLVType_Method']
9
+ case unpack_request[:method]
10
10
  when 3
11
11
  add_pairing
12
12
  when 4
@@ -18,20 +18,20 @@ module RubyHome
18
18
 
19
19
  def add_pairing
20
20
  pairing_params = {
21
- admin: !!unpack_request['kTLVType_Permissions'],
22
- identifier: unpack_request['kTLVType_Identifier'],
23
- public_key: unpack_request['kTLVType_PublicKey'].unpack1('H*')
21
+ admin: !!unpack_request[:permissions],
22
+ identifier: unpack_request[:identifier],
23
+ public_key: unpack_request[:public_key].unpack1('H*')
24
24
  }
25
25
  accessory_info.add_paired_client pairing_params
26
26
 
27
- HAP::TLV.encode({'kTLVType_State' => 2})
27
+ tlv state: 2
28
28
  end
29
29
 
30
30
  def remove_pairing
31
- accessory_info.remove_paired_client(unpack_request['kTLVType_Identifier'])
31
+ accessory_info.remove_paired_client(unpack_request[:identifier])
32
32
 
33
33
  response['connection'] = 'close'
34
- HAP::TLV.encode({'kTLVType_State' => 2})
34
+ tlv state: 2
35
35
  end
36
36
  end
37
37
  end
@@ -1,11 +1,7 @@
1
- require 'webrick/httprequest'
2
- require_relative '../hap/http_decryption'
3
-
4
1
  module RubyHome
5
2
  module HTTP
6
3
  class HAPRequest < WEBrick::HTTPRequest
7
- def initialize(*args, request_id: )
8
- @_request_id = request_id
4
+ def initialize(*args)
9
5
  cache[:controller_to_accessory_count] ||= 0
10
6
 
11
7
  super(*args)
@@ -49,7 +45,7 @@ module RubyHome
49
45
  end
50
46
 
51
47
  def cache
52
- GlobalCache.instance[@_request_id] ||= Cache.new
48
+ RequestStore.store
53
49
  end
54
50
  end
55
51
  end
@@ -1,11 +1,7 @@
1
- require 'webrick/httpresponse'
2
- require_relative '../hap/http_encryption'
3
-
4
1
  module RubyHome
5
2
  module HTTP
6
3
  class HAPResponse < WEBrick::HTTPResponse
7
- def initialize(*args, request_id: )
8
- @_request_id = request_id
4
+ def initialize(*args)
9
5
  cache[:accessory_to_controller_count] ||= 0
10
6
 
11
7
  super(*args)
@@ -48,10 +44,8 @@ module RubyHome
48
44
  end
49
45
 
50
46
  def cache
51
- GlobalCache.instance[@_request_id] ||= Cache.new
47
+ RequestStore.store
52
48
  end
53
49
  end
54
50
  end
55
51
  end
56
-
57
-
@@ -1,26 +1,21 @@
1
- require 'webrick/httpserver'
2
- require 'webrick/httpstatus'
3
- require_relative 'hap_request'
4
- require_relative 'hap_response'
5
-
6
1
  module RubyHome
7
2
  module HTTP
8
3
  class HAPServer < WEBrick::HTTPServer
9
- def run(sock)
4
+ def run(socket)
10
5
  while true
11
- res = RubyHome::HTTP::HAPResponse.new(@config, request_id: sock.object_id)
12
- req = RubyHome::HTTP::HAPRequest.new(@config, request_id: sock.object_id)
6
+ res = RubyHome::HTTP::HAPResponse.new(@config)
7
+ req = RubyHome::HTTP::HAPRequest.new(@config)
13
8
  server = self
14
9
  begin
15
10
  timeout = @config[:RequestTimeout]
16
11
  while timeout > 0
17
- break if sock.to_io.wait_readable(0.5)
12
+ break if socket.to_io.wait_readable(0.5)
18
13
  break if @status != :Running
19
14
  timeout -= 0.5
20
15
  end
21
16
  raise WEBrick::HTTPStatus::EOFError if timeout <= 0 || @status != :Running
22
- raise WEBrick::HTTPStatus::EOFError if sock.eof?
23
- req.parse(sock)
17
+ raise WEBrick::HTTPStatus::EOFError if socket.eof?
18
+ req.parse(socket)
24
19
  res.received_encrypted_request = req.received_encrypted_request?
25
20
  res.request_method = req.request_method
26
21
  res.request_uri = req.request_uri
@@ -50,7 +45,7 @@ module RubyHome
50
45
  if req.keep_alive? && res.keep_alive?
51
46
  req.fixup()
52
47
  end
53
- res.send_response(sock)
48
+ res.send_response(socket)
54
49
  server.access_log(@config, req, res)
55
50
  end
56
51
  end
@@ -1,5 +1,3 @@
1
- require 'oj'
2
-
3
1
  module RubyHome
4
2
  module HTTP
5
3
  module ObjectSerializer
@@ -0,0 +1,46 @@
1
+ class StartSRPService
2
+ def initialize(username: , password:)
3
+ @username = username
4
+ @password = password
5
+ end
6
+
7
+ def salt_bytes
8
+ [salt].pack('H*')
9
+ end
10
+
11
+ def public_key_bytes
12
+ [public_key].pack('H*')
13
+ end
14
+
15
+ def proof
16
+ challenge_and_proof[:proof]
17
+ end
18
+
19
+ private
20
+
21
+ def salt
22
+ user_auth[:salt]
23
+ end
24
+
25
+ def public_key
26
+ challenge[:B]
27
+ end
28
+
29
+ def challenge
30
+ challenge_and_proof[:challenge]
31
+ end
32
+
33
+ def challenge_and_proof
34
+ srp_verifier.get_challenge_and_proof(username, user_auth[:verifier], user_auth[:salt])
35
+ end
36
+
37
+ def user_auth
38
+ @_user_auth ||= srp_verifier.generate_userauth(username, password)
39
+ end
40
+
41
+ def srp_verifier
42
+ @_verifier ||= RubyHome::SRP::Verifier.new
43
+ end
44
+
45
+ attr_reader :username, :password
46
+ end
@@ -0,0 +1,55 @@
1
+ class VerifySRPService
2
+ def initialize(public_key: , device_proof: , srp_session: )
3
+ @device_proof = device_proof
4
+ @srp_session = srp_session
5
+ @public_key = public_key
6
+ end
7
+
8
+ def valid?
9
+ return false unless public_key
10
+ return false unless device_proof
11
+ return false unless srp_session
12
+ return false unless valid_session?
13
+
14
+ true
15
+ end
16
+
17
+ def session_key
18
+ srp_verifier.K
19
+ end
20
+
21
+ def server_proof
22
+ verify_session_bytes
23
+ end
24
+
25
+ private
26
+
27
+ def valid_session?
28
+ !!verify_session
29
+ end
30
+
31
+ def verify_session_bytes
32
+ [verify_session].pack('H*')
33
+ end
34
+
35
+ def verify_session
36
+ @_verify_session ||= srp_verifier.verify_session(
37
+ srp_session.merge({A: public_key_bytes}),
38
+ device_proof_bytes
39
+ )
40
+ end
41
+
42
+ def public_key_bytes
43
+ public_key.unpack1('H*')
44
+ end
45
+
46
+ def device_proof_bytes
47
+ device_proof.unpack1('H*')
48
+ end
49
+
50
+ def srp_verifier
51
+ @_verifier ||= RubyHome::SRP::Verifier.new
52
+ end
53
+
54
+ attr_reader :public_key, :device_proof, :srp_session
55
+ end
@@ -1,6 +1,3 @@
1
- require 'webrick'
2
- require_relative '../../http/hap_server'
3
-
4
1
  module RubyHome
5
2
  module Rack
6
3
  module Handler
@@ -11,10 +8,8 @@ module RubyHome
11
8
 
12
9
  options[:BindAddress] = options.delete(:Host) || default_host
13
10
  options[:Port] ||= 8080
14
- unless ENV['DEBUG']
15
- options[:Logger] = WEBrick::Log.new("/dev/null")
16
- options[:AccessLog] = []
17
- end
11
+ options[:Logger] = WEBrick::Log.new("/dev/null")
12
+ options[:AccessLog] = []
18
13
  @server = HTTP::HAPServer.new(options)
19
14
  @server.mount '/', Handler::HAPServer, app
20
15
  yield @server if block_given?
@@ -1,3 +1,3 @@
1
1
  module RubyHome
2
- VERSION = '0.1.2'
2
+ VERSION = '0.1.3'
3
3
  end