ruby_home 0.2.0 → 0.2.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/tests.yml +41 -0
  3. data/CHANGELOG.md +13 -0
  4. data/README.md +47 -84
  5. data/examples/air_purifier.rb +67 -0
  6. data/examples/air_quality_sensor.rb +120 -0
  7. data/examples/battery_service.rb +59 -0
  8. data/examples/carbon_dioxide_sensor.rb +79 -0
  9. data/examples/carbon_monoxide_sensor.rb +79 -0
  10. data/examples/contact_sensor.rb +67 -0
  11. data/examples/door.rb +49 -0
  12. data/examples/fan.rb +31 -0
  13. data/examples/fan_v2.rb +69 -0
  14. data/examples/garage_door_opener.rb +72 -0
  15. data/examples/heater_cooler.rb +93 -0
  16. data/examples/humidifier_dehumidifier.rb +89 -0
  17. data/examples/humidity_sensor.rb +63 -0
  18. data/examples/leak_sensor.rb +67 -0
  19. data/examples/light_sensor.rb +65 -0
  20. data/examples/lightbulb.rb +32 -0
  21. data/examples/lock_mechanism.rb +35 -0
  22. data/examples/motion_sensor.rb +67 -0
  23. data/examples/occupancy_sensor.rb +67 -0
  24. data/examples/outlet.rb +30 -0
  25. data/examples/security_system.rb +54 -0
  26. data/examples/smoke_sensor.rb +67 -0
  27. data/examples/switch.rb +17 -0
  28. data/examples/television.rb +78 -0
  29. data/examples/temperature_sensor.rb +63 -0
  30. data/examples/thermostat.rb +114 -0
  31. data/examples/window.rb +49 -0
  32. data/examples/window_covering.rb +73 -0
  33. data/lib/ruby_home.rb +0 -1
  34. data/lib/ruby_home/characteristic.rb +7 -1
  35. data/lib/ruby_home/config/categories.yml +39 -0
  36. data/lib/ruby_home/config/characteristics.yml +479 -3
  37. data/lib/ruby_home/config/services.yml +79 -4
  38. data/lib/ruby_home/configuration.rb +19 -0
  39. data/lib/ruby_home/dns/service.rb +2 -0
  40. data/lib/ruby_home/dns/text_record.rb +1 -1
  41. data/lib/ruby_home/errors.rb +4 -0
  42. data/lib/ruby_home/factories/characteristic_factory.rb +1 -0
  43. data/lib/ruby_home/factories/templates/characteristic_template.rb +1 -1
  44. data/lib/ruby_home/factories/templates/service_template.rb +1 -1
  45. data/lib/ruby_home/hap/server_handler.rb +4 -13
  46. data/lib/ruby_home/hap/values/uint8_value.rb +11 -1
  47. data/lib/ruby_home/http/controllers/application_controller.rb +10 -3
  48. data/lib/ruby_home/http/controllers/pair_setups_controller.rb +3 -3
  49. data/lib/ruby_home/http/controllers/pair_verifies_controller.rb +10 -12
  50. data/lib/ruby_home/http/controllers/pairings_controller.rb +1 -1
  51. data/lib/ruby_home/http/serializers/characteristic_serializer.rb +13 -5
  52. data/lib/ruby_home/http/serializers/service_serializer.rb +11 -1
  53. data/lib/ruby_home/http/services/verify_finish_service.rb +56 -0
  54. data/lib/ruby_home/http/services/verify_srp_service.rb +32 -41
  55. data/lib/ruby_home/persistable.rb +4 -12
  56. data/lib/ruby_home/service.rb +3 -0
  57. data/lib/ruby_home/version.rb +1 -1
  58. data/rubyhome.gemspec +7 -7
  59. data/sbin/service_generator.rb +12 -2
  60. metadata +65 -39
  61. data/.travis.yml +0 -31
@@ -152,6 +152,14 @@
152
152
  - 00000023-0000-1000-8000-0026BB765291
153
153
  :required_characteristics_uuids:
154
154
  - 000000AC-0000-1000-8000-0026BB765291
155
+ - :name: :faucet
156
+ :description: Faucet
157
+ :uuid: 000000D7-0000-1000-8000-0026BB765291
158
+ :optional_characteristics_uuids:
159
+ - 00000023-0000-1000-8000-0026BB765291
160
+ - 00000077-0000-1000-8000-0026BB765291
161
+ :required_characteristics_uuids:
162
+ - 000000B0-0000-1000-8000-0026BB765291
155
163
  - :name: :garage_door_opener
