pubnub 5.2.2 → 5.3.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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/.github/CODEOWNERS +2 -2
  3. data/.pubnub.yml +11 -4
  4. data/.tool-versions +1 -1
  5. data/CHANGELOG.md +9 -0
  6. data/Gemfile +1 -1
  7. data/Gemfile.lock +6 -6
  8. data/LICENSE +29 -0
  9. data/VERSION +1 -1
  10. data/features/step_definitions/access_steps.rb +0 -2
  11. data/features/step_definitions/crypto_steps.rb +99 -0
  12. data/features/support/cryptor.rb +58 -0
  13. data/features/support/hooks.rb +0 -1
  14. data/lib/pubnub/client.rb +30 -1
  15. data/lib/pubnub/error.rb +3 -0
  16. data/lib/pubnub/event.rb +13 -5
  17. data/lib/pubnub/events/add_message_action.rb +2 -2
  18. data/lib/pubnub/events/grant_token.rb +1 -1
  19. data/lib/pubnub/events/history.rb +18 -6
  20. data/lib/pubnub/events/publish.rb +7 -3
  21. data/lib/pubnub/events/remove_channel_members.rb +3 -3
  22. data/lib/pubnub/events/remove_channel_metadata.rb +1 -1
  23. data/lib/pubnub/events/remove_memberships.rb +3 -3
  24. data/lib/pubnub/events/remove_uuid_metadata.rb +1 -1
  25. data/lib/pubnub/events/set_channel_members.rb +3 -3
  26. data/lib/pubnub/events/set_channel_metadata.rb +2 -2
  27. data/lib/pubnub/events/set_memberships.rb +3 -3
  28. data/lib/pubnub/events/set_uuid_metadata.rb +2 -2
  29. data/lib/pubnub/events/signal.rb +1 -1
  30. data/lib/pubnub/events/subscribe.rb +5 -0
  31. data/lib/pubnub/formatter.rb +22 -11
  32. data/lib/pubnub/modules/crypto/crypto_module.rb +159 -0
  33. data/lib/pubnub/modules/crypto/crypto_provider.rb +31 -0
  34. data/lib/pubnub/modules/crypto/cryptor.rb +73 -0
  35. data/lib/pubnub/modules/crypto/cryptor_header.rb +251 -0
  36. data/lib/pubnub/modules/crypto/cryptors/aes_cbc_cryptor.rb +67 -0
  37. data/lib/pubnub/modules/crypto/cryptors/legacy_cryptor.rb +84 -0
  38. data/lib/pubnub/modules/crypto/module.rb +8 -0
  39. data/lib/pubnub/subscribe_event/formatter.rb +8 -8
  40. data/lib/pubnub/version.rb +1 -1
  41. data/pubnub.gemspec +2 -2
  42. metadata +16 -5
  43. data/LICENSE.txt +0 -30
  44. data/lib/pubnub/crypto.rb +0 -70
@@ -45,7 +45,7 @@ module Pubnub
45
45
  membership_object
46
46
  end
47
47
 
48
- body = Formatter.format_message({ set: memberships }, "", @random_iv, false)
48
+ body = Formatter.format_message({ set: memberships }, nil, false)
49
49
  response = send_request(body)
50
50
 
51
51
  envelopes = fire_callbacks(handle(response, uri))
@@ -86,11 +86,11 @@ module Pubnub
86
86
  def valid_envelope(parsed_response, req_res_objects)
