ruby_home 0.1.0 → 0.1.2

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
  SHA256:
3
- metadata.gz: 6ee1bab57c905588f1ea3b558c4a303a2935321cdb8e95e04416ff5812c40727
4
- data.tar.gz: 8383552d3c59b1360ba854b5cada8159a98dac180fa137a4be907057ca2c84e6
3
+ metadata.gz: 6ccc3aa90b30716b6e6d39417f7c1bef28d9f46c9eede93dde811436d2ddfb8e
4
+ data.tar.gz: ad86b1dbbb0744be8eb76d82da287a9d2ee13d1e43d3bb5f137669fe3c865c82
5
5
  SHA512:
6
- metadata.gz: 1a3be890c661e77d1c69348b04520b8426e6a7861d517880d99dd7d7619bf46eab47d433516139bb53af887c7429a007495e199877b0f58f3e4b0a5099ea1010
7
- data.tar.gz: ef7aec5965acaf52f8da6084a63de3d0e51d1ef9df4e8049d7dc8940d497b926988296f6e9bf014e8e414a26716f926e227e704c0c38e4de1fa53166c20219be
6
+ metadata.gz: 03c859f3cd8129b26576402d2ecc8d3fff54adb7fcce812e36a910bec254d8c26e6f1192b570889ed6c7462f83f2a7a3a7e9f616e4941be0b82fb7af688e0ed0
7
+ data.tar.gz: ea4e4033a81221b8f798a8adb50a3e625b405d1136f43c690834f53af9a07cdf36eb8269241a1a69397b68fe3ca4f8dbf1673c3e0ffd750420686bdb61ace4dd
data/README.md CHANGED
@@ -1,9 +1,9 @@
1
- [![Maintainability](https://api.codeclimate.com/v1/badges/a13f62c597b3746a7ef5/maintainability)](https://codeclimate.com/github/karlentwistle/ruby_home/maintainability)
1
+ [![Maintainability](https://api.codeclimate.com/v1/badges/c81f4cfdf5c13d716487/maintainability)](https://codeclimate.com/github/karlentwistle/ruby_home/maintainability)
2
2
  [![Build Status](https://travis-ci.org/karlentwistle/ruby_home.svg?branch=master)](https://travis-ci.org/karlentwistle/ruby_home)
3
3
 
4
- # Ruby Home
4
+ # ruby_home
5
5
 
6
- RubyHome is a lightweight service you can run on your home network that emulates the iOS HomeKit API. It supports community contributed plugins, which are modules that provide a bridge from HomeKit to various 3rd-party APIs provided by manufacturers of smart home devices.
6
+ ruby_home is an implementation of the HomeKit Accessory Protocol (HAP) to create your own HomeKit accessory in Ruby. HomeKit is a set of protocols and libraries to access devices for home automation. A non-commercial version of the protocol documentation is available on the [HomeKit developer website](https://developer.apple.com/homekit/).
7
7
 
8
8
  ## Installation
9
9
 
@@ -23,14 +23,30 @@ Or install it yourself as:
23
23
 
24
24
  ## Usage
25
25
 
26
- TODO: Write usage instructions here
26
+ Create a fan with an on/off switch.
27
+
28
+ ```ruby
29
+ require 'ruby_home'
30
+
31
+ accessory_information = RubyHome::AccessoryFactory.create(:accessory_information)
32
+ fan = RubyHome::AccessoryFactory.create(:fan)
33
+
34
+ fan.characteristic(:on).on(:updated) do |new_value|
35
+ if new_value == 1
36
+ puts "Fan switched on"
37
+ else
38
+ puts "Fan switched off"
39
+ end
40
+ end
41
+
42
+ RubyHome::Broadcast.run
43
+ ```
27
44
 
28
45
  ## Development
29
46
 
30
47
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
31
48
 
32
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
33
49
 
34
50
  ## Contributing
35
51
 
36
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/ruby_home.
52
+ Bug reports and pull requests are welcome on GitHub at https://github.com/karlentwistle/ruby_home.
data/lib/ruby_home.rb CHANGED
@@ -2,5 +2,6 @@ require_relative 'ruby_home/version'
2
2
  require_relative 'ruby_home/broadcast'
3
3
  require_relative 'ruby_home/identifier_cache'
4
4
  Dir[File.dirname(__FILE__) + '/ruby_home/factories/*.rb'].each { |file| require file }
5
+ Dir[File.dirname(__FILE__) + '/ruby_home/hap/crypto/*.rb'].each { |file| require file }
5
6
 
6
7
  module RubyHome; end
@@ -0,0 +1,31 @@
1
+ require 'rbnacl/libsodium'
2
+
3
+ module RubyHome
4
+ module HAP
5
+ module Crypto
6
+ class ChaCha20Poly1305
7
+ def initialize(key)
8
+ @key = key
9
+ end
10
+
11
+ def encrypt(nonce, message, additional_data=nil)
12
+ chacha20poly1305ietf.encrypt(nonce, message, additional_data)
13
+ end
14
+
15
+ def decrypt(nonce, ciphertext, additional_data=nil)
16
+ chacha20poly1305ietf.decrypt(nonce, ciphertext, additional_data)
17
+ end
18
+
19
+ private
20
+
21
+ attr_reader :key
22
+
23
+ def chacha20poly1305ietf
24
+ @_chacha20poly1305ietf ||= RbNaCl::AEAD::ChaCha20Poly1305IETF.new(key)
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+
31
+
@@ -0,0 +1,44 @@
1
+ require 'rbnacl/libsodium'
2
+
3
+ GEM_HKDF = HKDF
4
+
5
+ module RubyHome
6
+ module HAP
7
+ module Crypto
8
+ class HKDF
9
+ def initialize(salt:, info: )
10
+ @salt = salt
11
+ @info = info
12
+ end
13
+
14
+ def encrypt(source)
15
+ byte_string = convert_string_to_byte_string(source)
16
+ GEM_HKDF.new(byte_string, hkdf_opts).next_bytes(BYTE_LENGTH)
17
+ end
18
+
19
+ private
20
+
21
+ ALGORITHM = 'SHA512'
22
+ BYTE_LENGTH = 32
23
+
24
+ attr_reader :info, :salt, :source
25
+
26
+ def hkdf_opts
27
+ {
28
+ algorithm: ALGORITHM,
29
+ info: info,
30
+ salt: salt
31
+ }
32
+ end
33
+
34
+ def convert_string_to_byte_string(string)
35
+ if string.encoding == Encoding::ASCII_8BIT
36
+ string
37
+ else
38
+ [string].pack('H*')
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -51,7 +51,7 @@ module RubyHome
51
51
  end
52
52
 
53
53
  def chacha20poly1305ietf
54
- RbNaCl::AEAD::ChaCha20Poly1305IETF.new(key)
54
+ HAP::Crypto::ChaCha20Poly1305.new(key)
55
55
  end
56
56
  end
57
57
  end
@@ -34,7 +34,7 @@ module RubyHome
34
34
  end
35
35
 
36
36
  def chacha20poly1305ietf
37
- RbNaCl::AEAD::ChaCha20Poly1305IETF.new(key)
37
+ HAP::Crypto::ChaCha20Poly1305.new(key)
38
38
  end
39
39
  end
40
40
  end
@@ -0,0 +1,95 @@
1
+ require 'bindata'
2
+
3
+ module RubyHome
4
+ module HAP
5
+ module TLV
6
+ extend self
7
+
8
+ TYPE_NAMES = {
9
+ 0 => 'kTLVType_Method',
10
+ 1 => 'kTLVType_Identifier',
11
+ 2 => 'kTLVType_Salt',
12
+ 3 => 'kTLVType_PublicKey',
13
+ 4 => 'kTLVType_Proof',
14
+ 5 => 'kTLVType_EncryptedData',
15
+ 6 => 'kTLVType_State',
16
+ 7 => 'kTLVType_Error',
17
+ 8 => 'kTLVType_RetryDelay',
18
+ 9 => 'kTLVType_Certificate',
19
+ 10 => 'kTLVType_Signature',
20
+ 11 => 'kTLVType_Permissions',
21
+ 12 => 'kTLVType_FragmentData',
22
+ 13 => 'kTLVType_FragmentLast',
23
+ }.freeze
24
+ NAME_TYPES = TYPE_NAMES.invert.freeze
25
+
26
+ class Bytes < BinData::String; end
27
+
28
+ class UTF8String < BinData::String
29
+ def snapshot
30
+ super.force_encoding('UTF-8')
31
+ end
32
+ end
33
+
34
+ class Payload < BinData::Choice
35
+ bytes :default, read_length: :len
36
+
37
+ uint8 0, read_length: :len
38
+ utf8_string 1, read_length: :len
39
+ bytes 2, read_length: :len
40
+ bytes 3, read_length: :len
41
+ bytes 4, read_length: :len
42
+ bytes 5, read_length: :len
43
+ uint8 6, read_length: :len
44
+ uint8 7, read_length: :len
45
+ uint8 8, read_length: :len
46
+ bytes 9, read_length: :len
47
+ bytes 10, read_length: :len
48
+ uint8 11, read_length: :len
49
+ bytes 12, read_length: :len
50
+ bytes 13, read_length: :len
51
+ end
52
+
53
+ class TLV < BinData::Record
54
+ uint8 :type_id, read_length: 2
55
+ uint8 :len, read_length: 2
56
+ payload :val, selection: :type_id
57
+ end
58
+
59
+ READER = BinData::Array.new(type: :tlv, read_until: :eof)
60
+
61
+ def read(input)
62
+ READER.clear
63
+ READER.read(input)
64
+ READER.snapshot.each_with_object({}) do |(hash), memo|
65
+ type = TYPE_NAMES[hash[:type_id]]
66
+ next unless type
67
+
68
+ if memo[type]
69
+ memo[type] << hash[:val]
70
+ else
71
+ memo[type] = hash[:val]
72
+ end
73
+ end
74
+ end
75
+
76
+ def encode(hash)
77
+ hash.to_hash.each_with_object(String.new) do |(key, value), memo|
78
+ type_id = NAME_TYPES[key]
79
+ next unless type_id
80
+
81
+ if value.is_a?(String)
82
+ value.scan(/.{1,255}/m)
83
+ else
84
+ [value]
85
+ end.each do |frame_value|
86
+ tlv = TLV.new(type_id: type_id, val: frame_value).tap do |tlv|
87
+ tlv.len = tlv.val.to_binary_s.length
88
+ end
89
+ memo << tlv.to_binary_s
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
@@ -6,7 +6,7 @@ module RubyHome
6
6
  def unpack_request
7
7
  @_unpack_request ||= begin
8
8
  request.body.rewind
9
- TLV.unpack(request.body.read)
9
+ HAP::TLV.read(request.body.read)
10
10
  end
11
11
  end
12
12
 
@@ -3,7 +3,7 @@ require 'openssl'
3
3
  require 'rbnacl/libsodium'
4
4
  require 'ruby_home-srp'
5
5
  require_relative '../../hap/hex_pad'
6
- require_relative '../../tlv'
6
+ require_relative '../../hap/tlv'
7
7
  require_relative 'application_controller'
8
8
 
9
9
  module RubyHome
@@ -36,82 +36,81 @@ module RubyHome
36
36
  challenge_and_proof = srp_verifier.get_challenge_and_proof(username, verifier, salt)
37
37
  store_proof(challenge_and_proof[:proof])
38
38
 
39
- TLV.pack({
40
- 'kTLVType_Salt' => challenge_and_proof[:challenge][:salt],
41
- 'kTLVType_PublicKey' => challenge_and_proof[:challenge][:B],
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
42
  'kTLVType_State' => 2
43
43
  })
44
44
  end
45
45
 
46
46
  def srp_verify_response
47
47
  proof = retrieve_proof.dup
48
- proof[:A] = unpack_request['kTLVType_PublicKey']
48
+ proof[:A] = unpack_request['kTLVType_PublicKey'].unpack1('H*')
49
49
 
50
- client_m1_proof = unpack_request['kTLVType_Proof']
51
- server_m2_proof = srp_verifier.verify_session(proof, unpack_request['kTLVType_Proof'])
50
+ server_m2_proof = srp_verifier.verify_session(proof, unpack_request['kTLVType_Proof'].unpack1('H*'))
52
51
 
53
52
  store_session_key(srp_verifier.K)
54
53
  forget_proof!
55
54
 
56
- TLV.pack({
55
+ HAP::TLV.encode({
57
56
  'kTLVType_State' => 4,
58
- 'kTLVType_Proof' => server_m2_proof
57
+ 'kTLVType_Proof' => [server_m2_proof].pack('H*')
59
58
  })
60
59
  end
61
60
 
62
61
  def exchange_response
63
62
  encrypted_data = unpack_request['kTLVType_EncryptedData']
64
63
 
65
- hkdf = HAP::HKDFEncryption.new(info: 'Pair-Setup-Encrypt-Info', salt: 'Pair-Setup-Encrypt-Salt')
66
- key = hkdf.encrypt([session_key].pack('H*'))
64
+ hkdf = HAP::Crypto::HKDF.new(info: 'Pair-Setup-Encrypt-Info', salt: 'Pair-Setup-Encrypt-Salt')
65
+ key = hkdf.encrypt(session_key)
67
66
 
68
- chacha20poly1305ietf = RbNaCl::AEAD::ChaCha20Poly1305IETF.new(key)
67
+ chacha20poly1305ietf = HAP::Crypto::ChaCha20Poly1305.new(key)
69
68
 
70
69
  nonce = HAP::HexPad.pad('PS-Msg05')
71
- decrypted_data = chacha20poly1305ietf.decrypt(nonce, [encrypted_data].pack('H*'), nil)
72
- unpacked_decrypted_data = TLV.unpack(decrypted_data)
70
+ decrypted_data = chacha20poly1305ietf.decrypt(nonce, encrypted_data)
71
+ unpacked_decrypted_data = HAP::TLV.read(decrypted_data)
73
72
 
74
73
  iosdevicepairingid = unpacked_decrypted_data['kTLVType_Identifier']
75
74
  iosdevicesignature = unpacked_decrypted_data['kTLVType_Signature']
76
75
  iosdeviceltpk = unpacked_decrypted_data['kTLVType_PublicKey']
77
76
 
78
- hkdf = HAP::HKDFEncryption.new(info: 'Pair-Setup-Controller-Sign-Info', salt: 'Pair-Setup-Controller-Sign-Salt')
79
- iosdevicex = hkdf.encrypt([session_key].pack('H*'))
77
+ hkdf = HAP::Crypto::HKDF.new(info: 'Pair-Setup-Controller-Sign-Info', salt: 'Pair-Setup-Controller-Sign-Salt')
78
+ iosdevicex = hkdf.encrypt(session_key)
80
79
 
81
80
  iosdeviceinfo = [
82
- iosdevicex.unpack('H*'),
83
- TLV::Utf8.pack(iosdevicepairingid),
84
- iosdeviceltpk
81
+ iosdevicex.unpack1('H*'),
82
+ iosdevicepairingid.unpack1('H*'),
83
+ iosdeviceltpk.unpack1('H*')
85
84
  ].join
86
- verify_key = RbNaCl::Signatures::Ed25519::VerifyKey.new([iosdeviceltpk].pack('H*'))
85
+ verify_key = RbNaCl::Signatures::Ed25519::VerifyKey.new(iosdeviceltpk)
87
86
 
88
- if verify_key.verify([iosdevicesignature].pack('H*'), [iosdeviceinfo].pack('H*'))
89
- hkdf = HAP::HKDFEncryption.new(info: 'Pair-Setup-Accessory-Sign-Info', salt: 'Pair-Setup-Accessory-Sign-Salt')
90
- accessory_x = hkdf.encrypt([session_key].pack('H*'))
87
+ if verify_key.verify(iosdevicesignature, [iosdeviceinfo].pack('H*'))
88
+ hkdf = HAP::Crypto::HKDF.new(info: 'Pair-Setup-Accessory-Sign-Info', salt: 'Pair-Setup-Accessory-Sign-Salt')
89
+ accessory_x = hkdf.encrypt(session_key)
91
90
 
92
91
  signing_key = accessory_info.signing_key
93
- accessoryltpk = signing_key.verify_key.to_bytes.unpack('H*')[0]
92
+ accessoryltpk = signing_key.verify_key.to_bytes
94
93
  accessoryinfo = [
95
- accessory_x.unpack('H*'),
96
- TLV::Utf8.pack(accessory_info.device_id),
97
- accessoryltpk
94
+ accessory_x.unpack1('H*'),
95
+ accessory_info.device_id.unpack1('H*'),
96
+ accessoryltpk.unpack1('H*')
98
97
  ].join
99
98
 
100
- accessorysignature = signing_key.sign([accessoryinfo].pack('H*')).unpack('H*')[0]
99
+ accessorysignature = signing_key.sign([accessoryinfo].pack('H*'))
101
100
 
102
- subtlv = TLV.pack({
101
+ subtlv = HAP::TLV.encode({
103
102
  'kTLVType_Identifier' => accessory_info.device_id,
104
103
  'kTLVType_PublicKey' => accessoryltpk,
105
104
  'kTLVType_Signature' => accessorysignature
106
105
  })
107
106
 
108
107
  nonce = HAP::HexPad.pad('PS-Msg06')
109
- encrypted_data = chacha20poly1305ietf.encrypt(nonce, subtlv, nil).unpack('H*')[0]
108
+ encrypted_data = chacha20poly1305ietf.encrypt(nonce, subtlv)
110
109
 
111
- pairing_params = { admin: true, identifier: iosdevicepairingid, public_key: iosdeviceltpk }
110
+ pairing_params = { admin: true, identifier: iosdevicepairingid, public_key: iosdeviceltpk.unpack1('H*') }
112
111
  accessory_info.add_paired_client pairing_params
113
112
 
114
- TLV.pack({
113
+ HAP::TLV.encode({
115
114
  'kTLVType_State' => 6,
116
115
  'kTLVType_EncryptedData' => encrypted_data
117
116
  })
@@ -1,7 +1,7 @@
1
1
  require 'x25519'
2
+ require_relative '../../hap/crypto/hkdf'
2
3
  require_relative '../../hap/hex_pad'
3
- require_relative '../../hap/hkdf_encryption'
4
- require_relative '../../tlv'
4
+ require_relative '../../hap/tlv'
5
5
  require_relative 'application_controller'
6
6
 
7
7
  module RubyHome
@@ -22,34 +22,34 @@ module RubyHome
22
22
 
23
23
  def verify_start_response
24
24
  secret_key = X25519::Scalar.generate
25
- public_key = secret_key.public_key.to_bytes.unpack('H*')[0]
26
- client_public_key = X25519::MontgomeryU.new([unpack_request['kTLVType_PublicKey']].pack('H*'))
25
+ public_key = secret_key.public_key.to_bytes
26
+ client_public_key = X25519::MontgomeryU.new(unpack_request['kTLVType_PublicKey'])
27
27
  shared_secret = secret_key.multiply(client_public_key).to_bytes
28
28
  cache[:shared_secret] = shared_secret
29
29
 
30
30
  accessoryinfo = [
31
- public_key,
32
- TLV::Utf8.pack(accessory_info.device_id),
33
- client_public_key.to_bytes.unpack('H*')[0]
31
+ public_key.unpack1('H*'),
32
+ accessory_info.device_id.unpack1('H*'),
33
+ client_public_key.to_bytes.unpack1('H*')
34
34
  ].join
35
35
 
36
36
  signing_key = accessory_info.signing_key
37
- accessorysignature = signing_key.sign([accessoryinfo].pack('H*')).unpack('H*')[0]
37
+ accessorysignature = signing_key.sign([accessoryinfo].pack('H*'))
38
38
 
39
- subtlv = TLV.pack({
39
+ subtlv = HAP::TLV.encode({
40
40
  'kTLVType_Identifier' => accessory_info.device_id,
41
41
  'kTLVType_Signature' => accessorysignature
42
42
  })
43
43
 
44
- hkdf = HAP::HKDFEncryption.new(info: 'Pair-Verify-Encrypt-Info', salt: 'Pair-Verify-Encrypt-Salt')
44
+ hkdf = HAP::Crypto::HKDF.new(info: 'Pair-Verify-Encrypt-Info', salt: 'Pair-Verify-Encrypt-Salt')
45
45
  session_key = hkdf.encrypt(shared_secret)
46
46
  cache[:session_key] = session_key
47
47
 
48
- chacha20poly1305ietf = RbNaCl::AEAD::ChaCha20Poly1305IETF.new(session_key)
48
+ chacha20poly1305ietf = HAP::Crypto::ChaCha20Poly1305.new(session_key)
49
49
  nonce = HAP::HexPad.pad('PV-Msg02')
50
- encrypted_data = chacha20poly1305ietf.encrypt(nonce, subtlv, nil).unpack('H*')[0]
50
+ encrypted_data = chacha20poly1305ietf.encrypt(nonce, subtlv)
51
51
 
52
- TLV.pack({
52
+ HAP::TLV.encode({
53
53
  'kTLVType_State' => 2,
54
54
  'kTLVType_PublicKey' => public_key,
55
55
  'kTLVType_EncryptedData' => encrypted_data
@@ -59,21 +59,21 @@ module RubyHome
59
59
  def verify_finish_response
60
60
  encrypted_data = unpack_request['kTLVType_EncryptedData']
61
61
 
62
- chacha20poly1305ietf = RbNaCl::AEAD::ChaCha20Poly1305IETF.new(cache[:session_key])
62
+ chacha20poly1305ietf = HAP::Crypto::ChaCha20Poly1305.new(cache[:session_key])
63
63
  nonce = HAP::HexPad.pad('PV-Msg03')
64
- decrypted_data = chacha20poly1305ietf.decrypt(nonce, [encrypted_data].pack('H*'), nil)
65
- unpacked_decrypted_data = TLV.unpack(decrypted_data)
64
+ decrypted_data = chacha20poly1305ietf.decrypt(nonce, encrypted_data)
65
+ unpacked_decrypted_data = HAP::TLV.read(decrypted_data)
66
66
 
67
67
  if accessory_info.paired_clients.any? {|h| h[:identifier] == unpacked_decrypted_data['kTLVType_Identifier']}
68
- hkdf = HAP::HKDFEncryption.new(info: 'Control-Write-Encryption-Key', salt: 'Control-Salt')
68
+ hkdf = HAP::Crypto::HKDF.new(info: 'Control-Write-Encryption-Key', salt: 'Control-Salt')
69
69
  cache[:controller_to_accessory_key] = hkdf.encrypt(cache[:shared_secret])
70
70
 
71
- hkdf = HAP::HKDFEncryption.new(info: 'Control-Read-Encryption-Key', salt: 'Control-Salt')
71
+ hkdf = HAP::Crypto::HKDF.new(info: 'Control-Read-Encryption-Key', salt: 'Control-Salt')
72
72
  cache[:accessory_to_controller_key] = hkdf.encrypt(cache[:shared_secret])
73
73
 
74
- TLV.pack({'kTLVType_State' => 4})
74
+ HAP::TLV.encode({'kTLVType_State' => 4})
75
75
  else
76
- TLV.pack({'kTLVType_State' => 4, 'kTLVType_Error' => 2})
76
+ HAP::TLV.encode({'kTLVType_State' => 4, 'kTLVType_Error' => 2})
77
77
  end
78
78
  end
79
79
  end
@@ -20,18 +20,18 @@ module RubyHome
20
20
  pairing_params = {
21
21
  admin: !!unpack_request['kTLVType_Permissions'],
22
22
  identifier: unpack_request['kTLVType_Identifier'],
23
- public_key: unpack_request['kTLVType_PublicKey']
23
+ public_key: unpack_request['kTLVType_PublicKey'].unpack1('H*')
24
24
  }
25
25
  accessory_info.add_paired_client pairing_params
26
26
 
27
- TLV.pack({'kTLVType_State' => 2})
27
+ HAP::TLV.encode({'kTLVType_State' => 2})
28
28
  end
29
29
 
30
30
  def remove_pairing
31
31
  accessory_info.remove_paired_client(unpack_request['kTLVType_Identifier'])
32
32
 
33
33
  response['connection'] = 'close'
34
- TLV.pack({'kTLVType_State' => 2})
34
+ HAP::TLV.encode({'kTLVType_State' => 2})
35
35
  end
36
36
  end
37
37
  end
@@ -6,20 +6,27 @@ module RubyHome
6
6
  include ObjectSerializer
7
7
 
8
8
  def record_hash(characteristic)
9
- record_hash = {}
9
+ {
10
+ 'iid' => characteristic.instance_id,
11
+ 'type' => characteristic.uuid,
12
+ 'perms' => perms(characteristic),
13
+ 'format' => characteristic.format,
14
+ 'description' => characteristic.description,
15
+ }.merge(optional_hash(characteristic))
16
+ end
10
17
 
11
- record_hash['iid'] = characteristic.instance_id
12
- record_hash['type'] = characteristic.uuid
13
- record_hash['perms'] = characteristic.properties.map do |property|
18
+ def perms(characteristic)
19
+ characteristic.properties.map do |property|
14
20
  RubyHome::Characteristic::PROPERTIES[property]
15
21
  end.compact
16
- record_hash['format'] = characteristic.format
17
- if characteristic.value != nil
18
- record_hash['value'] = characteristic.value
19
- end
20
- record_hash['description'] = characteristic.description
22
+ end
21
23
 
22
- record_hash
24
+ def optional_hash(characteristic)
25
+ Hash.new.tap do |optional_hash|
26
+ if characteristic.value != nil
27
+ optional_hash['value'] = characteristic.value
28
+ end
29
+ end
23
30
  end
24
31
  end
25
32
  end
@@ -1,3 +1,3 @@
1
1
  module RubyHome
2
- VERSION = '0.1.0'
2
+ VERSION = '0.1.2'
3
3
  end
data/rubyhome.gemspec CHANGED
@@ -24,14 +24,15 @@ Gem::Specification.new do |spec|
24
24
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
25
25
  spec.require_paths = ['lib']
26
26
 
27
+ spec.add_dependency 'bindata', '~> 2.4', '>= 2.4.3'
27
28
  spec.add_dependency 'dnssd', '~> 3.0'
28
29
  spec.add_dependency 'ed25519', '~> 1.2', '>= 1.2.3'
29
30
  spec.add_dependency 'hkdf', '~> 0.3.0'
30
31
  spec.add_dependency 'oj', '~> 3.4'
31
32
  spec.add_dependency 'rbnacl', '~> 5.0'
32
33
  spec.add_dependency 'rbnacl-libsodium', '~> 1.0', '>= 1.0.16'
33
- spec.add_dependency 'ruby_home-srp', '~> 1.1.1'
34
- spec.add_dependency 'sinatra', '~> 2.0'
34
+ spec.add_dependency 'ruby_home-srp', '~> 1.2'
35
+ spec.add_dependency 'sinatra', '2.0.1'
35
36
  spec.add_dependency 'wisper', '~> 1.6', '>= 1.6.1'
36
37
  spec.add_dependency 'x25519', '~> 1.0', '>= 1.0.5'
37
38
  spec.add_development_dependency 'bundler', '~> 1.16'
metadata CHANGED
@@ -1,15 +1,35 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby_home
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Karl Entwistle
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-03-31 00:00:00.000000000 Z
11
+ date: 2018-06-26 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bindata
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.4'
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 2.4.3
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: '2.4'
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 2.4.3
13
33
  - !ruby/object:Gem::Dependency
14
34
  name: dnssd
15
35
  requirement: !ruby/object:Gem::Requirement
@@ -112,28 +132,28 @@ dependencies:
112
132
  requirements:
113
133
  - - "~>"
114
134
  - !ruby/object:Gem::Version
115
- version: 1.1.1
135
+ version: '1.2'
116
136
  type: :runtime
117
137
  prerelease: false
118
138
  version_requirements: !ruby/object:Gem::Requirement
119
139
  requirements:
120
140
  - - "~>"
121
141
  - !ruby/object:Gem::Version
122
- version: 1.1.1
142
+ version: '1.2'
123
143
  - !ruby/object:Gem::Dependency
124
144
  name: sinatra
125
145
  requirement: !ruby/object:Gem::Requirement
126
146
  requirements:
127
- - - "~>"
147
+ - - '='
128
148
  - !ruby/object:Gem::Version
129
- version: '2.0'
149
+ version: 2.0.1
130
150
  type: :runtime
131
151
  prerelease: false
132
152
  version_requirements: !ruby/object:Gem::Requirement
133
153
  requirements:
134
- - - "~>"
154
+ - - '='
135
155
  - !ruby/object:Gem::Version
136
- version: '2.0'
156
+ version: 2.0.1
137
157
  - !ruby/object:Gem::Dependency
138
158
  name: wisper
139
159
  requirement: !ruby/object:Gem::Requirement
@@ -295,11 +315,13 @@ files:
295
315
  - lib/ruby_home/factories/templates/service_template.rb
296
316
  - lib/ruby_home/hap/accessory.rb
297
317
  - lib/ruby_home/hap/characteristic.rb
318
+ - lib/ruby_home/hap/crypto/chacha20poly1305.rb
319
+ - lib/ruby_home/hap/crypto/hkdf.rb
298
320
  - lib/ruby_home/hap/hex_pad.rb
299
- - lib/ruby_home/hap/hkdf_encryption.rb
300
321
  - lib/ruby_home/hap/http_decryption.rb
301
322
  - lib/ruby_home/hap/http_encryption.rb
302
323
  - lib/ruby_home/hap/service.rb
324
+ - lib/ruby_home/hap/tlv.rb
303
325
  - lib/ruby_home/http/application.rb
304
326
  - lib/ruby_home/http/cache.rb
305
327
  - lib/ruby_home/http/controllers/accessories_controller.rb
@@ -318,10 +340,6 @@ files:
318
340
  - lib/ruby_home/http/serializers/service_serializer.rb
319
341
  - lib/ruby_home/identifier_cache.rb
320
342
  - lib/ruby_home/rack/handler/hap_server.rb
321
- - lib/ruby_home/tlv.rb
322
- - lib/ruby_home/tlv/bytes.rb
323
- - lib/ruby_home/tlv/int.rb
324
- - lib/ruby_home/tlv/utf8.rb
325
343
  - lib/ruby_home/version.rb
326
344
  - rubyhome.gemspec
327
345
  - sbin/characteristic_generator.rb
@@ -1,34 +0,0 @@
1
- require 'rbnacl/libsodium'
2
-
3
- module RubyHome
4
- module HAP
5
- class HKDFEncryption
6
- def initialize(salt:, info: )
7
- @salt = salt
8
- @info = info
9
- end
10
-
11
- def encrypt(source)
12
- HKDF.new(source, hkdf_opts).next_bytes(BYTE_LENGTH)
13
- end
14
-
15
- private
16
-
17
- BYTE_LENGTH = 32
18
-
19
- attr_reader :info, :salt, :source
20
-
21
- def hkdf_opts
22
- {
23
- algorithm: algorithm,
24
- info: info,
25
- salt: salt
26
- }
27
- end
28
-
29
- def algorithm
30
- 'SHA512'
31
- end
32
- end
33
- end
34
- end
data/lib/ruby_home/tlv.rb DELETED
@@ -1,83 +0,0 @@
1
- Dir[File.dirname(__FILE__) + '/tlv/*.rb'].each {|file| require file }
2
-
3
- module RubyHome
4
- module TLV
5
- extend self
6
-
7
- TLV = Struct.new(:type, :name, :handler)
8
- TLVs = [
9
- TLV.new('00', 'kTLVType_Method', Int),
10
- TLV.new('01', 'kTLVType_Identifier', Utf8),
11
- TLV.new('02', 'kTLVType_Salt', Bytes),
12
- TLV.new('03', 'kTLVType_PublicKey', Bytes),
13
- TLV.new('04', 'kTLVType_Proof', Bytes),
14
- TLV.new('05', 'kTLVType_EncryptedData', Bytes),
15
- TLV.new('06', 'kTLVType_State', Int),
16
- TLV.new('07', 'kTLVType_Error', Int),
17
- TLV.new('08', 'kTLVType_RetryDelay', Int),
18
- TLV.new('09', 'kTLVType_Certificate', Bytes),
19
- TLV.new('0a', 'kTLVType_Signature', Bytes),
20
- TLV.new('0b', 'kTLVType_Permissions', Int),
21
- TLV.new('0c', 'kTLVType_FragmentData', Bytes),
22
- TLV.new('0d', 'kTLVType_FragmentLast', Bytes),
23
- ].freeze
24
-
25
- def pack(hash)
26
- data = ''
27
-
28
- pack_objects(hash).each do |type, value|
29
- value.chars.each_slice(510).map(&:join).each do |value_slice|
30
- length = Int.pack([value_slice].pack('H*').length)
31
-
32
- data << type
33
- data << length
34
- data << value_slice
35
- end
36
- end
37
-
38
- [data].pack('H*')
39
- end
40
-
41
- def pack_objects(objects)
42
- objects.each_with_object({}) do |(unpacked_key, unpacked_value), memo|
43
- tlv_value = TLVs.find { |tlv| tlv.name == unpacked_key }
44
- packed_key = tlv_value.type
45
- packed_value = tlv_value.handler.pack(unpacked_value)
46
- memo[packed_key] = packed_value
47
- end
48
- end
49
-
50
- def unpack(input)
51
- data = input.unpack('H*')[0]
52
- objects = {}
53
- scanner_index = 0
54
-
55
- while scanner_index < data.length do
56
- type = data[scanner_index, 2]
57
- scanner_index += 2
58
-
59
- byte_length = Int.unpack(data[scanner_index, 2]) * 2
60
- scanner_index += 2
61
-
62
- newData = data[scanner_index, byte_length]
63
- if objects[type]
64
- objects[type] << newData
65
- else
66
- objects[type] = newData
67
- end
68
- scanner_index += byte_length
69
- end
70
-
71
- unpack_objects(objects)
72
- end
73
-
74
- def unpack_objects(objects)
75
- objects.each_with_object({}) do |(packed_key, packed_value), memo|
76
- tlv_value = TLVs.find { |tlv| tlv.type == packed_key }
77
- unpacked_key = tlv_value.name
78
- unpacked_value = tlv_value.handler.unpack(packed_value)
79
- memo[unpacked_key] = unpacked_value
80
- end
81
- end
82
- end
83
- end
@@ -1,19 +0,0 @@
1
- module RubyHome
2
- module TLV
3
- module Bytes
4
- extend self
5
-
6
- def pack(input)
7
- if input.length.odd?
8
- input.insert(0, '0')
9
- else
10
- input
11
- end
12
- end
13
-
14
- def unpack(input)
15
- input
16
- end
17
- end
18
- end
19
- end
@@ -1,15 +0,0 @@
1
- module RubyHome
2
- module TLV
3
- module Int
4
- extend self
5
-
6
- def pack(input)
7
- [input].pack('C*').unpack1('H*')
8
- end
9
-
10
- def unpack(input)
11
- [input].pack('H*').unpack1('C*')
12
- end
13
- end
14
- end
15
- end
@@ -1,18 +0,0 @@
1
- module RubyHome
2
- module TLV
3
- module Utf8
4
- extend self
5
-
6
- def pack(input)
7
- input.bytes.map { |b| b.to_s(16) }.join
8
- end
9
-
10
- def unpack(input)
11
- [input]
12
- .pack('H*')
13
- .force_encoding('utf-8')
14
- .encode('utf-8')
15
- end
16
- end
17
- end
18
- end