156
164
  :description: Garage Door Opener
157
165
  :uuid: 00000041-0000-1000-8000-0026BB765291
@@ -206,6 +214,30 @@
206
214
  - 00000023-0000-1000-8000-0026BB765291
207
215
  :required_characteristics_uuids:
208
216
  - 00000010-0000-1000-8000-0026BB765291
217
+ - :name: :input_source
218
+ :description: Input Source
219
+ :uuid: 000000D9-0000-1000-8000-0026BB765291
220
+ :optional_characteristics_uuids:
221
+ - 000000E6-0000-1000-8000-0026BB765291
222
+ - 000000DC-0000-1000-8000-0026BB765291
223
+ - 00000134-0000-1000-8000-0026BB765291
224
+ :required_characteristics_uuids:
225
+ - 000000E3-0000-1000-8000-0026BB765291
226
+ - 000000DB-0000-1000-8000-0026BB765291
227
+ - 000000D6-0000-1000-8000-0026BB765291
228
+ - 00000023-0000-1000-8000-0026BB765291
229
+ - 00000135-0000-1000-8000-0026BB765291
230
+ - :name: :irrigation_system
231
+ :description: Irrigation System
232
+ :uuid: 000000CF-0000-1000-8000-0026BB765291
233
+ :optional_characteristics_uuids:
234
+ - 00000023-0000-1000-8000-0026BB765291
235
+ - 000000D4-0000-1000-8000-0026BB765291
236
+ - 00000077-0000-1000-8000-0026BB765291
237
+ :required_characteristics_uuids:
238
+ - 000000B0-0000-1000-8000-0026BB765291
239
+ - 000000D1-0000-1000-8000-0026BB765291
240
+ - 000000D2-0000-1000-8000-0026BB765291
209
241
  - :name: :leak_sensor
210
242
  :description: Leak Sensor
211
243
  :uuid: '00000083-0000-1000-8000-0026BB765291'
@@ -221,11 +253,11 @@
221
253
  :description: Light Sensor
222
254
  :uuid: '00000084-0000-1000-8000-0026BB765291'
223
255
  :optional_characteristics_uuids:
256
+ - 00000023-0000-1000-8000-0026BB765291
224
257
  - 00000075-0000-1000-8000-0026BB765291
225
258
  - 00000077-0000-1000-8000-0026BB765291
226
259
  - 0000007A-0000-1000-8000-0026BB765291
227
260
  - '00000079-0000-1000-8000-0026BB765291'
228
- - 00000023-0000-1000-8000-0026BB765291
229
261
  :required_characteristics_uuids:
230
262
  - 0000006B-0000-1000-8000-0026BB765291
231
263
  - :name: :lightbulb
@@ -236,6 +268,7 @@
236
268
  - 00000013-0000-1000-8000-0026BB765291
237
269
  - 0000002F-0000-1000-8000-0026BB765291
238
270
  - 00000023-0000-1000-8000-0026BB765291
271
+ - 000000CE-0000-1000-8000-0026BB765291
239
272
  :required_characteristics_uuids:
240
273
  - 00000025-0000-1000-8000-0026BB765291
241
274
  - :name: :lock_management
@@ -265,9 +298,9 @@
265
298
  :description: Microphone
266
299
  :uuid: 00000112-0000-1000-8000-0026BB765291
267
300
  :optional_characteristics_uuids:
268
- - '00000119-0000-1000-8000-0026BB765291'
269
301
  - 00000023-0000-1000-8000-0026BB765291
270
302
  :required_characteristics_uuids:
303
+ - '00000119-0000-1000-8000-0026BB765291'
271
304
  - 0000011A-0000-1000-8000-0026BB765291
272
305
  - :name: :motion_sensor
273
306
  :description: Motion Sensor
@@ -284,11 +317,11 @@
284
317
  :description: Occupancy Sensor
285
318
  :uuid: '00000086-0000-1000-8000-0026BB765291'
286
319
  :optional_characteristics_uuids:
320
+ - 00000023-0000-1000-8000-0026BB765291
287
321
  - 00000075-0000-1000-8000-0026BB765291
