module NetPGP

def self.bignum_byte_count(bn)
  # Note: This probably assumes that the ruby implementation
  # uses the same BN representation that libnetpgp does.
  # It may be better to convert and use BN_num_bytes (or bits).
  bn.to_s(16).length / 2
end

def self.stream_errors(stream)
  error_ptr = stream[:errors]

  errors = []
  until error_ptr.null?
    error = LibNetPGP::PGPError.new(error_ptr)

    error_desc = "#{error[:file]}:#{error[:line]}: #{error[:errcode]} #{error[:comment]}"
    errors.push(error_desc)

    error_ptr = error[:next]
  end
  errors
end

def self.mpi_from_native(native)
  mpi = {}
  native.members.each {|member|
    if native[member].null?
      mpi[member] = nil
    else
      mpi[member] = LibNetPGP::bn2hex(native[member]).hex
    end
  }
  mpi
end

def self.mpis_from_native(alg, native)
  case alg
    when :PGP_PKA_RSA, :PGP_PKA_RSA_ENCRYPT_ONLY, :PGP_PKA_RSA_SIGN_ONLY
      material = native[:key][:rsa]
    when :PGP_PKA_DSA
      material = native[:key][:dsa]
    when :PGP_PKA_ELGAMAL
      material = native[:key][:elgamal]
    else
      raise "Unsupported PK algorithm: #{alg}"
  end
  NetPGP::mpi_from_native(material)
end

def self.mpi_to_native(mpi, native)
  mpi.each {|name,value|
    if mpi[name] == nil
      native[name] = nil
    else
      native[name] = LibNetPGP::num2bn(value)
    end
  }
end

def self.mpis_to_native(alg, mpi, native)
  case alg
  when :PGP_PKA_RSA, :PGP_PKA_RSA_ENCRYPT_ONLY, :PGP_PKA_RSA_SIGN_ONLY
    material = native[:key][:rsa]
  when :PGP_PKA_DSA
    material = native[:key][:dsa]
  when :PGP_PKA_ELGAMAL
    material = native[:key][:elgamal]
  else
    raise "Unsupported PK algorithm: #{alg}"
  end
  # Ensure we're not leaking memory from a prior call.
  # This just frees all the BNs.
  if native.is_a?(LibNetPGP::PGPSecKey)
    LibNetPGP::pgp_seckey_free(native)
  elsif native.is_a?(LibNetPGP::PGPPubKey)
    LibNetPGP::pgp_pubkey_free(native)
  else
    raise
  end
  NetPGP::mpi_to_native(mpi, material)
end


# Add a subkey binding signature (type 0x18) to a key.
# Note that this should be used for encryption subkeys.
#
# @param key [LibNetPGP::PGPKey]
# @param subkey [LibNetPGP::PGPKey]
def self.add_subkey_signature(key, subkey)
  sig = nil
  sigoutput = nil
  mem_sig = nil
  begin
    sig = LibNetPGP::pgp_create_sig_new
    LibNetPGP::pgp_sig_start_subkey_sig(sig, key[:key][:pubkey], subkey[:key][:pubkey], :PGP_SIG_SUBKEY)
    LibNetPGP::pgp_add_time(sig, subkey[:key][:pubkey][:birthtime], 'birth')
    # TODO expiration
    LibNetPGP::pgp_add_issuer_keyid(sig, key[:sigid])
    LibNetPGP::pgp_end_hashed_subpkts(sig)

    sigoutput_ptr = FFI::MemoryPointer.new(:pointer)
    mem_sig_ptr = FFI::MemoryPointer.new(:pointer)
    LibNetPGP::pgp_setup_memory_write(sigoutput_ptr, mem_sig_ptr, 128)
    sigoutput = LibNetPGP::PGPOutput.new(sigoutput_ptr.read_pointer)
    LibNetPGP::pgp_write_sig(sigoutput, sig, key[:key][:pubkey], key[:key][:seckey])
    mem_sig = LibNetPGP::PGPMemory.new(mem_sig_ptr.read_pointer)
    sigpkt = LibNetPGP::PGPSubPacket.new
    sigpkt[:length] = LibNetPGP::pgp_mem_len(mem_sig)
    sigpkt[:raw] = LibNetPGP::pgp_mem_data(mem_sig)
    LibNetPGP::pgp_add_subpacket(subkey, sigpkt)
  ensure
    LibNetPGP::pgp_create_sig_delete(sig) if sig
    LibNetPGP::pgp_teardown_memory_write(sigoutput, mem_sig) if mem_sig
  end
end

end # module NetPGP