# -*- coding: binary -*-

module Rex
  module Proto
    module Rmi
      module Model
        # This class provides a representation of an RMI return value
        class ReturnValue < Element

          # @!attribute code
          #   @return [Fixnum] the return code
          attr_accessor :code
          # @!attribute uid
          #   @return [Rex::Proto::Rmi::Model::UniqueIdentifier] unique identifier of the returned value
          attr_accessor :uid
          # @!attribute value
          #   @return [Array] the returned exception or value according to code
          attr_accessor :value

          # Encodes the Rex::Proto::Rmi::Model::ReturnValue into an String.
          #
          # @return [String]
          def encode
            stream = Rex::Java::Serialization::Model::Stream.new
            block_data = Rex::Java::Serialization::Model::BlockData.new(nil, encode_code + encode_uid)

            stream.contents << block_data
            value.each do |v|
              stream.contents << v
            end

            stream.encode
          end

          # Decodes the Rex::Proto::Rmi::Model::ReturnValue from the input.
          #
          # @param io [IO] the IO to read from
          # @return [Rex::Proto::Rmi::Model::ReturnValue]
          def decode(io)
            stream = Rex::Java::Serialization::Model::Stream.decode(io)

            block_data = stream.contents[0]
            block_data_io = StringIO.new(block_data.contents, 'rb')

            self.code = decode_code(block_data_io)
            self.uid = decode_uid(block_data_io)
            self.value = []

            stream.contents[1..stream.contents.length - 1].each do |content|
              self.value << content
            end

            self
          end

          # Answers if the ReturnValue is an exception
          #
          # @return [Boolean]
          def is_exception?
            code == RETURN_EXCEPTION
          end

          # The object/exception class of the returned value
          #
          # @return [String, NilClass] the returned value class, nil it cannot be retrieved
          def get_class_name
            unless value[0] && value[0].is_a?(Rex::Java::Serialization::Model::NewObject)
              return nil
            end

            case value[0].class_desc.description
            when Rex::Java::Serialization::Model::NewClassDesc
              return value[0].class_desc.description.class_name.contents
            when Rex::Java::Serialization::Model::ProxyClassDesc
              return value[0].class_desc.description.interfaces[0].contents
            else
              return nil
            end
          end

          private

          # Reads the return code from the IO
          #
          # @param io [IO] the IO to read from
          # @return [String]
          # @raise [Rex::Proto::Rmi::DecodeError] if fails to decode the return code
          def decode_code(io)
            code = read_byte(io)
            unless code == RETURN_VALUE || code == RETURN_EXCEPTION
              raise Rex::Proto::Rmi::DecodeError, 'Failed to decode the ReturnValue code'
            end

            code
          end

          # Reads and deserializes the uid from the IO
          #
          # @param io [IO] the IO to read from
          # @return [Rex::Proto::Rmi::Model::UniqueIdentifier]
          def decode_uid(io)
            uid = Rex::Proto::Rmi::Model::UniqueIdentifier.decode(io)

            uid
          end

          # Encodes the code field
          #
          # @return [String]
          def encode_code
            [code].pack('c')
          end

          # Encodes the uid field
          #
          # @return [String]
          def encode_uid
            uid.encode
          end
        end
      end
    end
  end
end