288
322
  - 00000077-0000-1000-8000-0026BB765291
289
323
  - 0000007A-0000-1000-8000-0026BB765291
290
324
  - '00000079-0000-1000-8000-0026BB765291'
291
- - 00000023-0000-1000-8000-0026BB765291
292
325
  :required_characteristics_uuids:
293
326
  - 00000071-0000-1000-8000-0026BB765291
294
327
  - :name: :outlet
@@ -343,8 +376,8 @@
343
376
  :description: Speaker
344
377
  :uuid: 00000113-0000-1000-8000-0026BB765291
345
378
  :optional_characteristics_uuids:
346
- - '00000119-0000-1000-8000-0026BB765291'
347
379
  - 00000023-0000-1000-8000-0026BB765291
380
+ - '00000119-0000-1000-8000-0026BB765291'
348
381
  :required_characteristics_uuids:
349
382
  - 0000011A-0000-1000-8000-0026BB765291
350
383
  - :name: :stateless_programmable_switch
@@ -362,6 +395,34 @@
362
395
  - 00000023-0000-1000-8000-0026BB765291
363
396
  :required_characteristics_uuids:
364
397
  - 00000025-0000-1000-8000-0026BB765291
398
+ - :name: :television
399
+ :description: Television
400
+ :uuid: '000000D8-0000-1000-8000-0026BB765291'
401
+ :optional_characteristics_uuids:
402
+ - 00000008-0000-1000-8000-0026BB765291
403
+ - 000000DD-0000-1000-8000-0026BB765291
404
+ - 00000136-0000-1000-8000-0026BB765291
405
+ - 000000E0-0000-1000-8000-0026BB765291
406
+ - 00000137-0000-1000-8000-0026BB765291
407
+ - 000000E2-0000-1000-8000-0026BB765291
408
+ - 000000DF-0000-1000-8000-0026BB765291
409
+ - 00000023-0000-1000-8000-0026BB765291
410
+ :required_characteristics_uuids:
411
+ - 000000B0-0000-1000-8000-0026BB765291
412
+ - 000000E7-0000-1000-8000-0026BB765291
413
+ - 000000E3-0000-1000-8000-0026BB765291
414
+ - 000000E1-0000-1000-8000-0026BB765291
415
+ - 000000E8-0000-1000-8000-0026BB765291
416
+ - :name: :television_speaker
417
+ :description: Television Speaker
418
+ :uuid: 00000113-0000-1000-8000-0026BB765291
419
+ :optional_characteristics_uuids:
420
+ - 000000B0-0000-1000-8000-0026BB765291
421
+ - 00000119-0000-1000-8000-0026BB765291
422
+ - 000000E9-0000-1000-8000-0026BB765291
423
+ - 000000EA-0000-1000-8000-0026BB765291
424
+ :required_characteristics_uuids:
425
+ - 0000011A-0000-1000-8000-0026BB765291
365
426
  - :name: :temperature_sensor
366
427
  :description: Temperature Sensor
367
428
  :uuid: '0000008A-0000-1000-8000-0026BB765291'
@@ -388,6 +449,20 @@
388
449
  - 00000011-0000-1000-8000-0026BB765291
389
450
  - 00000035-0000-1000-8000-0026BB765291
390
451
  - 00000036-0000-1000-8000-0026BB765291
452
+ - :name: :valve
453
+ :description: Valve
454
+ :uuid: 000000D0-0000-1000-8000-0026BB765291
455
+ :optional_characteristics_uuids:
456
+ - 000000D3-0000-1000-8000-0026BB765291
457
+ - 000000D4-0000-1000-8000-0026BB765291
458
+ - 000000D6-0000-1000-8000-0026BB765291
459
+ - 000000CB-0000-1000-8000-0026BB765291
460
+ - 00000077-0000-1000-8000-0026BB765291
461
+ - 00000023-0000-1000-8000-0026BB765291
462
+ :required_characteristics_uuids:
463
+ - 000000B0-0000-1000-8000-0026BB765291
464
+ - 000000D2-0000-1000-8000-0026BB765291
465
+ - 000000D5-0000-1000-8000-0026BB765291
391
466
  - :name: :window
392
467
  :description: Window
393
468
  :uuid: '0000008B-0000-1000-8000-0026BB765291'
