class Motion::Serializer

Constants

HASH_PEPPER
NULL_BYTE

Attributes

revision[R]
secret[R]

Public Class Methods

minimum_secret_byte_length() click to toggle source
# File lib/motion/serializer.rb, line 18
def self.minimum_secret_byte_length
  ActiveSupport::MessageEncryptor.key_len
end
new( secret: Motion.config.secret, revision: Motion.config.revision ) click to toggle source
# File lib/motion/serializer.rb, line 22
def initialize(
  secret: Motion.config.secret,
  revision: Motion.config.revision
)
  unless secret.each_byte.count >= self.class.minimum_secret_byte_length
    raise BadSecretError.new(self.class.minimum_secret_byte_length)
  end

  raise BadRevisionError if revision.include?(NULL_BYTE)

  @secret = secret
  @revision = revision
end

Public Instance Methods

deserialize(serialized_component) click to toggle source
# File lib/motion/serializer.rb, line 50
def deserialize(serialized_component)
  state_with_revision = decrypt_and_verify(serialized_component)
  serialized_revision, state = state_with_revision.split(NULL_BYTE, 2)
  component = load(inflate(state))

  if revision == serialized_revision
    component
  else
    component.class.upgrade_from(serialized_revision, component)
  end
end
serialize(component) click to toggle source
# File lib/motion/serializer.rb, line 40
def serialize(component)
  state = deflate(dump(component))
  state_with_revision = "#{revision}#{NULL_BYTE}#{state}"

  [
    salted_digest(state_with_revision),
    encrypt_and_sign(state_with_revision)
  ]
end
weak_digest(component) click to toggle source
# File lib/motion/serializer.rb, line 36
def weak_digest(component)
  dump(component).hash
end

Private Instance Methods

decrypt_and_verify(cypertext) click to toggle source
# File lib/motion/serializer.rb, line 86
def decrypt_and_verify(cypertext)
  encryptor.decrypt_and_verify(cypertext)
rescue ActiveSupport::MessageEncryptor::InvalidMessage,
  ActiveSupport::MessageVerifier::InvalidSignature
  raise InvalidSerializedStateError
end
deflate(dumped_component) click to toggle source
# File lib/motion/serializer.rb, line 74
def deflate(dumped_component)
  LZ4.compress(dumped_component)
end
derive_encryptor_key() click to toggle source
# File lib/motion/serializer.rb, line 105
def derive_encryptor_key
  secret.byteslice(0, self.class.minimum_secret_byte_length)
end
derive_hash_salt() click to toggle source
# File lib/motion/serializer.rb, line 109
def derive_hash_salt
  Digest::SHA256.digest(HASH_PEPPER + secret)
end
dump(component) click to toggle source
# File lib/motion/serializer.rb, line 64
def dump(component)
  Marshal.dump(component)
rescue TypeError => e
  raise UnrepresentableStateError.new(component, e.message)
end
encrypt_and_sign(cleartext) click to toggle source
# File lib/motion/serializer.rb, line 82
def encrypt_and_sign(cleartext)
  encryptor.encrypt_and_sign(cleartext)
end
encryptor() click to toggle source
# File lib/motion/serializer.rb, line 97
def encryptor
  @encryptor ||= ActiveSupport::MessageEncryptor.new(derive_encryptor_key)
end
hash_salt() click to toggle source
# File lib/motion/serializer.rb, line 101
def hash_salt
  @hash_salt ||= derive_hash_salt
end
inflate(deflated_state) click to toggle source
# File lib/motion/serializer.rb, line 78
def inflate(deflated_state)
  LZ4.uncompress(deflated_state)
end
load(state) click to toggle source
# File lib/motion/serializer.rb, line 70
def load(state)
  Marshal.load(state)
end
salted_digest(input) click to toggle source
# File lib/motion/serializer.rb, line 93
def salted_digest(input)
  Digest::SHA256.base64digest(hash_salt + input)
end