87
87
  memberships = parsed_response['data'].map { |uuid_membership|
88
88
  membership = Hash.new
89
- uuid_membership.each{ |k,v| membership[k.to_sym] = v }
89
+ uuid_membership.each { |k, v| membership[k.to_sym] = v }
90
90
 
91
91
  unless membership[:channel].nil?
92
92
  channel_metadata = Hash.new
93
- membership[:channel].each{ |k,v| channel_metadata[k.to_sym] = v }
93
+ membership[:channel].each { |k, v| channel_metadata[k.to_sym] = v }
94
94
  channel_metadata[:updated] = Date._parse(channel_metadata[:updated]) unless channel_metadata[:updated].nil?
95
95
  membership[:channel] = channel_metadata
96
96
  end
@@ -28,7 +28,7 @@ module Pubnub
28
28
  def fire
29
29
  Pubnub.logger.debug('Pubnub::SetUuidMetadata') { "Fired event #{self.class}" }
30
30
 
31
- body = Formatter.format_message(@metadata, "", @random_iv, false)
31
+ body = Formatter.format_message(@metadata, nil, false)
32
32
  response = send_request(body)
33
33
 
34
34
  envelopes = fire_callbacks(handle(response, uri))
@@ -61,7 +61,7 @@ module Pubnub
61
61
  def valid_envelope(parsed_response, req_res_objects)
62
62
  data = parsed_response['data']
63
63
  metadata = Hash.new
64
- data.each{ |k,v| metadata[k.to_sym] = v }
64
+ data.each { |k, v| metadata[k.to_sym] = v }
65
65
  metadata[:updated] = Date._parse(metadata[:updated]) unless metadata[:updated].nil?
66
66
 
67
67
  Pubnub::Envelope.new(
@@ -39,7 +39,7 @@ module Pubnub
39
39
  '0',
40
40
  Formatter.format_channel(@channel, true),
41
41
  '0',
42
- Formatter.format_message(@message, @cipher_key, @random_iv)
42
+ Formatter.format_message(@message, @crypto_module)
43
43
  ].join('/')
44
44
  end
45
45
 
@@ -8,6 +8,11 @@ module Pubnub
8
8
 
9
9
  def initialize(options, app)
10
10
  @event = :subscribe
11
+
12
+ # Override crypto module if custom cipher key has been used.
13
+ random_iv = options.key?(:random_iv) ? options[:random_iv] : true
14
+ options[:crypto_module] = Crypto::CryptoModule.new_legacy(options[:cipher_key], random_iv) if options[:cipher_key]
15
+
11
16
  super
12
17
  app.apply_state(self)
13
18
  end
@@ -41,17 +41,28 @@ module Pubnub
41
41
  end
42
42
  end
43
43
 
44
- # Transforms message to json and encode it
45
- def format_message(message, cipher_key = "", use_random_iv = false, uri_escape = true)
46
- if cipher_key && !cipher_key.empty?
47
- pc = Pubnub::Crypto.new(cipher_key, use_random_iv)
48
- message = pc.encrypt(message).to_json
49
- message = Addressable::URI.escape(message) if uri_escape
50
- else
51
- message = message.to_json
52
- message = Formatter.encode(message) if uri_escape
44
+ # TODO: Uncomment code below when cryptor implementations will be added.
45
+ # Transforms message to json and encode it.
46
+ #
47
+ # @param message [Hash, String, Integer, Boolean] Message data which
48
+ # should be formatted.
49
+ # @param crypto [Crypto::CryptoProvider, nil] Crypto which should be used to
50
+ # encrypt message data.
51
+ # @param uri_escape [Boolean, nil] Whether formatted message should escape
52
+ # to be used as part of URI or not.
53
+ # @return [String, nil] Formatted message data.
54
+ def format_message(message, crypto = nil, uri_escape = true)
55
+ json_message = message.to_json
56
+ if crypto
57
+ encrypted_data = crypto&.encrypt(json_message)
58
+ json_message = Base64.strict_encode64(encrypted_data).to_json unless encrypted_data.nil?
59
+ end
60
+
61
+ if uri_escape
62
+ json_message = Formatter.encode(json_message) if crypto.nil?
63
+ json_message = Addressable::URI.escape(json_message).to_s unless crypto.nil?
53
64
  end
54
- message
65
+ json_message
55
66
  end
56
67
 
57
68
  # Quite lazy way, but good enough for current usage
@@ -100,7 +111,7 @@ module Pubnub
100
111
  # Parses string to JSON
101
112
  def parse_json(string)
102
113
  [JSON.parse(string), nil]
103
- rescue JSON::ParserError => _error
114
+ rescue JSON::ParserError => _e
104
115
  [nil, JSON::ParserError]
105
116
  end
106
117
 
@@ -0,0 +1,159 @@
1
+ module Pubnub
2
+ module Crypto
3
+ # Crypto module for data processing.
4
+ #
5
+ # The PubNub client uses a module to encrypt and decrypt sent data in a way
6
+ # that's compatible with previous versions (if additional cryptors have been
7
+ # registered).
8
+ class CryptoModule < CryptoProvider
9
+ # AES-CBC cryptor based module.
10
+ #
11
+ # Data <i>encryption</i> and <i>decryption</i> will be done by default
12
+ # using the <i>AesCbcCryptor</i>. In addition to the <i>AesCbcCryptor</i>
13
+ # for data <i>decryption</i>, the <i>LegacyCryptor</i> will be registered
14
+ # for backward-compatibility.
15
+ #
16
+ # @param cipher_key [String] Key for data encryption and decryption.
17
+ # @param use_random_iv [Boolean] Whether random IV should be used for data
18
+ # decryption.
19
+ #
20
+ # @raise [ArgumentError] If the <i>cipher_key</i> is missing or empty.
21
+ def self.new_aes_cbc(cipher_key, use_random_iv)
22
+ if cipher_key.nil? || cipher_key.empty?
23
+ raise ArgumentError, {
24
+ message: '\'cipher_key\' is missing or empty.'
25
+ }
26
+ end
27
+
28
+ CryptoModule.new AesCbcCryptor.new(cipher_key), [LegacyCryptor.new(cipher_key, use_random_iv)]
29
+ end
30
+
31
+ # Legacy AES-CBC cryptor based module.
32
+ #
33
+ # Data <i>encryption</i> and <i>decryption</i> will be done by default
34
+ # using the <i>LegacyCrypto</i>. In addition to the <i>LegacyCrypto</i>
35
+ # for data <i>decryption</i>, the <i>AesCbcCryptor</i> will be registered
36
+ # for future-compatibility (which will help with gradual application
37
+ # updates).
38
+ #
39
+ # @param cipher_key [String] Key for data encryption and decryption.
40
+ # @param use_random_iv [Boolean] Whether random IV should be used for data
41
+ # decryption.
42
+ #
43
+ # @raise [ArgumentError] If the <i>cipher_key</i> is missing or empty.
44
+ def self.new_legacy(cipher_key, use_random_iv)
45
+ if cipher_key.nil? || cipher_key.empty?
46
+ raise ArgumentError, {
47
+ message: '\'cipher_key\' is missing or empty.'
48
+ }
49
+ end
50
+
51
+ CryptoModule.new LegacyCryptor.new(cipher_key, use_random_iv), [AesCbcCryptor.new(cipher_key)]
52
+ end
53
+
54
+ # Create crypto module.
55
+ #
56
+ # @param default [Cryptor] Default cryptor used to encrypt and decrypt
57
+ # data.
58
+ # @param cryptors [Array<Cryptor>, nil] Additional cryptors which will be
59
+ # used to decrypt data encrypted by previously used cryptors.
60
+ def initialize(default, cryptors)
61
+ if default.nil?
62
+ raise ArgumentError, {
63
+ message: '\'default\' cryptor required for data encryption.'
64
+ }
65
+ end
66
+
67
+ @default = default
68
+ @cryptors = cryptors&.each_with_object({}) do |value, hash|
69
+ hash[value.identifier] = value
70
+ end || {}
71
+ super()
72
+ end
73
+
74
+ def encrypt(data)
75
+ # Encrypting provided data.
76
+ encrypted_data = default_cryptor.encrypt(data)
77
+ return nil if encrypted_data.nil?
78
+
79
+ payload = Crypto::CryptorHeader.new(default_cryptor.identifier, encrypted_data.metadata).to_s
80
+ payload << encrypted_data.metadata unless encrypted_data.metadata.nil?
81
+ payload << encrypted_data.data
82
+ end
83
+
84
+ def decrypt(data)
85
+ if data.nil? || data.empty?
86
+ puts 'Pubnub :: DECRYPTION ERROR: Empty data for decryption'
87
+ return nil
88
+ end
89
+
90
+ header = Crypto::CryptorHeader.parse(data)
91
+ return nil if header.nil?
92
+
93
+ cryptor_identifier = header.identifier || '\x00\x00\x00\x00'
94
+ cryptor = cryptor cryptor_identifier
95
+
96
+ # Check whether there is a cryptor to decrypt data or not.
97
+ if cryptor.nil?
98
+ identifier = header.identifier || 'UNKN'
99
+ raise UnknownCryptorError, {
100
+ message: "Decrypting data created by unknown cryptor. Please make sure to register
101
+ #{identifier} or update SDK."
102
+ }
103
+ end
104
+
105
+ encrypted_data = data[header.length..-1]
106
+ metadata = metadata encrypted_data, header.data_size
107
+
108
+ # Check whether there is still some data for processing or not.
109
+ return nil if encrypted_data.nil? || encrypted_data.empty?
110
+
111
+ cryptor.decrypt(EncryptedData.new(encrypted_data, metadata))
112
+ end
113
+
114
+ private
115
+
116
+ # Cryptor used by the module by default to encrypt data.
117
+ #
118
+ # @return [Cryptor] Default cryptor used to encrypt and decrypt data.
119
+ def default_cryptor
120
+ @default
121
+ end
122
+
123
+ # Additional cryptors that can be used to decrypt data if the
124
+ # <i>default_cryptor</i> can't.
125
+ #
126
+ # @return [Hash<String, Cryptor>] Map of Cryptor to their identifiers.
127
+ def additional_cryptors
128
+ @cryptors
129
+ end
130
+
131
+ # Extract metadata information from source data.
132
+ #
133
+ # @param data [String, nil] Encrypted data from which cryptor metadata
134
+ # should be extracted.
135
+ # @param size [Integer] Size of cryptor-defined data.
136
+ # @return [String, nil] Extracted metadata or <i>nil</i> in case if
137
+ # <i>size</i> is <b>0</b>.
138
+ def metadata(data, size)
139
+ return nil if !data || !size.positive?
140
+
141
+ data&.slice!(0..(size - 1))
142
+ end
143
+
144
+ # Find cryptor with a specified identifier.
145
+ #
146
+ # Data decryption can only be done with registered cryptors. An identifier
147
+ # in the cryptor data header is used to identify a suitable cryptor.
148
+ #
149
+ # @param identifier [String] A unicode cryptor identifier.
150
+ # @return [Cryptor, nil] Target cryptor or `nil` in case there is none
151
+ # with the specified identifier.
152
+ def cryptor(identifier)
153
+ return default_cryptor if default_cryptor.identifier == identifier
154
+
155
+ additional_cryptors.fetch(identifier, nil)
156
+ end
157
+ end
158
+ end
159
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pubnub
4
+ module Crypto
5
+ # Base class which is used to implement a module that can be used to
6
+ # configure <b>PubNub</b> client or for manual data encryption and
7
+ # decryption.
8
+ class CryptoProvider
9
+ # Encrypt provided data.
10
+ #
11
+ # @param data [String] Source data for encryption.
12
+ # @return [String, nil] Encrypted data or <i>nil</i> in case of encryption
13
+ # error.
14
+ def encrypt(data)
15
+ raise NotImplementedError, 'Subclass should provide "encrypt" method implementation.'
16
+ end
17
+
18
+ # Decrypt provided data.
19
+ #
20
+ # @param data [String] Encrypted data for decryption.
21
+ # @return [String, nil] Decrypted data or <i>nil</i> in case of decryption
22
+ # error.
23
+ #
24
+ # @raise [UnknownCryptorError] If the <i>cryptor</i> for data processing is
25
+ # not registered.
26
+ def decrypt(data)
27
+ raise NotImplementedError, 'Subclass should provide "decrypt" method implementation.'
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pubnub
4
+ module Crypto
5
+ # Encrypted data representation object.
6
+ #
7
+ # Objects contain both encrypted data and additional data created by cryptor
8
+ # that will be required to decrypt the data.
9
+ class EncryptedData
10
+ # Cryptor may provide here any information which will be useful when data
11
+ # should be decrypted.
12
+ #
13
+ # For example <i>metadata</i> may contain:
14
+ # * initialization vector
15
+ # * cipher key identifier
16
+ # * encrypted <i>data</i> length
17
+ #
18
+ # @return [String, nil] Cryptor-defined information.
19
+ attr_reader :metadata
20
+
21
+ # Encrypted data.
22
+ #
23
+ # @return [String] Encrypted data.
24
+ attr_reader :data
25
+
26
+ # Create encrypted data object.
27
+ #
28
+ # An object used to keep track of the results of data encryption and the
29
+ # additional data the <i>cryptor</i> needs to handle it later.
30
+ #
31
+ # @param data [String] Outcome of successful cryptor <i>encrypt</i> method
32
+ # call.
33
+ # @param metadata [String, nil] Additional information is provided by
34
+ # <i>cryptor</i> so that encrypted data can be handled later.
35
+ def initialize(data, metadata = nil)
36
+ @data = data
37
+ @metadata = metadata
38
+ end
39
+ end
40
+
41
+ # Base class which is used to implement cryptor that should be used with
42
+ # <i>CryptorProvider</i> implementation for data encryption and decryption.
43
+ class Cryptor
44
+ # Identifier will be encoded into cryptor data header and passed along
45
+ # with encrypted <i>data</i> and <i>metadata</i>.
46
+ #
47
+ # The identifier <b>must</b> be 4 bytes long.
48
+ #
49
+ # @return [String] Unique cryptor identifier.
50
+ def identifier
51
+ raise NotImplementedError, 'Subclass should provide "identifier" method implementation.'
52
+ end
53
+
54
+ # Encrypt provided data.
55
+ #
56
+ # @param data [String] Source data for encryption.
57
+ # @return [EncryptedData, nil] Encrypted data or <i>nil</i> in case of
58
+ # encryption error.
59
+ def encrypt(data)
60
+ raise NotImplementedError, 'Subclass should provide "encrypt" method implementation.'
61
+ end
62
+
63
+ # Decrypt provided data.
64
+ #
65
+ # @param data [EncryptedData] Encrypted data for decryption.
66
+ # @return [String, nil] Decrypted data or <i>nil</i> in case of decryption
67
+ # error.
68
+ def decrypt(data)
69
+ raise NotImplementedError, 'Subclass should provide "decrypt" method implementation.'
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,251 @@
1
+ module Pubnub
2
+ module Crypto
3
+ # Cryptor data header.
4
+ #
5
+ # This instance used to parse header from received data and encode into
6
+ # binary for sending.
7
+ class CryptorHeader
8
+ module Versions
9
+ # Currently used cryptor data header schema version.
10
+ CURRENT_VERSION = 1
11
+
12
+ # Base class for cryptor data schema.
13
+ class CryptorHeaderData
14
+ # Cryptor header version.
15
+ #
16
+ # @return [Integer] Cryptor header version.
17
+ def version
18
+ raise NotImplementedError, 'Subclass should provide "version" method implementation.'
19
+ end
20
+
21
+ # Cryptor identifier.
22
+ #
23
+ # @return [String] Identifier of the cryptor which has been used to
24
+ # encrypt data.
25
+ def identifier
26
+ raise NotImplementedError, 'Subclass should provide "identifier" method implementation.'
27
+ end
28
+
29
+ # Cryptor-defined data size.
30
+ #
31
+ # @return [Integer] Cryptor-defined data size.
32
+ def data_size
33
+ raise NotImplementedError, 'Subclass should provide "data_size" method implementation.'
34
+ end
35
+ end
36
+
37
+ # v1 cryptor header schema.
38
+ #
39
+ # This header consists of:
40
+ # * sentinel (4 bytes)
41
+ # * version (1 byte)
42
+ # * cryptor identifier (4 bytes)
43
+ # * cryptor data size (1 byte if less than 255 and 3 bytes in other cases)
44
+ # * cryptor-defined data
45
+ class CryptorHeaderV1Data < CryptorHeaderData
46
+ # Identifier of the cryptor which has been used to encrypt data.
47
+ #
48
+ # @return [String] Identifier of the cryptor which has been used to
49
+ # encrypt data.
50
+ attr_reader :identifier
51
+
52
+ # Cryptor-defined data size.
53
+ #
54
+ # @return [Integer] Cryptor-defined data size.
55
+ attr_reader :data_size
56
+
57
+ # Create cryptor header data.
58
+ #
59
+ # @param identifier [String] Identifier of the cryptor which has been
60
+ # used to encrypt data.
61
+ # @param data_size [Integer] Cryptor-defined data size.
62
+ def initialize(identifier, data_size)
63
+ @identifier = identifier
64
+ @data_size = data_size
65
+ end
66
+
67
+ def version
68
+ 1
69
+ end
70
+ end
71
+ end
72
+
73
+ # Create cryptor header.
74
+ #
75
+ # @param identifier [String] Identifier of the cryptor which has been used
76
+ # to encrypt data.
77
+ # @param metadata [String, nil] Cryptor-defined information.
78
+ def initialize(identifier = nil, metadata = nil)
79
+ @data = if identifier && identifier != '\x00\x00\x00\x00'
80
+ Versions::CryptorHeaderV1Data.new(
81
+ identifier.to_s,
82
+ metadata&.length || 0
83
+ )
84
+ end
85
+ end
86
+
87
+ # Parse cryptor header data to create instance.
88
+ #
89
+ # @param data [String] Data which <i>may</i> contain cryptor header
90
+ # information.
91
+ # @return [CryptorHeader, nil] Header instance or <i>nil</i> in case of
92
+ # encrypted data parse error.
93
+ #
94
+ # @raise [ArgumentError] Raise an exception if <i>data</i> is <i>nil</i>
95
+ # or empty.
96
+ # @raise [UnknownCryptorError] Raise an exception if, during cryptor
97
+ # header data parsing, an unknown cryptor header version is encountered.
98
+ def self.parse(data)
99
+ if data.nil? || data.empty?
100
+ raise ArgumentError, {
101
+ message: '\'data\' is required and should not be empty.'
102
+ }
103
+ end
104
+
105
+ # Data is too short to be encrypted. Assume legacy cryptor without
106
+ # header.
107
+ return CryptorHeader.new if data.length < 4 || data.unpack('A4').last != 'PNED'
108
+
109
+ # Malformed crypto header.
110
+ return nil if data.length < 10
111
+
112
+ # Unpack header bytes.
113
+ _, version, identifier, data_size = data.unpack('A4 C A4 C')
114
+
115
+ # Check whether version is within known range.
116
+ if version > current_version
117
+ raise UnknownCryptorError, {
118
+ message: 'Decrypting data created by unknown cryptor.'
119
+ }
120
+ end
121
+
122
+ if data_size == 255
123
+ data_size = data.unpack('A4 C A4 C n').last if data.length >= 12
124
+ return CryptorHeader.new if data.length < 12
125
+ end
126
+
127
+ header = CryptorHeader.new
128
+ header.send(
129
+ :update_header_data,
130
+ create_header_data(version.to_i, identifier.to_s, data_size.to_i)
131
+ )
132
+ header
133
+ end
134
+
135
+ # Overall header size.
136
+ #
137
+ # Full header size which includes:
138
+ # * sentinel
139
+ # * version
140
+ # * cryptor identifier
141
+ # * cryptor data size
142
+ # * cryptor-defined fields size.
143
+ def length
144
+ # Legacy payload doesn't have header.
145
+ return 0 if @data.nil?
146
+
147
+ 9 + (data_size < 255 ? 1 : 3)
148
+ end
149
+
150
+ # Crypto header version Version module.
151
+ #
152
+ # @return [Integer] One of known versions from Version module.
153
+ def version
154
+ header_data&.version || 0
155
+ end
156
+
157
+ # Identifier of the cryptor which has been used to encrypt data.
158
+ #
159
+ # @return [String, nil] Identifier of the cryptor which has been used to
160
+ # encrypt data.
161
+ def identifier
162
+ header_data&.identifier || nil
163
+ end
164
+
165
+ # Cryptor-defined information size.
166
+ #
167
+ # @return [Integer] Cryptor-defined information size.
168
+ def data_size
169
+ header_data&.data_size || 0
170
+ end
171
+
172
+ # Create cryptor header data object.
173
+ #
174
+ # @param version [Integer] Cryptor header data schema version.
175
+ # @param identifier [String] Encrypting cryptor identifier.
176
+ # @param size [Integer] Cryptor-defined data size
177
+ # @return [Versions::CryptorHeaderData] Cryptor header data.
178
+ def self.create_header_data(version, identifier, size)
179
+ Versions::CryptorHeaderV1Data.new(identifier, size) if version == 1
180
+ end
181
+
182
+ # Crypto header which is currently used to encrypt data.
183
+ #
184
+ # @return [Integer] Current cryptor header version.
185
+ def self.current_version
186
+ Versions::CURRENT_VERSION
187
+ end
188
+
189
+ # Serialize cryptor header.
190
+ #
191
+ # @return [String] Cryptor header data, which is serialized as a binary
192
+ # string.
193
+ #
194
+ # @raise [ArgumentError] Raise an exception if a <i>cryptor</i> identifier
195
+ # is not provided for a non-legacy <i>cryptor</i>.
196
+ def to_s
197
+ # We don't need to serialize header for legacy cryptor.
198
+ return '' if version.zero?
199
+
200
+ cryptor_identifier = identifier
201
+ if cryptor_identifier.nil? || cryptor_identifier.empty?
202
+ raise ArgumentError, {
203
+ message: '\'identifier\' is missing or empty.'
204
+ }
205
+ end
206
+
207
+ header_bytes = ['PNED', version, cryptor_identifier]
208
+ if data_size < 255
209
+ header_bytes.push(data_size)
210
+ else
211
+ header_bytes.push(255, data_size)
212
+ end
213
+
214
+ header_bytes.pack(data_size < 255 ? 'A4 C A4 C' : 'A4 C A4 C n')
215
+ end
216
+
217
+ private
218
+
219
+ # Versioned cryptor header data
220
+ #
221
+ # @return [Versions::CryptorHeaderData, nil] Cryptor header data.
222
+ def header_data
223
+ @data
224
+ end
225
+
226
+ # Update crypto header version.
227
+ #
228
+ # @param data [Versions::CryptorHeaderData] Header version number parsed from binary data.
229
+ def update_header_data(data)
230
+ @data = data
231
+ end
232
+
233
+ # Update crypto header version.
234
+ #
235
+ # @param value [Integer] Header version number parsed from binary data.
236
+ def update_version(value)
237
+ @version = value
238
+ end
239
+
240
+ # Update cryptor-defined data size.
241
+ #
242
+ # @param value [Integer] Cryptor-defined data size parsed from binary
243
+ # data.
244
+ def update_data_size(value)
245
+ @data_size = value
246
+ end
247
+
248
+ private_class_method :create_header_data, :current_version
249
+ end
250
+ end
251
+ end