@@ -1,7 +1,12 @@
1
1
  module RubyHome
2
+ class UnknownCategoriyIdentifierError < StandardError; end
3
+
2
4
  class Configuration
3
5
  extend Forwardable
4
6
 
7
+ CATEGORIES_FILEPATH = (File.dirname(__FILE__) + '/config/categories.yml').freeze
8
+ CATEGORIES = YAML.load_file(CATEGORIES_FILEPATH).freeze
9
+
5
10
  def initialize(accessory_info = AccessoryInfo.instance)
6
11
  @accessory_info = accessory_info
7
12
  end
@@ -11,6 +16,7 @@ module RubyHome
11
16
  DEFAULT_PORT = 4567
12
17
  DEFAULT_MODEL_NAME = DEFAULT_NAME
13
18
  DEFAULT_DISCOVERY_NAME = DEFAULT_NAME
19
+ DEFAULT_CATEGORY_IDENTIFIER = 2
14
20
 
15
21
  def discovery_name
16
22
  @discovery_name || DEFAULT_DISCOVERY_NAME
@@ -28,6 +34,19 @@ module RubyHome
28
34
  @host || DEFAULT_HOST
29
35
  end
30
36
 
37
+ def category_identifier
38
+ @category_identifier || DEFAULT_CATEGORY_IDENTIFIER
39
+ end
40
+
41
+ def category_identifier=(value)
42
+ if value.is_a?(Symbol)
43
+ raise UnknownCategoriyIdentifierError if !CATEGORIES.include?(value)
44
+ @category_identifier = CATEGORIES[value]
45
+ else
46
+ @category_identifier = value.to_i
47
+ end
48
+ end
49
+
31
50
  attr_writer :discovery_name, :model_name, :host, :port
32
51
 
33
52
  def_delegators :@accessory_info, :password, :password=
@@ -6,6 +6,8 @@ module RubyHome
6
6
  end
7
7
 
8
8
  def update
9
+ return if RbConfig::CONFIG['target_os'] =~ /linux/
10
+
9
11
  dnssd_service.add_record(DNSSD::Record::TXT, text_record.encode)
10
12
  end
11
13
 
@@ -38,7 +38,7 @@ module RubyHome
38
38
  # 1-65535.
39
39
 
40
40
  def accessory_category_identifier
41
- 2
41
+ @configuration.category_identifier
42
42
  end
43
43
 
44
44
  # Feature flags (e.g. "0x3" for bits 0 and 1). Required if non-zero.
@@ -0,0 +1,4 @@
1
+ module RubyHome
2
+ class RubyHomeError < StandardError; end
3
+ class UnknownValueError < RubyHomeError; end
4
+ end
@@ -15,6 +15,7 @@ module RubyHome
15
15
  format: template.format,
16
16
  name: characteristic_name,
17
17
  properties: template.properties,
18
+ constraints: template.constraints,
18
19
  service: service,
19
20
  unit: template.unit,
20
21
  uuid: template.uuid,
@@ -4,7 +4,7 @@ module RubyHome
4
4
  DATA = YAML.load_file(FILEPATH).freeze
5
5
 
6
6
  def self.all
7
- @@all ||= DATA.map { |data| new(data) }
7
+ @@all ||= DATA.map { |data| new(**data) }
8
8
  end
9
9
 
10
10
  def self.find_by(options)
@@ -4,7 +4,7 @@ module RubyHome
4
4
  DATA = YAML.load_file(FILEPATH).freeze
5
5
 
6
6
  def self.all
7
- @all ||= DATA.map { |data| new(data) }
7
+ @all ||= DATA.map { |data| new(**data) }
8
8
  end
9
9
 
10
10
  def self.find_by(options)
@@ -35,29 +35,20 @@ module RubyHome
35
35
  Host: bind_address,
36
36
  ServerSoftware: 'RubyHome',
37
37
  Logger: server_logger,
38
- AccessLog: access_logger
38
+ AccessLog: []
39
39
  }
40
40
  end
41
41
 
42
42
  def server_logger
43
- if ENV['DEBUG']
43
+ if ENV['DEBUG'] == 'debug'
44
44
  WEBrick::Log::new(STDOUT, WEBrick::BasicLog::DEBUG)
