# This file is generated by rake. Do not edit.

module OpenSSL
  module Digest
    # Native OpenSSL functions and structs
    module Foreign
      extend FFI::Library

      ffi_lib "libssl"

      # OpenSSL structure representing a message digest algorithm
      class EVP_MD < FFI::Struct
        layout :type, :int, 0,
               :pkey_type, :int, 4,
               :md_size, :int, 8,
               :flags, :ulong, 16,
               :init, :pointer, 24,
               :update, :pointer, 32,
               :final, :pointer, 40,
               :copy, :pointer, 48,
               :cleanup, :pointer, 56,
               :sign, :pointer, 64,
               :verify, :pointer, 72,
               :block_size, :int, 100,
               :ctx_size, :int, 104








      end

      # OpenSSL structure representing a message digest context
      class EVP_MD_CTX < FFI::Struct
        layout :digest, :pointer, 0,
               :engine, :pointer, 8,
               :flags, :ulong, 16,
               :md_data, :pointer, 24








        # Returns the size of the digest associated with this context
        def digest_size
          evp_md = EVP_MD.new self[:digest]
          evp_md[:md_size]
        end
      end

      # const EVP_MD *EVP_get_digestbyname(const char *name);
      attach_function :ossl_digest_getbyname, "EVP_get_digestbyname", [:string], :pointer

      #void OpenSSL_add_all_digests(void);
      attach_function :ossl_add_digests, "OpenSSL_add_all_digests", [], :void

      # int EVP_DigestInit_ex(EVP_MD_CTX *ctx, const EVP_MD *type, ENGINE *impl);
      attach_function :ossl_digest_init_ex, "EVP_DigestInit_ex",
            [:pointer, :pointer, :pointer], :int

      #int EVP_DigestUpdate(EVP_MD_CTX *ctx, const void *d, size_t cnt);
      attach_function :ossl_digest_update, "EVP_DigestUpdate",
            [:pointer, :string, :size_t], :int

      #EVP_MD_CTX *EVP_MD_CTX_create(void);
      attach_function :ossl_digest_ctx_create, "EVP_MD_CTX_create", [], :pointer

      #int EVP_MD_CTX_copy_ex(EVP_MD_CTX *out,const EVP_MD_CTX *in);
      attach_function :ossl_digest_ctx_copy, "EVP_MD_CTX_copy_ex", [:pointer, :pointer], :int

      #int EVP_MD_CTX_cleanup(EVP_MD_CTX *ctx);
      attach_function :ossl_digest_ctx_cleanup, "EVP_MD_CTX_cleanup", [:pointer], :int

      #int EVP_DigestFinal_ex(EVP_MD_CTX *ctx, unsigned char *md, unsigned int *s);
      attach_function :ossl_digest_final_ex, "EVP_DigestFinal_ex", [:pointer, :pointer, :pointer], :int

    end # Foreign

    class Digest
      # Populate OpenSSL's digest type table
      Foreign.ossl_add_digests

      # The API for OpenSSL::Digest is insane, and the args to "new"
      # differ in subclasses. This madness here is designed to handle that.
      def self.new(digest_type = nil)
        # We are in a subclass, so no further work is needed
        return super(digest_type) if const_defined?(:DigestName)

        if digest_type then
          subclass = OpenSSL::Digest::const_get(digest_type.to_s) rescue nil
          return subclass.new if subclass
          raise RuntimeError, "Unsupported digest algorithm (#{digest_type})."
        else
          raise ArgumentError, "wrong number of arguments (0 for 1)"
        end
      end

      # Initialize the OpenSSL structures. This code is only executed in
      # Digest subclasses, never in Digest itself.
      def initialize(data = nil)
        @context, @digest = nil, nil
        # Fetch the digest algorithm that matches the name of this class
        @digest = Foreign.ossl_digest_getbyname self.class.const_get(:DigestName)
        Errno.handle if @digest.nil?
        # Create a new digest context (EVP_MD_CTX)
        @context = Foreign.ossl_digest_ctx_create
        Errno.handle if @context.nil?
        # Initialize the digest context
        Foreign.ossl_digest_init_ex(@context, @digest, nil)
        # Subclasses of Digest allow the initial data to be passed as an arg
        update(data) if data
      end

      # Update the digest with new data
      def update(data)
        data = StringValue(data)
        err = Foreign.ossl_digest_update @context, data, data.size
        Errno.handle if err.zero?
        return self
      end

      # Returns the digest in binary form
      def digest
        buffer = finalized_context
        buffer.read_string(buffer.total)
      end

      # Returns the digest as a lowercase hex string
      def hexdigest
        buffer = finalized_context
        OpenSSL.digest_to_hex digest
      end
      alias_method :to_s, :hexdigest
      alias_method :inspect, :hexdigest

      # Copy the current digest context and then finalize it
      # to prevent further updates
      def finalized_context
        # Create a blank context and then duplicate the current one
        final = Foreign.ossl_digest_ctx_create
        err = Foreign.ossl_digest_ctx_copy(final, @context)
        Errno.handle if err.zero?

        # Wrap the returned pointer in a context struct so that we
        # can access its fields.
        final_ctx = Foreign::EVP_MD_CTX.new(final)

        # Create a buffer to hold the finalized output
        buffer = FFI::MemoryPointer.new(final_ctx.digest_size)
        buffer_size = FFI::MemoryPointer.new(:uint)
        buffer_size.write_int(buffer.total)

        # Finalize and write-protect the duplicated context
        err = Foreign.ossl_digest_final_ex(final, buffer, buffer_size)
        Foreign.ossl_digest_ctx_cleanup(final)
        Errno.handle if err.zero?
        buffer
      end
      private :finalized_context

      # Returns the underlying EVP_MD structure. Used by OpenSSL::HMAC
      def message_digest_backend
        @digest
      end
      private :message_digest_backend
    end # Digest

    class SHA1   < Digest; DigestName = "SHA1";  end
    class MD5    < Digest; DigestName = "MD5" ;  end
  end # OpenSSL::Digest
end # OpenSSL
