pubnub 5.2.2 → 5.3.0

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