45
+ elsif ENV['DEBUG'] == 'info'
46
+ WEBrick::Log::new(STDOUT, WEBrick::BasicLog::INFO)
45
47
  else
46
48
  WEBrick::Log::new("/dev/null", WEBrick::BasicLog::WARN)
47
49
  end
48
50
  end
49
51
 
50
- def access_logger
51
- if ENV['DEBUG']
52
- [
53
- [ STDOUT, WEBrick::AccessLog::COMMON_LOG_FORMAT ],
54
- [ STDOUT, WEBrick::AccessLog::REFERER_LOG_FORMAT ]
55
- ]
56
- else
57
- []
58
- end
59
- end
60
-
61
52
  def bind_address
62
53
  configuration.host
63
54
  end
@@ -13,7 +13,17 @@ module RubyHome
13
13
  end
14
14
 
15
15
  def valid_values
16
- template.constraints.fetch('ValidValues')
16
+ defined_values || range_values || raise(UnknownValueError, "Constraint contains an unrecognized list of values: #{template.constraints.inspect }")
17
+ end
18
+
19
+ def defined_values
20
+ template.constraints.dig('ValidValues')
21
+ end
22
+
23
+ def range_values
24
+ if min = template.constraints.dig('MinimumValue')
25
+ { min.to_s => min }
26
+ end
17
27
  end
18
28
  end
19
29
  end
@@ -5,10 +5,17 @@ module RubyHome
5
5
  class ApplicationController < Sinatra::Base
6
6
  disable :protection
7
7
 
8
- if ENV['DEBUG']
9
- enable :logging
10
- set :logger, Logger.new(STDOUT)
8
+ logger = Logger.new(STDOUT)
9
+ logger.level = case ENV['DEBUG']
10
+ when 'debug'
11
+ Logger::DEBUG
12
+ when 'info'
13
+ Logger::INFO
14
+ else
15
+ Logger::WARN
11
16
  end
17
+ set :logger, logger
18
+ enable :logging if ENV['DEBUG']
12
19
 
13
20
  before do
14
21
  logger.debug "Session"
@@ -38,9 +38,9 @@ module RubyHome
38
38
  device_proof: unpack_request[:proof],
39
39
  srp_session: session.srp_session,
40
40
  public_key: unpack_request[:public_key],
41
- )
41
+ ).run
42
42
 
43
- if verify_srp.valid?
43
+ if verify_srp.success?
44
44
  session.session_key = verify_srp.session_key
45
45
  session.srp_session = nil
46
46
 
@@ -96,7 +96,7 @@ module RubyHome
96
96
  encrypted_data = chacha20poly1305ietf.encrypt(nonce, subtlv)
97
97
 
98
98
  pairing_params = { admin: true, identifier: iosdevicepairingid, public_key: iosdeviceltpk.unpack1('H*') }
99
- accessory_info.add_paired_client pairing_params
99
+ accessory_info.add_paired_client(**pairing_params)
100
100
 
101
101
  tlv state: 6, encrypted_data: encrypted_data
102
102
  end
@@ -48,18 +48,16 @@ module RubyHome
48
48
  end
49
49
 
50
50
  def verify_finish_response
51
- encrypted_data = unpack_request[:encrypted_data]
52
-
53
- chacha20poly1305ietf = HAP::Crypto::ChaCha20Poly1305.new(session.session_key)
54
- nonce = HexHelper.pad('PV-Msg03')
55
- decrypted_data = chacha20poly1305ietf.decrypt(nonce, encrypted_data)
56
- unpacked_decrypted_data = TLV.decode(decrypted_data)
57
-
58
- if accessory_info.paired_clients.any? {|h| h[:identifier] == unpacked_decrypted_data[:identifier]}
59
- shared_secret = HAP::Crypto::SessionKey.new(session.shared_secret)
60
-
61
- session.controller_to_accessory_key = shared_secret.controller_to_accessory_key
62
- session.accessory_to_controller_key = shared_secret.accessory_to_controller_key
51
+ verify_finish = VerifyFinishService.new(
52
+ encrypted_data: unpack_request[:encrypted_data],
53
+ session_key: session.session_key,
54
+ shared_secret: session.shared_secret,
55
+ accessory_info: accessory_info
56
+ ).run
57
+
58
+ if verify_finish.success?
59
+ session.controller_to_accessory_key = verify_finish.controller_to_accessory_key
60
+ session.accessory_to_controller_key = verify_finish.accessory_to_controller_key
63
61
 
