44uk/nis-ruby

View on GitHub
lib/nis/util/serializer.rb

Summary

Maintainability
A
0 mins
Test Coverage
module Nis::Util
  module Serializer
    # Serialize a transaction object
    # @param [Hash] entity
    # @return [Array]
    def self.serialize_transaction(entity)
      specific = case entity[:type]
                 when 0x0101 then serialize_transfer(entity)
                 when 0x0801 then serialize_importance_transfer(entity)
                 when 0x1001 then serialize_multisig_aggregate_modification(entity)
                 when 0x1002 then serialize_multisig_signature(entity)
                 when 0x1004 then serialize_multisig(entity)
                 when 0x2001 then serialize_provision_namespace(entity)
                 when 0x4001 then serialize_mosaic_definition_creation(entity)
                 when 0x4002 then serialize_mosaic_supply_change(entity)
        else raise "Not implemented entity type: #{entity[:type]}"
      end
      serialize_common(entity) + specific
    end

    private

    def self.serialize_transfer(entity)
      a = []
      a.concat serialize_safe_string(entity[:recipient])
      a.concat serialize_long(entity[:amount])
      payload = Nis::Util::Convert.hex2ua(entity[:message][:payload])
      if payload.size == 0
        a.concat [0, 0, 0, 0]
      else
        a.concat serialize_int(payload.size + 8)
        a.concat serialize_int(entity[:message][:type])
        a.concat serialize_int(payload.size)
        a.concat payload
      end
      if entity[:mosaics] && entity[:mosaics].size > 0
        a.concat serialize_mosaics(entity[:mosaics])
      end
      a
    end

    def self.serialize_importance_transfer(entity)
      a = []
      a.concat serialize_int(entity[:mode])
      temp = Nis::Util::Convert.hex2ua(entity[:remoteAccount])
      a.concat serialize_int(temp.size)
      a.concat temp
    end

    def self.serialize_multisig_aggregate_modification(entity)
      a = []
      a.concat serialize_int(entity[:modifications].size)
      mods = entity[:modifications].inject([]) do |b, mod|
        b.concat serialize_int(40)
        b.concat serialize_int(mod[:modificationType])
        b.concat serialize_int(32)
        b.concat Nis::Util::Convert.hex2ua(mod[:cosignatoryAccount])
        b
      end
      a.concat mods

      # The following part describes the minimum cosignatories modification.
      # The part is optional. Version 1 aggregate modification transactions should omit this part.
      # Version 2 aggregate modification transactions with no minimum cosignatories modification
      # should only write the length field with value 0x00, 0x00, 0x00, 0x00.
      if true # only version2
        if entity[:minCosignatories][:relativeChange] > 0
          a.concat serialize_int(4)
          a.concat serialize_int(entity[:minCosignatories][:relativeChange])
        else
          a.concat [0, 0, 0, 0]
        end
      end
      a
    end

    def self.serialize_multisig_signature(entity)
      a = []
      temp = Nis::Util::Convert.hex2ua(entity[:otherHash][:data])
      a.concat serialize_int(4 + temp.size)
      a.concat serialize_int(temp.size)
      a.concat temp
      a.concat serialize_safe_string(entity[:otherAccount])
    end

    def self.serialize_multisig(entity)
      a = []
      other = entity[:otherTrans]
      tx = case other[:type]
           when 0x0101 then serialize_transfer(other)
           when 0x0801 then serialize_importance_transfer(other)
           when 0x1001 then serialize_multisig_aggregate_modification(other)
        else raise "Unexpected type #{other_tx[:type]}"
      end
      tx = serialize_common(other) + tx
      a.concat serialize_int(tx.size)
      a.concat tx
    end

    def self.serialize_provision_namespace(entity)
      a = []
      a.concat serialize_safe_string(entity[:rentalFeeSink])
      a.concat serialize_long(entity[:rentalFee])
      temp = Nis::Util::Convert.hex2ua(Nis::Util::Convert.utf8_to_hex(entity[:newPart]))
      a.concat serialize_int(temp.size)
      a.concat temp
      if entity[:parent]
        temp = Nis::Util::Convert.hex2ua(Nis::Util::Convert.utf8_to_hex(entity[:parent]))
        a.concat serialize_int(temp.size)
        a.concat temp
      else
        a.concat [255, 255, 255, 255]
      end
      a
    end

    def self.serialize_mosaic_definition_creation(entity)
      a = []
      a.concat serialize_mosaic_definition(entity[:mosaicDefinition])
      a.concat serialize_safe_string(entity[:creationFeeSink])
      a.concat serialize_long(entity[:creationFee])
    end

    def self.serialize_mosaic_supply_change(entity)
      a = []
      a.concat serialize_mosaic_id(entity[:mosaicId])
      a.concat serialize_int(entity[:supplyType])
      a.concat serialize_long(entity[:delta])
    end

    def self.serialize_common(entity)
      a = []
      a.concat serialize_int(entity[:type])
      a.concat serialize_int(entity[:version])
      a.concat serialize_int(entity[:timeStamp])

      signer = Nis::Util::Convert.hex2ua(entity[:signer])
      a.concat serialize_int(signer.size)
      a.concat signer

      a.concat serialize_long(entity[:fee].to_i)
      a.concat serialize_int(entity[:deadline])
    end

    # Safe String - Each char is 8 bit
    # @param [String] str
    # @return [Array]
    def self.serialize_safe_string(str)
      return [255, 255, 255, 255] if str.nil?
      return [0, 0, 0, 0] if str.empty?
      [str.size, 0, 0, 0] + str.bytes
    end

    # @param [String] str
    # @return [Array]
    def self.serialize_bin_string(str)
      return [255, 255, 255, 255] if str.nil?
      return [0, 0, 0, 0] if str.empty?
      chars = str.is_a?(String) ? str.chars : str
      [chars.size, 0, 0, 0] + chars.map(&:to_i)
    end

    # @param [Integer] value
    # @return [Array]
    def self.serialize_int(value)
      [value].pack('I').unpack('C4')
      # a = [0, 0, 0, 0]
      # bin = sprintf('%032b', value)
      # 0.upto(bin.size / 8 - 1) do |i|
      #   a[i] = 0xFF & (value >> 8 * i)
      # end
      # a
    end

    # @param [Integer] value
    # @return [Array]
    def self.serialize_long(value)
      a = [0, 0, 0, 0, 0, 0, 0, 0]
      bin = sprintf('%040b', value)
      0.upto(bin.size / 8 - 1) do |i|
        a[i] = 0xFF & (value >> 8 * i)
      end
      a
    end

    # @param [Nis::Struct::Mosaic] mosaic
    # @return [Array]
    def self.serialize_mosaic_and_quantity(mosaic_attachment)
      a = []
      a.concat serialize_mosaic_id(mosaic_attachment[:mosaicId])
      a.concat serialize_long(mosaic_attachment[:quantity])
    end

    # @param [Array <Nis::Struct::Mosaic>] entities
    # @return [Array]
    def self.serialize_mosaics(entities)
      a = []
      a.concat serialize_int(entities.size)
      mosaics = entities.inject([]) do |memo, ent|
        memo.concat serialize_mosaic_and_quantity(ent)
      end
      a.concat serialize_int(mosaics.size)
      a.concat mosaics
    end

    # @param [Hash] entity
    # @return [Array]
    def self.serialize_mosaic_id(entity)
      a = []
      a.concat serialize_safe_string(entity[:namespaceId])
      a.concat serialize_safe_string(entity[:name])
      serialize_int(a.size) + a
    end

    # @param [Hash] entity
    # @return [Array]
    def self.serialize_property(entity)
      a = []
      a.concat serialize_safe_string(entity[:name])
      a.concat serialize_safe_string(entity[:value])
      serialize_int(a.size) + a
    end

    # @param [Array] entities
    # @return [Array]
    def self.serialize_properties(entities)
      order = {
        'divisibility'  => 1,
        'initialSupply' => 2,
        'supplyMutable' => 3,
        'transferable'  => 4
      }
      serialize_int(entities.size) + entities
        .sort_by { |ent| order[ent[:name]] }
        .inject([]) { |memo, ent| memo + serialize_property(ent) }
    end

    # @param [Hash] entity
    # @return [Array]
    def self.serialize_levy(entity)
      return [0, 0, 0, 0] if entity.nil?
      a = []
      a.concat serialize_int(entity[:type])
      a.concat serialize_safe_string(entity[:recipient])
      a.concat serialize_mosaic_id(entity[:mosaicId])
      a.concat serialize_long(entity[:fee])
      serialize_int(a.size) + a
    end

    # @param [Hash] entity
    # @return [Array]
    def self.serialize_mosaic_definition(entity)
      a = []
      creator = Nis::Util::Convert.hex2ua(entity[:creator])
      a.concat serialize_int(creator.size)
      a.concat creator
      a.concat serialize_mosaic_id(entity[:id])
      a.concat serialize_bin_string(Nis::Util::Convert.hex2ua(Nis::Util::Convert.utf8_to_hex(entity[:description])))
      a.concat serialize_properties(entity[:properties])
      a.concat serialize_levy(entity[:levy])
      serialize_int(a.size) + a
    end
  end
end