require 'minitest/autorun'
require 'minitest/unit'
require 'test_helper'

# bcrypt_pbkdf in ruby
require 'openssl'

BCRYPT_BLOCKS = 8
BCRYPT_HASHSIZE = BCRYPT_BLOCKS * 4

def bcrypt_pbkdf(password, salt, keylen, rounds)
  stride = (keylen + BCRYPT_HASHSIZE - 1) / BCRYPT_HASHSIZE
  amt = (keylen + stride - 1) / stride

  sha2pass = OpenSSL::Digest::SHA512.new(password).digest
  #puts "[RB] sha2pass:#{sha2pass.inspect} #{sha2pass.size}"

  remlen = keylen

  countsalt = salt + "\x00"*4
  saltlen = salt.size

  key = "\x00"*keylen

  # generate key in BCRYPT_HASHSIZE pieces
  count = 1
  while remlen > 0
    countsalt[saltlen + 0] = ((count >> 24) & 0xff).chr
    countsalt[saltlen + 1] = ((count >> 16) & 0xff).chr
    countsalt[saltlen + 2] = ((count >> 8) & 0xff).chr
    countsalt[saltlen + 3] = (count & 0xff).chr
    #puts "[RC] countsalt: #{countsalt.inspect} len:#{countsalt.size}"

    sha2salt = OpenSSL::Digest::SHA512.new(countsalt).digest
    tmpout = BCryptPbkdf::Engine::__bc_crypt_hash(sha2pass, sha2salt)
    out = tmpout.clone

    #puts "[RB] out: #{out.inspect} keylen:#{remlen} count:#{count}"
    (1...rounds).each do |i|
      sha2salt = OpenSSL::Digest::SHA512.new(tmpout).digest
      tmpout = BCryptPbkdf::Engine::__bc_crypt_hash(sha2pass, sha2salt)
      out.bytes.each_with_index {|o,j| out.setbyte(j,o ^ tmpout[j].ord) }
    end

    amt = [amt, remlen].min
    (0...amt).each do |i|
      dest = i * stride + (count - 1)
      key[dest] = out[i] if (dest < keylen)
    end

    remlen -= amt
    count += 1
  end
  key
end


class TestExt < Minitest::Unit::TestCase
  def test_table
    assert_equal table, table.map{ |p,s,l,r| [p,s,l,r,BCryptPbkdf::Engine::__bc_crypt_pbkdf(p,s,l,r).bytes] }
  end
  def test_ruby_and_native_returns_the_same
    table.each do |p,s,l,r|
      assert_equal bcrypt_pbkdf(p,s,l,r), BCryptPbkdf::Engine::__bc_crypt_pbkdf(p,s,l,r)
      assert_equal bcrypt_pbkdf(p,s,l,r), BCryptPbkdf::key(p,s,l,r)
    end
  end


  def table
    [
      ["pass2", "salt2", 12, 2, [214, 14, 48, 162, 131, 206, 121, 176, 50, 104, 231, 252]],
      ["\u0000\u0001foo", "\u0001\u0002fooo3", 14, 5, [46, 189, 32, 185, 94, 85, 232, 10, 84, 26, 44, 161, 49, 126]],
      ["doozoasd", "fooo$AS!", 14, 22, [57, 62, 50, 107, 70, 155, 65, 5, 129, 211, 189, 169, 188, 65]]
    ]
  end
end