64
62
  session.session_key = nil
65
63
  session.shared_secret = nil
@@ -22,7 +22,7 @@ module RubyHome
22
22
  identifier: unpack_request[:identifier],
23
23
  public_key: unpack_request[:public_key].unpack1('H*')
24
24
  }
25
- accessory_info.add_paired_client pairing_params
25
+ accessory_info.add_paired_client(**pairing_params)
26
26
 
27
27
  tlv state: 2
28
28
  end
@@ -14,13 +14,13 @@ module RubyHome
14
14
  'perms' => perms(characteristic),
15
15
  'format' => characteristic.format,
16
16
  'description' => characteristic.description,
17
- }.merge(value_hash(characteristic))
17
+ }
18
+ .merge(value_hash(characteristic))
19
+ .merge(valid_values_hash(characteristic))
18
20
  end
19
21
 
20
22
  private
21
23
 
22
- HIDDEN_VALUE_OBJECTS = [ IdentifyValue ].freeze
23
-
24
24
  def perms(characteristic)
25
25
  characteristic.properties.map do |property|
26
26
  RubyHome::Characteristic::PROPERTIES[property]
@@ -28,10 +28,18 @@ module RubyHome
28
28
  end
29
29
 
30
30
  def value_hash(characteristic)
31
- if HIDDEN_VALUE_OBJECTS.include?(characteristic.value_object.class)
31
+ if characteristic.properties.include?('read')
32
+ { 'value' => characteristic.value }
33
+ else
34
+ {}
35
+ end
36
+ end
37
+
38
+ def valid_values_hash(characteristic)
39
+ if characteristic.valid_values.empty?
32
40
  {}
33
41
  else
34
- { 'value' => characteristic.value }.compact
42
+ { 'valid-values' => characteristic.valid_values }
35
43
  end
36
44
  end
37
45
  end
@@ -15,7 +15,17 @@ module RubyHome
15
15
  'characteristics' => CharacteristicSerializer.new(service.characteristics).serializable_hash,
16
16
  'primary' => service.primary,
17
17
  'hidden' => service.hidden,
18
- }
18
+ }.merge(linked_services(service))
19
+ end
20
+
21
+ private
22
+
23
+ def linked_services(service)
24
+ if service.linked.empty?
25
+ {}
26
+ else
27
+ { 'linked' => service.linked.map(&:instance_id) }
28
+ end
19
29
  end
20
30
  end
21
31
  end
@@ -0,0 +1,56 @@
1
+ module RubyHome
2
+ class VerifyFinishService
3
+ def initialize(accessory_info: , encrypted_data: , session_key:, shared_secret:)
4
+ @accessory_info = accessory_info
5
+ @encrypted_data = encrypted_data
6
+ @session_key = session_key
7
+ @shared_secret = HAP::Crypto::SessionKey.new(shared_secret)
8
+ end
9
+
10
+ def run
11
+ if paired_client_exists?
12
+ RubyHome.dns_service.update
13
+
14
+ OpenStruct.new(
15
+ success?: true,
16
+ controller_to_accessory_key: shared_secret.controller_to_accessory_key,
17
+ accessory_to_controller_key: shared_secret.accessory_to_controller_key
18
+ )
19
+ else
20
+ OpenStruct.new(success?: false)
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ NONCE = -'PV-Msg03'
27
+
28
+ attr_reader :accessory_info, :encrypted_data, :session_key, :shared_secret
29
+
30
+ def paired_client_exists?
31
+ accessory_info.paired_clients.any? do |paired_client|
32
+ paired_client[:identifier] == identifier
33
+ end
34
+ end
35
+
36
+ def chacha20poly1305
37
+ HAP::Crypto::ChaCha20Poly1305.new(session_key)
38
+ end
39
+
40
+ def nonce
41
+ HexHelper.pad(NONCE)
42
+ end
43
+
44
+ def decrypted_data
45
+ chacha20poly1305.decrypt(nonce, encrypted_data)
46
+ end
47
+
48
+ def unpacked_decrypted_data
49
+ TLV.decode(decrypted_data)
50
+ end
51
+
52
+ def identifier
53
+ unpacked_decrypted_data[:identifier]
54
+ end
55
+ end
56
+ end